2016-03-28 10:46:51 +00:00
/ * *
2016-04-18 12:34:29 +00:00
* @ license AngularJS v1 . 2.9
* ( c ) 2010 - 2014 Google , Inc . http : //angularjs.org
2016-03-28 10:46:51 +00:00
* License : MIT
2016-05-18 00:10:50 +00: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 .
2016-04-18 12:34:29 +00:00
* @ returns { function ( string , string , ... ) : Error } instance
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
function minErr ( module ) {
return function ( ) {
var code = arguments [ 0 ] ,
prefix = '[' + ( module ? module + ':' : '' ) + code + '] ' ,
template = arguments [ 1 ] ,
templateArgs = arguments ,
stringify = function ( obj ) {
if ( typeof obj === 'function' ) {
return obj . toString ( ) . replace ( / \{[\s\S]*$/ , '' ) ;
} else if ( typeof obj === 'undefined' ) {
return 'undefined' ;
} else if ( typeof obj !== 'string' ) {
return JSON . stringify ( obj ) ;
}
return obj ;
} ,
message , i ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
message = prefix + template . replace ( /\{\d+\}/g , function ( match ) {
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 ) {
arg = templateArgs [ index + 2 ] ;
if ( typeof arg === 'function' ) {
return arg . toString ( ) . replace ( / ?\{[\s\S]*$/ , '' ) ;
} else if ( typeof arg === 'undefined' ) {
return 'undefined' ;
} else if ( typeof arg !== 'string' ) {
return toJson ( arg ) ;
}
return arg ;
2016-03-28 10:46:51 +00:00
}
return match ;
} ) ;
2016-04-18 12:34:29 +00:00
message = message + '\nhttp://errors.angularjs.org/1.2.9/' +
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 ++ ) {
message = message + ( i == 2 ? '?' : '&' ) + 'p' + ( i - 2 ) + '=' +
encodeURIComponent ( stringify ( arguments [ i ] ) ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
return new Error ( message ) ;
2016-03-28 10:46:51 +00:00
} ;
}
/* We need to tell jshint what variables are being exported */
2016-04-18 12:34:29 +00:00
/ * g l o b a l
- angular ,
- msie ,
- jqLite ,
- jQuery ,
- slice ,
- push ,
- toString ,
- ngMinErr ,
- _angular ,
- angularModule ,
- nodeName _ ,
- uid ,
- lowercase ,
- uppercase ,
- manualLowercase ,
- manualUppercase ,
- nodeName _ ,
- isArrayLike ,
- forEach ,
- sortedKeys ,
- forEachSorted ,
- reverseParams ,
- nextUid ,
- setHashKey ,
- extend ,
- int ,
- inherit ,
- noop ,
- identity ,
- valueFn ,
- isUndefined ,
- isDefined ,
- isObject ,
- isString ,
- isNumber ,
- isDate ,
- isArray ,
- isFunction ,
- isRegExp ,
- isWindow ,
- isScope ,
- isFile ,
- isBoolean ,
- trim ,
- isElement ,
- makeMap ,
- map ,
- size ,
- includes ,
- indexOf ,
- arrayRemove ,
- isLeafNode ,
- copy ,
- shallowCopy ,
- equals ,
- csp ,
- concat ,
- sliceArgs ,
- bind ,
- toJsonReplacer ,
- toJson ,
- fromJson ,
- toBoolean ,
- startingTag ,
- tryDecodeURIComponent ,
- parseKeyValue ,
- toKeyValue ,
- encodeUriSegment ,
- encodeUriQuery ,
- angularInit ,
- bootstrap ,
- snake _case ,
- bindJQuery ,
- assertArg ,
- assertArgFn ,
- assertNotHasOwnProperty ,
- getter ,
- getBlockElements ,
2016-03-28 10:46:51 +00:00
* /
////////////////////////////////////
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name angular . lowercase
* @ 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
* /
2016-04-18 12:34:29 +00:00
var lowercase = function ( string ) { return isString ( string ) ? string . toLowerCase ( ) : string ; } ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/ * *
* @ ngdoc function
* @ name angular . uppercase
* @ function
*
* @ description Converts the specified string to uppercase .
* @ param { string } string String to be converted to uppercase .
* @ returns { string } Uppercased string .
* /
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 ;
}
2016-04-18 12:34:29 +00:00
var /** holds major version number for IE or NaN for real browsers */
msie ,
2016-03-28 10:46:51 +00:00
jqLite , // delay binding since jQuery could be loaded after us.
jQuery , // delay binding
slice = [ ] . slice ,
push = [ ] . push ,
toString = Object . prototype . toString ,
ngMinErr = minErr ( 'ng' ) ,
2016-04-18 12:34:29 +00:00
_angular = window . angular ,
2016-03-28 10:46:51 +00:00
/** @name angular */
angular = window . angular || ( window . angular = { } ) ,
angularModule ,
2016-04-18 12:34:29 +00:00
nodeName _ ,
uid = [ '0' , '0' , '0' ] ;
2016-03-28 10:46:51 +00:00
/ * *
2016-04-18 12:34:29 +00:00
* IE 11 changed the format of the UserAgent string .
* See http : //msdn.microsoft.com/en-us/library/ms537503.aspx
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
msie = int ( ( /msie (\d+)/ . exec ( lowercase ( navigator . userAgent ) ) || [ ] ) [ 1 ] ) ;
if ( isNaN ( msie ) ) {
msie = int ( ( /trident\/.*; rv:(\d+)/ . exec ( lowercase ( navigator . userAgent ) ) || [ ] ) [ 1 ] ) ;
}
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
2016-04-18 12:34:29 +00:00
var length = obj . length ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( obj . nodeType === 1 && length ) {
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
2016-04-18 12:34:29 +00:00
* @ 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
2016-04-18 12:34:29 +00:00
* object or an array . The ` iterator ` function is invoked with ` iterator(value, key) ` , where ` value `
* is the value of an object property or an array element and ` key ` is the object property key or
* array element index . Specifying a ` context ` for the function is optional .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* It is worth nothing that ` .forEach ` does not iterate over inherited properties because it filters
2016-03-28 10:46:51 +00:00
* using the ` hasOwnProperty ` method .
*
2016-04-18 12:34:29 +00:00
< pre >
2016-03-28 10:46:51 +00:00
var values = { name : 'misko' , gender : 'male' } ;
var log = [ ] ;
2016-04-18 12:34:29 +00:00
angular . forEach ( values , function ( value , key ) {
2016-03-28 10:46:51 +00:00
this . push ( key + ': ' + value ) ;
} , log ) ;
2016-04-18 12:34:29 +00:00
expect ( log ) . toEqual ( [ 'name: misko' , 'gender:male' ] ) ;
< / p r e >
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 ` .
* /
function forEach ( obj , iterator , context ) {
2016-04-18 12:34:29 +00:00
var key ;
2016-03-28 10:46:51 +00:00
if ( obj ) {
2016-04-18 12:34:29 +00: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 ) ) ) {
2016-04-18 12:34:29 +00:00
iterator . call ( context , obj [ key ] , key ) ;
2016-03-28 10:46:51 +00:00
}
}
} else if ( obj . forEach && obj . forEach !== forEach ) {
2016-04-18 12:34:29 +00:00
obj . forEach ( iterator , context ) ;
} else if ( isArrayLike ( obj ) ) {
for ( key = 0 ; key < obj . length ; key ++ )
iterator . call ( context , obj [ key ] , key ) ;
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 ) ) {
iterator . call ( context , obj [ key ] , key ) ;
2016-03-28 10:46:51 +00:00
}
}
}
}
return obj ;
}
2016-04-18 12:34:29 +00:00
function sortedKeys ( obj ) {
var keys = [ ] ;
for ( var key in obj ) {
if ( obj . hasOwnProperty ( key ) ) {
keys . push ( key ) ;
}
}
return keys . sort ( ) ;
}
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 ) ;
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
}
/ * *
2016-04-18 12:34:29 +00:00
* A consistent way of creating unique IDs in angular . The ID is a sequence of alpha numeric
* characters such as '012ABC' . The reason why we are not using simply a number counter is that
* the number string gets longer over time , and it can also overflow , where as the nextId
* will grow much slower , it is a string , and it will never overflow .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ returns an unique alpha - numeric string
2016-03-28 10:46:51 +00:00
* /
function nextUid ( ) {
2016-04-18 12:34:29 +00:00
var index = uid . length ;
var digit ;
while ( index ) {
index -- ;
digit = uid [ index ] . charCodeAt ( 0 ) ;
if ( digit == 57 /*'9'*/ ) {
uid [ index ] = 'A' ;
return uid . join ( '' ) ;
}
if ( digit == 90 /*'Z'*/ ) {
uid [ index ] = '0' ;
} else {
uid [ index ] = String . fromCharCode ( digit + 1 ) ;
return uid . join ( '' ) ;
}
}
uid . unshift ( '0' ) ;
return uid . join ( '' ) ;
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 ;
}
2016-04-18 12:34:29 +00:00
else {
delete obj . $$hashKey ;
2016-03-28 10:46:51 +00:00
}
}
/ * *
* @ ngdoc function
* @ name angular . extend
2016-04-18 12:34:29 +00:00
* @ function
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* Extends the destination object ` dst ` by copying all of the properties from the ` src ` object ( s )
* to ` dst ` . You can specify multiple ` src ` objects .
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 ;
forEach ( arguments , function ( obj ) {
if ( obj !== dst ) {
forEach ( obj , function ( value , key ) {
dst [ key ] = value ;
} ) ;
}
} ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
setHashKey ( dst , h ) ;
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 ) {
2016-04-18 12:34:29 +00:00
return extend ( new ( extend ( function ( ) { } , { prototype : parent } ) ) ( ) , extra ) ;
2016-03-28 10:46:51 +00:00
}
/ * *
* @ ngdoc function
* @ name angular . noop
2016-04-18 12:34:29 +00:00
* @ 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 .
2016-04-18 12:34:29 +00:00
< pre >
2016-03-28 10:46:51 +00:00
function foo ( callback ) {
var result = calculateResult ( ) ;
( callback || angular . noop ) ( result ) ;
}
2016-04-18 12:34:29 +00:00
< / p r e >
2016-03-28 10:46:51 +00:00
* /
function noop ( ) { }
noop . $inject = [ ] ;
/ * *
* @ ngdoc function
* @ name angular . identity
2016-04-18 12:34:29 +00:00
* @ 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 .
*
2016-04-18 12:34:29 +00:00
< pre >
2016-03-28 10:46:51 +00:00
function transformer ( transformationFn , value ) {
return ( transformationFn || angular . identity ) ( value ) ;
} ;
2016-04-18 12:34:29 +00:00
< / p r e >
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
2016-04-18 12:34:29 +00:00
* @ 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 .
* /
2016-04-18 12:34:29 +00:00
function isUndefined ( value ) { return typeof value === 'undefined' ; }
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc function
* @ name angular . isDefined
2016-04-18 12:34:29 +00:00
* @ 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 .
* /
2016-04-18 12:34:29 +00:00
function isDefined ( value ) { return typeof value !== 'undefined' ; }
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc function
* @ name angular . isObject
2016-04-18 12:34:29 +00:00
* @ 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
2016-04-18 12:34:29 +00:00
* considered to be 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 ` .
* /
2016-04-18 12:34:29 +00:00
function isObject ( value ) { return value != null && typeof value === 'object' ; }
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc function
* @ name angular . isString
2016-04-18 12:34:29 +00:00
* @ 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 ` .
* /
2016-04-18 12:34:29 +00:00
function isString ( value ) { return typeof value === 'string' ; }
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc function
* @ name angular . isNumber
2016-04-18 12:34:29 +00:00
* @ function
2016-03-28 10:46:51 +00:00
*
* @ description
* Determines if a reference is a ` Number ` .
*
* @ param { * } value Reference to check .
* @ returns { boolean } True if ` value ` is a ` Number ` .
* /
2016-04-18 12:34:29 +00:00
function isNumber ( value ) { return typeof value === 'number' ; }
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc function
* @ name angular . isDate
2016-04-18 12:34:29 +00:00
* @ 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 ` .
* /
2016-04-18 12:34:29 +00:00
function isDate ( value ) {
2016-03-28 10:46:51 +00:00
return toString . call ( value ) === '[object Date]' ;
}
/ * *
* @ ngdoc function
* @ name angular . isArray
2016-04-18 12:34:29 +00:00
* @ 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 ` .
* /
2016-04-18 12:34:29 +00:00
function isArray ( value ) {
return toString . call ( value ) === '[object Array]' ;
}
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc function
* @ name angular . isFunction
2016-04-18 12:34:29 +00:00
* @ 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 ` .
* /
2016-04-18 12:34:29 +00: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 ) {
2016-04-18 12:34:29 +00:00
return obj && obj . document && obj . location && obj . alert && obj . setInterval ;
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]' ;
}
function isBoolean ( value ) {
return typeof value === 'boolean' ;
}
2016-04-18 12:34:29 +00:00
var trim = ( function ( ) {
// native trim is way faster: http://jsperf.com/angular-trim-test
// but IE doesn't have it... :-(
// TODO: we should move this into IE/ES5 polyfill
if ( ! String . prototype . trim ) {
return function ( value ) {
return isString ( value ) ? value . replace ( /^\s\s*/ , '' ) . replace ( /\s\s*$/ , '' ) : value ;
} ;
}
return function ( value ) {
return isString ( value ) ? value . trim ( ) : value ;
} ;
} ) ( ) ;
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc function
* @ name angular . isElement
2016-04-18 12:34:29 +00:00
* @ 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
2016-04-18 12:34:29 +00:00
|| ( node . on && 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 , ... }
* /
2016-04-18 12:34:29 +00:00
function makeMap ( str ) {
var obj = { } , items = str . split ( "," ) , i ;
for ( i = 0 ; i < items . length ; i ++ )
obj [ items [ i ] ] = true ;
2016-03-28 10:46:51 +00:00
return obj ;
}
2016-04-18 12:34:29 +00:00
if ( msie < 9 ) {
nodeName _ = function ( element ) {
element = element . nodeName ? element : element [ 0 ] ;
return ( element . scopeName && element . scopeName != 'HTML' )
? uppercase ( element . scopeName + ':' + element . nodeName ) : element . nodeName ;
} ;
} else {
nodeName _ = function ( element ) {
return element . nodeName ? element . nodeName : element [ 0 ] . nodeName ;
} ;
}
function map ( obj , iterator , context ) {
var results = [ ] ;
forEach ( obj , function ( value , index , list ) {
results . push ( iterator . call ( context , value , index , list ) ) ;
} ) ;
return results ;
}
/ * *
* @ description
* Determines the number of elements in an array , the number of properties an object has , or
* the length of a string .
*
* Note : This function is used to augment the Object type in Angular expressions . See
* { @ link angular . Object } for more information about Angular arrays .
*
* @ param { Object | Array | string } obj Object , array , or string to inspect .
* @ param { boolean } [ ownPropsOnly = false ] Count only "own" properties in an object
* @ returns { number } The size of ` obj ` or ` 0 ` if ` obj ` is neither an object nor an array .
* /
function size ( obj , ownPropsOnly ) {
var count = 0 , key ;
if ( isArray ( obj ) || isString ( obj ) ) {
return obj . length ;
} else if ( isObject ( obj ) ) {
for ( key in obj )
if ( ! ownPropsOnly || obj . hasOwnProperty ( key ) )
count ++ ;
}
return count ;
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 includes ( array , obj ) {
2016-04-18 12:34:29 +00:00
return indexOf ( array , obj ) != - 1 ;
}
function indexOf ( array , obj ) {
if ( array . indexOf ) return array . indexOf ( obj ) ;
for ( var i = 0 ; i < array . length ; i ++ ) {
if ( obj === array [ i ] ) return i ;
}
return - 1 ;
2016-03-28 10:46:51 +00:00
}
function arrayRemove ( array , value ) {
2016-04-18 12:34:29 +00:00
var index = indexOf ( array , 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 ;
}
function isLeafNode ( node ) {
if ( node ) {
switch ( node . nodeName ) {
case "OPTION" :
case "PRE" :
case "TITLE" :
return true ;
}
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
return false ;
2016-03-28 10:46:51 +00:00
}
/ * *
* @ ngdoc function
* @ name angular . copy
2016-04-18 12:34:29 +00:00
* @ 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 .
2016-04-18 12:34:29 +00:00
* * If a destination is provided , all of its elements ( for array ) 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
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
< div ng - controller = "Controller" >
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 >
2016-04-18 12:34:29 +00:00
function Controller ( $scope ) {
$scope . master = { } ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
$scope . update = function ( user ) {
// Example with 1 argument
$scope . master = angular . copy ( user ) ;
} ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
$scope . reset = function ( ) {
// Example with 2 arguments
angular . copy ( $scope . master , $scope . user ) ;
} ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
$scope . reset ( ) ;
}
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2016-04-18 12:34:29 +00:00
< / d o c : s o u r c e >
< / d o c : e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
function copy ( source , destination ) {
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 ) ) {
destination = copy ( source , [ ] ) ;
} else if ( isDate ( source ) ) {
destination = new Date ( source . getTime ( ) ) ;
} else if ( isRegExp ( source ) ) {
destination = new RegExp ( source . source ) ;
} else if ( isObject ( source ) ) {
destination = copy ( source , { } ) ;
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." ) ;
if ( isArray ( source ) ) {
destination . length = 0 ;
for ( var i = 0 ; i < source . length ; i ++ ) {
destination . push ( copy ( source [ i ] ) ) ;
2016-03-28 10:46:51 +00:00
}
} else {
2016-04-18 12:34:29 +00:00
var h = destination . $$hashKey ;
forEach ( destination , function ( value , key ) {
delete destination [ key ] ;
} ) ;
for ( var key in source ) {
destination [ key ] = copy ( source [ key ] ) ;
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
}
}
2016-04-18 12:34:29 +00:00
return destination ;
2016-03-28 10:46:51 +00:00
}
/ * *
2016-04-18 12:34:29 +00:00
* Create a shallow copy of an object
2016-03-28 10:46:51 +00:00
* /
function shallowCopy ( src , dst ) {
2016-04-18 12:34:29 +00:00
dst = dst || { } ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
for ( var key in src ) {
// shallowCopy is only ever called by $compile nodeLinkFn, which has control over src
// so we don't need to worry about using our custom hasOwnProperty here
if ( src . hasOwnProperty ( key ) && key . charAt ( 0 ) !== '$' && key . charAt ( 1 ) !== '$' ) {
dst [ key ] = src [ key ] ;
2016-03-28 10:46:51 +00:00
}
}
2016-04-18 12:34:29 +00:00
return dst ;
2016-03-28 10:46:51 +00:00
}
/ * *
* @ ngdoc function
* @ name angular . equals
2016-04-18 12:34:29 +00:00
* @ 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 )
2016-04-18 12:34:29 +00:00
* * Both values represent the same regular expression ( In JavasScript ,
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 ) {
for ( key = 0 ; key < length ; key ++ ) {
if ( ! equals ( o1 [ key ] , o2 [ key ] ) ) return false ;
}
return true ;
}
} else if ( isDate ( o1 ) ) {
return isDate ( o2 ) && o1 . getTime ( ) == o2 . getTime ( ) ;
} else if ( isRegExp ( o1 ) && isRegExp ( o2 ) ) {
return o1 . toString ( ) == o2 . toString ( ) ;
} else {
if ( isScope ( o1 ) || isScope ( o2 ) || isWindow ( o1 ) || isWindow ( o2 ) || isArray ( o2 ) ) return false ;
keySet = { } ;
for ( key in o1 ) {
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 ;
}
for ( key in o2 ) {
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 ;
}
2016-04-18 12:34:29 +00:00
function csp ( ) {
return ( document . securityPolicy && document . securityPolicy . isActive ) ||
( document . querySelector &&
! ! ( document . querySelector ( '[ng-csp]' ) || document . querySelector ( '[data-ng-csp]' ) ) ) ;
}
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
2016-04-18 12:34:29 +00:00
* @ 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
2016-04-18 12:34:29 +00:00
? fn . apply ( self , curryArgs . concat ( slice . call ( 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 ;
2016-04-18 12:34:29 +00:00
if ( typeof key === 'string' && key . charAt ( 0 ) === '$' ) {
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
2016-04-18 12:34:29 +00:00
* @ function
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00: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 .
2016-04-18 12:34:29 +00:00
* @ param { boolean = } pretty If set to true , the JSON output will contain newlines and whitespace .
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 ;
return JSON . stringify ( obj , toJsonReplacer , pretty ? ' ' : null ) ;
2016-03-28 10:46:51 +00:00
}
/ * *
* @ ngdoc function
* @ name angular . fromJson
2016-04-18 12:34:29 +00:00
* @ function
2016-03-28 10:46:51 +00:00
*
* @ description
* Deserializes a JSON string .
*
* @ param { string } json JSON string to deserialize .
2016-04-18 12:34:29 +00:00
* @ returns { Object | Array | Date | string | number } Deserialized thingy .
2016-03-28 10:46:51 +00:00
* /
function fromJson ( json ) {
return isString ( json )
? JSON . parse ( json )
: json ;
}
2016-04-18 12:34:29 +00:00
function toBoolean ( value ) {
if ( typeof value === 'function' ) {
value = true ;
} else if ( value && value . length !== 0 ) {
var v = lowercase ( "" + value ) ;
value = ! ( v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]' ) ;
} else {
value = false ;
}
return value ;
2016-03-28 10:46:51 +00:00
}
/ * *
* @ 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 ( ) ;
2016-04-18 12:34:29 +00:00
} catch ( e ) { }
// As Per DOM Standards
var TEXT _NODE = 3 ;
2016-03-28 10:46:51 +00:00
var elemHtml = jqLite ( '<div>' ) . append ( element ) . html ( ) ;
try {
2016-04-18 12:34:29 +00:00
return element [ 0 ] . nodeType === TEXT _NODE ? 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 ) ; } ) ;
} 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 ) ;
2016-04-18 12:34:29 +00: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 .
2016-04-18 12:34:29 +00:00
* @ returns Object . < ( string | boolean ) >
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 ;
forEach ( ( keyValue || "" ) . split ( '&' ) , function ( keyValue ) {
if ( keyValue ) {
key _value = keyValue . split ( '=' ) ;
key = tryDecodeURIComponent ( key _value [ 0 ] ) ;
if ( isDefined ( key ) ) {
var val = isDefined ( key _value [ 1 ] ) ? tryDecodeURIComponent ( key _value [ 1 ] ) : true ;
if ( ! obj [ key ] ) {
2016-03-28 10:46:51 +00:00
obj [ key ] = val ;
2016-04-18 12:34:29 +00: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 , ',' ) .
replace ( /%20/g , ( pctEncodeSpaces ? '%20' : '+' ) ) ;
}
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : ngApp
2016-03-28 10:46:51 +00:00
*
* @ element ANY
* @ param { angular . Module } ngApp an optional application
* { @ link angular . module module } name to load .
*
* @ 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
2016-04-18 12:34:29 +00:00
* module will be loaded into the { @ link AUTO . $injector } when the application is bootstrapped and
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 ` .
*
2016-04-18 12:34:29 +00: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 } }
< / 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 >
*
2016-04-18 12:34:29 +00:00
* /
function angularInit ( element , bootstrap ) {
var elements = [ element ] ,
appElement ,
module ,
names = [ 'ng:app' , 'ng-app' , 'x-ng-app' , 'data-ng-app' ] ,
NG _APP _CLASS _REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/ ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
function append ( element ) {
element && elements . push ( element ) ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
forEach ( names , function ( name ) {
names [ name ] = true ;
append ( document . getElementById ( name ) ) ;
name = name . replace ( ':' , '\\:' ) ;
if ( element . querySelectorAll ) {
forEach ( element . querySelectorAll ( '.' + name ) , append ) ;
forEach ( element . querySelectorAll ( '.' + name + '\\:' ) , append ) ;
forEach ( element . querySelectorAll ( '[' + name + ']' ) , append ) ;
2016-03-28 10:46:51 +00:00
}
} ) ;
2016-04-18 12:34:29 +00:00
forEach ( elements , function ( element ) {
if ( ! appElement ) {
var className = ' ' + element . className + ' ' ;
var match = NG _APP _CLASS _REGEXP . exec ( className ) ;
if ( match ) {
appElement = element ;
module = ( match [ 2 ] || '' ) . replace ( /\s+/g , ',' ) ;
} else {
forEach ( element . attributes , function ( attr ) {
if ( ! appElement && names [ attr . name ] ) {
appElement = element ;
module = attr . value ;
}
} ) ;
}
2016-03-28 10:46:51 +00:00
}
} ) ;
if ( appElement ) {
2016-04-18 12:34:29 +00:00
bootstrap ( appElement , module ? [ module ] : [ ] ) ;
2016-03-28 10:46:51 +00:00
}
}
/ * *
* @ ngdoc function
* @ name angular . bootstrap
* @ 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
*
2016-04-18 12:34:29 +00:00
* Note that ngScenario - based end - to - end tests cannot use this function to bootstrap manually .
* They must use { @ link api / ng . directive : ngApp ngApp } .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ param { Element } 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 )
2016-04-18 12:34:29 +00:00
* function that will be invoked by the injector as a run block .
2016-03-28 10:46:51 +00:00
* See : { @ link angular . module modules }
2016-04-18 12:34:29 +00:00
* @ returns { AUTO . $injector } Returns the newly created injector for this app .
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
function bootstrap ( element , modules ) {
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 ) ;
2016-04-18 12:34:29 +00:00
throw ngMinErr ( 'btstrpd' , "App Already Bootstrapped with this Element '{0}'" , tag ) ;
2016-03-28 10:46:51 +00:00
}
modules = modules || [ ] ;
modules . unshift ( [ '$provide' , function ( $provide ) {
$provide . value ( '$rootElement' , element ) ;
} ] ) ;
modules . unshift ( 'ng' ) ;
2016-04-18 12:34:29 +00:00
var injector = createInjector ( modules ) ;
injector . invoke ( [ '$rootScope' , '$rootElement' , '$compile' , '$injector' , '$animate' ,
function ( scope , element , compile , injector , animate ) {
2016-03-28 10:46:51 +00:00
scope . $apply ( function ( ) {
element . data ( '$injector' , injector ) ;
compile ( element ) ( scope ) ;
} ) ;
} ]
) ;
return injector ;
} ;
var NG _DEFER _BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/ ;
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 ) ;
} ) ;
2016-04-18 12:34:29 +00:00
doBootstrap ( ) ;
2016-03-28 10:46:51 +00:00
} ;
}
var SNAKE _CASE _REGEXP = /[A-Z]/g ;
2016-04-18 12:34:29 +00: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 ( ) ;
} ) ;
}
function bindJQuery ( ) {
// bind to jQuery if present;
2016-04-18 12:34:29 +00:00
jQuery = window . jQuery ;
// reset to jQuery or default to us.
if ( jQuery ) {
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
} ) ;
2016-04-18 12:34:29 +00:00
// Method signature:
// jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments)
jqLitePatchJQueryRemove ( 'remove' , true , true , false ) ;
jqLitePatchJQueryRemove ( 'empty' , false , false , false ) ;
jqLitePatchJQueryRemove ( 'html' , false , false , true ) ;
2016-03-28 10:46:51 +00:00
} else {
jqLite = JQLite ;
}
angular . element = jqLite ;
}
/ * *
* 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 ' +
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00:00
* @ param { string } path path to traverse
* @ param { boolean = true } bindFnToScope
* @ returns 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
2016-04-18 12:34:29 +00:00
* @ returns jQlite object containing the elements
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
function getBlockElements ( nodes ) {
var startNode = nodes [ 0 ] ,
endNode = nodes [ nodes . length - 1 ] ;
if ( startNode === endNode ) {
return jqLite ( startNode ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
var element = startNode ;
var elements = [ element ] ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
do {
element = element . nextSibling ;
if ( ! element ) break ;
elements . push ( element ) ;
} while ( element !== endNode ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
return jqLite ( elements ) ;
2016-03-28 10:46:51 +00:00
}
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc interface
2016-03-28 10:46:51 +00:00
* @ name angular . Module
* @ 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
* @ 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
*
2016-04-18 12:34:29 +00:00
* A module is a collection of services , directives , filters , and configuration information .
* ` angular.module ` is used to configure the { @ link AUTO . $injector $injector } .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* < pre >
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.
2016-04-18 12:34:29 +00:00
* myModule . config ( function ( $locationProvider ) {
2016-03-28 10:46:51 +00:00
* // Configure existing providers
* $locationProvider . hashPrefix ( '!' ) ;
2016-04-18 12:34:29 +00:00
* } ) ;
* < / p r e >
2016-03-28 10:46:51 +00:00
*
* Then you can create an injector and load your modules like this :
*
2016-04-18 12:34:29 +00:00
* < pre >
* var injector = angular . injector ( [ 'ng' , 'MyModule' ] )
* < / p r e >
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 .
2016-04-18 12:34:29 +00:00
* @ param { Array . < string >= } requires If specified then new module is being created . If
* unspecified then the the module is being retrieved for further configuration .
* @ param { Function } configFn Optional configuration function for the module . Same as
* { @ link angular . Module # methods _config Module # config ( ) } .
* @ 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 = [ ] ;
/** @type {!Array.<Function>} */
var runBlocks = [ ] ;
2016-04-18 12:34:29 +00:00
var config = invokeLater ( '$injector' , 'invoke' ) ;
2016-03-28 10:46:51 +00:00
/** @type {angular.Module} */
var moduleInstance = {
// Private state
_invokeQueue : invokeQueue ,
_runBlocks : runBlocks ,
/ * *
* @ ngdoc property
* @ name angular . Module # requires
2016-04-18 12:34:29 +00:00
* @ propertyOf angular . Module
* @ returns { Array . < string > } List of module names which must be loaded before this module .
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
2016-04-18 12:34:29 +00:00
* @ propertyOf angular . Module
* @ returns { string } Name of the module .
2016-03-28 10:46:51 +00:00
* @ description
* /
name : name ,
/ * *
* @ ngdoc method
* @ name angular . Module # provider
2016-04-18 12:34:29 +00:00
* @ methodOf angular . Module
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
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00:00
* @ methodOf angular . Module
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
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00:00
* @ methodOf angular . Module
2016-03-28 10:46:51 +00:00
* @ param { string } name service name
* @ param { Function } constructor A constructor function that will be instantiated .
* @ description
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00:00
* @ methodOf angular . Module
2016-03-28 10:46:51 +00:00
* @ param { string } name service name
* @ param { * } object Service instance object .
* @ description
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00:00
* @ methodOf angular . Module
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 .
* 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
2016-04-18 12:34:29 +00:00
* @ methodOf angular . Module
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
*
2016-04-18 12:34:29 +00:00
* < pre >
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
* }
* }
* }
* } )
2016-04-18 12:34:29 +00:00
* < / p r e >
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* See { @ link ngAnimate . $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
2016-04-18 12:34:29 +00:00
* @ methodOf angular . Module
* @ param { string } name Filter name .
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 ( ) } .
* /
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
2016-04-18 12:34:29 +00:00
* @ methodOf angular . Module
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
2016-04-18 12:34:29 +00:00
* @ methodOf angular . Module
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
2016-04-18 12:34:29 +00:00
* See { @ link ng . $compileProvider # methods _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
2016-04-18 12:34:29 +00:00
* @ methodOf angular . Module
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 .
* /
config : config ,
/ * *
* @ ngdoc method
* @ name angular . Module # run
2016-04-18 12:34:29 +00:00
* @ methodOf angular . Module
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 ) ;
}
2016-04-18 12:34:29 +00:00
return moduleInstance ;
2016-03-28 10:46:51 +00:00
/ * *
* @ param { string } provider
* @ param { string } method
* @ param { String = } insertMethod
* @ returns { angular . Module }
* /
2016-04-18 12:34:29 +00:00
function invokeLater ( provider , method , insertMethod ) {
2016-03-28 10:46:51 +00:00
return function ( ) {
2016-04-18 12:34:29 +00:00
invokeQueue [ insertMethod || 'push' ] ( [ provider , method , arguments ] ) ;
2016-03-28 10:46:51 +00:00
return moduleInstance ;
} ;
}
} ) ;
} ;
} ) ;
}
2016-04-18 12:34:29 +00:00
/ * g l o b a l
angularModule : true ,
version : true ,
2016-05-18 00:10:50 +00:00
2016-04-18 12:34:29 +00:00
$LocaleProvider ,
$CompileProvider ,
2016-05-18 00:10:50 +00:00
2016-04-18 12:34:29 +00:00
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 ,
requiredDirective ,
requiredDirective ,
ngValueDirective ,
ngAttributeAliasDirectives ,
ngEventDirectives ,
$AnchorScrollProvider ,
$AnimateProvider ,
$BrowserProvider ,
$CacheFactoryProvider ,
$ControllerProvider ,
$DocumentProvider ,
$ExceptionHandlerProvider ,
$FilterProvider ,
$InterpolateProvider ,
$IntervalProvider ,
$HttpProvider ,
$HttpBackendProvider ,
$LocationProvider ,
$LogProvider ,
$ParseProvider ,
$RootScopeProvider ,
$QProvider ,
$$SanitizeUriProvider ,
$SceProvider ,
$SceDelegateProvider ,
$SnifferProvider ,
$TemplateCacheProvider ,
$TimeoutProvider ,
$WindowProvider
2016-03-28 10:46:51 +00:00
* /
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc property
2016-03-28 10:46:51 +00:00
* @ name angular . version
* @ 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 = {
2016-04-18 12:34:29 +00:00
full : '1.2.9' , // all of these placeholder strings will be replaced by grunt's
2016-03-28 10:46:51 +00:00
major : 1 , // package task
2016-04-18 12:34:29 +00:00
minor : 2 ,
dot : 9 ,
codeName : 'enchanted-articulacy'
2016-03-28 10:46:51 +00:00
} ;
2016-04-18 12:34:29 +00: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 ,
2016-04-18 12:34:29 +00:00
'noop' : noop ,
'bind' : bind ,
2016-03-28 10:46:51 +00:00
'toJson' : toJson ,
'fromJson' : fromJson ,
2016-04-18 12:34:29 +00: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 } ,
'$$minErr' : minErr ,
2016-04-18 12:34:29 +00:00
'$$csp' : csp
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 ,
required : requiredDirective ,
ngRequired : requiredDirective ,
2016-04-18 12:34:29 +00:00
ngValue : ngValueDirective
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 ,
$sce : $SceProvider ,
$sceDelegate : $SceDelegateProvider ,
$sniffer : $SnifferProvider ,
$templateCache : $TemplateCacheProvider ,
$timeout : $TimeoutProvider ,
2016-04-18 12:34:29 +00:00
$window : $WindowProvider
2016-03-28 10:46:51 +00:00
} ) ;
}
] ) ;
}
2016-04-18 12:34:29 +00:00
/ * g l o b a l
- JQLitePrototype ,
- addEventListenerFn ,
- removeEventListenerFn ,
- BOOLEAN _ATTR
2016-03-28 10:46:51 +00:00
* /
//////////////////////////////////
//JQLite
//////////////////////////////////
/ * *
* @ ngdoc function
* @ name angular . element
2016-04-18 12:34:29 +00:00
* @ 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
*
2016-04-18 12:34:29 +00:00
* To use jQuery , simply load it before ` DOMContentLoaded ` event fired .
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/)
2016-04-18 12:34:29 +00:00
* - [ ` attr() ` ] ( http : //api.jquery.com/attr/)
* - [ ` bind() ` ] ( http : //api.jquery.com/on/) - 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/)
2016-04-18 12:34:29 +00:00
* - [ ` css() ` ] ( http : //api.jquery.com/css/)
2016-03-28 10:46:51 +00:00
* - [ ` data() ` ] ( http : //api.jquery.com/data/)
* - [ ` 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.
2016-04-18 12:34:29 +00:00
* - [ ` unbind() ` ] ( http : //api.jquery.com/off/) - 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 .
2016-04-18 12:34:29 +00:00
* - ` scope() ` - retrieves the { @ link api / ng . $rootScope . Scope scope } of the current
* element or its parent .
* - ` isolateScope() ` - retrieves an isolate { @ link api / 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 .
* - ` 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 .
* /
var jqCache = JQLite . cache = { } ,
2016-04-18 12:34:29 +00:00
jqName = JQLite . expando = 'ng-' + new Date ( ) . getTime ( ) ,
2016-03-28 10:46:51 +00:00
jqId = 1 ,
2016-04-18 12:34:29 +00:00
addEventListenerFn = ( window . document . addEventListener
? function ( element , type , fn ) { element . addEventListener ( type , fn , false ) ; }
: function ( element , type , fn ) { element . attachEvent ( 'on' + type , fn ) ; } ) ,
removeEventListenerFn = ( window . document . removeEventListener
? function ( element , type , fn ) { element . removeEventListener ( type , fn , false ) ; }
: function ( element , type , fn ) { element . detachEvent ( 'on' + type , fn ) ; } ) ;
2016-03-28 10:46:51 +00:00
function jqNextId ( ) { return ++ jqId ; }
var SPECIAL _CHARS _REGEXP = /([\:\-\_]+(.))/g ;
var MOZ _HACK _REGEXP = /^moz([A-Z])/ ;
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' ) ;
}
2016-04-18 12:34:29 +00:00
/////////////////////////////////////////////
// jQuery mutation patch
//
// In conjunction with bindJQuery intercepts all jQuery's DOM destruction apis and fires a
// $destroy event on all DOM nodes being removed.
//
/////////////////////////////////////////////
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
function jqLitePatchJQueryRemove ( name , dispatchThis , filterElems , getterIfNoArguments ) {
var originalJqFn = jQuery . fn [ name ] ;
originalJqFn = originalJqFn . $original || originalJqFn ;
removePatch . $original = originalJqFn ;
jQuery . fn [ name ] = removePatch ;
function removePatch ( param ) {
// jshint -W040
var list = filterElems && param ? [ this . filter ( param ) ] : [ this ] ,
fireEvent = dispatchThis ,
set , setIndex , setLength ,
element , childIndex , childLength , children ;
if ( ! getterIfNoArguments || param != null ) {
while ( list . length ) {
set = list . shift ( ) ;
for ( setIndex = 0 , setLength = set . length ; setIndex < setLength ; setIndex ++ ) {
element = jqLite ( set [ setIndex ] ) ;
if ( fireEvent ) {
element . triggerHandler ( '$destroy' ) ;
} else {
fireEvent = ! fireEvent ;
}
for ( childIndex = 0 , childLength = ( children = element . children ( ) ) . length ;
childIndex < childLength ;
childIndex ++ ) {
list . push ( jQuery ( children [ childIndex ] ) ) ;
}
}
}
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
return originalJqFn . apply ( this , arguments ) ;
2016-03-28 10:46:51 +00:00
}
}
/////////////////////////////////////////////
function JQLite ( element ) {
if ( element instanceof JQLite ) {
return element ;
}
if ( ! ( this instanceof JQLite ) ) {
2016-04-18 12:34:29 +00:00
if ( isString ( element ) && 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 ) ;
}
2016-04-18 12:34:29 +00:00
if ( isString ( element ) ) {
var div = document . createElement ( 'div' ) ;
// Read about the NoScope elements here:
// http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx
div . innerHTML = '<div> </div>' + element ; // IE insanity to make NoScope elements work!
div . removeChild ( div . firstChild ) ; // remove the superfluous div
jqLiteAddNodes ( this , div . childNodes ) ;
var fragment = jqLite ( document . createDocumentFragment ( ) ) ;
fragment . append ( this ) ; // detach the elements from the temporary DOM div.
2016-03-28 10:46:51 +00:00
} else {
jqLiteAddNodes ( this , element ) ;
}
}
function jqLiteClone ( element ) {
return element . cloneNode ( true ) ;
}
2016-04-18 12:34:29 +00:00
function jqLiteDealoc ( element ) {
jqLiteRemoveData ( element ) ;
for ( var i = 0 , children = element . childNodes || [ ] ; i < children . length ; i ++ ) {
jqLiteDealoc ( children [ 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' ) ;
2016-04-18 12:34:29 +00:00
var events = jqLiteExpandoStore ( element , 'events' ) ,
handle = jqLiteExpandoStore ( element , 'handle' ) ;
2016-03-28 10:46:51 +00:00
if ( ! handle ) return ; //no listeners registered
2016-04-18 12:34:29 +00:00
if ( isUndefined ( type ) ) {
forEach ( events , function ( eventHandler , type ) {
removeEventListenerFn ( element , type , eventHandler ) ;
2016-03-28 10:46:51 +00:00
delete events [ type ] ;
2016-04-18 12:34:29 +00:00
} ) ;
2016-03-28 10:46:51 +00:00
} else {
forEach ( type . split ( ' ' ) , function ( type ) {
2016-04-18 12:34:29 +00:00
if ( isUndefined ( fn ) ) {
removeEventListenerFn ( element , type , events [ type ] ) ;
delete events [ type ] ;
} else {
arrayRemove ( events [ type ] || [ ] , fn ) ;
2016-03-28 10:46:51 +00:00
}
} ) ;
}
}
function jqLiteRemoveData ( element , name ) {
2016-04-18 12:34:29 +00:00
var expandoId = element [ jqName ] ,
expandoStore = jqCache [ expandoId ] ;
2016-03-28 10:46:51 +00:00
if ( expandoStore ) {
if ( name ) {
2016-04-18 12:34:29 +00:00
delete jqCache [ expandoId ] . data [ name ] ;
2016-03-28 10:46:51 +00:00
return ;
}
if ( expandoStore . handle ) {
2016-04-18 12:34:29 +00:00
expandoStore . events . $destroy && expandoStore . handle ( { } , '$destroy' ) ;
2016-03-28 10:46:51 +00:00
jqLiteOff ( element ) ;
}
delete jqCache [ expandoId ] ;
2016-04-18 12:34:29 +00:00
element [ jqName ] = undefined ; // ie does not allow deletion of attributes on elements.
2016-03-28 10:46:51 +00:00
}
}
2016-04-18 12:34:29 +00:00
function jqLiteExpandoStore ( element , key , value ) {
var expandoId = element [ jqName ] ,
expandoStore = jqCache [ expandoId || - 1 ] ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( isDefined ( value ) ) {
if ( ! expandoStore ) {
element [ jqName ] = expandoId = jqNextId ( ) ;
expandoStore = jqCache [ expandoId ] = { } ;
}
expandoStore [ key ] = value ;
} else {
return expandoStore && expandoStore [ key ] ;
2016-03-28 10:46:51 +00:00
}
}
function jqLiteData ( element , key , value ) {
2016-04-18 12:34:29 +00:00
var data = jqLiteExpandoStore ( element , 'data' ) ,
isSetter = isDefined ( value ) ,
keyDefined = ! isSetter && isDefined ( key ) ,
isSimpleGetter = keyDefined && ! isObject ( key ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( ! data && ! isSimpleGetter ) {
jqLiteExpandoStore ( element , 'data' , data = { } ) ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( isSetter ) {
data [ key ] = value ;
} else {
if ( keyDefined ) {
if ( isSimpleGetter ) {
// don't create data in this case.
return data && data [ key ] ;
2016-03-28 10:46:51 +00:00
} else {
2016-04-18 12:34:29 +00:00
extend ( data , key ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
} else {
return data ;
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 , " " ) .
2016-04-18 12:34:29 +00: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 ) ) ;
}
}
function jqLiteAddNodes ( root , elements ) {
if ( elements ) {
2016-04-18 12:34:29 +00:00
elements = ( ! elements . nodeName && isDefined ( elements . length ) && ! isWindow ( elements ) )
? elements
: [ elements ] ;
for ( var i = 0 ; i < elements . length ; i ++ ) {
root . push ( elements [ i ] ) ;
2016-03-28 10:46:51 +00:00
}
}
}
function jqLiteController ( element , name ) {
2016-04-18 12:34:29 +00:00
return jqLiteInheritedData ( element , '$' + ( name || 'ngController' ) + 'Controller' ) ;
2016-03-28 10:46:51 +00:00
}
function jqLiteInheritedData ( element , name , value ) {
2016-04-18 12:34:29 +00:00
element = jqLite ( element ) ;
2016-03-28 10:46:51 +00:00
// if element is the document object work with the html element instead
// this makes $(document).scope() possible
2016-04-18 12:34:29 +00:00
if ( element [ 0 ] . nodeType == 9 ) {
element = element . find ( 'html' ) ;
2016-03-28 10:46:51 +00:00
}
var names = isArray ( name ) ? name : [ name ] ;
2016-04-18 12:34:29 +00:00
while ( element . length ) {
2016-03-28 10:46:51 +00:00
for ( var i = 0 , ii = names . length ; i < ii ; i ++ ) {
2016-04-18 12:34:29 +00:00
if ( ( value = element . data ( names [ i ] ) ) !== undefined ) return value ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
element = element . parent ( ) ;
2016-03-28 10:46:51 +00:00
}
}
function jqLiteEmpty ( element ) {
2016-04-18 12:34:29 +00:00
for ( var i = 0 , childNodes = element . childNodes ; i < childNodes . length ; i ++ ) {
jqLiteDealoc ( childNodes [ i ] ) ;
}
2016-03-28 10:46:51 +00:00
while ( element . firstChild ) {
element . removeChild ( element . firstChild ) ;
}
}
//////////////////////////////////////////
// Functions which are declared directly.
//////////////////////////////////////////
var JQLitePrototype = JQLite . prototype = {
ready : function ( fn ) {
var fired = false ;
function trigger ( ) {
if ( fired ) return ;
fired = true ;
fn ( ) ;
}
2016-04-18 12:34:29 +00:00
// check if document already is 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 = [ ] ;
2016-04-18 12:34:29 +00: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 ) {
2016-04-18 12:34:29 +00:00
BOOLEAN _ELEMENTS [ uppercase ( value ) ] = true ;
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
2016-04-18 12:34:29 +00:00
return booleanAttr && BOOLEAN _ELEMENTS [ element . nodeName ] && booleanAttr ;
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!
2016-04-18 12:34:29 +00:00
return jqLite ( element ) . data ( '$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!
2016-04-18 12:34:29 +00:00
return jqLite ( element ) . data ( '$isolateScope' ) || jqLite ( element ) . data ( '$isolateScopeNoTemplate' ) ;
2016-03-28 10:46:51 +00:00
} ,
2016-04-18 12:34:29 +00:00
controller : jqLiteController ,
2016-03-28 10:46:51 +00:00
injector : function ( element ) {
return jqLiteInheritedData ( element , '$injector' ) ;
} ,
2016-04-18 12:34:29 +00: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 {
2016-04-18 12:34:29 +00:00
var val ;
if ( msie <= 8 ) {
// this is some IE specific weirdness that jQuery 1.6.4 does not sure why
val = element . currentStyle && element . currentStyle [ name ] ;
if ( val === '' ) val = 'auto' ;
}
val = val || element . style [ name ] ;
if ( msie <= 8 ) {
// jquery weirdness :-/
val = ( val === '' ) ? undefined : val ;
}
return val ;
2016-03-28 10:46:51 +00:00
}
} ,
2016-04-18 12:34:29 +00:00
attr : function ( element , name , value ) {
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 ] ||
2016-04-18 12:34:29 +00: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 ( ) {
2016-04-18 12:34:29 +00:00
var NODE _TYPE _TEXT _PROPERTY = [ ] ;
if ( msie < 9 ) {
NODE _TYPE _TEXT _PROPERTY [ 1 ] = 'innerText' ; /** Element **/
NODE _TYPE _TEXT _PROPERTY [ 3 ] = 'nodeValue' ; /** Text **/
} else {
NODE _TYPE _TEXT _PROPERTY [ 1 ] = /** Element **/
NODE _TYPE _TEXT _PROPERTY [ 3 ] = 'textContent' ; /** Text **/
}
2016-03-28 10:46:51 +00:00
getText . $dv = '' ;
return getText ;
function getText ( element , value ) {
2016-04-18 12:34:29 +00:00
var textProp = NODE _TYPE _TEXT _PROPERTY [ element . nodeType ] ;
2016-03-28 10:46:51 +00:00
if ( isUndefined ( value ) ) {
2016-04-18 12:34:29 +00:00
return textProp ? element [ textProp ] : '' ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
element [ textProp ] = value ;
2016-03-28 10:46:51 +00:00
}
} ) ( ) ,
val : function ( element , value ) {
if ( isUndefined ( value ) ) {
2016-04-18 12:34:29 +00:00
if ( nodeName _ ( element ) === 'SELECT' && element . multiple ) {
2016-03-28 10:46:51 +00:00
var result = [ ] ;
2016-04-18 12:34:29 +00: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 ;
}
2016-04-18 12:34:29 +00:00
for ( var i = 0 , childNodes = element . childNodes ; i < childNodes . length ; i ++ ) {
jqLiteDealoc ( childNodes [ i ] ) ;
}
2016-03-28 10:46:51 +00:00
element . innerHTML = value ;
} ,
empty : jqLiteEmpty
2016-04-18 12:34:29 +00: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 ;
// 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
2016-04-18 12:34:29 +00:00
for ( i = 0 ; i < this . length ; 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.
var value = fn . $dv ;
// Only if we have $dv do we iterate over all, otherwise it is just the first element.
2016-04-18 12:34:29 +00:00
var jj = ( value === undefined ) ? Math . min ( this . length , 1 ) : this . length ;
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
2016-04-18 12:34:29 +00:00
for ( i = 0 ; i < this . length ; i ++ ) {
2016-03-28 10:46:51 +00:00
fn ( this [ i ] , arg1 , arg2 ) ;
}
// return self for chaining
return this ;
}
} ;
} ) ;
function createEventHandler ( element , events ) {
2016-04-18 12:34:29 +00:00
var eventHandler = function ( event , type ) {
if ( ! event . preventDefault ) {
event . preventDefault = function ( ) {
event . returnValue = false ; //ie
} ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( ! event . stopPropagation ) {
event . stopPropagation = function ( ) {
event . cancelBubble = true ; //ie
} ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( ! event . target ) {
event . target = event . srcElement || document ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( isUndefined ( event . defaultPrevented ) ) {
var prevent = event . preventDefault ;
event . preventDefault = function ( ) {
event . defaultPrevented = true ;
prevent . call ( event ) ;
2016-03-28 10:46:51 +00:00
} ;
2016-04-18 12:34:29 +00:00
event . defaultPrevented = false ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
event . isDefaultPrevented = function ( ) {
return event . defaultPrevented || event . returnValue === false ;
2016-03-28 10:46:51 +00:00
} ;
// Copy event handlers in case event handlers array is modified during execution.
2016-04-18 12:34:29 +00:00
var eventHandlersCopy = shallowCopy ( events [ type || event . type ] || [ ] ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
forEach ( eventHandlersCopy , function ( fn ) {
fn . call ( element , event ) ;
} ) ;
// Remove monkey-patched methods (IE),
// as they would cause memory leaks in IE8.
if ( msie <= 8 ) {
// IE7/8 does not allow to delete property on native object
event . preventDefault = null ;
event . stopPropagation = null ;
event . isDefaultPrevented = null ;
} else {
// It shouldn't affect normal browsers (native methods are defined on prototype).
delete event . preventDefault ;
delete event . stopPropagation ;
delete event . isDefaultPrevented ;
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 ,
2016-04-18 12:34:29 +00:00
dealoc : jqLiteDealoc ,
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
on : function onFn ( element , type , fn , unsupported ) {
if ( isDefined ( unsupported ) ) throw jqLiteMinErr ( 'onargs' , 'jqLite#on() does not support the `selector` or `eventData` parameters' ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
var events = jqLiteExpandoStore ( element , 'events' ) ,
handle = jqLiteExpandoStore ( element , 'handle' ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( ! events ) jqLiteExpandoStore ( element , 'events' , events = { } ) ;
if ( ! handle ) jqLiteExpandoStore ( element , 'handle' , handle = createEventHandler ( element , events ) ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
forEach ( type . split ( ' ' ) , function ( type ) {
2016-03-28 10:46:51 +00:00
var eventFns = events [ type ] ;
if ( ! eventFns ) {
2016-04-18 12:34:29 +00:00
if ( type == 'mouseenter' || type == 'mouseleave' ) {
var contains = document . body . contains || document . body . compareDocumentPosition ?
function ( a , b ) {
// jshint bitwise: false
var adown = a . nodeType === 9 ? a . documentElement : a ,
bup = b && b . parentNode ;
return a === bup || ! ! ( bup && bup . nodeType === 1 && (
adown . contains ?
adown . contains ( bup ) :
a . compareDocumentPosition && a . compareDocumentPosition ( bup ) & 16
) ) ;
} :
function ( a , b ) {
if ( b ) {
while ( ( b = b . parentNode ) ) {
if ( b === a ) {
return true ;
}
}
}
return false ;
} ;
events [ type ] = [ ] ;
// Refer to jQuery's implementation of mouseenter & mouseleave
// Read about mouseenter and mouseleave:
// http://www.quirksmode.org/js/events_mouse.html#link8
var eventmap = { mouseleave : "mouseout" , mouseenter : "mouseover" } ;
onFn ( element , eventmap [ type ] , function ( event ) {
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
if ( ! related || ( related !== target && ! contains ( target , related ) ) ) {
handle ( event , type ) ;
}
} ) ;
} else {
2016-03-28 10:46:51 +00:00
addEventListenerFn ( element , type , handle ) ;
2016-04-18 12:34:29 +00:00
events [ type ] = [ ] ;
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 ) ;
2016-04-18 12:34:29 +00: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 ) ;
2016-04-18 12:34:29 +00: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 = [ ] ;
2016-04-18 12:34:29 +00:00
forEach ( element . childNodes , function ( element ) {
if ( element . nodeType === 1 )
2016-03-28 10:46:51 +00:00
children . push ( element ) ;
} ) ;
return children ;
} ,
contents : function ( element ) {
2016-04-18 12:34:29 +00:00
return element . childNodes || [ ] ;
2016-03-28 10:46:51 +00:00
} ,
append : function ( element , node ) {
2016-04-18 12:34:29 +00:00
forEach ( new JQLite ( node ) , function ( child ) {
if ( element . nodeType === 1 || element . nodeType === 11 ) {
element . appendChild ( child ) ;
}
} ) ;
2016-03-28 10:46:51 +00:00
} ,
prepend : function ( element , node ) {
2016-04-18 12:34:29 +00:00
if ( element . nodeType === 1 ) {
2016-03-28 10:46:51 +00:00
var index = element . firstChild ;
2016-04-18 12:34:29 +00:00
forEach ( new JQLite ( node ) , function ( child ) {
2016-03-28 10:46:51 +00:00
element . insertBefore ( child , index ) ;
} ) ;
}
} ,
wrap : function ( element , wrapNode ) {
2016-04-18 12:34:29 +00:00
wrapNode = jqLite ( wrapNode ) [ 0 ] ;
var parent = element . parentNode ;
if ( parent ) {
parent . replaceChild ( wrapNode , element ) ;
}
wrapNode . appendChild ( element ) ;
2016-03-28 10:46:51 +00:00
} ,
2016-04-18 12:34:29 +00:00
remove : function ( element ) {
jqLiteDealoc ( element ) ;
var parent = element . parentNode ;
if ( parent ) parent . removeChild ( element ) ;
2016-03-28 10:46:51 +00:00
} ,
after : function ( element , newElement ) {
var index = element , parent = element . parentNode ;
2016-04-18 12:34:29 +00:00
forEach ( new JQLite ( newElement ) , function ( node ) {
2016-03-28 10:46:51 +00:00
parent . insertBefore ( node , index . nextSibling ) ;
index = node ;
2016-04-18 12:34:29 +00:00
} ) ;
2016-03-28 10:46:51 +00:00
} ,
addClass : jqLiteAddClass ,
removeClass : jqLiteRemoveClass ,
toggleClass : function ( element , selector , condition ) {
2016-04-18 12:34:29 +00:00
if ( isUndefined ( condition ) ) {
condition = ! jqLiteHasClass ( element , selector ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
( condition ? jqLiteAddClass : jqLiteRemoveClass ) ( element , selector ) ;
2016-03-28 10:46:51 +00:00
} ,
parent : function ( element ) {
var parent = element . parentNode ;
2016-04-18 12:34:29 +00:00
return parent && parent . nodeType !== 11 ? parent : null ;
2016-03-28 10:46:51 +00:00
} ,
next : function ( element ) {
2016-04-18 12:34:29 +00:00
if ( element . nextElementSibling ) {
return element . nextElementSibling ;
}
// IE8 doesn't have nextElementSibling
var elm = element . nextSibling ;
while ( elm != null && elm . nodeType !== 1 ) {
elm = elm . nextSibling ;
}
return elm ;
2016-03-28 10:46:51 +00:00
} ,
find : function ( element , selector ) {
if ( element . getElementsByTagName ) {
return element . getElementsByTagName ( selector ) ;
} else {
return [ ] ;
}
} ,
clone : jqLiteClone ,
2016-04-18 12:34:29 +00:00
triggerHandler : function ( element , eventName , eventData ) {
var eventFns = ( jqLiteExpandoStore ( element , 'events' ) || { } ) [ eventName ] ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
eventData = eventData || [ ] ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
var event = [ {
preventDefault : noop ,
stopPropagation : noop
} ] ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
forEach ( eventFns , function ( fn ) {
fn . apply ( element , event . concat ( eventData ) ) ;
} ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
} , function ( fn , name ) {
2016-03-28 10:46:51 +00:00
/ * *
* chaining functions
* /
JQLite . prototype [ name ] = function ( arg1 , arg2 , arg3 ) {
var value ;
2016-04-18 12:34:29 +00:00
for ( var i = 0 ; i < this . length ; 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 ;
} ) ;
/ * *
* 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 .
* /
2016-04-18 12:34:29 +00:00
function hashKey ( obj ) {
var objType = typeof obj ,
key ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( objType == 'object' && obj !== null ) {
if ( typeof ( key = obj . $$hashKey ) == 'function' ) {
// must invoke on object to keep the right this
2016-03-28 10:46:51 +00:00
key = obj . $$hashKey ( ) ;
2016-04-18 12:34:29 +00:00
} else if ( key === undefined ) {
key = obj . $$hashKey = nextUid ( ) ;
2016-03-28 10:46:51 +00:00
}
} else {
2016-04-18 12:34:29 +00:00
key = obj ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
return objType + ':' + key ;
2016-03-28 10:46:51 +00:00
}
/ * *
* HashMap which can use objects as keys
* /
2016-04-18 12:34:29 +00:00
function HashMap ( array ) {
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 ) {
2016-04-18 12:34:29 +00:00
this [ hashKey ( key ) ] = value ;
2016-03-28 10:46:51 +00:00
} ,
/ * *
* @ param key
2016-04-18 12:34:29 +00:00
* @ returns the value for the key
2016-03-28 10:46:51 +00:00
* /
get : function ( key ) {
2016-04-18 12:34:29 +00:00
return this [ hashKey ( key ) ] ;
2016-03-28 10:46:51 +00:00
} ,
/ * *
* Remove the key / value pair
* @ param key
* /
remove : function ( key ) {
2016-04-18 12:34:29 +00:00
var value = this [ key = hashKey ( key ) ] ;
2016-03-28 10:46:51 +00:00
delete this [ key ] ;
return value ;
}
} ;
/ * *
* @ ngdoc function
* @ name angular . injector
2016-04-18 12:34:29 +00:00
* @ function
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* Creates an injector function 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 } ) .
*
2016-04-18 12:34:29 +00:00
2016-03-28 10:46:51 +00:00
* @ param { Array . < string | Function > } modules A list of module functions or their aliases . See
2016-04-18 12:34:29 +00:00
* { @ link angular . module } . The ` ng ` module must be explicitly added .
* @ returns { function ( ) } Injector function . See { @ link AUTO . $injector $injector } .
2016-03-28 10:46:51 +00:00
*
* @ example
* Typical usage
2016-04-18 12:34:29 +00:00
* < pre >
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
2016-04-18 12:34:29 +00:00
* $injector . invoke ( function ( $rootScope , $compile , $document ) {
2016-03-28 10:46:51 +00:00
* $compile ( $document ) ( $rootScope ) ;
* $rootScope . $digest ( ) ;
* } ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
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
2016-04-18 12:34:29 +00:00
* application has been bootstrapped . You can do this using 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 .
*
2016-04-18 12:34:29 +00:00
* < pre >
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 ) ;
* } ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
2016-03-28 10:46:51 +00:00
* /
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc overview
* @ name AUTO
2016-03-28 10:46:51 +00:00
* @ description
*
2016-04-18 12:34:29 +00: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' ) ;
2016-04-18 12:34:29 +00:00
function annotate ( fn ) {
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 ;
2016-04-18 12:34:29 +00:00
if ( typeof fn == 'function' ) {
2016-03-28 10:46:51 +00:00
if ( ! ( $inject = fn . $inject ) ) {
$inject = [ ] ;
if ( fn . length ) {
2016-04-18 12:34:29 +00:00
fnText = fn . toString ( ) . replace ( STRIP _COMMENTS , '' ) ;
argDecl = fnText . match ( FN _ARGS ) ;
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 ;
}
///////////////////////////////////////
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc object
* @ name AUTO . $injector
* @ function
2016-03-28 10:46:51 +00:00
*
* @ description
*
* ` $ injector ` is used to retrieve object instances as defined by
2016-04-18 12:34:29 +00: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 :
*
2016-04-18 12:34:29 +00:00
* < pre >
2016-03-28 10:46:51 +00:00
* var $injector = angular . injector ( ) ;
* expect ( $injector . get ( '$injector' ) ) . toBe ( $injector ) ;
2016-04-18 12:34:29 +00:00
* expect ( $injector . invoke ( function ( $injector ) {
2016-03-28 10:46:51 +00:00
* return $injector ;
2016-04-18 12:34:29 +00:00
* } ) . toBe ( $injector ) ;
* < / p r e >
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 .
*
2016-04-18 12:34:29 +00:00
* < pre >
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 ) { } ] ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
2016-03-28 10:46:51 +00:00
*
* # # Inference
*
* In JavaScript calling ` toString() ` on a function returns the function definition . The definition
2016-04-18 12:34:29 +00:00
* can then be parsed and the function arguments can be extracted . * 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
2016-04-18 12:34:29 +00:00
* By adding a ` $ 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
2016-04-18 12:34:29 +00:00
* @ name AUTO . $injector # get
* @ methodOf AUTO . $injector
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 .
* @ return { * } The instance .
* /
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name AUTO . $injector # invoke
* @ methodOf AUTO . $injector
2016-03-28 10:46:51 +00:00
*
* @ description
* Invoke the method and supply the method arguments from the ` $ injector ` .
*
2016-04-18 12:34:29 +00:00
* @ param { ! function } fn The 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
2016-04-18 12:34:29 +00:00
* @ name AUTO . $injector # has
* @ methodOf AUTO . $injector
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* Allows the user to query if the particular service exist .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ param { string } Name of the service to query .
* @ returns { boolean } returns true if injector has given service .
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name AUTO . $injector # instantiate
* @ methodOf AUTO . $injector
2016-03-28 10:46:51 +00:00
* @ description
2016-04-18 12:34:29 +00: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 .
*
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00:00
* @ name AUTO . $injector # annotate
* @ methodOf AUTO . $injector
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 .
2016-04-18 12:34:29 +00:00
* < pre >
2016-03-28 10:46:51 +00:00
* // Given
* function MyController ( $scope , $route ) {
* // ...
* }
*
* // Then
* expect ( injector . annotate ( MyController ) ) . toEqual ( [ '$scope' , '$route' ] ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
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 .
2016-04-18 12:34:29 +00:00
* < pre >
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' ] ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
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 :
*
2016-04-18 12:34:29 +00:00
* < pre >
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' ] ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00: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 .
*
* @ returns { Array . < string > } The names of the services which the function requires .
* /
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc object
* @ name AUTO . $provide
2016-03-28 10:46:51 +00:00
*
* @ description
*
2016-04-18 12:34:29 +00: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 .
*
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00: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 .
*
2016-04-18 12:34:29 +00:00
* * { @ link AUTO . $provide # methods _provider provider ( provider ) } - registers a * * service provider * * with the
* { @ link AUTO . $injector $injector }
* * { @ link AUTO . $provide # methods _constant constant ( obj ) } - registers a value / object that can be accessed by
2016-03-28 10:46:51 +00:00
* providers and services .
2016-04-18 12:34:29 +00:00
* * { @ link AUTO . $provide # methods _value value ( obj ) } - registers a value / object that can only be accessed by
2016-03-28 10:46:51 +00:00
* services , not providers .
2016-04-18 12:34:29 +00:00
* * { @ link AUTO . $provide # methods _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 .
2016-04-18 12:34:29 +00:00
* * { @ link AUTO . $provide # methods _service service ( class ) } - registers a * * constructor function * * , ` class ` that
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
2016-04-18 12:34:29 +00:00
* @ name AUTO . $provide # provider
* @ methodOf AUTO . $provide
2016-03-28 10:46:51 +00:00
* @ description
*
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00:00
* { @ link AUTO . $provide # methods _provider $provide . provider ( ) } .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* < pre >
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 } ) ;
* } ) ) ;
* } ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name AUTO . $provide # factory
* @ methodOf AUTO . $provide
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 .
2016-04-18 12:34:29 +00: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 .
2016-04-18 12:34:29 +00:00
* @ param { function ( ) } $getFn The $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
2016-04-18 12:34:29 +00:00
* < pre >
2016-03-28 10:46:51 +00:00
* $provide . factory ( 'ping' , [ '$http' , function ( $http ) {
* return function ping ( ) {
* return $http . send ( '/ping' ) ;
* } ;
* } ] ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
2016-03-28 10:46:51 +00:00
* You would then inject and use this service like this :
2016-04-18 12:34:29 +00:00
* < pre >
2016-03-28 10:46:51 +00:00
* someModule . controller ( 'Ctrl' , [ 'ping' , function ( ping ) {
* ping ( ) ;
* } ] ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name AUTO . $provide # service
* @ methodOf AUTO . $provide
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
*
2016-04-18 12:34:29 +00:00
* You should use { @ link AUTO . $provide # methods _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 .
2016-04-18 12:34:29 +00:00
* @ param { Function } constructor A 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
2016-04-18 12:34:29 +00:00
* { @ link AUTO . $provide # methods _service $provide . service ( class ) } .
* < pre >
* $provide . service ( 'ping' , [ '$http' , function ( $http ) {
* var Ping = function ( ) {
* this . $http = $http ;
* } ;
2016-05-18 00:10:50 +00:00
*
2016-04-18 12:34:29 +00:00
* Ping . prototype . send = function ( ) {
* return this . $http . get ( '/ping' ) ;
2016-05-18 00:10:50 +00:00
* } ;
*
2016-04-18 12:34:29 +00:00
* return Ping ;
* } ] ) ;
* < / p r e >
2016-03-28 10:46:51 +00:00
* You would then inject and use this service like this :
2016-04-18 12:34:29 +00:00
* < pre >
2016-03-28 10:46:51 +00:00
* someModule . controller ( 'Ctrl' , [ 'ping' , function ( ping ) {
* ping . send ( ) ;
* } ] ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name AUTO . $provide # value
* @ methodOf AUTO . $provide
2016-03-28 10:46:51 +00:00
* @ description
*
2016-04-18 12:34:29 +00:00
* Register a * * value service * * with the { @ link AUTO . $injector $injector } , such as a string , a
* 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
* { @ 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 .
2016-04-18 12:34:29 +00:00
* < pre >
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 ;
* } ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name AUTO . $provide # constant
* @ methodOf AUTO . $provide
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 ,
* 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
2016-04-18 12:34:29 +00: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 :
2016-04-18 12:34:29 +00:00
* < pre >
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 ;
* } ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name AUTO . $provide # decorator
* @ methodOf AUTO . $provide
2016-03-28 10:46:51 +00:00
* @ description
*
2016-04-18 12:34:29 +00:00
* Register a * * service decorator * * with the { @ link AUTO . $injector $injector } . A service decorator
* 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 .
2016-04-18 12:34:29 +00:00
* @ param { 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
2016-04-18 12:34:29 +00: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 ( ) } .
2016-04-18 12:34:29 +00:00
* < pre >
* $provider . decorator ( '$log' , [ '$delegate' , function ( $delegate ) {
2016-03-28 10:46:51 +00:00
* $delegate . warn = $delegate . error ;
* return $delegate ;
* } ] ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
function createInjector ( modulesToLoad ) {
2016-03-28 10:46:51 +00:00
var INSTANTIATING = { } ,
providerSuffix = 'Provider' ,
path = [ ] ,
2016-04-18 12:34:29 +00:00
loadedModules = new HashMap ( ) ,
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 =
2016-04-18 12:34:29 +00:00
createInternalInjector ( providerCache , function ( ) {
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 =
createInternalInjector ( instanceCache , function ( servicename ) {
var provider = providerInjector . get ( servicename + providerSuffix ) ;
return instanceInjector . invoke ( provider . $get , provider ) ;
} ) ) ;
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 _ ;
}
2016-04-18 12:34:29 +00:00
function factory ( name , factoryFn ) { return provider ( name , { $get : factoryFn } ) ; }
2016-03-28 10:46:51 +00:00
function service ( name , constructor ) {
return factory ( name , [ '$injector' , function ( $injector ) {
return $injector . instantiate ( constructor ) ;
} ] ) ;
}
2016-04-18 12:34:29 +00:00
function value ( name , val ) { return factory ( name , valueFn ( val ) ) ; }
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
////////////////////////////////////
2016-04-18 12:34:29 +00:00
function loadModules ( modulesToLoad ) {
var runBlocks = [ ] , moduleFn , invokeQueue , i , ii ;
2016-03-28 10:46:51 +00:00
forEach ( modulesToLoad , function ( module ) {
if ( loadedModules . get ( module ) ) return ;
loadedModules . put ( module , true ) ;
try {
if ( isString ( module ) ) {
moduleFn = angularModule ( module ) ;
runBlocks = runBlocks . concat ( loadModules ( moduleFn . requires ) ) . concat ( moduleFn . _runBlocks ) ;
2016-04-18 12:34:29 +00:00
for ( invokeQueue = moduleFn . _invokeQueue , i = 0 , ii = invokeQueue . length ; i < ii ; i ++ ) {
var invokeArgs = invokeQueue [ i ] ,
provider = providerInjector . get ( invokeArgs [ 0 ] ) ;
provider [ invokeArgs [ 1 ] ] . apply ( provider , invokeArgs [ 2 ] ) ;
}
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 ) {
2016-04-18 12:34:29 +00:00
function getService ( serviceName ) {
2016-03-28 10:46:51 +00:00
if ( cache . hasOwnProperty ( serviceName ) ) {
if ( cache [ serviceName ] === INSTANTIATING ) {
2016-04-18 12:34:29 +00:00
throw $injectorMinErr ( 'cdep' , 'Circular dependency found: {0}' , path . join ( ' <- ' ) ) ;
2016-03-28 10:46:51 +00:00
}
return cache [ serviceName ] ;
} else {
try {
path . unshift ( serviceName ) ;
cache [ serviceName ] = INSTANTIATING ;
2016-04-18 12:34:29 +00:00
return cache [ serviceName ] = factory ( serviceName ) ;
2016-03-28 10:46:51 +00:00
} catch ( err ) {
if ( cache [ serviceName ] === INSTANTIATING ) {
delete cache [ serviceName ] ;
}
throw err ;
} finally {
path . shift ( ) ;
}
}
}
2016-04-18 12:34:29 +00:00
function invoke ( fn , self , locals ) {
2016-03-28 10:46:51 +00:00
var args = [ ] ,
2016-04-18 12:34:29 +00:00
$inject = annotate ( fn ) ,
length , i ,
key ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
for ( i = 0 , length = $inject . length ; i < length ; i ++ ) {
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 ]
: getService ( key )
) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
if ( ! fn . $inject ) {
// this means that we must be an array.
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
}
2016-04-18 12:34:29 +00:00
function instantiate ( Type , locals ) {
var Constructor = function ( ) { } ,
instance , returnedValue ;
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) {}]);
2016-04-18 12:34:29 +00:00
Constructor . prototype = ( isArray ( Type ) ? Type [ Type . length - 1 ] : Type ) . prototype ;
instance = new Constructor ( ) ;
returnedValue = invoke ( Type , instance , locals ) ;
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 ,
2016-04-18 12:34:29 +00:00
annotate : annotate ,
2016-03-28 10:46:51 +00:00
has : function ( name ) {
return providerCache . hasOwnProperty ( name + providerSuffix ) || cache . hasOwnProperty ( name ) ;
}
} ;
}
}
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $anchorScroll
* @ requires $window
* @ requires $location
* @ requires $rootScope
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* When called , it checks current value of ` $ location.hash() ` and scroll to related element ,
* according to rules specified in
* { @ link http : //dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document Html5 spec}.
*
* It also watches the ` $ location.hash() ` and scrolls whenever it changes to match any anchor .
* This can be disabled by calling ` $ anchorScrollProvider.disableAutoScrolling() ` .
2016-05-18 00:10:50 +00:00
*
2016-04-18 12:34:29 +00:00
* @ example
< example >
< file name = "index.html" >
< div id = "scrollArea" ng - controller = "ScrollCtrl" >
< 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" >
function ScrollCtrl ( $scope , $location , $anchorScroll ) {
$scope . gotoBottom = function ( ) {
// set the location.hash to the id of
// the element you wish to scroll to.
$location . hash ( 'bottom' ) ;
2016-05-18 00:10:50 +00:00
2016-04-18 12:34:29 +00:00
// call $anchorScroll()
$anchorScroll ( ) ;
}
}
< / f i l e >
< file name = "style.css" >
# scrollArea {
height : 350 px ;
overflow : auto ;
}
# bottom {
display : block ;
margin - top : 2000 px ;
}
< / f i l e >
< / e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
function $AnchorScrollProvider ( ) {
var autoScrollingEnabled = true ;
this . disableAutoScrolling = function ( ) {
autoScrollingEnabled = false ;
} ;
this . $get = [ '$window' , '$location' , '$rootScope' , function ( $window , $location , $rootScope ) {
var document = $window . document ;
2016-04-18 12:34:29 +00:00
// helper function to get first anchor from a NodeList
// can't use filter.filter, as it accepts only instances of Array
// and IE can't convert NodeList to an array using [].slice
// TODO(vojta): use filter if we change it to accept lists as well
2016-03-28 10:46:51 +00:00
function getFirstAnchor ( list ) {
var result = null ;
2016-04-18 12:34:29 +00:00
forEach ( list , function ( element ) {
if ( ! result && lowercase ( element . nodeName ) === 'a' ) result = element ;
2016-03-28 10:46:51 +00:00
} ) ;
return result ;
}
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
2016-04-18 12:34:29 +00:00
if ( ! hash ) $window . scrollTo ( 0 , 0 ) ;
2016-03-28 10:46:51 +00:00
// element with given id
2016-04-18 12:34:29 +00:00
else if ( ( elm = document . getElementById ( hash ) ) ) elm . scrollIntoView ( ) ;
2016-03-28 10:46:51 +00:00
// first anchor with given name :-D
2016-04-18 12:34:29 +00:00
else if ( ( elm = getFirstAnchor ( document . getElementsByName ( hash ) ) ) ) elm . scrollIntoView ( ) ;
2016-03-28 10:46:51 +00:00
// no element and hash == 'top', scroll to the top of the page
2016-04-18 12:34:29 +00:00
else if ( hash === 'top' ) $window . scrollTo ( 0 , 0 ) ;
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 ( ) ; } ,
2016-04-18 12:34:29 +00:00
function autoScrollWatchAction ( ) {
$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
/ * *
* @ ngdoc object
* @ name ng . $animateProvider
*
* @ 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
/ * *
* @ ngdoc function
* @ name ng . $animateProvider # register
* @ methodOf ng . $animateProvider
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
*
*
2016-04-18 12:34:29 +00:00
* < pre >
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
* }
* }
* }
* < / p r e >
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ param { string } name The name of the animation .
* @ 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 ) ;
} ;
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $animateProvider # classNameFilter
* @ methodOf ng . $animateProvider
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 ) {
2016-04-18 12:34:29 +00:00
if ( arguments . length === 1 ) {
2016-03-28 10:46:51 +00:00
this . $$classNameFilter = ( expression instanceof RegExp ) ? expression : null ;
}
return this . $$classNameFilter ;
} ;
2016-04-18 12:34:29 +00:00
this . $get = [ '$timeout' , function ( $timeout ) {
2016-03-28 10:46:51 +00:00
/ * *
*
2016-04-18 12:34:29 +00:00
* @ ngdoc object
* @ name ng . $animate
* @ 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 {
/ * *
*
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $animate # enter
* @ methodOf ng . $animate
* @ function
* @ description Inserts the element into the DOM either after the ` after ` element or within
* the ` parent ` element . Once complete , the done ( ) callback will be fired ( if provided ) .
* @ param { jQuery / jqLite element } element the element which will be inserted into the DOM
* @ param { jQuery / jqLite element } parent the parent element which will append the element as
* a child ( if the after element is not present )
* @ param { jQuery / jqLite element } after the sibling element which will append the element
* after itself
* @ param { function = } done callback function that will be called after the element has been
* inserted into the DOM
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
enter : function ( element , parent , after , done ) {
if ( after ) {
after . after ( element ) ;
} else {
if ( ! parent || ! parent [ 0 ] ) {
parent = after . parent ( ) ;
}
parent . append ( element ) ;
}
done && $timeout ( done , 0 , false ) ;
2016-03-28 10:46:51 +00:00
} ,
/ * *
*
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $animate # leave
* @ methodOf ng . $animate
* @ function
* @ description Removes the element from the DOM . Once complete , the done ( ) callback will be
* fired ( if provided ) .
* @ param { jQuery / jqLite element } element the element which will be removed from the DOM
* @ param { function = } done callback function that will be called after the element has been
* removed from the DOM
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
leave : function ( element , done ) {
element . remove ( ) ;
done && $timeout ( done , 0 , false ) ;
2016-03-28 10:46:51 +00:00
} ,
/ * *
*
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $animate # move
* @ methodOf ng . $animate
* @ function
* @ description Moves the position of the provided element within the DOM to be placed
* either after the ` after ` element or inside of the ` parent ` element . Once complete , the
* done ( ) callback will be fired ( if provided ) .
2016-05-18 00:10:50 +00:00
*
2016-04-18 12:34:29 +00:00
* @ param { jQuery / jqLite element } element the element which will be moved around within the
* DOM
* @ param { jQuery / jqLite element } parent the parent element where the element will be
* inserted into ( if the after element is not present )
* @ param { jQuery / jqLite element } after the sibling element where the element will be
* positioned next to
* @ param { function = } done the callback function ( if provided ) that will be fired after the
* element has been moved to its new position
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
move : function ( element , parent , after , done ) {
// Do not remove element before insert. Removing will cause data associated with the
// element to be dropped. Insert will implicitly do the remove.
this . enter ( element , parent , after , done ) ;
2016-03-28 10:46:51 +00:00
} ,
/ * *
*
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $animate # addClass
* @ methodOf ng . $animate
* @ function
* @ description Adds the provided className CSS class value to the provided element . Once
* complete , the done ( ) callback will be fired ( if provided ) .
* @ param { jQuery / jqLite element } element the element which will have the className value
* added to it
* @ param { string } className the CSS class which will be added to the element
* @ param { function = } done the callback function ( if provided ) that will be fired after the
* className value has been added to the element
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
addClass : function ( element , className , done ) {
className = isString ( className ) ?
className :
isArray ( className ) ? className . join ( ' ' ) : '' ;
forEach ( element , function ( element ) {
jqLiteAddClass ( element , className ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
done && $timeout ( done , 0 , false ) ;
2016-03-28 10:46:51 +00:00
} ,
/ * *
*
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $animate # removeClass
* @ methodOf ng . $animate
* @ function
* @ description Removes the provided className CSS class value from the provided element .
* Once complete , the done ( ) callback will be fired ( if provided ) .
* @ param { jQuery / jqLite element } element the element which will have the className value
* removed from it
* @ param { string } className the CSS class which will be removed from the element
* @ param { function = } done the callback function ( if provided ) that will be fired after the
* className value has been removed from the element
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
removeClass : function ( element , className , done ) {
className = isString ( className ) ?
className :
isArray ( className ) ? className . join ( ' ' ) : '' ;
forEach ( element , function ( element ) {
jqLiteRemoveClass ( element , className ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
done && $timeout ( done , 0 , false ) ;
2016-03-28 10:46:51 +00:00
} ,
2016-04-18 12:34:29 +00:00
enabled : 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
/ * *
* ! This is a private undocumented service !
*
2016-04-18 12:34:29 +00:00
* @ name ng . $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 .
2016-04-18 12:34:29 +00:00
* @ param { function ( ) } XHR XMLHttpRequest constructor .
* @ param { object } $log console . log 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 ) {
2016-04-18 12:34:29 +00:00
while ( outstandingRequestCallbacks . length ) {
2016-03-28 10:46:51 +00:00
try {
outstandingRequestCallbacks . pop ( ) ( ) ;
} catch ( e ) {
$log . error ( e ) ;
}
}
}
}
}
/ * *
* @ 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.
forEach ( pollFns , function ( pollFn ) { pollFn ( ) ; } ) ;
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
/ * *
* @ name ng . $browser # addPollFn
* @ methodOf ng . $browser
*
* @ 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 ( ) {
forEach ( pollFns , function ( pollFn ) { pollFn ( ) ; } ) ;
pollTimeout = setTimeout ( check , interval ) ;
} ) ( ) ;
}
//////////////////////////////////////////////////////////////
// URL API
//////////////////////////////////////////////////////////////
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
var lastBrowserUrl = location . href ,
baseElement = document . find ( 'base' ) ,
newLocation = null ;
2016-03-28 10:46:51 +00:00
/ * *
2016-04-18 12:34:29 +00:00
* @ name ng . $browser # url
* @ methodOf ng . $browser
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 )
2016-04-18 12:34:29 +00:00
* @ param { boolean = } replace Should new url replace current history record ?
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
self . url = function ( url , replace ) {
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 ) {
2016-04-18 12:34:29 +00:00
if ( lastBrowserUrl == url ) return ;
2016-03-28 10:46:51 +00:00
lastBrowserUrl = url ;
2016-04-18 12:34:29 +00:00
if ( $sniffer . history ) {
if ( replace ) history . replaceState ( null , '' , url ) ;
else {
history . pushState ( null , '' , url ) ;
// Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462
baseElement . attr ( 'href' , baseElement . attr ( 'href' ) ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
} else {
newLocation = url ;
2016-03-28 10:46:51 +00:00
if ( replace ) {
location . replace ( url ) ;
} else {
2016-04-18 12:34:29 +00:00
location . href = url ;
2016-03-28 10:46:51 +00:00
}
}
return self ;
// getter
} else {
2016-04-18 12:34:29 +00:00
// - newLocation is a workaround for an IE7-9 issue with location.replace and location.href
// methods not updating location.href synchronously.
2016-03-28 10:46:51 +00:00
// - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
2016-04-18 12:34:29 +00:00
return newLocation || location . href . replace ( /%27/g , "'" ) ;
2016-03-28 10:46:51 +00:00
}
} ;
var urlChangeListeners = [ ] ,
urlChangeInit = false ;
function fireUrlChange ( ) {
2016-04-18 12:34:29 +00:00
newLocation = null ;
if ( lastBrowserUrl == self . url ( ) ) return ;
2016-03-28 10:46:51 +00:00
lastBrowserUrl = self . url ( ) ;
forEach ( urlChangeListeners , function ( listener ) {
2016-04-18 12:34:29 +00:00
listener ( self . url ( ) ) ;
2016-03-28 10:46:51 +00:00
} ) ;
}
/ * *
2016-04-18 12:34:29 +00:00
* @ name ng . $browser # onUrlChange
* @ methodOf ng . $browser
* @ TODO ( vojta ) : refactor to use node ' s syntax for events
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 ) {
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
2016-04-18 12:34:29 +00:00
if ( $sniffer . history ) jqLite ( window ) . on ( 'popstate' , fireUrlChange ) ;
2016-03-28 10:46:51 +00:00
// hashchange event
2016-04-18 12:34:29 +00:00
if ( $sniffer . hashchange ) jqLite ( window ) . on ( 'hashchange' , fireUrlChange ) ;
// polling
else self . addPollFn ( fireUrlChange ) ;
2016-03-28 10:46:51 +00:00
urlChangeInit = true ;
}
urlChangeListeners . push ( callback ) ;
return callback ;
} ;
//////////////////////////////////////////////////////////////
// Misc API
//////////////////////////////////////////////////////////////
/ * *
2016-04-18 12:34:29 +00:00
* @ name ng . $browser # baseHref
* @ methodOf ng . $browser
2016-03-28 10:46:51 +00:00
*
* @ description
* Returns current < base href >
* ( always relative - without domain )
*
2016-04-18 12:34:29 +00:00
* @ returns { string = } 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 ( ) ;
/ * *
* @ name ng . $browser # cookies
* @ methodOf ng . $browser
*
* @ 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 ) {
/* global escape: false, unescape: false */
var cookieLength , cookieArray , cookie , i , index ;
if ( name ) {
if ( value === undefined ) {
rawDocument . cookie = escape ( name ) + "=;path=" + cookiePath +
";expires=Thu, 01 Jan 1970 00:00:00 GMT" ;
} else {
if ( isString ( value ) ) {
cookieLength = ( rawDocument . cookie = escape ( name ) + '=' + escape ( value ) +
';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 ) {
$log . warn ( "Cookie '" + name +
"' possibly not set or overflowed because it was too large (" +
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
name = unescape ( cookie . substring ( 0 , index ) ) ;
// 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 ) {
lastCookies [ name ] = unescape ( cookie . substring ( index + 1 ) ) ;
}
}
}
}
return lastCookies ;
}
} ;
2016-03-28 10:46:51 +00:00
/ * *
2016-04-18 12:34:29 +00:00
* @ name ng . $browser # defer
* @ methodOf ng . $browser
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 ;
} ;
/ * *
2016-04-18 12:34:29 +00:00
* @ name ng . $browser # defer . cancel
* @ methodOf ng . $browser . defer
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 ;
} ;
}
2016-04-18 12:34:29 +00:00
function $BrowserProvider ( ) {
2016-03-28 10:46:51 +00:00
this . $get = [ '$window' , '$log' , '$sniffer' , '$document' ,
2016-04-18 12:34:29 +00:00
function ( $window , $log , $sniffer , $document ) {
2016-03-28 10:46:51 +00:00
return new Browser ( $window , $document , $log , $sniffer ) ;
} ] ;
}
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc object
* @ name ng . $cacheFactory
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* Factory that constructs cache objects and gives access to them .
2016-05-18 00:10:50 +00:00
*
2016-04-18 12:34:29 +00:00
* < pre >
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 } ) ;
*
2016-04-18 12:34:29 +00:00
* < / p r e >
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 .
*
* /
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 ;
return caches [ cacheId ] = {
put : function ( key , value ) {
2016-04-18 12:34:29 +00:00
var lruEntry = lruHash [ key ] || ( lruHash [ key ] = { key : key } ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00: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 ;
} ,
2016-04-18 12:34:29 +00:00
2016-03-28 10:46:51 +00:00
get : function ( key ) {
2016-04-18 12:34:29 +00:00
var lruEntry = lruHash [ key ] ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( ! lruEntry ) return ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
refresh ( lruEntry ) ;
2016-03-28 10:46:51 +00:00
return data [ key ] ;
} ,
remove : function ( key ) {
2016-04-18 12:34:29 +00:00
var lruEntry = lruHash [ key ] ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( ! lruEntry ) return ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( lruEntry == freshEnd ) freshEnd = lruEntry . p ;
if ( lruEntry == staleEnd ) staleEnd = lruEntry . n ;
link ( lruEntry . n , lruEntry . p ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
delete lruHash [ key ] ;
2016-03-28 10:46:51 +00:00
delete data [ key ] ;
size -- ;
} ,
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 ;
} ,
destroy : function ( ) {
data = null ;
stats = null ;
lruHash = null ;
delete caches [ cacheId ] ;
} ,
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
2016-04-18 12:34:29 +00:00
* @ name ng . $cacheFactory # info
* @ methodOf ng . $cacheFactory
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* Get information about all the of 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
2016-04-18 12:34:29 +00:00
* @ name ng . $cacheFactory # get
* @ methodOf ng . $cacheFactory
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 ;
} ;
}
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc object
* @ name ng . $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 :
2016-04-18 12:34:29 +00:00
* < pre >
* < html ng - app >
* < head >
* < script type = "text/ng-template" id = "templateId.html" >
* This is the content of the template
* < / s c r i p t >
* < / h e a d >
* ...
* < / h t m l >
* < / p r e >
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
2016-04-18 12:34:29 +00:00
* the document , but it must be below the ` ng-app ` definition .
2016-05-18 00:10:50 +00:00
*
2016-04-18 12:34:29 +00:00
* Adding via the $templateCache service :
2016-05-18 00:10:50 +00:00
*
2016-04-18 12:34:29 +00:00
* < pre >
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' ) ;
* } ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
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 :
2016-04-18 12:34:29 +00:00
* < pre >
2016-03-28 10:46:51 +00:00
* < div ng - include = " 'templateId.html' " > < / d i v >
2016-04-18 12:34:29 +00:00
* < / p r e >
2016-05-18 00:10:50 +00:00
*
2016-03-28 10:46:51 +00:00
* or get it via Javascript :
2016-04-18 12:34:29 +00:00
* < pre >
2016-03-28 10:46:51 +00:00
* $templateCache . get ( 'templateId.html' )
2016-04-18 12:34:29 +00:00
* < / p r e >
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' ) ;
} ] ;
}
/ * ! 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 )
* /
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $compile
* @ 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
2016-04-18 12:34:29 +00:00
* { @ link ng . $compileProvider # methods _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 :
*
2016-04-18 12:34:29 +00:00
* < pre >
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) { ... },
2016-04-18 12:34:29 +00:00
* replace : false ,
2016-03-28 10:46:51 +00:00
* transclude : false ,
* restrict : 'A' ,
* scope : false ,
* controller : function ( $scope , $element , $attrs , $transclude , otherInjectables ) { ... } ,
* 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 ;
* } ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
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 :
*
2016-04-18 12:34:29 +00:00
* < pre >
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) { ... }
* } ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
2016-03-28 10:46:51 +00:00
*
*
*
* # # # Directive Definition Object
*
2016-04-18 12:34:29 +00:00
* The directive definition object provides instructions to the { @ link api / ng . $compile
2016-03-28 10:46:51 +00:00
* compiler } . The attributes are :
*
* # # # # ` 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
2016-04-18 12:34:29 +00:00
* as the order of execution on same ` priority ` is undefined ) .
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
* can avoid this behavior using ` =? ` or ` =?attr ` in order to flag the property as 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
* pass data from the isolated scope via an expression and to the parent scope , this can be
* 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
*
*
*
* # # # # ` 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
2016-04-18 12:34:29 +00:00
* * ` $ transclude ` - A transclude linking function pre - bound to the correct transclusion scope .
* The scope can be overridden by an optional first argument .
* ` function([scope], cloneLinkingFn) ` .
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
* found , or if the directive does not have a controller , then an error is raised . 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 .
2016-04-18 12:34:29 +00:00
* * ` ^ ` - 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 ' 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
2016-04-18 12:34:29 +00:00
* declaration style . If omitted , the default ( attributes only ) is used .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* * ` E ` - Element name : ` <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 --> `
*
*
* # # # # ` template `
2016-04-18 12:34:29 +00:00
* replace the current element with the contents of the HTML . The replacement process
* migrates all of the attributes / classes from the old element to the new one . See the
* { @ link guide / directive # creating - custom - directives _creating - directives _template - expanding - directive
* Directives Guide } for an example .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* You can specify ` template ` as a string representing the template 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 template .
2016-03-28 10:46:51 +00:00
*
*
* # # # # ` templateUrl `
2016-04-18 12:34:29 +00:00
* Same as ` template ` but the template is loaded from the specified URL . Because
* the template loading is asynchronous the compilation / linking is suspended until the template
* is loaded .
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
2016-04-18 12:34:29 +00:00
* api / ng . $sce # methods _getTrustedResourceUrl $sce . getTrustedResourceUrl } .
2016-03-28 10:46:51 +00:00
*
*
2016-04-18 12:34:29 +00:00
* # # # # ` replace `
* specify where the template should be inserted . Defaults to ` false ` .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* * ` true ` - the template will replace the current element .
* * ` false ` - the template will replace the contents of the current element .
2016-03-28 10:46:51 +00:00
*
*
* # # # # ` transclude `
2016-04-18 12:34:29 +00:00
* compile the content of the element and make it available to the directive .
* Typically used with { @ link api / ng . directive : ngTransclude
* ngTransclude } . The advantage of transclusion is that the linking function receives a
* transclusion function which is pre - bound to the correct scope . In a typical setup the widget
* creates an ` isolate ` scope , but the transclusion is not a child , but a sibling of the ` isolate `
* scope . This makes it possible for the widget to have private state , and the transclusion to
* be bound to the parent ( pre - ` isolate ` ) scope .
*
* * ` true ` - transclude the content of the directive .
* * ` 'element' ` - transclude the whole element including any directives defined at lower priority .
2016-03-28 10:46:51 +00:00
*
*
* # # # # ` compile `
*
2016-04-18 12:34:29 +00:00
* < pre >
2016-03-28 10:46:51 +00:00
* function compile ( tElement , tAttrs , transclude ) { ... }
2016-04-18 12:34:29 +00:00
* < / p r e >
2016-03-28 10:46:51 +00:00
*
* The compile function deals with transforming the template DOM . Since most directives do not do
2016-04-18 12:34:29 +00:00
* template transformation , it is not used often . Examples that require compile functions are
* directives that transform template DOM , such as { @ link
* api / ng . directive : ngRepeat ngRepeat } , or load the contents
* asynchronously , such as { @ link api / ngRoute . directive : ngView ngView } . 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 >
*
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 .
*
2016-04-18 12:34:29 +00:00
* < pre >
2016-03-28 10:46:51 +00:00
* function link ( scope , iElement , iAttrs , controller , transcludeFn ) { ... }
2016-04-18 12:34:29 +00:00
* < / p r e >
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 .
*
2016-04-18 12:34:29 +00:00
* * ` scope ` - { @ link api / ng . $rootScope . Scope Scope } - The scope to be used by the
* directive for registering { @ link api / ng . $rootScope . Scope # methods _$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 .
*
2016-04-18 12:34:29 +00:00
* * ` controller ` - a controller instance - A controller instance if at least one directive on the
* element defines a controller . The controller is shared among all the directives , which allows
* the directives to use the controllers as a communication channel .
2016-03-28 10:46:51 +00:00
*
* * ` transcludeFn ` - A transclude linking function pre - bound to the correct transclusion scope .
2016-04-18 12:34:29 +00:00
* The scope can be overridden by an optional first argument . This is the same as the ` $ transclude `
* parameter of directive controllers .
* ` function([scope], cloneLinkingFn) ` .
*
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
*
2016-04-18 12:34:29 +00:00
* Executed after the child elements are linked . It is safe to do DOM transformation in the post - linking function .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* < a name = "Attributes" > < / a >
* # # # Attributes
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* The { @ link api / ng . $compile . directive . Attributes Attributes } object - passed as a parameter in the
* ` link() ` or ` compile() ` functions . It has a variety of uses .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* 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
* 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
*
2016-04-18 12:34:29 +00:00
* < pre >
* 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 ) ;
* } ) ;
* }
* < / p r e >
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* Below is an example using ` $ compileProvider ` .
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 >
*
2016-04-18 12:34:29 +00:00
< doc : example module = "compile" >
< doc : source >
2016-03-28 10:46:51 +00:00
< script >
2016-04-18 12:34:29 +00:00
angular . module ( 'compile' , [ ] , 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 ) ;
}
) ;
} ;
2016-04-18 12:34:29 +00:00
} )
} ) ;
function Ctrl ( $scope ) {
2016-03-28 10:46:51 +00:00
$scope . name = 'Angular' ;
$scope . html = 'Hello {{name}}' ;
2016-04-18 12:34:29 +00:00
}
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2016-04-18 12:34:29 +00:00
< div ng - controller = "Ctrl" >
< 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 >
2016-04-18 12:34:29 +00:00
< / d o c : s o u r c e >
< doc : scenario >
2016-03-28 10:46:51 +00:00
it ( 'should auto compile' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( element ( 'div[compile]' ) . text ( ) ) . toBe ( 'Hello Angular' ) ;
input ( 'html' ) . enter ( '{{name}}!' ) ;
expect ( element ( 'div[compile]' ) . text ( ) ) . toBe ( 'Angular!' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : s c e n a r i o >
< / d o c : 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 .
2016-04-18 12:34:29 +00:00
* @ param { function ( angular . Scope [ , cloneAttachFn ] } transclude function available to directives .
* @ param { number } maxPriority only apply directives lower then given priority ( Only effects the
2016-03-28 10:46:51 +00:00
* root element ( s ) , not their children )
2016-04-18 12:34:29 +00:00
* @ returns { function ( scope [ , cloneAttachFn ] ) } 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 .
*
* 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 .
2016-04-18 12:34:29 +00:00
* < pre >
2016-03-28 10:46:51 +00:00
* var element = $compile ( '<p>{{total}}</p>' ) ( scope ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
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 :
2016-04-18 12:34:29 +00:00
* < pre >
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`
2016-04-18 12:34:29 +00:00
* < / p r e >
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' ) ;
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc service
* @ name ng . $compileProvider
* @ function
2016-03-28 10:46:51 +00:00
*
* @ description
* /
$CompileProvider . $inject = [ '$provide' , '$$sanitizeUriProvider' ] ;
function $CompileProvider ( $provide , $$sanitizeUriProvider ) {
var hasDirectives = { } ,
Suffix = 'Directive' ,
2016-04-18 12:34:29 +00:00
COMMENT _DIRECTIVE _REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/ ,
CLASS _DIRECTIVE _REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/ ;
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)$/ ;
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $compileProvider # directive
* @ methodOf ng . $compileProvider
* @ 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 .
2016-04-18 12:34:29 +00:00
* @ param { function | Array } directiveFactory An injectable directive factory function . See
* { @ 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 ) ;
2016-04-18 12:34:29 +00:00
directive . restrict = directive . restrict || 'A' ;
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 ;
} ;
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $compileProvider # aHrefSanitizationWhitelist
* @ methodOf ng . $compileProvider
* @ 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 .
*
2016-04-18 12:34:29 +00:00
* The sanitization is a security measure aimed at prevent 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 ( ) ;
}
} ;
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $compileProvider # imgSrcSanitizationWhitelist
* @ methodOf ng . $compileProvider
* @ 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 ( ) ;
}
} ;
this . $get = [
2016-04-18 12:34:29 +00:00
'$injector' , '$interpolate' , '$exceptionHandler' , '$http' , '$templateCache' , '$parse' ,
'$controller' , '$rootScope' , '$document' , '$sce' , '$animate' , '$$sanitizeUri' ,
function ( $injector , $interpolate , $exceptionHandler , $http , $templateCache , $parse ,
$controller , $rootScope , $document , $sce , $animate , $$sanitizeUri ) {
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
var Attributes = function ( element , attr ) {
2016-03-28 10:46:51 +00:00
this . $$element = element ;
2016-04-18 12:34:29 +00:00
this . $attr = attr || { } ;
} ;
2016-03-28 10:46:51 +00:00
Attributes . prototype = {
$normalize : directiveNormalize ,
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $compile . directive . Attributes # $addClass
* @ methodOf ng . $compile . directive . Attributes
* @ 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
* /
2016-04-18 12:34:29 +00:00
$addClass : function ( classVal ) {
if ( classVal && classVal . length > 0 ) {
2016-03-28 10:46:51 +00:00
$animate . addClass ( this . $$element , classVal ) ;
}
} ,
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $compile . directive . Attributes # $removeClass
* @ methodOf ng . $compile . directive . Attributes
* @ 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
* /
2016-04-18 12:34:29 +00:00
$removeClass : function ( classVal ) {
if ( classVal && classVal . length > 0 ) {
2016-03-28 10:46:51 +00:00
$animate . removeClass ( this . $$element , classVal ) ;
}
} ,
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $compile . directive . Attributes # $updateClass
* @ methodOf ng . $compile . directive . Attributes
* @ 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
* /
2016-04-18 12:34:29 +00:00
$updateClass : function ( newClasses , oldClasses ) {
this . $removeClass ( tokenDifference ( oldClasses , newClasses ) ) ;
this . $addClass ( tokenDifference ( newClasses , oldClasses ) ) ;
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.
2016-04-18 12:34:29 +00:00
var booleanKey = getBooleanAttrName ( this . $$element [ 0 ] , key ) ,
normalizedVal ,
2016-03-28 10:46:51 +00:00
nodeName ;
if ( booleanKey ) {
this . $$element . prop ( key , value ) ;
attrName = booleanKey ;
}
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 ) ;
2016-04-18 12:34:29 +00:00
// sanitize a[href] and img[src] values
if ( ( nodeName === 'A' && key === 'href' ) ||
( nodeName === 'IMG' && key === 'src' ) ) {
2016-03-28 10:46:51 +00:00
this [ key ] = value = $$sanitizeUri ( value , key === 'src' ) ;
}
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 ;
2016-04-18 12:34:29 +00:00
$$observers && forEach ( $$observers [ key ] , function ( fn ) {
2016-03-28 10:46:51 +00:00
try {
fn ( value ) ;
} catch ( e ) {
$exceptionHandler ( e ) ;
}
} ) ;
} ,
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $compile . directive . Attributes # $observe
* @ methodOf ng . $compile . directive . Attributes
* @ 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 .
2016-04-18 12:34:29 +00:00
* See the { @ link guide / directive # Attributes Directives } guide for more info .
* @ returns { function ( ) } the ` fn ` parameter .
2016-03-28 10:46:51 +00:00
* /
$observe : function ( key , fn ) {
var attrs = this ,
2016-04-18 12:34:29 +00:00
$$observers = ( attrs . $$observers || ( attrs . $$observers = { } ) ) ,
2016-03-28 10:46:51 +00:00
listeners = ( $$observers [ key ] || ( $$observers [ key ] = [ ] ) ) ;
listeners . push ( fn ) ;
$rootScope . $evalAsync ( function ( ) {
2016-04-18 12:34:29 +00:00
if ( ! listeners . $$inter ) {
2016-03-28 10:46:51 +00:00
// no one registered attribute interpolation function, so lets call it manually
fn ( attrs [ key ] ) ;
}
} ) ;
2016-04-18 12:34:29 +00:00
return fn ;
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]/ ;
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>
2016-04-18 12:34:29 +00:00
forEach ( $compileNodes , function ( node , index ) {
if ( node . nodeType == 3 /* text node */ && node . nodeValue . match ( /\S+/ ) /* non-empty */ ) {
$compileNodes [ index ] = node = 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 ) ;
2016-04-18 12:34:29 +00:00
safeAddClass ( $compileNodes , 'ng-scope' ) ;
return function publicLinkFn ( scope , cloneConnectFn , transcludeControllers ) {
2016-03-28 10:46:51 +00:00
assertArg ( scope , 'scope' ) ;
2016-04-18 12:34:29 +00:00
// important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
// and sometimes changes the structure of the DOM.
var $linkNode = cloneConnectFn
? JQLitePrototype . clone . call ( $compileNodes ) // IMPORTANT!!!
: $compileNodes ;
forEach ( transcludeControllers , function ( instance , name ) {
$linkNode . data ( '$' + name + 'Controller' , instance ) ;
} ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
// Attach scope only to non-text nodes.
for ( var i = 0 , ii = $linkNode . length ; i < ii ; i ++ ) {
var node = $linkNode [ i ] ,
nodeType = node . nodeType ;
if ( nodeType === 1 /* element */ || nodeType === 9 /* document */ ) {
$linkNode . eq ( i ) . data ( '$scope' , scope ) ;
2016-03-28 10:46:51 +00:00
}
}
if ( cloneConnectFn ) cloneConnectFn ( $linkNode , scope ) ;
2016-04-18 12:34:29 +00:00
if ( compositeLinkFn ) compositeLinkFn ( scope , $linkNode , $linkNode ) ;
2016-03-28 10:46:51 +00:00
return $linkNode ;
} ;
}
2016-04-18 12:34:29 +00: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
* 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
2016-04-18 12:34:29 +00: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 .
2016-04-18 12:34:29 +00: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 = [ ] ,
2016-04-18 12:34:29 +00:00
attrs , directives , nodeLinkFn , childNodes , childLinkFn , linkFnFound ;
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 ) {
2016-04-18 12:34:29 +00:00
safeAddClass ( jqLite ( nodeList [ i ] ) , 'ng-scope' ) ;
2016-03-28 10:46:51 +00:00
}
childLinkFn = ( nodeLinkFn && nodeLinkFn . terminal ||
! ( childNodes = nodeList [ i ] . childNodes ) ||
! childNodes . length )
? null
: compileNodes ( childNodes ,
2016-04-18 12:34:29 +00:00
nodeLinkFn ? nodeLinkFn . transclude : transcludeFn ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
linkFns . push ( nodeLinkFn , childLinkFn ) ;
linkFnFound = linkFnFound || nodeLinkFn || childLinkFn ;
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 ;
2016-04-18 12:34:29 +00:00
function compositeLinkFn ( scope , nodeList , $rootElement , boundTranscludeFn ) {
var nodeLinkFn , childLinkFn , node , $node , childScope , childTranscludeFn , i , ii , n ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
// copy nodeList so that linking doesn't break due to live list updates.
var nodeListLength = nodeList . length ,
stableNodeList = new Array ( nodeListLength ) ;
for ( i = 0 ; i < nodeListLength ; i ++ ) {
stableNodeList [ i ] = nodeList [ i ] ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
for ( i = 0 , n = 0 , ii = linkFns . length ; i < ii ; n ++ ) {
node = stableNodeList [ n ] ;
2016-03-28 10:46:51 +00:00
nodeLinkFn = linkFns [ i ++ ] ;
childLinkFn = linkFns [ i ++ ] ;
2016-04-18 12:34:29 +00:00
$node = jqLite ( node ) ;
2016-03-28 10:46:51 +00:00
if ( nodeLinkFn ) {
if ( nodeLinkFn . scope ) {
childScope = scope . $new ( ) ;
2016-04-18 12:34:29 +00:00
$node . data ( '$scope' , childScope ) ;
2016-03-28 10:46:51 +00:00
} else {
childScope = scope ;
}
2016-04-18 12:34:29 +00:00
childTranscludeFn = nodeLinkFn . transclude ;
if ( childTranscludeFn || ( ! boundTranscludeFn && transcludeFn ) ) {
nodeLinkFn ( childLinkFn , childScope , node , $rootElement ,
createBoundTranscludeFn ( scope , childTranscludeFn || transcludeFn )
) ;
2016-03-28 10:46:51 +00:00
} else {
2016-04-18 12:34:29 +00:00
nodeLinkFn ( childLinkFn , childScope , node , $rootElement , boundTranscludeFn ) ;
2016-03-28 10:46:51 +00:00
}
} else if ( childLinkFn ) {
2016-04-18 12:34:29 +00:00
childLinkFn ( scope , node . childNodes , undefined , boundTranscludeFn ) ;
2016-03-28 10:46:51 +00:00
}
}
}
}
2016-04-18 12:34:29 +00:00
function createBoundTranscludeFn ( scope , transcludeFn ) {
return function boundTranscludeFn ( transcludedScope , cloneFn , controllers ) {
var scopeCreated = false ;
2016-03-28 10:46:51 +00:00
if ( ! transcludedScope ) {
2016-04-18 12:34:29 +00:00
transcludedScope = scope . $new ( ) ;
2016-03-28 10:46:51 +00:00
transcludedScope . $$transcluded = true ;
2016-04-18 12:34:29 +00:00
scopeCreated = true ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
var clone = transcludeFn ( transcludedScope , cloneFn , controllers ) ;
if ( scopeCreated ) {
clone . on ( '$destroy' , bind ( transcludedScope , transcludedScope . $destroy ) ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
return clone ;
} ;
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 ;
2016-04-18 12:34:29 +00:00
switch ( nodeType ) {
case 1 : /* Element */
2016-03-28 10:46:51 +00:00
// use the node name: <directive>
addDirective ( directives ,
2016-04-18 12:34:29 +00:00
directiveNormalize ( nodeName _ ( node ) . toLowerCase ( ) ) , 'E' , maxPriority , ignoreDirective ) ;
2016-03-28 10:46:51 +00:00
// iterate over the attributes
2016-04-18 12:34:29 +00:00
for ( var attr , name , nName , ngAttrName , value , 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 ] ;
2016-04-18 12:34:29 +00:00
if ( ! msie || msie >= 8 || attr . specified ) {
name = attr . name ;
// support ngAttr attribute binding
ngAttrName = directiveNormalize ( name ) ;
if ( NG _ATTR _BINDING . test ( ngAttrName ) ) {
name = snake _case ( ngAttrName . substr ( 6 ) , '-' ) ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
var directiveNName = ngAttrName . replace ( /(Start|End)$/ , '' ) ;
if ( ngAttrName === directiveNName + 'Start' ) {
attrStartName = name ;
attrEndName = name . substr ( 0 , name . length - 5 ) + 'end' ;
name = name . substr ( 0 , name . length - 6 ) ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
nName = directiveNormalize ( name . toLowerCase ( ) ) ;
attrsMap [ nName ] = name ;
attrs [ nName ] = value = trim ( attr . value ) ;
if ( getBooleanAttrName ( node , nName ) ) {
attrs [ nName ] = true ; // presence means true
}
addAttrInterpolateDirective ( node , directives , value , nName ) ;
addDirective ( directives , nName , 'A' , maxPriority , ignoreDirective , attrStartName ,
attrEndName ) ;
2016-03-28 10:46:51 +00:00
}
}
// use class as directive
className = node . className ;
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 ;
2016-04-18 12:34:29 +00:00
case 3 : /* Text Node */
2016-03-28 10:46:51 +00:00
addTextInterpolateDirective ( directives , node . nodeValue ) ;
break ;
2016-04-18 12:34:29 +00:00
case 8 : /* 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 ) ) {
2016-04-18 12:34:29 +00:00
var startNode = node ;
2016-03-28 10:46:51 +00:00
do {
if ( ! node ) {
throw $compileMinErr ( 'uterdir' ,
"Unterminated attribute, found '{0}' but no matching '{1}' found." ,
attrStart , attrEnd ) ;
}
2016-04-18 12:34:29 +00:00
if ( node . nodeType == 1 /** 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
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00:00
* @ returns 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 ,
newIsolateScopeDirective = previousCompileContext . newIsolateScopeDirective ,
templateDirective = previousCompileContext . templateDirective ,
nonTlbTranscludeDirective = previousCompileContext . nonTlbTranscludeDirective ,
hasTranscludeDirective = false ,
2016-04-18 12:34:29 +00:00
hasElementTranscludeDirective = false ,
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
2016-04-18 12:34:29 +00: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 ) {
2016-04-18 12:34:29 +00:00
newScopeDirective = newScopeDirective || directive ;
2016-03-28 10:46:51 +00:00
// skip the check for directives with async templates, we'll check the derived sync
// directive when the template arrives
if ( ! directive . templateUrl ) {
2016-04-18 12:34:29 +00:00
assertNoDuplicate ( 'new/isolated scope' , newIsolateScopeDirective , directive ,
$compileNode ) ;
2016-03-28 10:46:51 +00:00
if ( isObject ( directiveValue ) ) {
newIsolateScopeDirective = directive ;
}
}
}
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.
2016-04-18 12:34:29 +00:00
// This option should only be used by directives that know how to 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 ;
2016-04-18 12:34:29 +00:00
$template = groupScan ( compileNode , attrStart , attrEnd ) ;
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 ] ;
2016-04-18 12:34:29 +00:00
replaceWith ( jqCollection , jqLite ( 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 ) {
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 ;
2016-04-18 12:34:29 +00:00
$template = jqLite ( '<div>' +
trim ( directiveValue ) +
'</div>' ) . contents ( ) ;
2016-03-28 10:46:51 +00:00
compileNode = $template [ 0 ] ;
2016-04-18 12:34:29 +00:00
if ( $template . length != 1 || compileNode . nodeType !== 1 ) {
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 ) {
assertNoDuplicate ( 'template' , templateDirective , directive , $compileNode ) ;
templateDirective = directive ;
if ( directive . replace ) {
replaceDirective = directive ;
}
nodeLinkFn = compileTemplateUrl ( directives . splice ( i , directives . length - i ) , $compileNode ,
2016-04-18 12:34:29 +00:00
templateAttrs , jqCollection , 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 ;
2016-04-18 12:34:29 +00:00
nodeLinkFn . transclude = hasTranscludeDirective && childTranscludeFn ;
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 ;
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 ;
if ( newIsolateScopeDirective === directive || directive . $$isolateScope ) {
post = cloneAndAnnotateFn ( post , { isolateScope : true } ) ;
}
postLinkFns . push ( post ) ;
}
}
2016-04-18 12:34:29 +00:00
function getControllers ( require , $element , elementControllers ) {
var value , retrievalMethod = 'data' , optional = false ;
if ( isString ( require ) ) {
while ( ( value = require . charAt ( 0 ) ) == '^' || value == '?' ) {
require = require . substr ( 1 ) ;
if ( value == '^' ) {
retrievalMethod = 'inheritedData' ;
}
optional = optional || value == '?' ;
}
value = null ;
if ( elementControllers && retrievalMethod === 'data' ) {
value = elementControllers [ require ] ;
}
value = value || $element [ retrievalMethod ] ( '$' + require + 'Controller' ) ;
if ( ! value && ! optional ) {
throw $compileMinErr ( 'ctreq' ,
"Controller '{0}', required by directive '{1}', can't be found!" ,
require , directiveName ) ;
}
return value ;
} else if ( isArray ( require ) ) {
value = [ ] ;
forEach ( require , function ( require ) {
value . push ( getControllers ( require , $element , elementControllers ) ) ;
} ) ;
}
return value ;
}
2016-03-28 10:46:51 +00:00
function nodeLinkFn ( childLinkFn , scope , linkNode , $rootElement , boundTranscludeFn ) {
2016-04-18 12:34:29 +00:00
var attrs , $element , i , ii , linkFn , controller , isolateScope , elementControllers = { } , transcludeFn ;
2016-03-28 10:46:51 +00:00
if ( compileNode === linkNode ) {
attrs = templateAttrs ;
} else {
2016-04-18 12:34:29 +00:00
attrs = shallowCopy ( templateAttrs , new Attributes ( jqLite ( linkNode ) , templateAttrs . $attr ) ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
$element = attrs . $$element ;
2016-03-28 10:46:51 +00:00
if ( newIsolateScopeDirective ) {
2016-04-18 12:34:29 +00:00
var LOCAL _REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/ ;
var $linkNode = jqLite ( linkNode ) ;
2016-03-28 10:46:51 +00:00
isolateScope = scope . $new ( true ) ;
2016-04-18 12:34:29 +00:00
if ( templateDirective && ( templateDirective === newIsolateScopeDirective . $$originalDirective ) ) {
$linkNode . data ( '$isolateScope' , isolateScope ) ;
} else {
$linkNode . data ( '$isolateScopeNoTemplate' , isolateScope ) ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
safeAddClass ( $linkNode , 'ng-isolate-scope' ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
forEach ( newIsolateScopeDirective . scope , function ( definition , scopeName ) {
var match = definition . match ( LOCAL _REGEXP ) || [ ] ,
attrName = match [ 3 ] || scopeName ,
optional = ( match [ 2 ] == '?' ) ,
mode = match [ 1 ] , // @, =, or &
lastValue ,
parentGet , parentSet , compare ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
isolateScope . $$isolateBindings [ scopeName ] = mode + attrName ;
switch ( mode ) {
case '@' :
attrs . $observe ( attrName , function ( value ) {
isolateScope [ scopeName ] = value ;
} ) ;
attrs . $$observers [ attrName ] . $$scope = scope ;
if ( attrs [ attrName ] ) {
// If the attribute has been provided then we trigger an interpolation to ensure
// the value is there for use in the link fn
isolateScope [ scopeName ] = $interpolate ( attrs [ attrName ] ) ( scope ) ;
}
break ;
case '=' :
if ( optional && ! attrs [ attrName ] ) {
return ;
}
parentGet = $parse ( attrs [ attrName ] ) ;
if ( parentGet . literal ) {
compare = equals ;
} else {
compare = function ( a , b ) { return a === b ; } ;
}
parentSet = parentGet . assign || function ( ) {
// reset the change, or we will throw this exception on every $digest
lastValue = isolateScope [ scopeName ] = parentGet ( scope ) ;
throw $compileMinErr ( 'nonassign' ,
"Expression '{0}' used with directive '{1}' is non-assignable!" ,
attrs [ attrName ] , newIsolateScopeDirective . name ) ;
} ;
lastValue = isolateScope [ scopeName ] = parentGet ( scope ) ;
isolateScope . $watch ( function parentValueWatch ( ) {
var parentValue = parentGet ( scope ) ;
if ( ! compare ( parentValue , isolateScope [ scopeName ] ) ) {
// we are out of sync and need to copy
if ( ! compare ( parentValue , lastValue ) ) {
// parent changed and it has precedence
isolateScope [ scopeName ] = parentValue ;
} else {
// if the parent can be assigned then do so
parentSet ( scope , parentValue = isolateScope [ scopeName ] ) ;
}
}
return lastValue = parentValue ;
} , null , parentGet . literal ) ;
break ;
case '&' :
parentGet = $parse ( attrs [ attrName ] ) ;
isolateScope [ scopeName ] = function ( locals ) {
return parentGet ( scope , locals ) ;
} ;
break ;
default :
throw $compileMinErr ( 'iscp' ,
"Invalid isolate scope definition for directive '{0}'." +
" Definition: {... {1}: '{2}' ...}" ,
newIsolateScopeDirective . name , scopeName , definition ) ;
}
} ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
transcludeFn = boundTranscludeFn && controllersBoundTransclude ;
if ( controllerDirectives ) {
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 ] ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
controllerInstance = $controller ( controller , locals ) ;
// 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 ) ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( directive . controllerAs ) {
locals . $scope [ directive . controllerAs ] = controllerInstance ;
}
} ) ;
}
2016-03-28 10:46:51 +00:00
// PRELINKING
2016-04-18 12:34:29 +00:00
for ( i = 0 , ii = preLinkFns . length ; i < ii ; i ++ ) {
try {
linkFn = preLinkFns [ i ] ;
linkFn ( linkFn . isolateScope ? isolateScope : scope , $element , attrs ,
linkFn . require && getControllers ( linkFn . require , $element , elementControllers ) , transcludeFn ) ;
} catch ( e ) {
$exceptionHandler ( e , startingTag ( $element ) ) ;
}
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
2016-04-18 12:34:29 +00:00
for ( i = postLinkFns . length - 1 ; i >= 0 ; i -- ) {
try {
linkFn = postLinkFns [ i ] ;
linkFn ( linkFn . isolateScope ? isolateScope : scope , $element , attrs ,
linkFn . require && getControllers ( linkFn . require , $element , elementControllers ) , transcludeFn ) ;
} catch ( e ) {
$exceptionHandler ( e , startingTag ( $element ) ) ;
2016-03-28 10:46:51 +00:00
}
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`.
2016-04-18 12:34:29 +00:00
function controllersBoundTransclude ( scope , cloneAttachFn ) {
2016-03-28 10:46:51 +00:00
var transcludeControllers ;
2016-04-18 12:34:29 +00:00
// no scope passed
if ( arguments . length < 2 ) {
2016-03-28 10:46:51 +00:00
cloneAttachFn = scope ;
scope = undefined ;
}
if ( hasElementTranscludeDirective ) {
transcludeControllers = elementControllers ;
}
2016-04-18 12:34:29 +00:00
return boundTranscludeFn ( scope , cloneAttachFn , transcludeControllers ) ;
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
2016-04-18 12:34:29 +00:00
* @ returns 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 ) ) {
2016-04-18 12:34:29 +00: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 ] ;
2016-04-18 12:34:29 +00: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 ;
}
2016-04-18 12:34:29 +00:00
} catch ( e ) { $exceptionHandler ( e ) ; }
2016-03-28 10:46:51 +00:00
}
}
return match ;
}
/ * *
* 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 ) != '$' ) {
2016-04-18 12:34:29 +00:00
if ( src [ key ] ) {
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 ( ) ,
2016-04-18 12:34:29 +00:00
// The fact that we have to copy and patch the directive seems wrong!
derivedSyncDirective = extend ( { } , 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 )
2016-04-18 12:34:29 +00:00
: origAsyncDirective . templateUrl ;
2016-03-28 10:46:51 +00:00
$compileNode . empty ( ) ;
2016-04-18 12:34:29 +00:00
$http . get ( $sce . getTrustedResourceUrl ( templateUrl ) , { cache : $templateCache } ) .
success ( function ( content ) {
2016-03-28 10:46:51 +00:00
var compileNode , tempTemplateAttrs , $template , childBoundTranscludeFn ;
content = denormalizeTemplate ( content ) ;
if ( origAsyncDirective . replace ) {
2016-04-18 12:34:29 +00:00
$template = jqLite ( '<div>' + trim ( content ) + '</div>' ) . contents ( ) ;
2016-03-28 10:46:51 +00:00
compileNode = $template [ 0 ] ;
2016-04-18 12:34:29 +00:00
if ( $template . length != 1 || compileNode . nodeType !== 1 ) {
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 ) ;
2016-04-18 12:34:29 +00: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 ] ;
if ( beforeTemplateLinkNode !== beforeTemplateCompileNode ) {
2016-04-18 12:34:29 +00:00
// 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 ) ;
}
2016-04-18 12:34:29 +00:00
if ( afterTemplateNodeLinkFn . transclude ) {
childBoundTranscludeFn = createBoundTranscludeFn ( scope , afterTemplateNodeLinkFn . transclude ) ;
2016-03-28 10:46:51 +00:00
} else {
childBoundTranscludeFn = boundTranscludeFn ;
}
afterTemplateNodeLinkFn ( afterTemplateChildLinkFn , scope , linkNode , $rootElement ,
childBoundTranscludeFn ) ;
}
linkQueue = null ;
2016-04-18 12:34:29 +00:00
} ) .
error ( function ( response , code , headers , config ) {
throw $compileMinErr ( 'tpload' , 'Failed to load template: {0}' , config . url ) ;
2016-03-28 10:46:51 +00:00
} ) ;
return function delayedNodeLinkFn ( ignoreChildLinkFn , scope , node , rootElement , boundTranscludeFn ) {
if ( linkQueue ) {
2016-04-18 12:34:29 +00:00
linkQueue . push ( scope ) ;
linkQueue . push ( node ) ;
linkQueue . push ( rootElement ) ;
linkQueue . push ( boundTranscludeFn ) ;
2016-03-28 10:46:51 +00:00
} else {
2016-04-18 12:34:29 +00:00
afterTemplateNodeLinkFn ( afterTemplateChildLinkFn , scope , node , rootElement , boundTranscludeFn ) ;
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 ,
2016-04-18 12:34:29 +00:00
compile : valueFn ( function textInterpolateLinkFn ( scope , node ) {
var parent = node . parent ( ) ,
bindings = parent . data ( '$binding' ) || [ ] ;
bindings . push ( interpolateFn ) ;
safeAddClass ( parent . data ( '$binding' , bindings ) , 'ng-binding' ) ;
scope . $watch ( interpolateFn , function interpolateFnWatchAction ( value ) {
node [ 0 ] . nodeValue = value ;
} ) ;
} )
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" ||
2016-04-18 12:34:29 +00:00
( tag == "FORM" && attrNormalizedName == "action" ) ||
( tag != "IMG" && ( attrNormalizedName == "src" ||
2016-03-28 10:46:51 +00:00
attrNormalizedName == "ngSrc" ) ) ) {
return $sce . RESOURCE _URL ;
}
}
2016-04-18 12:34:29 +00:00
function addAttrInterpolateDirective ( node , directives , value , name ) {
var interpolateFn = $interpolate ( value , true ) ;
2016-03-28 10:46:51 +00:00
// no interpolation found -> ignore
if ( ! interpolateFn ) return ;
2016-04-18 12:34:29 +00: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." ) ;
}
2016-04-18 12:34:29 +00:00
// we need to interpolate again, in case the attribute value has been updated
// (e.g. by another directive's compile function)
interpolateFn = $interpolate ( attr [ name ] , true , getTrustedContext ( node , name ) ) ;
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 ;
2016-04-18 12:34:29 +00:00
// TODO(i): this should likely be attr.$set(name, iterpolateFn(scope) so that we reset the
// actual attr value
2016-03-28 10:46:51 +00:00
attr [ name ] = interpolateFn ( scope ) ;
( $$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
2016-04-18 12:34:29 +00: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 ) {
2016-04-18 12:34:29 +00: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 ;
break ;
}
}
}
if ( parent ) {
parent . replaceChild ( newNode , firstElementToRemove ) ;
}
var fragment = document . createDocumentFragment ( ) ;
2016-04-18 12:34:29 +00:00
fragment . appendChild ( firstElementToRemove ) ;
newNode [ jqLite . expando ] = firstElementToRemove [ jqLite . expando ] ;
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 ) ;
}
2016-04-18 12:34:29 +00:00
} ] ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
var PREFIX _REGEXP = /^(x[\:\-_]|data[\:\-_])/i ;
/ * *
* Converts all accepted directives format into proper directive name .
* All of these will become 'myDirective' :
* my : Directive
* my - directive
* x - my - directive
* data - my : directive
*
* Also there is special case for Moz prefix starting with upper case letter .
* @ 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
/ * *
* @ ngdoc object
* @ name ng . $compile . directive . Attributes
*
* @ 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 :
*
* < span ng : bind = "a" ng - bind = "a" data - ng - bind = "a" x - ng - bind = "a" >
* /
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/ * *
* @ ngdoc property
* @ name ng . $compile . directive . Attributes # $attr
* @ propertyOf ng . $compile . directive . Attributes
* @ returns { object } 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-03-28 10:46:51 +00:00
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $compile . directive . Attributes # $set
* @ methodOf ng . $compile . directive . Attributes
* @ 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
2016-04-18 12:34:29 +00:00
) { }
2016-03-28 10:46:51 +00:00
function directiveLinkingFn (
/* nodesetLinkingFn */ nodesetLinkingFn ,
/* angular.Scope */ scope ,
/* Node */ node ,
/* Element */ rootElement ,
/* function(Function) */ boundTranscludeFn
2016-04-18 12:34:29 +00:00
) { }
2016-03-28 10:46:51 +00:00
function tokenDifference ( str1 , str2 ) {
var values = '' ,
tokens1 = str1 . split ( /\s+/ ) ,
tokens2 = str2 . split ( /\s+/ ) ;
outer :
2016-04-18 12:34:29 +00:00
for ( var i = 0 ; i < tokens1 . length ; i ++ ) {
2016-03-28 10:46:51 +00:00
var token = tokens1 [ i ] ;
2016-04-18 12:34:29 +00: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 ;
}
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc object
* @ name ng . $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
2016-04-18 12:34:29 +00:00
* { @ link ng . $controllerProvider # methods _register register } method .
2016-03-28 10:46:51 +00:00
* /
function $ControllerProvider ( ) {
var controllers = { } ,
2016-04-18 12:34:29 +00:00
CNTRL _REG = /^(\S+)(\s+as\s+(\w+))?$/ ;
2016-03-28 10:46:51 +00:00
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $controllerProvider # register
* @ methodOf ng . $controllerProvider
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 ;
}
} ;
this . $get = [ '$injector' , '$window' , function ( $injector , $window ) {
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $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
2016-04-18 12:34:29 +00:00
* * check ` window[constructor] ` on the global ` window ` object
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 .
*
2016-04-18 12:34:29 +00: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 { @ link https : //gist.github.com/1649788
* BC version } .
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
return function ( expression , locals ) {
2016-03-28 10:46:51 +00:00
var instance , match , constructor , identifier ;
2016-04-18 12:34:29 +00:00
if ( isString ( expression ) ) {
match = expression . match ( CNTRL _REG ) ,
2016-03-28 10:46:51 +00:00
constructor = match [ 1 ] ,
2016-04-18 12:34:29 +00:00
identifier = match [ 3 ] ;
2016-03-28 10:46:51 +00:00
expression = controllers . hasOwnProperty ( constructor )
? controllers [ constructor ]
2016-04-18 12:34:29 +00:00
: getter ( locals . $scope , constructor , true ) || getter ( $window , constructor , true ) ;
2016-03-28 10:46:51 +00:00
assertArgFn ( expression , constructor , true ) ;
}
2016-04-18 12:34:29 +00:00
instance = $injector . instantiate ( expression , locals ) ;
2016-03-28 10:46:51 +00:00
if ( identifier ) {
2016-04-18 12:34:29 +00:00
if ( ! ( locals && typeof locals . $scope == 'object' ) ) {
throw minErr ( '$controller' ) ( 'noscp' ,
"Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`." ,
constructor || expression . name , identifier ) ;
}
locals . $scope [ identifier ] = instance ;
2016-03-28 10:46:51 +00:00
}
return instance ;
} ;
} ] ;
}
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc object
* @ name ng . $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 .
* /
2016-04-18 12:34:29 +00:00
function $DocumentProvider ( ) {
this . $get = [ '$window' , function ( window ) {
2016-03-28 10:46:51 +00:00
return jqLite ( window . document ) ;
} ] ;
}
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $exceptionHandler
* @ requires $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
*
2016-04-18 12:34:29 +00:00
* < pre >
* 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 ;
* } ;
* } ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
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 .
*
* @ 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 ) ;
} ;
} ] ;
}
/ * *
* 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 ) {
2016-04-18 12:34:29 +00:00
var parsed = { } , key , val , i ;
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 ) {
2016-04-18 12:34:29 +00:00
if ( parsed [ key ] ) {
parsed [ key ] += ', ' + val ;
} else {
parsed [ key ] = 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 ) {
2016-04-18 12:34:29 +00:00
return headersObj [ lowercase ( name ) ] || null ;
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 .
2016-04-18 12:34:29 +00:00
* @ param { function ( string = ) } headers Http headers getter fn .
* @ param { ( function | Array . < function > ) } fns Function or an array of functions .
2016-03-28 10:46:51 +00:00
* @ returns { * } Transformed data .
* /
2016-04-18 12:34:29 +00:00
function transformData ( data , headers , fns ) {
if ( isFunction ( fns ) )
return fns ( data , headers ) ;
2016-03-28 10:46:51 +00:00
forEach ( fns , function ( fn ) {
2016-04-18 12:34:29 +00:00
data = fn ( data , headers ) ;
2016-03-28 10:46:51 +00:00
} ) ;
return data ;
}
function isSuccess ( status ) {
return 200 <= status && status < 300 ;
}
function $HttpProvider ( ) {
2016-04-18 12:34:29 +00:00
var JSON _START = /^\s*(\[|\{[^\{])/ ,
JSON _END = /[\}\]]\s*$/ ,
PROTECTION _PREFIX = /^\)\]\}',?\n/ ,
CONTENT _TYPE _APPLICATION _JSON = { 'Content-Type' : 'application/json;charset=utf-8' } ;
2016-03-28 10:46:51 +00:00
var defaults = this . defaults = {
// transform incoming response data
2016-04-18 12:34:29 +00:00
transformResponse : [ function ( data ) {
if ( isString ( data ) ) {
// strip json vulnerability protection prefix
data = data . replace ( PROTECTION _PREFIX , '' ) ;
if ( JSON _START . test ( data ) && JSON _END . test ( data ) )
data = fromJson ( data ) ;
}
return data ;
} ] ,
2016-03-28 10:46:51 +00:00
// transform outgoing request data
transformRequest : [ function ( d ) {
2016-04-18 12:34:29 +00:00
return isObject ( d ) && ! isFile ( d ) ? toJson ( d ) : d ;
2016-03-28 10:46:51 +00:00
} ] ,
// default headers
headers : {
common : {
'Accept' : 'application/json, text/plain, */*'
} ,
2016-04-18 12:34:29 +00:00
post : copy ( CONTENT _TYPE _APPLICATION _JSON ) ,
put : copy ( CONTENT _TYPE _APPLICATION _JSON ) ,
patch : copy ( 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
} ;
/ * *
2016-04-18 12:34:29 +00:00
* Are ordered by request , i . e . they are applied in the same order as the
* array , on request , but reverse order , on response .
* /
var interceptorFactories = this . interceptors = [ ] ;
2016-03-28 10:46:51 +00:00
/ * *
2016-04-18 12:34:29 +00:00
* For historical reasons , response interceptors are ordered by the order in which
* they are applied to the response . ( This is the opposite of interceptorFactories )
* /
var responseInterceptorFactories = this . responseInterceptors = [ ] ;
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 ) ) ;
} ) ;
2016-04-18 12:34:29 +00:00
forEach ( responseInterceptorFactories , function ( interceptorFactory , index ) {
var responseFn = isString ( interceptorFactory )
? $injector . get ( interceptorFactory )
: $injector . invoke ( interceptorFactory ) ;
/ * *
* Response interceptors go before "around" interceptors ( no real reason , just
* had to pick one . ) But they are already reversed , so we can ' t use unshift , hence
* the splice .
* /
reversedInterceptors . splice ( index , 0 , {
response : function ( response ) {
return responseFn ( $q . when ( response ) ) ;
} ,
responseError : function ( response ) {
return responseFn ( $q . reject ( response ) ) ;
}
} ) ;
} ) ;
2016-03-28 10:46:51 +00:00
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $http
* @ requires $httpBackend
* @ requires $browser
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
2016-04-18 12:34:29 +00:00
* HTTP servers via the browser ' s { @ link https : //developer.mozilla.org/en/xmlhttprequest
* XMLHttpRequest } object or via { @ link http : //en.wikipedia.org/wiki/JSONP 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 .
*
*
2016-04-18 12:34:29 +00:00
* # General usage
* 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
*
2016-04-18 12:34:29 +00:00
* < pre >
* $http ( { method : 'GET' , url : '/someUrl' } ) .
* 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.
* } ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
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 .
*
2016-04-18 12:34:29 +00:00
* # Calling $http from outside AngularJS
* The ` $ http ` service will not actually send the request until the next ` $ digest() ` is
* executed . Normally this is not an issue , since almost all the time your call to ` $ http ` will
* be from within a ` $ apply() ` block .
* If you are calling ` $ http ` from outside Angular , then you should wrap it in a call to
* ` $ apply ` to cause a $digest to occur and also to handle errors in the block correctly .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* ` ` `
* $scope . $apply ( function ( ) {
* $http ( ... ) ;
* } ) ;
* ` ` `
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* # Writing Unit Tests that use $http
* When unit testing you are mostly responsible for scheduling the ` $ digest ` cycle . If you do
* not trigger a ` $ digest ` before calling ` $ httpBackend.flush() ` then the request will not have
* been made and ` $ httpBackend.expect(...) ` expectations will fail . The solution is to run the
* code that calls the ` $ http() ` method inside a $apply block as explained in the previous
* section .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* ` ` `
* $httpBackend . expectGET ( ... ) ;
* $scope . $apply ( function ( ) {
* $http . get ( ... ) ;
* } ) ;
* $httpBackend . flush ( ) ;
2016-03-28 10:46:51 +00:00
* ` ` `
*
2016-04-18 12:34:29 +00:00
* # Shortcut methods
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* Since all invocations of the $http service require passing in an HTTP method and URL , and
* POST / PUT requests require request data to be provided as well , shortcut methods
* were created :
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* < pre >
* $http . get ( '/someUrl' ) . success ( successCallback ) ;
* $http . post ( '/someUrl' , data ) . success ( successCallback ) ;
* < / p r e >
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
*
2016-04-18 12:34:29 +00:00
* - { @ link ng . $http # methods _get $http . get }
* - { @ link ng . $http # methods _head $http . head }
* - { @ link ng . $http # methods _post $http . post }
* - { @ link ng . $http # methods _put $http . put }
* - { @ link ng . $http # methods _delete $http . delete }
* - { @ link ng . $http # methods _jsonp $http . jsonp }
2016-03-28 10:46:51 +00:00
*
*
2016-04-18 12:34:29 +00: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 ) {
2016-04-18 12:34:29 +00:00
* $http . defaults . headers . common . Authentication = '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 .
*
*
2016-04-18 12:34:29 +00:00
* # Transforming Requests and Responses
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* Both requests and responses can be transformed using transform functions . By default , Angular
* applies these transformations :
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* Request transformations :
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 .
*
2016-04-18 12:34:29 +00:00
* Response transformations :
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 .
*
2016-04-18 12:34:29 +00:00
* To globally augment or override the default transforms , modify the
* ` $ httpProvider.defaults.transformRequest ` and ` $ httpProvider.defaults.transformResponse `
* properties . These properties are by default an array of transform functions , which allows you
* to ` push ` or ` unshift ` a new transformation function into the transformation chain . You can
* also decide to completely override any default transformations by assigning your
* transformation functions to these properties directly without the array wrapper . These defaults
* are again available on the $http factory at run - time , which may be useful if you have run - time
* services you wish to be involved in your transformations .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* Similarly , to locally override the request / response transforms , augment the
* ` transformRequest ` and / or ` transformResponse ` properties of the configuration object passed
2016-03-28 10:46:51 +00:00
* into ` $ http ` .
*
*
2016-04-18 12:34:29 +00: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
* { @ link ng . $http # properties _defaults ` $ http.defaults.cache ` } property . All requests who set
* 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
*
2016-04-18 12:34:29 +00: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 ) :
*
2016-04-18 12:34:29 +00:00
* * ` request ` : interceptors get called with http ` config ` object . The function is free to
* modify the ` config ` or create a new one . The function needs to return the ` config `
* directly or as a promise .
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
2016-04-18 12:34:29 +00:00
* modify the ` response ` or create a new one . The function needs to return the ` response `
* directly or as a promise .
2016-03-28 10:46:51 +00:00
* * ` responseError ` : interceptor gets called when a previous interceptor threw an error or
* resolved with a rejection .
*
*
2016-04-18 12:34:29 +00:00
* < pre >
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
2016-04-18 12:34:29 +00:00
* return config || $q . when ( 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
2016-04-18 12:34:29 +00:00
* return response || $q . when ( 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
* }
* } ;
* } ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
*
* # Response interceptors ( DEPRECATED )
*
* Before you start creating interceptors , be sure to understand the
* { @ link ng . $q $q and deferred / promise APIs } .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* For purposes of global error handling , authentication or any kind of synchronous or
* asynchronous preprocessing of received responses , it is desirable to be able to intercept
* responses for http requests before they are handed over to the application code that
* initiated these requests . The response interceptors leverage the { @ link ng . $q
* promise apis } to fulfil this need for both synchronous and asynchronous preprocessing .
*
* The interceptors are service factories that are registered with the $httpProvider by
* adding them to the ` $ httpProvider.responseInterceptors ` array . The factory is called and
* injected with dependencies ( if specified ) and returns the interceptor — a function that
* takes a { @ link ng . $q promise } and returns the original or a new promise .
*
* < pre >
* // register the interceptor as a service
* $provide . factory ( 'myHttpInterceptor' , function ( $q , dependency1 , dependency2 ) {
* return function ( promise ) {
* return promise . then ( function ( response ) {
* // do something on success
* return response ;
* } , function ( response ) {
* // do something on error
* if ( canRecover ( response ) ) {
* return responseOrNewPromise
* }
* return $q . reject ( response ) ;
* } ) ;
* }
* } ) ;
*
* $httpProvider . responseInterceptors . push ( 'myHttpInterceptor' ) ;
*
*
* // register the interceptor via an anonymous factory
* $httpProvider . responseInterceptors . push ( function ( $q , dependency1 , dependency2 ) {
* return function ( promise ) {
* // same as above
* }
* } ) ;
* < / p r e >
*
*
* # Security Considerations
2016-03-28 10:46:51 +00:00
*
* When designing web applications , consider security threats from :
*
2016-04-18 12:34:29 +00:00
* - { @ link http : //haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx
* JSON vulnerability }
* - { @ link http : //en.wikipedia.org/wiki/Cross-site_request_forgery XSRF}
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 .
*
2016-04-18 12:34:29 +00:00
* # # JSON Vulnerability Protection
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* A { @ link http : //haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx
* JSON vulnerability } allows third party website to turn your JSON resource URL into
* { @ link http : //en.wikipedia.org/wiki/JSONP 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 :
2016-04-18 12:34:29 +00:00
* < pre >
2016-03-28 10:46:51 +00:00
* [ 'one' , 'two' ]
2016-04-18 12:34:29 +00:00
* < / p r e >
2016-03-28 10:46:51 +00:00
*
* which is vulnerable to attack , your server can return :
2016-04-18 12:34:29 +00:00
* < pre >
2016-03-28 10:46:51 +00:00
* ) ] } ' ,
* [ 'one' , 'two' ]
2016-04-18 12:34:29 +00:00
* < / p r e >
2016-03-28 10:46:51 +00:00
*
* Angular will strip the prefix , before processing the JSON .
*
*
2016-04-18 12:34:29 +00:00
* # # Cross Site Request Forgery ( XSRF ) Protection
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* { @ link http : //en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} is a technique by which
* 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
2016-04-18 12:34:29 +00:00
* authentication cookie with a { @ link https : //en.wikipedia.org/wiki/Salt_(cryptography) salt}
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 .
* - * * transformResponse * * –
2016-04-18 12:34:29 +00:00
* ` {function(data, headersGetter)|Array.<function(data, headersGetter)>} ` –
2016-03-28 10:46:51 +00:00
* transform function or an array of such functions . The transform function takes the http
2016-04-18 12:34:29 +00:00
* response body and headers and returns its transformed ( typically deserialized ) version .
* - * * 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 .
2016-04-18 12:34:29 +00:00
* - * * withCredentials * * - ` {boolean} ` - whether to to set the ` withCredentials ` flag on the
* XHR object . See { @ link https : //developer.mozilla.org/en/http_access_control#section_5
* requests with credentials } for more information .
* - * * responseType * * - ` {string} ` - see { @ link
* https : //developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}.
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 .
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
2016-04-18 12:34:29 +00:00
< example >
2016-03-28 10:46:51 +00:00
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
< div ng - controller = "FetchCtrl" >
< 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" / >
< button ng - click = "fetch()" > fetch < / b u t t o n > < b r >
< button ng - click = "updateModel('GET', 'http-hello.html')" > Sample GET < / b u t t o n >
< button
2016-03-28 10:46:51 +00:00
ng - click = " updateModel ( 'JSONP' ,
2016-04-18 12:34:29 +00:00
'http://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 >
2016-04-18 12:34:29 +00:00
< button
ng - click = "updateModel('JSONP', 'http://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" >
2016-04-18 12:34:29 +00:00
function FetchCtrl ( $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
2016-04-18 12:34:29 +00: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 >
2016-04-18 12:34:29 +00:00
< file name = "scenario.js" >
2016-03-28 10:46:51 +00:00
it ( 'should make an xhr GET request' , function ( ) {
2016-04-18 12:34:29 +00:00
element ( ':button:contains("Sample GET")' ) . click ( ) ;
element ( ':button:contains("fetch")' ) . click ( ) ;
expect ( binding ( 'status' ) ) . toBe ( '200' ) ;
expect ( binding ( 'data' ) ) . toMatch ( /Hello, \$http!/ ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
it ( 'should make a JSONP request to angularjs.org' , function ( ) {
element ( ':button:contains("Sample JSONP")' ) . click ( ) ;
element ( ':button:contains("fetch")' ) . click ( ) ;
expect ( binding ( 'status' ) ) . toBe ( '200' ) ;
expect ( binding ( 'data' ) ) . 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 ( ) {
2016-04-18 12:34:29 +00:00
element ( ':button:contains("Invalid JSONP")' ) . click ( ) ;
element ( ':button:contains("fetch")' ) . click ( ) ;
expect ( binding ( 'status' ) ) . toBe ( '0' ) ;
expect ( binding ( 'data' ) ) . toBe ( 'Request failed' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
< / f i l e >
< / e x a m p l e >
* /
function $http ( requestConfig ) {
2016-04-18 12:34:29 +00:00
var config = {
transformRequest : defaults . transformRequest ,
transformResponse : defaults . transformResponse
} ;
var headers = mergeHeaders ( requestConfig ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
extend ( config , requestConfig ) ;
config . headers = headers ;
config . method = uppercase ( config . method ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
var xsrfValue = urlIsSameOrigin ( config . url )
? $browser . cookies ( ) [ config . xsrfCookieName || defaults . xsrfCookieName ]
: undefined ;
if ( xsrfValue ) {
headers [ ( config . xsrfHeaderName || defaults . xsrfHeaderName ) ] = xsrfValue ;
2016-03-28 10:46:51 +00:00
}
var serverRequest = function ( config ) {
2016-04-18 12:34:29 +00:00
headers = config . headers ;
var reqData = transformData ( config . data , headersGetter ( headers ) , config . transformRequest ) ;
2016-03-28 10:46:51 +00:00
// strip content-type if data is undefined
2016-04-18 12:34:29 +00:00
if ( isUndefined ( config . data ) ) {
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
2016-04-18 12:34:29 +00:00
return sendReq ( config , reqData , headers ) . 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 ) ;
}
} ) ;
2016-04-18 12:34:29 +00: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 ) {
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 ) {
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
2016-04-18 12:34:29 +00:00
var resp = extend ( { } , response , {
data : transformData ( response . data , response . headers , config . transformResponse )
} ) ;
2016-03-28 10:46:51 +00:00
return ( isSuccess ( response . status ) )
? resp
: $q . reject ( resp ) ;
}
function mergeHeaders ( config ) {
var defHeaders = defaults . headers ,
reqHeaders = extend ( { } , config . headers ) ,
defHeaderName , lowercaseDefHeaderName , reqHeaderName ;
defHeaders = extend ( { } , defHeaders . common , defHeaders [ lowercase ( config . method ) ] ) ;
2016-04-18 12:34:29 +00:00
// execute if header value is function
execHeaders ( defHeaders ) ;
execHeaders ( reqHeaders ) ;
// 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 ] ;
}
2016-04-18 12:34:29 +00:00
return reqHeaders ;
function execHeaders ( headers ) {
var headerContent ;
forEach ( headers , function ( headerFn , header ) {
if ( isFunction ( headerFn ) ) {
headerContent = headerFn ( ) ;
if ( headerContent != null ) {
headers [ header ] = headerContent ;
} else {
delete headers [ header ] ;
}
}
} ) ;
}
2016-03-28 10:46:51 +00:00
}
}
$http . pendingRequests = [ ] ;
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name ng . $http # get
* @ methodOf ng . $http
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
2016-04-18 12:34:29 +00:00
* @ name ng . $http # delete
* @ methodOf ng . $http
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
2016-04-18 12:34:29 +00:00
* @ name ng . $http # head
* @ methodOf ng . $http
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
2016-04-18 12:34:29 +00:00
* @ name ng . $http # jsonp
* @ methodOf ng . $http
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 .
2016-04-18 12:34:29 +00:00
* Should contain ` JSON_CALLBACK ` string .
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
2016-04-18 12:34:29 +00:00
* @ name ng . $http # post
* @ methodOf ng . $http
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
2016-04-18 12:34:29 +00:00
* @ name ng . $http # put
* @ methodOf ng . $http
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
* /
2016-04-18 12:34:29 +00:00
createShortMethodsWithData ( 'post' , 'put' ) ;
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc property
2016-04-18 12:34:29 +00:00
* @ name ng . $http # defaults
* @ propertyOf ng . $http
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
* /
2016-04-18 12:34:29 +00:00
function sendReq ( config , reqData , reqHeaders ) {
2016-03-28 10:46:51 +00:00
var deferred = $q . defer ( ) ,
promise = deferred . promise ,
cache ,
cachedResp ,
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 ) ;
2016-04-18 12:34:29 +00:00
if ( ( config . cache || defaults . cache ) && config . cache !== false && config . method == 'GET' ) {
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 ) ) {
2016-04-18 12:34:29 +00:00
if ( cachedResp . then ) {
2016-03-28 10:46:51 +00:00
// cached request has already been sent, but there is no response yet
2016-04-18 12:34:29 +00:00
cachedResp . then ( removePendingReq , removePendingReq ) ;
return cachedResp ;
2016-03-28 10:46:51 +00:00
} else {
// serving from cache
if ( isArray ( cachedResp ) ) {
2016-04-18 12:34:29 +00:00
resolvePromise ( cachedResp [ 1 ] , cachedResp [ 0 ] , copy ( cachedResp [ 2 ] ) ) ;
2016-03-28 10:46:51 +00:00
} else {
2016-04-18 12:34:29 +00:00
resolvePromise ( cachedResp , 200 , { } ) ;
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 ) ;
}
}
2016-04-18 12:34:29 +00:00
// if we won't have the response in cache, send the request to the backend
2016-03-28 10:46:51 +00:00
if ( isUndefined ( cachedResp ) ) {
$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
* /
2016-04-18 12:34:29 +00:00
function done ( status , response , headersString ) {
2016-03-28 10:46:51 +00:00
if ( cache ) {
if ( isSuccess ( status ) ) {
2016-04-18 12:34:29 +00:00
cache . put ( url , [ status , response , parseHeaders ( headersString ) ] ) ;
2016-03-28 10:46:51 +00:00
} else {
// remove promise from the cache
cache . remove ( url ) ;
}
}
2016-04-18 12:34:29 +00:00
resolvePromise ( response , status , headersString ) ;
if ( ! $rootScope . $$phase ) $rootScope . $apply ( ) ;
2016-03-28 10:46:51 +00:00
}
/ * *
* Resolves the raw $http promise .
* /
2016-04-18 12:34:29 +00:00
function resolvePromise ( response , status , headers ) {
// normalize internal statuses to 0
status = Math . max ( status , 0 ) ;
2016-03-28 10:46:51 +00:00
( isSuccess ( status ) ? deferred . resolve : deferred . reject ) ( {
data : response ,
status : status ,
headers : headersGetter ( headers ) ,
2016-04-18 12:34:29 +00:00
config : config
2016-03-28 10:46:51 +00:00
} ) ;
}
function removePendingReq ( ) {
2016-04-18 12:34:29 +00:00
var idx = indexOf ( $http . pendingRequests , 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 ) {
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 ) ) {
v = toJson ( v ) ;
}
parts . push ( encodeUriQuery ( key ) + '=' +
encodeUriQuery ( v ) ) ;
} ) ;
} ) ;
return url + ( ( url . indexOf ( '?' ) == - 1 ) ? '?' : '&' ) + parts . join ( '&' ) ;
}
2016-03-28 10:46:51 +00:00
} ] ;
}
2016-04-18 12:34:29 +00:00
function createXhr ( method ) {
// IE8 doesn't support PATCH method, but the ActiveX object does
/* global ActiveXObject */
return ( msie <= 8 && lowercase ( method ) === 'patch' )
? new ActiveXObject ( 'Microsoft.XMLHTTP' )
: new window . XMLHttpRequest ( ) ;
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 object
* @ name ng . $httpBackend
* @ requires $browser
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 ) {
2016-04-18 12:34:29 +00:00
var ABORTED = - 1 ;
2016-03-28 10:46:51 +00:00
// TODO(vojta): fix the signature
return function ( method , url , post , callback , headers , timeout , withCredentials , responseType ) {
2016-04-18 12:34:29 +00:00
var status ;
2016-03-28 10:46:51 +00:00
$browser . $$incOutstandingRequestCount ( ) ;
url = url || $browser . url ( ) ;
if ( lowercase ( method ) == 'jsonp' ) {
var callbackId = '_' + ( callbacks . counter ++ ) . toString ( 36 ) ;
callbacks [ callbackId ] = function ( data ) {
callbacks [ callbackId ] . data = data ;
} ;
var jsonpDone = jsonpReq ( url . replace ( 'JSON_CALLBACK' , 'angular.callbacks.' + callbackId ) ,
2016-04-18 12:34:29 +00:00
function ( ) {
if ( callbacks [ callbackId ] . data ) {
completeRequest ( callback , 200 , callbacks [ callbackId ] . data ) ;
} else {
completeRequest ( callback , status || - 2 ) ;
}
callbacks [ callbackId ] = angular . noop ;
2016-03-28 10:46:51 +00:00
} ) ;
} else {
2016-04-18 12:34:29 +00:00
var xhr = createXhr ( method ) ;
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 ) ;
}
} ) ;
2016-04-18 12:34:29 +00:00
// In IE6 and 7, this might be called synchronously when xhr.send below is called and the
// response is in the cache. the promise api will ensure that to the app code the api is
// always async
xhr . onreadystatechange = function ( ) {
// onreadystatechange might get called multiple times with readyState === 4 on mobile webkit caused by
// xhrs that are resolved while the app is in the background (see #5426).
// since calling completeRequest sets the `xhr` variable to null, we just check if it's not null before
// continuing
//
// we can't set xhr.onreadystatechange to undefined or delete it because that breaks IE8 (method=PATCH) and
// Safari respectively.
if ( xhr && xhr . readyState == 4 ) {
var responseHeaders = null ,
response = null ;
if ( status !== ABORTED ) {
responseHeaders = xhr . getAllResponseHeaders ( ) ;
// responseText is the old-school way of retrieving response (supported by IE8 & 9)
// response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
response = ( 'response' in xhr ) ? xhr . response : xhr . responseText ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
completeRequest ( callback ,
status || xhr . status ,
response ,
responseHeaders ) ;
2016-03-28 10:46:51 +00:00
}
} ;
if ( withCredentials ) {
xhr . withCredentials = true ;
}
if ( responseType ) {
2016-04-18 12:34:29 +00:00
xhr . responseType = responseType ;
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 ) ;
2016-04-18 12:34:29 +00:00
} else if ( timeout && timeout . then ) {
2016-03-28 10:46:51 +00:00
timeout . then ( timeoutRequest ) ;
}
function timeoutRequest ( ) {
2016-04-18 12:34:29 +00:00
status = ABORTED ;
2016-03-28 10:46:51 +00:00
jsonpDone && jsonpDone ( ) ;
xhr && xhr . abort ( ) ;
}
2016-04-18 12:34:29 +00:00
function completeRequest ( callback , status , response , headersString ) {
2016-03-28 10:46:51 +00:00
// cancel timeout and subsequent timeout promise resolution
2016-04-18 12:34:29 +00:00
timeoutId && $browserDefer . cancel ( timeoutId ) ;
2016-03-28 10:46:51 +00:00
jsonpDone = xhr = null ;
2016-04-18 12:34:29 +00:00
// fix status code when it is 0 (0 status is undocumented).
// Occurs when accessing file resources.
// On Android 4.1 stock browser it occurs while retrieving files from application cache.
status = ( status === 0 ) ? ( response ? 200 : 404 ) : status ;
// normalize IE bug (http://bugs.jquery.com/ticket/1450)
status = status == 1223 ? 204 : status ;
callback ( status , response , headersString ) ;
2016-03-28 10:46:51 +00:00
$browser . $$completeOutstandingRequest ( noop ) ;
}
} ;
2016-04-18 12:34:29 +00:00
function jsonpReq ( url , done ) {
// we can't use jQuery/jqLite here because jQuery does crazy shit 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
2016-04-18 12:34:29 +00:00
var script = rawDocument . createElement ( 'script' ) ,
doneWrapper = function ( ) {
script . onreadystatechange = script . onload = script . onerror = null ;
rawDocument . body . removeChild ( script ) ;
if ( done ) done ( ) ;
} ;
script . type = 'text/javascript' ;
2016-03-28 10:46:51 +00:00
script . src = url ;
2016-04-18 12:34:29 +00:00
if ( msie && msie <= 8 ) {
script . onreadystatechange = function ( ) {
if ( /loaded|complete/ . test ( script . readyState ) ) {
doneWrapper ( ) ;
}
} ;
} else {
script . onload = script . onerror = function ( ) {
doneWrapper ( ) ;
} ;
}
2016-03-28 10:46:51 +00:00
rawDocument . body . appendChild ( script ) ;
2016-04-18 12:34:29 +00:00
return doneWrapper ;
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
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc object
* @ name ng . $interpolateProvider
* @ function
2016-03-28 10:46:51 +00:00
*
* @ description
*
* Used for configuring the interpolation markup . Defaults to ` {{ ` and ` }} ` .
*
* @ example
2016-04-18 12:34:29 +00:00
< doc : example module = "customInterpolationApp" >
< doc : source >
2016-03-28 10:46:51 +00:00
< script >
var customInterpolationApp = angular . module ( 'customInterpolationApp' , [ ] ) ;
customInterpolationApp . config ( function ( $interpolateProvider ) {
$interpolateProvider . startSymbol ( '//' ) ;
$interpolateProvider . endSymbol ( '//' ) ;
} ) ;
2016-04-18 12:34:29 +00:00
customInterpolationApp . controller ( 'DemoController' , function DemoController ( ) {
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 >
2016-04-18 12:34:29 +00:00
< / d o c : s o u r c e >
< doc : scenario >
it ( 'should interpolate binding with custom symbols' , function ( ) {
expect ( binding ( 'demo.label' ) ) . toBe ( 'This binding is brought you by // interpolation symbols.' ) ;
} ) ;
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
function $InterpolateProvider ( ) {
var startSymbol = '{{' ;
var endSymbol = '}}' ;
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name ng . $interpolateProvider # startSymbol
* @ methodOf ng . $interpolateProvider
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 .
* /
2016-04-18 12:34:29 +00:00
this . startSymbol = function ( value ) {
2016-03-28 10:46:51 +00:00
if ( value ) {
startSymbol = value ;
return this ;
} else {
return startSymbol ;
}
} ;
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name ng . $interpolateProvider # endSymbol
* @ methodOf ng . $interpolateProvider
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 .
* /
2016-04-18 12:34:29 +00: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 ,
2016-04-18 12:34:29 +00:00
endSymbolLength = endSymbol . length ;
2016-03-28 10:46:51 +00:00
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $interpolate
* @ 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 .
*
*
2016-04-18 12:34:29 +00:00
< pre >
var $interpolate = ... ; // injected
var exp = $interpolate ( 'Hello {{name | uppercase}}!' ) ;
expect ( exp ( { name : 'Angular' } ) . toEqual ( 'Hello ANGULAR!' ) ;
< / p r 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
2016-04-18 12:34:29 +00:00
* result through { @ link ng . $sce # methods _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 .
* @ returns { function ( context ) } an interpolation function which is used to compute the
* interpolated string . The function has these parameters :
*
2016-04-18 12:34:29 +00:00
* * ` context ` : an object against which any expressions embedded in the strings are evaluated
* against .
*
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
function $interpolate ( text , mustHaveExpression , trustedContext ) {
2016-03-28 10:46:51 +00:00
var startIndex ,
endIndex ,
index = 0 ,
2016-04-18 12:34:29 +00:00
parts = [ ] ,
length = text . length ,
hasInterpolation = false ,
fn ,
2016-03-28 10:46:51 +00:00
exp ,
2016-04-18 12:34:29 +00:00
concat = [ ] ;
while ( index < length ) {
if ( ( ( startIndex = text . indexOf ( startSymbol , index ) ) != - 1 ) &&
( ( endIndex = text . indexOf ( endSymbol , startIndex + startSymbolLength ) ) != - 1 ) ) {
( index != startIndex ) && parts . push ( text . substring ( index , startIndex ) ) ;
parts . push ( fn = $parse ( exp = text . substring ( startIndex + startSymbolLength , endIndex ) ) ) ;
fn . exp = exp ;
2016-03-28 10:46:51 +00:00
index = endIndex + endSymbolLength ;
2016-04-18 12:34:29 +00:00
hasInterpolation = true ;
2016-03-28 10:46:51 +00:00
} else {
2016-04-18 12:34:29 +00:00
// we did not find anything, so we have to add the remainder to the parts array
( index != length ) && parts . push ( text . substring ( index ) ) ;
index = length ;
2016-03-28 10:46:51 +00:00
}
}
2016-04-18 12:34:29 +00:00
if ( ! ( length = parts . length ) ) {
// we added, nothing, must have been an empty string.
parts . push ( '' ) ;
length = 1 ;
}
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.
2016-04-18 12:34:29 +00:00
if ( trustedContext && parts . length > 1 ) {
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
}
2016-04-18 12:34:29 +00:00
if ( ! mustHaveExpression || hasInterpolation ) {
concat . length = length ;
fn = function ( context ) {
try {
for ( var i = 0 , ii = length , part ; i < ii ; i ++ ) {
if ( typeof ( part = parts [ i ] ) == 'function' ) {
part = part ( context ) ;
if ( trustedContext ) {
part = $sce . getTrusted ( trustedContext , part ) ;
} else {
part = $sce . valueOf ( part ) ;
}
if ( part === null || isUndefined ( part ) ) {
part = '' ;
} else if ( typeof part != 'string' ) {
part = toJson ( part ) ;
}
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
concat [ i ] = part ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
return concat . join ( '' ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
catch ( err ) {
var newErr = $interpolateMinErr ( 'interr' , "Can't interpolate: {0}\n{1}" , text ,
err . toString ( ) ) ;
$exceptionHandler ( newErr ) ;
}
} ;
fn . exp = text ;
fn . parts = parts ;
return fn ;
2016-03-28 10:46:51 +00:00
}
}
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name ng . $interpolate # startSymbol
* @ methodOf ng . $interpolate
2016-03-28 10:46:51 +00:00
* @ description
* Symbol to denote the start of expression in the interpolated string . Defaults to ` {{ ` .
*
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00:00
* @ name ng . $interpolate # endSymbol
* @ methodOf ng . $interpolate
2016-03-28 10:46:51 +00:00
* @ description
* Symbol to denote the end of expression in the interpolated string . Defaults to ` }} ` .
*
2016-04-18 12:34:29 +00:00
* Use { @ link ng . $interpolateProvider # endSymbol $interpolateProvider # endSymbol } to change
2016-03-28 10:46:51 +00:00
* the symbol .
*
2016-04-18 12:34:29 +00:00
* @ returns { string } start symbol .
2016-03-28 10:46:51 +00:00
* /
$interpolate . endSymbol = function ( ) {
return endSymbol ;
} ;
return $interpolate ;
} ] ;
}
function $IntervalProvider ( ) {
2016-04-18 12:34:29 +00:00
this . $get = [ '$rootScope' , '$window' , '$q' ,
function ( $rootScope , $window , $q ) {
2016-03-28 10:46:51 +00:00
var intervals = { } ;
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $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) ` .
*
2016-04-18 12:34:29 +00:00
* In tests you can use { @ link ngMock . $interval # methods _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
2016-04-18 12:34:29 +00:00
* will invoke ` fn ` within the { @ link ng . $rootScope . Scope # methods _$apply $apply } block .
2016-03-28 10:46:51 +00:00
* @ returns { promise } A promise which will be notified on each iteration .
*
* @ example
2016-04-18 12:34:29 +00:00
< doc : example module = "time" >
< doc : source >
< script >
function Ctrl2 ( $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 ( ) ;
} ) ;
}
angular . module ( 'time' , [ ] )
// Register the 'myCurrentTime' directive factory method.
// We inject $interval and dateFilter service since the factory method is DI.
. directive ( 'myCurrentTime' , 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 ofter the DOM element was removed.
element . bind ( '$destroy' , function ( ) {
$interval . cancel ( stopTime ) ;
} ) ;
}
} ) ;
< / s c r i p t >
< div >
< div ng - controller = "Ctrl2" >
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 >
< / d o c : s o u r c e >
< / d o c : 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 ,
2016-04-18 12:34:29 +00:00
deferred = $q . defer ( ) ,
promise = deferred . promise ,
2016-03-28 10:46:51 +00:00
iteration = 0 ,
2016-04-18 12:34:29 +00:00
skipApply = ( isDefined ( invokeApply ) && ! invokeApply ) ;
2016-05-18 00:10:50 +00:00
2016-04-18 12:34:29 +00: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 ;
}
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $interval # cancel
* @ methodOf ng . $interval
2016-03-28 10:46:51 +00:00
*
* @ description
* Cancels a task associated with the ` promise ` .
*
2016-04-18 12:34:29 +00:00
* @ param { number } 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' ) ;
2016-04-18 12:34:29 +00:00
clearInterval ( promise . $$intervalId ) ;
2016-03-28 10:46:51 +00:00
delete intervals [ promise . $$intervalId ] ;
return true ;
}
return false ;
} ;
return interval ;
} ] ;
}
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc object
* @ name ng . $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 ` )
* /
2016-04-18 12:34:29 +00:00
function $LocaleProvider ( ) {
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' ,
short : 'M/d/yy h:mm a' ,
fullDate : 'EEEE, MMMM d, y' ,
longDate : 'MMMM d, y' ,
mediumDate : 'MMM d, y' ,
shortDate : 'M/d/yy' ,
mediumTime : 'h:mm:ss a' ,
shortTime : 'h:mm a'
} ,
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 ( '/' ) ;
}
2016-04-18 12:34:29 +00:00
function parseAbsoluteUrl ( absoluteUrl , locationObj , appBase ) {
var parsedUrl = urlResolve ( absoluteUrl , appBase ) ;
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
}
2016-04-18 12:34:29 +00:00
function parseAppUrl ( relativeUrl , locationObj , appBase ) {
2016-03-28 10:46:51 +00:00
var prefixed = ( relativeUrl . charAt ( 0 ) !== '/' ) ;
if ( prefixed ) {
relativeUrl = '/' + relativeUrl ;
}
2016-04-18 12:34:29 +00:00
var match = urlResolve ( relativeUrl , appBase ) ;
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 ) ;
}
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
* @ param { string } basePrefix url path prefix
* /
2016-04-18 12:34:29 +00:00
function LocationHtml5Url ( appBase , basePrefix ) {
2016-03-28 10:46:51 +00:00
this . $$html5 = true ;
basePrefix = basePrefix || '' ;
2016-04-18 12:34:29 +00:00
var appBaseNoFile = stripFile ( appBase ) ;
parseAbsoluteUrl ( appBase , this , appBase ) ;
2016-03-28 10:46:51 +00:00
/ * *
* Parse given html5 ( regular ) url string into properties
2016-04-18 12:34:29 +00:00
* @ param { string } newAbsoluteUrl 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 ) ;
}
2016-04-18 12:34:29 +00:00
parseAppUrl ( pathUrl , this , appBase ) ;
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 '/'
} ;
2016-04-18 12:34:29 +00:00
this . $$rewrite = function ( url ) {
2016-03-28 10:46:51 +00:00
var appUrl , prevAppUrl ;
2016-04-18 12:34:29 +00:00
if ( ( appUrl = beginsWith ( appBase , url ) ) !== undefined ) {
2016-03-28 10:46:51 +00:00
prevAppUrl = appUrl ;
2016-04-18 12:34:29 +00:00
if ( ( appUrl = beginsWith ( basePrefix , appUrl ) ) !== undefined ) {
return appBaseNoFile + ( beginsWith ( '/' , appUrl ) || appUrl ) ;
2016-03-28 10:46:51 +00:00
} else {
2016-04-18 12:34:29 +00:00
return appBase + prevAppUrl ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
} else if ( ( appUrl = beginsWith ( appBaseNoFile , url ) ) !== undefined ) {
return appBaseNoFile + appUrl ;
2016-03-28 10:46:51 +00:00
} else if ( appBaseNoFile == url + '/' ) {
2016-04-18 12:34:29 +00:00
return appBaseNoFile ;
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
* @ param { string } hashPrefix hashbang prefix
* /
2016-04-18 12:34:29 +00:00
function LocationHashbangUrl ( appBase , hashPrefix ) {
var appBaseNoFile = stripFile ( appBase ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
parseAbsoluteUrl ( appBase , this , appBase ) ;
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 ) ;
2016-04-18 12:34:29 +00:00
var withoutHashUrl = withoutBaseUrl . charAt ( 0 ) == '#'
? beginsWith ( hashPrefix , withoutBaseUrl )
: ( this . $$html5 )
? withoutBaseUrl
: '' ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( ! isString ( withoutHashUrl ) ) {
throw $locationMinErr ( 'ihshprfx' , 'Invalid url "{0}", missing hash prefix "{1}".' , url ,
hashPrefix ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
parseAppUrl ( withoutHashUrl , this , appBase ) ;
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 .
* /
2016-04-18 12:34:29 +00: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 .
* /
2016-04-18 12:34:29 +00:00
var windowsFilePathExp = /^\/?.*?:(\/.*)/ ;
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 , '' ) ;
}
2016-04-18 12:34:29 +00: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 : '' ) ;
} ;
2016-04-18 12:34:29 +00:00
this . $$rewrite = function ( url ) {
if ( stripHash ( appBase ) == stripHash ( url ) ) {
return url ;
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
* @ param { string } hashPrefix hashbang prefix
* /
2016-04-18 12:34:29 +00:00
function LocationHashbangInHtml5Url ( appBase , hashPrefix ) {
2016-03-28 10:46:51 +00:00
this . $$html5 = true ;
LocationHashbangUrl . apply ( this , arguments ) ;
2016-04-18 12:34:29 +00:00
var appBaseNoFile = stripFile ( appBase ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
this . $$rewrite = function ( url ) {
2016-03-28 10:46:51 +00:00
var appUrl ;
2016-04-18 12:34:29 +00:00
if ( appBase == stripHash ( url ) ) {
return url ;
} else if ( ( appUrl = beginsWith ( appBaseNoFile , url ) ) ) {
return appBase + hashPrefix + appUrl ;
} else if ( appBaseNoFile === url + '/' ) {
return appBaseNoFile ;
2016-03-28 10:46:51 +00:00
}
} ;
}
2016-04-18 12:34:29 +00:00
LocationHashbangInHtml5Url . prototype =
LocationHashbangUrl . prototype =
LocationHtml5Url . prototype = {
2016-03-28 10:46:51 +00:00
/ * *
* Are we in html5 mode ?
* @ private
* /
$$html5 : false ,
/ * *
2016-04-18 12:34:29 +00:00
* Has any change been replacing ?
2016-03-28 10:46:51 +00:00
* @ private
* /
$$replace : false ,
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name ng . $location # absUrl
* @ methodOf ng . $location
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
2016-04-18 12:34:29 +00:00
* { @ link http : //www.ietf.org/rfc/rfc3986.txt RFC 3986}.
2016-03-28 10:46:51 +00:00
*
* @ return { string } full url
* /
absUrl : locationGetter ( '$$absUrl' ) ,
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name ng . $location # url
* @ methodOf ng . $location
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 ` .
*
* @ param { string = } url New url without base prefix ( e . g . ` /path?a=b#hash ` )
2016-04-18 12:34:29 +00:00
* @ param { string = } replace The path that will be changed
2016-03-28 10:46:51 +00:00
* @ return { string } url
* /
2016-04-18 12:34:29 +00:00
url : function ( url , replace ) {
if ( isUndefined ( url ) )
2016-03-28 10:46:51 +00:00
return this . $$url ;
var match = PATH _MATCH . exec ( url ) ;
2016-04-18 12:34:29 +00:00
if ( match [ 1 ] ) this . path ( decodeURIComponent ( match [ 1 ] ) ) ;
if ( match [ 2 ] || match [ 1 ] ) this . search ( match [ 3 ] || '' ) ;
this . hash ( match [ 5 ] || '' , replace ) ;
2016-03-28 10:46:51 +00:00
return this ;
} ,
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name ng . $location # protocol
* @ methodOf ng . $location
2016-03-28 10:46:51 +00:00
*
* @ description
* This method is getter only .
*
* Return protocol of current url .
*
* @ return { string } protocol of current url
* /
protocol : locationGetter ( '$$protocol' ) ,
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name ng . $location # host
* @ methodOf ng . $location
2016-03-28 10:46:51 +00:00
*
* @ description
* This method is getter only .
*
* Return host of current url .
*
* @ return { string } host of current url .
* /
host : locationGetter ( '$$host' ) ,
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name ng . $location # port
* @ methodOf ng . $location
2016-03-28 10:46:51 +00:00
*
* @ description
* This method is getter only .
*
* Return port of current url .
*
* @ return { Number } port
* /
port : locationGetter ( '$$port' ) ,
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name ng . $location # path
* @ methodOf ng . $location
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 .
*
2016-04-18 12:34:29 +00:00
* @ param { string = } path New path
2016-03-28 10:46:51 +00:00
* @ return { string } path
* /
path : locationGetterSetter ( '$$path' , function ( path ) {
return path . charAt ( 0 ) == '/' ? path : '/' + path ;
} ) ,
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name ng . $location # search
* @ methodOf ng . $location
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 ` .
*
* @ param { string | Object . < string > | Object . < Array . < string >> } search New search params - string or
2016-04-18 12:34:29 +00:00
* hash object . Hash object may contain an array of values , which will be decoded as duplicates in
* the url .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ param { ( string | Array < string > ) = } paramValue If ` search ` is a string , then ` paramValue ` will override only a
* single search parameter . If ` paramValue ` is an array , it will set the parameter as a
* comma - separated value . If ` paramValue ` is ` null ` , the parameter will be deleted .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ return { string } search
2016-03-28 10:46:51 +00:00
* /
search : function ( search , paramValue ) {
switch ( arguments . length ) {
case 0 :
return this . $$search ;
case 1 :
2016-04-18 12:34:29 +00:00
if ( isString ( search ) ) {
2016-03-28 10:46:51 +00:00
this . $$search = parseKeyValue ( search ) ;
} else if ( isObject ( search ) ) {
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
2016-04-18 12:34:29 +00:00
* @ name ng . $location # hash
* @ methodOf ng . $location
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
*
2016-04-18 12:34:29 +00:00
* @ param { string = } hash New hash fragment
2016-03-28 10:46:51 +00:00
* @ return { string } hash
* /
2016-04-18 12:34:29 +00:00
hash : locationGetterSetter ( '$$hash' , identity ) ,
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name ng . $location # replace
* @ methodOf ng . $location
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 ;
}
} ;
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 ;
} ;
}
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc object
* @ name ng . $location
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ requires $browser
* @ requires $sniffer
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
2016-04-18 12:34:29 +00:00
* { @ link https : //developer.mozilla.org/en/window.location 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 ) .
*
2016-04-18 12:34:29 +00:00
* For more information see { @ link guide / dev _guide . services . $location Developer Guide : Angular
* Services : Using $location }
2016-03-28 10:46:51 +00:00
* /
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc object
* @ name ng . $locationProvider
2016-03-28 10:46:51 +00:00
* @ description
* Use the ` $ locationProvider ` to configure how the application deep linking paths are stored .
* /
2016-04-18 12:34:29 +00:00
function $LocationProvider ( ) {
2016-03-28 10:46:51 +00:00
var hashPrefix = '' ,
2016-04-18 12:34:29 +00:00
html5Mode = false ;
2016-03-28 10:46:51 +00:00
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc property
* @ name ng . $locationProvider # hashPrefix
* @ methodOf ng . $locationProvider
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 ;
}
} ;
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc property
* @ name ng . $locationProvider # html5Mode
* @ methodOf ng . $locationProvider
2016-03-28 10:46:51 +00:00
* @ description
2016-04-18 12:34:29 +00:00
* @ param { boolean = } mode Use HTML5 strategy if available .
* @ returns { * } current value if used as getter or itself ( chaining ) if used as setter
2016-03-28 10:46:51 +00:00
* /
this . html5Mode = function ( mode ) {
2016-04-18 12:34:29 +00:00
if ( isDefined ( mode ) ) {
html5Mode = mode ;
2016-03-28 10:46:51 +00:00
return this ;
} else {
return html5Mode ;
}
} ;
/ * *
* @ ngdoc event
2016-04-18 12:34:29 +00:00
* @ name ng . $location # $locationChangeStart
* @ eventOf ng . $location
2016-03-28 10:46:51 +00:00
* @ eventType broadcast on root scope
* @ description
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00:00
* { @ link ng . $location # events _$locationChangeSuccess $locationChangeSuccess } is fired .
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 .
* /
/ * *
* @ ngdoc event
2016-04-18 12:34:29 +00:00
* @ name ng . $location # $locationChangeSuccess
* @ eventOf ng . $location
2016-03-28 10:46:51 +00:00
* @ eventType broadcast on root scope
* @ description
* Broadcasted after a URL was changed .
*
* @ param { Object } angularEvent Synthetic event object .
* @ param { string } newUrl New URL
* @ param { string = } oldUrl URL that was before it was changed .
* /
2016-04-18 12:34:29 +00:00
this . $get = [ '$rootScope' , '$browser' , '$sniffer' , '$rootElement' ,
function ( $rootScope , $browser , $sniffer , $rootElement ) {
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 ;
2016-04-18 12:34:29 +00:00
if ( html5Mode ) {
2016-03-28 10:46:51 +00:00
appBase = serverBase ( initialUrl ) + ( baseHref || '/' ) ;
LocationMode = $sniffer . history ? LocationHtml5Url : LocationHashbangInHtml5Url ;
} else {
appBase = stripHash ( initialUrl ) ;
LocationMode = LocationHashbangUrl ;
}
2016-04-18 12:34:29 +00:00
$location = new LocationMode ( appBase , '#' + hashPrefix ) ;
$location . $$parse ( $location . $$rewrite ( initialUrl ) ) ;
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
2016-04-18 12:34:29 +00:00
if ( event . ctrlKey || event . metaKey || event . which == 2 ) return ;
2016-03-28 10:46:51 +00:00
var elm = jqLite ( event . target ) ;
// traverse the DOM up to find first A tag
2016-04-18 12:34:29 +00:00
while ( lowercase ( elm [ 0 ] . nodeName ) !== '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' ) ;
if ( isObject ( absHref ) && absHref . toString ( ) === '[object SVGAnimatedString]' ) {
// SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during
// an animation.
absHref = urlResolve ( absHref . animVal ) . href ;
}
2016-04-18 12:34:29 +00:00
var rewrittenUrl = $location . $$rewrite ( absHref ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( absHref && ! elm . attr ( 'target' ) && rewrittenUrl && ! event . isDefaultPrevented ( ) ) {
event . preventDefault ( ) ;
if ( rewrittenUrl != $browser . url ( ) ) {
2016-03-28 10:46:51 +00:00
// update location manually
2016-04-18 12:34:29 +00:00
$location . $$parse ( rewrittenUrl ) ;
$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
2016-04-18 12:34:29 +00:00
if ( $location . absUrl ( ) != initialUrl ) {
2016-03-28 10:46:51 +00:00
$browser . url ( $location . absUrl ( ) , true ) ;
}
// update $location when $browser url changes
2016-04-18 12:34:29 +00:00
$browser . onUrlChange ( function ( newUrl ) {
if ( $location . absUrl ( ) != newUrl ) {
$rootScope . $evalAsync ( function ( ) {
var oldUrl = $location . absUrl ( ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
$location . $$parse ( newUrl ) ;
if ( $rootScope . $broadcast ( '$locationChangeStart' , newUrl ,
oldUrl ) . defaultPrevented ) {
$location . $$parse ( oldUrl ) ;
$browser . url ( oldUrl ) ;
} else {
afterLocationChange ( oldUrl ) ;
}
} ) ;
if ( ! $rootScope . $$phase ) $rootScope . $digest ( ) ;
2016-03-28 10:46:51 +00:00
}
} ) ;
// update browser
2016-04-18 12:34:29 +00:00
var changeCounter = 0 ;
2016-03-28 10:46:51 +00:00
$rootScope . $watch ( function $locationWatch ( ) {
2016-04-18 12:34:29 +00:00
var oldUrl = $browser . url ( ) ;
2016-03-28 10:46:51 +00:00
var currentReplace = $location . $$replace ;
2016-04-18 12:34:29 +00:00
if ( ! changeCounter || oldUrl != $location . absUrl ( ) ) {
changeCounter ++ ;
2016-03-28 10:46:51 +00:00
$rootScope . $evalAsync ( function ( ) {
2016-04-18 12:34:29 +00:00
if ( $rootScope . $broadcast ( '$locationChangeStart' , $location . absUrl ( ) , oldUrl ) .
defaultPrevented ) {
2016-03-28 10:46:51 +00:00
$location . $$parse ( oldUrl ) ;
} else {
2016-04-18 12:34:29 +00:00
$browser . url ( $location . absUrl ( ) , currentReplace ) ;
afterLocationChange ( oldUrl ) ;
2016-03-28 10:46:51 +00:00
}
} ) ;
}
$location . $$replace = false ;
2016-04-18 12:34:29 +00:00
return changeCounter ;
2016-03-28 10:46:51 +00:00
} ) ;
return $location ;
2016-04-18 12:34:29 +00:00
function afterLocationChange ( oldUrl ) {
$rootScope . $broadcast ( '$locationChangeSuccess' , $location . absUrl ( ) , oldUrl ) ;
2016-03-28 10:46:51 +00:00
}
} ] ;
}
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc object
* @ name ng . $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
2016-04-18 12:34:29 +00:00
< example >
2016-03-28 10:46:51 +00:00
< file name = "script.js" >
2016-04-18 12:34:29 +00:00
function LogCtrl ( $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" >
2016-04-18 12:34:29 +00:00
< div ng - controller = "LogCtrl" >
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 >
< / d i v >
< / f i l e >
< / e x a m p l e >
* /
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc object
* @ name ng . $logProvider
2016-03-28 10:46:51 +00:00
* @ description
* Use the ` $ logProvider ` to configure how the application logs messages
* /
2016-04-18 12:34:29 +00: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
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc property
* @ name ng . $logProvider # debugEnabled
* @ methodOf ng . $logProvider
2016-03-28 10:46:51 +00:00
* @ description
2016-04-18 12:34:29 +00:00
* @ param { string = } 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
2016-04-18 12:34:29 +00:00
this . $get = [ '$window' , function ( $window ) {
2016-03-28 10:46:51 +00:00
return {
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name ng . $log # log
* @ methodOf ng . $log
2016-03-28 10:46:51 +00:00
*
* @ description
* Write a log message
* /
log : consoleLog ( 'log' ) ,
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name ng . $log # info
* @ methodOf ng . $log
2016-03-28 10:46:51 +00:00
*
* @ description
* Write an information message
* /
info : consoleLog ( 'info' ) ,
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name ng . $log # warn
* @ methodOf ng . $log
2016-03-28 10:46:51 +00:00
*
* @ description
* Write a warning message
* /
warn : consoleLog ( 'warn' ) ,
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name ng . $log # error
* @ methodOf ng . $log
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
2016-04-18 12:34:29 +00:00
* @ name ng . $log # debug
* @ methodOf ng . $log
2016-05-18 00:10:50 +00:00
*
2016-03-28 10:46:51 +00:00
* @ description
* Write a debug message
* /
2016-04-18 12:34:29 +00: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 {
2016-04-18 12:34:29 +00: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 ) ;
} ;
}
} ] ;
}
var $parseMinErr = minErr ( '$parse' ) ;
2016-04-18 12:34:29 +00:00
var promiseWarningCache = { } ;
var promiseWarning ;
2016-03-28 10:46:51 +00:00
// Sandboxing Angular Expressions
// ------------------------------
// Angular expressions are generally considered safe because these expressions only have direct
2016-04-18 12:34:29 +00: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:
//
2016-04-18 12:34:29 +00:00
// {}.toString.constructor(alert("evil JS code"))
//
// We want to prevent this type of access. For the sake of performance, during the lexing phase we
// disallow any "dotted" access to any member named "constructor".
//
// For reflective calls (a[b]) we check that the value of the lookup is not the Function constructor
// while evaluating the expression, which is a stronger but more expensive test. Since reflective
// calls are expensive anyway, this is not such a big deal compared to static dereferencing.
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
2016-04-18 12:34:29 +00: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.
//
2016-04-18 12:34:29 +00:00
// A developer could foil the name check by aliasing the Function constructor under a different
// name on the scope.
//
2016-03-28 10:46:51 +00:00
// 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.
function ensureSafeMemberName ( name , fullExpression ) {
2016-04-18 12:34:29 +00:00
if ( name === "constructor" ) {
2016-03-28 10:46:51 +00:00
throw $parseMinErr ( 'isecfld' ,
2016-04-18 12:34:29 +00:00
'Referencing "constructor" field in Angular expressions is disallowed! 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)
2016-04-18 12:34:29 +00:00
obj . document && obj . location && obj . alert && obj . setInterval ) {
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)
2016-04-18 12:34:29 +00:00
obj . children && ( obj . nodeName || ( obj . on && obj . find ) ) ) {
2016-03-28 10:46:51 +00:00
throw $parseMinErr ( 'isecdom' ,
'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}' ,
fullExpression ) ;
}
}
return obj ;
}
2016-04-18 12:34:29 +00:00
var OPERATORS = {
/* jshint bitwise : false */
'null' : function ( ) { return null ; } ,
'true' : function ( ) { return true ; } ,
'false' : function ( ) { return false ; } ,
undefined : noop ,
'+' : function ( self , locals , a , b ) {
a = a ( self , locals ) ; b = b ( self , locals ) ;
if ( isDefined ( a ) ) {
if ( isDefined ( b ) ) {
return a + b ;
}
return a ;
}
return isDefined ( b ) ? b : undefined ; } ,
'-' : function ( self , locals , a , b ) {
a = a ( self , locals ) ; b = b ( self , locals ) ;
return ( isDefined ( a ) ? a : 0 ) - ( isDefined ( b ) ? b : 0 ) ;
} ,
'*' : 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 ) ; } ,
'=' : noop ,
'===' : 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|b;},
'|' : function ( self , locals , a , b ) { return b ( self , locals ) ( self , locals , a ( self , locals ) ) ; } ,
'!' : function ( self , locals , a ) { return ! a ( self , locals ) ; }
} ;
/* jshint bitwise: true */
2016-03-28 10:46:51 +00:00
var ESCAPE = { "n" : "\n" , "f" : "\f" , "r" : "\r" , "t" : "\t" , "v" : "\v" , "'" : "'" , '"' : '"' } ;
/////////////////////////////////////////
/ * *
* @ constructor
* /
2016-04-18 12:34:29 +00:00
var Lexer = function ( options ) {
2016-03-28 10:46:51 +00:00
this . options = options ;
} ;
Lexer . prototype = {
constructor : Lexer ,
2016-04-18 12:34:29 +00:00
lex : function ( text ) {
2016-03-28 10:46:51 +00:00
this . text = text ;
2016-04-18 12:34:29 +00:00
2016-03-28 10:46:51 +00:00
this . index = 0 ;
2016-04-18 12:34:29 +00:00
this . ch = undefined ;
this . lastCh = ':' ; // can start regexp
2016-03-28 10:46:51 +00:00
this . tokens = [ ] ;
2016-04-18 12:34:29 +00:00
var token ;
var json = [ ] ;
2016-03-28 10:46:51 +00:00
while ( this . index < this . text . length ) {
2016-04-18 12:34:29 +00:00
this . ch = this . text . charAt ( this . index ) ;
if ( this . is ( '"\'' ) ) {
this . readString ( this . ch ) ;
} else if ( this . isNumber ( this . ch ) || this . is ( '.' ) && this . isNumber ( this . peek ( ) ) ) {
2016-03-28 10:46:51 +00:00
this . readNumber ( ) ;
2016-04-18 12:34:29 +00:00
} else if ( this . isIdent ( this . ch ) ) {
2016-03-28 10:46:51 +00:00
this . readIdent ( ) ;
2016-04-18 12:34:29 +00:00
// identifiers can only be if the preceding char was a { or ,
if ( this . was ( '{,' ) && json [ 0 ] === '{' &&
( token = this . tokens [ this . tokens . length - 1 ] ) ) {
token . json = token . text . indexOf ( '.' ) === - 1 ;
}
} else if ( this . is ( '(){}[].,;:?' ) ) {
this . tokens . push ( {
index : this . index ,
text : this . ch ,
json : ( this . was ( ':[,' ) && this . is ( '{[' ) ) || this . is ( '}]:,' )
} ) ;
if ( this . is ( '{[' ) ) json . unshift ( this . ch ) ;
if ( this . is ( '}]' ) ) json . shift ( ) ;
2016-03-28 10:46:51 +00:00
this . index ++ ;
2016-04-18 12:34:29 +00:00
} else if ( this . isWhitespace ( this . ch ) ) {
2016-03-28 10:46:51 +00:00
this . index ++ ;
2016-04-18 12:34:29 +00:00
continue ;
2016-03-28 10:46:51 +00:00
} else {
2016-04-18 12:34:29 +00:00
var ch2 = this . ch + this . peek ( ) ;
2016-03-28 10:46:51 +00:00
var ch3 = ch2 + this . peek ( 2 ) ;
2016-04-18 12:34:29 +00:00
var fn = OPERATORS [ this . ch ] ;
var fn2 = OPERATORS [ ch2 ] ;
var fn3 = OPERATORS [ ch3 ] ;
if ( fn3 ) {
this . tokens . push ( { index : this . index , text : ch3 , fn : fn3 } ) ;
this . index += 3 ;
} else if ( fn2 ) {
this . tokens . push ( { index : this . index , text : ch2 , fn : fn2 } ) ;
this . index += 2 ;
} else if ( fn ) {
this . tokens . push ( {
index : this . index ,
text : this . ch ,
fn : fn ,
json : ( this . was ( '[,:' ) && this . is ( '+-' ) )
} ) ;
this . index += 1 ;
2016-03-28 10:46:51 +00:00
} else {
this . throwError ( 'Unexpected next character ' , this . index , this . index + 1 ) ;
}
}
2016-04-18 12:34:29 +00:00
this . lastCh = this . ch ;
2016-03-28 10:46:51 +00:00
}
return this . tokens ;
} ,
2016-04-18 12:34:29 +00:00
is : function ( chars ) {
return chars . indexOf ( this . ch ) !== - 1 ;
2016-03-28 10:46:51 +00:00
} ,
2016-04-18 12:34:29 +00:00
was : function ( chars ) {
return chars . indexOf ( this . lastCh ) !== - 1 ;
} ,
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 ) {
2016-04-18 12:34:29 +00:00
return ( '0' <= ch && ch <= '9' ) ;
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 ++ ;
}
2016-04-18 12:34:29 +00:00
number = 1 * number ;
2016-03-28 10:46:51 +00:00
this . tokens . push ( {
index : start ,
text : number ,
2016-04-18 12:34:29 +00:00
json : true ,
fn : function ( ) { return number ; }
2016-03-28 10:46:51 +00:00
} ) ;
} ,
readIdent : function ( ) {
2016-04-18 12:34:29 +00:00
var parser = this ;
var ident = '' ;
2016-03-28 10:46:51 +00:00
var start = this . index ;
2016-04-18 12:34:29 +00:00
var lastDot , peekIndex , methodName , ch ;
2016-03-28 10:46:51 +00:00
while ( this . index < this . text . length ) {
2016-04-18 12:34:29 +00:00
ch = this . text . charAt ( this . index ) ;
if ( ch === '.' || this . isIdent ( ch ) || this . isNumber ( ch ) ) {
if ( ch === '.' ) lastDot = this . index ;
ident += ch ;
} else {
2016-03-28 10:46:51 +00:00
break ;
}
this . index ++ ;
}
2016-04-18 12:34:29 +00:00
//check if this is not a method invocation and if it is back out to last dot
if ( lastDot ) {
peekIndex = this . index ;
while ( peekIndex < this . text . length ) {
ch = this . text . charAt ( peekIndex ) ;
if ( ch === '(' ) {
methodName = ident . substr ( lastDot - start + 1 ) ;
ident = ident . substr ( 0 , lastDot - start ) ;
this . index = peekIndex ;
break ;
}
if ( this . isWhitespace ( ch ) ) {
peekIndex ++ ;
} else {
break ;
}
}
}
var token = {
2016-03-28 10:46:51 +00:00
index : start ,
2016-04-18 12:34:29 +00:00
text : ident
} ;
// OPERATORS is our own object so we don't need to use special hasOwnPropertyFn
if ( OPERATORS . hasOwnProperty ( ident ) ) {
token . fn = OPERATORS [ ident ] ;
token . json = OPERATORS [ ident ] ;
} else {
var getter = getterFn ( ident , this . options , this . text ) ;
token . fn = extend ( function ( self , locals ) {
return ( getter ( self , locals ) ) ;
} , {
assign : function ( self , value ) {
return setter ( self , ident , value , parser . text , parser . options ) ;
}
} ) ;
}
this . tokens . push ( token ) ;
if ( methodName ) {
this . tokens . push ( {
index : lastDot ,
text : '.' ,
json : false
} ) ;
this . tokens . push ( {
index : lastDot + 1 ,
text : methodName ,
json : false
} ) ;
}
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 ] ;
2016-04-18 12:34:29 +00:00
if ( rep ) {
string += rep ;
} else {
string += 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 ,
2016-04-18 12:34:29 +00:00
string : string ,
json : true ,
fn : function ( ) { return 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
/ * *
* @ constructor
* /
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 ;
} ;
2016-04-18 12:34:29 +00:00
Parser . ZERO = function ( ) { return 0 ; } ;
Parser . prototype = {
constructor : Parser ,
parse : function ( text , json ) {
2016-03-28 10:46:51 +00:00
this . text = text ;
2016-04-18 12:34:29 +00:00
//TODO(i): strip all the obsolte json stuff from this file
this . json = json ;
2016-03-28 10:46:51 +00:00
this . tokens = this . lexer . lex ( text ) ;
2016-04-18 12:34:29 +00:00
if ( json ) {
// The extra level of aliasing is here, just in case the lexer misses something, so that
// we prevent any accidental execution in JSON.
this . assignment = this . logicalOR ;
this . functionCall =
this . fieldAccess =
this . objectIndex =
this . filterChain = function ( ) {
this . throwError ( 'is not valid json' , { text : text , index : 0 } ) ;
} ;
}
var value = json ? this . primary ( ) : 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 ;
} ,
2016-04-18 12:34:29 +00:00
primary : function ( ) {
var primary ;
if ( this . expect ( '(' ) ) {
primary = this . filterChain ( ) ;
this . consume ( ')' ) ;
} else if ( this . expect ( '[' ) ) {
primary = this . arrayDeclaration ( ) ;
} else if ( this . expect ( '{' ) ) {
primary = this . object ( ) ;
} else {
var token = this . expect ( ) ;
primary = token . fn ;
if ( ! primary ) {
this . throwError ( 'not a primary expression' , token ) ;
}
if ( token . json ) {
primary . constant = true ;
primary . literal = true ;
}
}
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 ) {
if ( this . tokens . length > 0 ) {
var token = this . tokens [ 0 ] ;
var t = token . text ;
if ( t === e1 || t === e2 || t === e3 || t === e4 ||
( ! e1 && ! e2 && ! e3 && ! e4 ) ) {
return token ;
}
}
return false ;
} ,
expect : function ( e1 , e2 , e3 , e4 ) {
var token = this . peek ( e1 , e2 , e3 , e4 ) ;
if ( token ) {
if ( this . json && ! token . json ) {
this . throwError ( 'is not valid json' , token ) ;
}
this . tokens . shift ( ) ;
return token ;
}
return false ;
} ,
consume : function ( e1 ) {
if ( ! this . expect ( e1 ) ) {
this . throwError ( 'is unexpected, expecting [' + e1 + ']' , this . peek ( ) ) ;
}
} ,
unaryFn : function ( fn , right ) {
return extend ( function ( self , locals ) {
return fn ( self , locals , right ) ;
} , {
constant : right . constant
} ) ;
} ,
ternaryFn : function ( left , middle , right ) {
return extend ( function ( self , locals ) {
return left ( self , locals ) ? middle ( self , locals ) : right ( self , locals ) ;
} , {
constant : left . constant && middle . constant && right . constant
} ) ;
} ,
binaryFn : function ( left , fn , right ) {
return extend ( function ( self , locals ) {
return fn ( self , locals , left , right ) ;
} , {
constant : left . constant && right . constant
} ) ;
} ,
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 ]
: function ( self , locals ) {
var value ;
for ( var i = 0 ; i < statements . length ; i ++ ) {
var statement = statements [ i ] ;
if ( statement ) {
value = statement ( self , locals ) ;
}
}
return value ;
} ;
2016-03-28 10:46:51 +00:00
}
}
} ,
filterChain : function ( ) {
var left = this . expression ( ) ;
var token ;
2016-04-18 12:34:29 +00:00
while ( true ) {
if ( ( token = this . expect ( '|' ) ) ) {
left = this . binaryFn ( left , token . fn , this . filter ( ) ) ;
} else {
return left ;
}
}
} ,
filter : function ( ) {
var token = this . expect ( ) ;
var fn = this . $filter ( token . text ) ;
var argsFn = [ ] ;
while ( true ) {
if ( ( token = this . expect ( ':' ) ) ) {
argsFn . push ( this . expression ( ) ) ;
} else {
var fnInvoke = function ( self , locals , input ) {
var args = [ input ] ;
for ( var i = 0 ; i < argsFn . length ; i ++ ) {
args . push ( argsFn [ i ] ( self , locals ) ) ;
}
return fn . apply ( self , args ) ;
} ;
return function ( ) {
return fnInvoke ;
} ;
}
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 ( ) ;
return function ( scope , locals ) {
return left . assign ( scope , right ( scope , locals ) , locals ) ;
} ;
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 ( '?' ) ) ) {
middle = this . ternary ( ) ;
if ( ( token = this . expect ( ':' ) ) ) {
return this . ternaryFn ( left , middle , this . ternary ( ) ) ;
} else {
this . throwError ( 'expected :' , token ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
} else {
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 ;
while ( true ) {
if ( ( token = this . expect ( '||' ) ) ) {
left = this . binaryFn ( left , token . fn , this . logicalAND ( ) ) ;
} else {
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 ;
if ( ( token = this . expect ( '&&' ) ) ) {
left = this . binaryFn ( left , token . fn , this . logicalAND ( ) ) ;
2016-03-28 10:46:51 +00:00
}
return left ;
} ,
equality : function ( ) {
var left = this . relational ( ) ;
var token ;
2016-04-18 12:34:29 +00:00
if ( ( token = this . expect ( '==' , '!=' , '===' , '!==' ) ) ) {
left = this . binaryFn ( left , token . fn , this . equality ( ) ) ;
2016-03-28 10:46:51 +00:00
}
return left ;
} ,
relational : function ( ) {
var left = this . additive ( ) ;
var token ;
2016-04-18 12:34:29 +00:00
if ( ( token = this . expect ( '<' , '>' , '<=' , '>=' ) ) ) {
left = this . binaryFn ( left , token . fn , this . relational ( ) ) ;
2016-03-28 10:46:51 +00:00
}
return left ;
} ,
additive : function ( ) {
var left = this . multiplicative ( ) ;
var token ;
while ( ( token = this . expect ( '+' , '-' ) ) ) {
2016-04-18 12:34:29 +00:00
left = this . binaryFn ( left , token . fn , this . multiplicative ( ) ) ;
2016-03-28 10:46:51 +00:00
}
return left ;
} ,
multiplicative : function ( ) {
var left = this . unary ( ) ;
var token ;
while ( ( token = this . expect ( '*' , '/' , '%' ) ) ) {
2016-04-18 12:34:29 +00:00
left = this . binaryFn ( left , token . fn , 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 ( '-' ) ) ) {
return this . binaryFn ( Parser . ZERO , token . fn , this . unary ( ) ) ;
} else if ( ( token = this . expect ( '!' ) ) ) {
return this . unaryFn ( token . fn , 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 ) {
var parser = this ;
var field = this . expect ( ) . text ;
var getter = getterFn ( field , this . options , this . text ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
return extend ( function ( scope , locals , self ) {
return getter ( self || object ( scope , locals ) , locals ) ;
} , {
assign : function ( scope , value , locals ) {
return setter ( object ( scope , locals ) , field , value , parser . text , parser . options ) ;
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 ) {
var parser = this ;
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
2016-04-18 12:34:29 +00:00
return extend ( function ( self , locals ) {
var o = obj ( self , locals ) ,
i = indexFn ( self , locals ) ,
v , p ;
if ( ! o ) return undefined ;
v = ensureSafeObject ( o [ i ] , parser . text ) ;
if ( v && v . then && parser . options . unwrapPromises ) {
p = v ;
if ( ! ( '$$v' in v ) ) {
p . $$v = undefined ;
p . then ( function ( val ) { p . $$v = val ; } ) ;
}
v = v . $$v ;
}
return v ;
} , {
assign : function ( self , value , locals ) {
var key = indexFn ( self , locals ) ;
// prevent overwriting of Function.constructor which would break ensureSafeObject check
var safe = ensureSafeObject ( obj ( self , locals ) , parser . text ) ;
return safe [ key ] = value ;
}
} ) ;
2016-03-28 10:46:51 +00:00
} ,
2016-04-18 12:34:29 +00:00
functionCall : function ( fn , contextGetter ) {
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
2016-04-18 12:34:29 +00:00
var parser = this ;
return function ( scope , locals ) {
var args = [ ] ;
var context = contextGetter ? contextGetter ( scope , locals ) : scope ;
for ( var i = 0 ; i < argsFn . length ; i ++ ) {
args . push ( argsFn [ i ] ( scope , locals ) ) ;
}
var fnPtr = fn ( scope , locals , context ) || noop ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
ensureSafeObject ( context , parser . text ) ;
ensureSafeObject ( fnPtr , parser . text ) ;
// IE stupidity! (IE doesn't have apply for some native functions)
var v = fnPtr . apply
? fnPtr . apply ( context , args )
: fnPtr ( args [ 0 ] , args [ 1 ] , args [ 2 ] , args [ 3 ] , args [ 4 ] ) ;
return ensureSafeObject ( v , parser . text ) ;
} ;
2016-03-28 10:46:51 +00:00
} ,
2016-04-18 12:34:29 +00:00
// This is used with json array declaration
arrayDeclaration : function ( ) {
var elementFns = [ ] ;
var allConstant = true ;
2016-03-28 10:46:51 +00:00
if ( this . peekToken ( ) . text !== ']' ) {
do {
2016-04-18 12:34:29 +00:00
var elementFn = this . expression ( ) ;
elementFns . push ( elementFn ) ;
if ( ! elementFn . constant ) {
allConstant = false ;
2016-03-28 10:46:51 +00:00
}
} while ( this . expect ( ',' ) ) ;
}
this . consume ( ']' ) ;
2016-04-18 12:34:29 +00:00
return extend ( function ( self , locals ) {
var array = [ ] ;
for ( var i = 0 ; i < elementFns . length ; i ++ ) {
array . push ( elementFns [ i ] ( self , locals ) ) ;
}
return array ;
} , {
literal : true ,
constant : allConstant
} ) ;
2016-03-28 10:46:51 +00:00
} ,
2016-04-18 12:34:29 +00:00
object : function ( ) {
var keyValues = [ ] ;
var allConstant = true ;
2016-03-28 10:46:51 +00:00
if ( this . peekToken ( ) . text !== '}' ) {
do {
2016-04-18 12:34:29 +00:00
var token = this . expect ( ) ,
key = token . string || token . text ;
2016-03-28 10:46:51 +00:00
this . consume ( ':' ) ;
2016-04-18 12:34:29 +00:00
var value = this . expression ( ) ;
keyValues . push ( { key : key , value : value } ) ;
if ( ! value . constant ) {
allConstant = false ;
}
2016-03-28 10:46:51 +00:00
} while ( this . expect ( ',' ) ) ;
}
this . consume ( '}' ) ;
2016-04-18 12:34:29 +00:00
return extend ( function ( self , locals ) {
var object = { } ;
for ( var i = 0 ; i < keyValues . length ; i ++ ) {
var keyValue = keyValues [ i ] ;
object [ keyValue . key ] = keyValue . value ( self , locals ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
return object ;
} , {
literal : true ,
constant : allConstant
} ) ;
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
2016-04-18 12:34:29 +00:00
function setter ( obj , path , setValue , fullExp , options ) {
//needed?
options = options || { } ;
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 ) ;
var propertyObj = obj [ key ] ;
if ( ! propertyObj ) {
propertyObj = { } ;
obj [ key ] = propertyObj ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
obj = propertyObj ;
if ( obj . then && options . unwrapPromises ) {
promiseWarning ( fullExp ) ;
if ( ! ( "$$v" in obj ) ) {
( function ( promise ) {
promise . then ( function ( val ) { promise . $$v = val ; } ) ; }
) ( obj ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
if ( obj . $$v === undefined ) {
obj . $$v = { } ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
obj = obj . $$v ;
}
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
key = ensureSafeMemberName ( element . shift ( ) , fullExp ) ;
obj [ key ] = setValue ;
return setValue ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
var getterFnCache = { } ;
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
* /
function cspSafeGetterFn ( key0 , key1 , key2 , key3 , key4 , fullExp , options ) {
ensureSafeMemberName ( key0 , fullExp ) ;
ensureSafeMemberName ( key1 , fullExp ) ;
ensureSafeMemberName ( key2 , fullExp ) ;
ensureSafeMemberName ( key3 , fullExp ) ;
ensureSafeMemberName ( key4 , fullExp ) ;
return ! options . unwrapPromises
? function cspSafeGetter ( scope , locals ) {
var pathVal = ( locals && locals . hasOwnProperty ( key0 ) ) ? locals : scope ;
if ( pathVal == null ) return pathVal ;
pathVal = pathVal [ key0 ] ;
if ( ! key1 ) return pathVal ;
if ( pathVal == null ) return undefined ;
pathVal = pathVal [ key1 ] ;
if ( ! key2 ) return pathVal ;
if ( pathVal == null ) return undefined ;
pathVal = pathVal [ key2 ] ;
if ( ! key3 ) return pathVal ;
if ( pathVal == null ) return undefined ;
pathVal = pathVal [ key3 ] ;
if ( ! key4 ) return pathVal ;
if ( pathVal == null ) return undefined ;
pathVal = pathVal [ key4 ] ;
return pathVal ;
}
: function cspSafePromiseEnabledGetter ( scope , locals ) {
var pathVal = ( locals && locals . hasOwnProperty ( key0 ) ) ? locals : scope ,
promise ;
if ( pathVal == null ) return pathVal ;
pathVal = pathVal [ key0 ] ;
if ( pathVal && pathVal . then ) {
promiseWarning ( fullExp ) ;
if ( ! ( "$$v" in pathVal ) ) {
promise = pathVal ;
promise . $$v = undefined ;
promise . then ( function ( val ) { promise . $$v = val ; } ) ;
}
pathVal = pathVal . $$v ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( ! key1 ) return pathVal ;
if ( pathVal == null ) return undefined ;
pathVal = pathVal [ key1 ] ;
if ( pathVal && pathVal . then ) {
promiseWarning ( fullExp ) ;
if ( ! ( "$$v" in pathVal ) ) {
promise = pathVal ;
promise . $$v = undefined ;
promise . then ( function ( val ) { promise . $$v = val ; } ) ;
}
pathVal = pathVal . $$v ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( ! key2 ) return pathVal ;
if ( pathVal == null ) return undefined ;
pathVal = pathVal [ key2 ] ;
if ( pathVal && pathVal . then ) {
promiseWarning ( fullExp ) ;
if ( ! ( "$$v" in pathVal ) ) {
promise = pathVal ;
promise . $$v = undefined ;
promise . then ( function ( val ) { promise . $$v = val ; } ) ;
}
pathVal = pathVal . $$v ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( ! key3 ) return pathVal ;
if ( pathVal == null ) return undefined ;
pathVal = pathVal [ key3 ] ;
if ( pathVal && pathVal . then ) {
promiseWarning ( fullExp ) ;
if ( ! ( "$$v" in pathVal ) ) {
promise = pathVal ;
promise . $$v = undefined ;
promise . then ( function ( val ) { promise . $$v = val ; } ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
pathVal = pathVal . $$v ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
if ( ! key4 ) return pathVal ;
if ( pathVal == null ) return undefined ;
pathVal = pathVal [ key4 ] ;
if ( pathVal && pathVal . then ) {
promiseWarning ( fullExp ) ;
if ( ! ( "$$v" in pathVal ) ) {
promise = pathVal ;
promise . $$v = undefined ;
promise . then ( function ( val ) { promise . $$v = val ; } ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
pathVal = pathVal . $$v ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
return pathVal ;
} ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
function simpleGetterFn1 ( key0 , fullExp ) {
ensureSafeMemberName ( key0 , fullExp ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
return function simpleGetterFn1 ( scope , locals ) {
if ( scope == null ) return undefined ;
return ( ( locals && locals . hasOwnProperty ( key0 ) ) ? locals : scope ) [ key0 ] ;
} ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
function simpleGetterFn2 ( key0 , key1 , fullExp ) {
ensureSafeMemberName ( key0 , fullExp ) ;
ensureSafeMemberName ( key1 , fullExp ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
return function simpleGetterFn2 ( scope , locals ) {
if ( scope == null ) return undefined ;
scope = ( ( locals && locals . hasOwnProperty ( key0 ) ) ? locals : scope ) [ key0 ] ;
return scope == null ? undefined : scope [ key1 ] ;
} ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
function getterFn ( path , options , fullExp ) {
// Check whether the cache has this getter already.
// We can use hasOwnProperty directly on the cache because we ensure,
// see below, that the cache never stores a path called 'hasOwnProperty'
if ( getterFnCache . hasOwnProperty ( path ) ) {
return getterFnCache [ path ] ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
var pathKeys = path . split ( '.' ) ,
pathKeysLength = pathKeys . length ,
fn ;
// When we have only 1 or 2 tokens, use optimized special case closures.
// http://jsperf.com/angularjs-parse-getter/6
if ( ! options . unwrapPromises && pathKeysLength === 1 ) {
fn = simpleGetterFn1 ( pathKeys [ 0 ] , fullExp ) ;
} else if ( ! options . unwrapPromises && pathKeysLength === 2 ) {
fn = simpleGetterFn2 ( pathKeys [ 0 ] , pathKeys [ 1 ] , fullExp ) ;
} else if ( options . csp ) {
if ( pathKeysLength < 6 ) {
fn = cspSafeGetterFn ( pathKeys [ 0 ] , pathKeys [ 1 ] , pathKeys [ 2 ] , pathKeys [ 3 ] , pathKeys [ 4 ] , fullExp ,
options ) ;
2016-03-28 10:46:51 +00:00
} else {
2016-04-18 12:34:29 +00:00
fn = function ( scope , locals ) {
var i = 0 , val ;
do {
val = cspSafeGetterFn ( pathKeys [ i ++ ] , pathKeys [ i ++ ] , pathKeys [ i ++ ] , pathKeys [ i ++ ] ,
pathKeys [ i ++ ] , fullExp , options ) ( 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 {
var code = 'var p;\n' ;
forEach ( pathKeys , function ( key , index ) {
ensureSafeMemberName ( key , fullExp ) ;
code += 'if(s == null) return undefined;\n' +
's=' + ( index
// 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
: '((k&&k.hasOwnProperty("' + key + '"))?k:s)' ) + '["' + key + '"]' + ';\n' +
( options . unwrapPromises
? 'if (s && s.then) {\n' +
' pw("' + fullExp . replace ( /(["\r\n])/g , '\\$1' ) + '");\n' +
' if (!("$$v" in s)) {\n' +
' p=s;\n' +
' p.$$v = undefined;\n' +
' p.then(function(v) {p.$$v=v;});\n' +
'}\n' +
' s=s.$$v\n' +
'}\n'
: '' ) ;
} ) ;
code += 'return s;' ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/* jshint -W054 */
var evaledFnGetter = new Function ( 's' , 'k' , 'pw' , code ) ; // s=scope, k=locals, pw=promiseWarning
/* jshint +W054 */
evaledFnGetter . toString = valueFn ( code ) ;
fn = options . unwrapPromises ? function ( scope , locals ) {
return evaledFnGetter ( scope , locals , promiseWarning ) ;
} : evaledFnGetter ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
// Only cache the value if it's not going to mess up the cache object
// This is more performant that using Object.prototype.hasOwnProperty.call
if ( path !== 'hasOwnProperty' ) {
getterFnCache [ path ] = fn ;
}
return fn ;
}
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 function
* @ name ng . $parse
* @ function
*
* @ description
*
* Converts Angular { @ link guide / expression expression } into a function .
*
* < pre >
* 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' ) ;
* < / p r e >
*
*
* @ 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
/ * *
* @ ngdoc object
* @ name ng . $parseProvider
* @ function
*
* @ description
* ` $ parseProvider ` can be used for configuring the default behavior of the { @ link ng . $parse $parse }
* service .
* /
function $ParseProvider ( ) {
var cache = { } ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
var $parseOptions = {
csp : false ,
unwrapPromises : false ,
logPromiseWarnings : true
} ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/ * *
* @ deprecated Promise unwrapping via $parse is deprecated and will be removed in the future .
*
* @ ngdoc method
* @ name ng . $parseProvider # unwrapPromises
* @ methodOf ng . $parseProvider
* @ description
*
* * * This feature is deprecated , see deprecation notes below for more info * *
*
* If set to true ( default is false ) , $parse will unwrap promises automatically when a promise is
* found at any part of the expression . In other words , if set to true , the expression will always
* result in a non - promise value .
*
* While the promise is unresolved , it ' s treated as undefined , but once resolved and fulfilled ,
* the fulfillment value is used in place of the promise while evaluating the expression .
*
* * * Deprecation notice * *
*
* This is a feature that didn ' t prove to be wildly useful or popular , primarily because of the
* dichotomy between data access in templates ( accessed as raw values ) and controller code
* ( accessed as promises ) .
*
* In most code we ended up resolving promises manually in controllers anyway and thus unifying
* the model access there .
*
* Other downsides of automatic promise unwrapping :
*
* - when building components it ' s often desirable to receive the raw promises
* - adds complexity and slows down expression evaluation
* - makes expression code pre - generation unattractive due to the amount of code that needs to be
* generated
* - makes IDE auto - completion and tool support hard
*
* * * Warning Logs * *
*
* If the unwrapping is enabled , Angular will log a warning about each expression that unwraps a
* promise ( to reduce the noise , each expression is logged only once ) . To disable this logging use
* ` $ parseProvider.logPromiseWarnings(false) ` api .
*
*
* @ param { boolean = } value New value .
* @ returns { boolean | self } Returns the current setting when used as getter and self if used as
* setter .
* /
this . unwrapPromises = function ( value ) {
if ( isDefined ( value ) ) {
$parseOptions . unwrapPromises = ! ! value ;
return this ;
} else {
return $parseOptions . unwrapPromises ;
}
} ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/ * *
* @ deprecated Promise unwrapping via $parse is deprecated and will be removed in the future .
*
* @ ngdoc method
* @ name ng . $parseProvider # logPromiseWarnings
* @ methodOf ng . $parseProvider
* @ description
*
* Controls whether Angular should log a warning on any encounter of a promise in an expression .
*
* The default is set to ` true ` .
*
* This setting applies only if ` $ parseProvider.unwrapPromises ` setting is set to true as well .
*
* @ param { boolean = } value New value .
* @ returns { boolean | self } Returns the current setting when used as getter and self if used as
* setter .
* /
this . logPromiseWarnings = function ( value ) {
if ( isDefined ( value ) ) {
$parseOptions . logPromiseWarnings = value ;
return this ;
} else {
return $parseOptions . logPromiseWarnings ;
}
} ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
this . $get = [ '$filter' , '$sniffer' , '$log' , function ( $filter , $sniffer , $log ) {
$parseOptions . csp = $sniffer . csp ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
promiseWarning = function promiseWarningFn ( fullExp ) {
if ( ! $parseOptions . logPromiseWarnings || promiseWarningCache . hasOwnProperty ( fullExp ) ) return ;
promiseWarningCache [ fullExp ] = true ;
$log . warn ( '[$parse] Promise found in the expression `' + fullExp + '`. ' +
'Automatic unwrapping of promises in Angular expressions is deprecated.' ) ;
2016-03-28 10:46:51 +00:00
} ;
2016-04-18 12:34:29 +00:00
return function ( exp ) {
var parsedExpression ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
switch ( typeof exp ) {
case 'string' :
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( cache . hasOwnProperty ( exp ) ) {
return cache [ exp ] ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
var lexer = new Lexer ( $parseOptions ) ;
var parser = new Parser ( lexer , $filter , $parseOptions ) ;
parsedExpression = parser . parse ( exp , false ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( exp !== 'hasOwnProperty' ) {
// Only cache the value if it's not going to mess up the cache object
// This is more performant that using Object.prototype.hasOwnProperty.call
cache [ exp ] = parsedExpression ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
return parsedExpression ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
case 'function' :
return exp ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
default :
return noop ;
2016-03-28 10:46:51 +00:00
}
} ;
} ] ;
}
/ * *
* @ ngdoc service
2016-04-18 12:34:29 +00:00
* @ name ng . $q
2016-03-28 10:46:51 +00:00
* @ requires $rootScope
*
* @ description
2016-04-18 12:34:29 +00:00
* A promise / deferred implementation inspired by [ Kris Kowal ' s Q ] ( https : //github.com/kriskowal/q).
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 .
*
2016-04-18 12:34:29 +00:00
* < pre >
* // for the purpose of this example let's assume that variables `$q` and `scope` 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 ( ) {
2016-04-18 12:34:29 +00:00
* // since this fn executes async in a future turn of the event loop, we need to wrap
* // our code into an $apply call so that the model changes are properly observed.
* scope . $apply ( function ( ) {
* deferred . notify ( 'About to greet ' + name + '.' ) ;
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00: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 ) ;
* } ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
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 .
*
2016-04-18 12:34:29 +00:00
*
2016-03-28 10:46:51 +00:00
* # 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 ` .
2016-04-18 12:34:29 +00:00
* - ` notify(value) ` - provides updates on the status of the promises 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
* ` notifyCallback ` method . The promise can not be resolved or rejected from the notifyCallback
* method .
2016-03-28 10:46:51 +00:00
*
* - ` catch(errorCallback) ` – shorthand for ` promise.then(null, errorCallback) `
*
2016-04-18 12:34:29 +00:00
* - ` finally(callback) ` – 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 .
*
2016-04-18 12:34:29 +00:00
* Because ` finally ` is a reserved word in JavaScript and reserved keywords are not supported as
* property names by ES3 , you 'll need to invoke the method like `promise[' finally ' ] ( callback ) ` to
* make your code IE8 compatible .
*
2016-03-28 10:46:51 +00:00
* # 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 :
*
2016-04-18 12:34:29 +00:00
* < pre >
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
2016-04-18 12:34:29 +00:00
* < / p r e >
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
*
2016-04-18 12:34:29 +00:00
* < pre >
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 ) ;
* } ) ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
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 ) ;
} ] ;
}
/ * *
* 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 ) {
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc
2016-03-28 10:46:51 +00:00
* @ name ng . $q # defer
2016-04-18 12:34:29 +00:00
* @ methodOf ng . $q
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 ( ) {
2016-04-18 12:34:29 +00:00
var pending = [ ] ,
value , deferred ;
deferred = {
resolve : function ( val ) {
if ( pending ) {
var callbacks = pending ;
pending = undefined ;
value = ref ( val ) ;
if ( callbacks . length ) {
nextTick ( function ( ) {
var callback ;
for ( var i = 0 , ii = callbacks . length ; i < ii ; i ++ ) {
callback = callbacks [ i ] ;
value . then ( callback [ 0 ] , callback [ 1 ] , callback [ 2 ] ) ;
}
} ) ;
}
}
} ,
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
reject : function ( reason ) {
deferred . resolve ( reject ( reason ) ) ;
} ,
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
notify : function ( progress ) {
if ( pending ) {
var callbacks = pending ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( pending . length ) {
nextTick ( function ( ) {
var callback ;
for ( var i = 0 , ii = callbacks . length ; i < ii ; i ++ ) {
callback = callbacks [ i ] ;
callback [ 2 ] ( progress ) ;
}
} ) ;
}
}
} ,
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
promise : {
then : function ( callback , errback , progressback ) {
var result = defer ( ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
var wrappedCallback = function ( value ) {
try {
result . resolve ( ( isFunction ( callback ) ? callback : defaultCallback ) ( value ) ) ;
} catch ( e ) {
result . reject ( e ) ;
exceptionHandler ( e ) ;
}
} ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
var wrappedErrback = function ( reason ) {
try {
result . resolve ( ( isFunction ( errback ) ? errback : defaultErrback ) ( reason ) ) ;
} catch ( e ) {
result . reject ( e ) ;
exceptionHandler ( e ) ;
}
} ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
var wrappedProgressback = function ( progress ) {
try {
result . notify ( ( isFunction ( progressback ) ? progressback : defaultCallback ) ( progress ) ) ;
} catch ( e ) {
exceptionHandler ( e ) ;
}
} ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( pending ) {
pending . push ( [ wrappedCallback , wrappedErrback , wrappedProgressback ] ) ;
} else {
value . then ( wrappedCallback , wrappedErrback , wrappedProgressback ) ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
return result . promise ;
} ,
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
"catch" : function ( callback ) {
return this . then ( null , callback ) ;
} ,
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
"finally" : function ( callback ) {
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
function makePromise ( value , resolved ) {
var result = defer ( ) ;
if ( resolved ) {
result . resolve ( value ) ;
} else {
result . reject ( value ) ;
}
return result . promise ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
function handleCallback ( value , isResolved ) {
var callbackOutput = null ;
try {
callbackOutput = ( callback || defaultCallback ) ( ) ;
} catch ( e ) {
return makePromise ( e , false ) ;
}
if ( callbackOutput && isFunction ( callbackOutput . then ) ) {
return callbackOutput . then ( function ( ) {
return makePromise ( value , isResolved ) ;
} , function ( error ) {
return makePromise ( error , false ) ;
} ) ;
} else {
return makePromise ( value , isResolved ) ;
}
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
return this . then ( function ( value ) {
return handleCallback ( value , true ) ;
} , function ( error ) {
return handleCallback ( error , false ) ;
} ) ;
}
}
} ;
return deferred ;
} ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
var ref = function ( value ) {
if ( value && isFunction ( value . then ) ) return value ;
return {
then : function ( callback ) {
var result = defer ( ) ;
2016-03-28 10:46:51 +00:00
nextTick ( function ( ) {
2016-04-18 12:34:29 +00:00
result . resolve ( callback ( value ) ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
return result . promise ;
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
* @ name ng . $q # reject
* @ methodOf ng . $q
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 ` .
*
2016-04-18 12:34:29 +00:00
* < pre >
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 ) ;
* } ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
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 ) {
2016-04-18 12:34:29 +00:00
return {
then : function ( callback , errback ) {
var result = defer ( ) ;
nextTick ( function ( ) {
try {
result . resolve ( ( isFunction ( errback ) ? errback : defaultErrback ) ( reason ) ) ;
} catch ( e ) {
result . reject ( e ) ;
exceptionHandler ( e ) ;
}
} ) ;
return result . promise ;
}
} ;
2016-03-28 10:46:51 +00:00
} ;
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc
* @ name ng . $q # when
* @ methodOf ng . $q
2016-03-28 10:46:51 +00:00
* @ description
* 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
* /
2016-04-18 12:34:29 +00:00
var when = function ( value , callback , errback , progressback ) {
var result = defer ( ) ,
done ;
var wrappedCallback = function ( value ) {
try {
return ( isFunction ( callback ) ? callback : defaultCallback ) ( value ) ;
} catch ( e ) {
exceptionHandler ( e ) ;
return reject ( e ) ;
}
} ;
var wrappedErrback = function ( reason ) {
try {
return ( isFunction ( errback ) ? errback : defaultErrback ) ( reason ) ;
} catch ( e ) {
exceptionHandler ( e ) ;
return reject ( e ) ;
}
} ;
var wrappedProgressback = function ( progress ) {
try {
return ( isFunction ( progressback ) ? progressback : defaultCallback ) ( progress ) ;
} catch ( e ) {
exceptionHandler ( e ) ;
}
} ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
nextTick ( function ( ) {
ref ( value ) . then ( function ( value ) {
if ( done ) return ;
done = true ;
result . resolve ( ref ( value ) . then ( wrappedCallback , wrappedErrback , wrappedProgressback ) ) ;
} , function ( reason ) {
if ( done ) return ;
done = true ;
result . resolve ( wrappedErrback ( reason ) ) ;
} , function ( progress ) {
if ( done ) return ;
result . notify ( wrappedProgressback ( progress ) ) ;
} ) ;
} ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
return result . promise ;
2016-03-28 10:46:51 +00:00
} ;
2016-04-18 12:34:29 +00:00
function defaultCallback ( value ) {
return value ;
}
function defaultErrback ( reason ) {
return reject ( reason ) ;
}
2016-03-28 10:46:51 +00:00
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc
* @ name ng . $q # all
* @ methodOf ng . $q
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 .
* /
function all ( promises ) {
2016-04-18 12:34:29 +00:00
var deferred = defer ( ) ,
2016-03-28 10:46:51 +00:00
counter = 0 ,
results = isArray ( promises ) ? [ ] : { } ;
forEach ( promises , function ( promise , key ) {
counter ++ ;
2016-04-18 12:34:29 +00:00
ref ( 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 ;
}
2016-04-18 12:34:29 +00:00
return {
defer : defer ,
reject : reject ,
when : when ,
all : all
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
* items to the array at the beginning ( shift ) 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
* /
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc object
* @ name ng . $rootScopeProvider
2016-03-28 10:46:51 +00:00
* @ description
*
* Provider for the $rootScope service .
* /
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $rootScopeProvider # digestTtl
* @ methodOf ng . $rootScopeProvider
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 .
* /
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc object
* @ name ng . $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 } .
* /
2016-04-18 12:34:29 +00:00
function $RootScopeProvider ( ) {
2016-03-28 10:46:51 +00:00
var TTL = 10 ;
var $rootScopeMinErr = minErr ( '$rootScope' ) ;
var lastDirtyWatch = null ;
this . digestTtl = function ( value ) {
if ( arguments . length ) {
TTL = value ;
}
return TTL ;
} ;
2016-04-18 12:34:29 +00:00
this . $get = [ '$injector' , '$exceptionHandler' , '$parse' , '$browser' ,
function ( $injector , $exceptionHandler , $parse , $browser ) {
2016-03-28 10:46:51 +00:00
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $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
2016-04-18 12:34:29 +00:00
* { @ link AUTO . $injector $injector } . Child scopes are created using the
* { @ link ng . $rootScope . Scope # methods _$new $new ( ) } method . ( Most scopes are created automatically when
* compiled HTML template is executed . )
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* Here is a simple scope snippet to show how you can interact with the scope .
* < pre >
* < file src = "./test/ng/rootScopeSpec.js" tag = "docs1" / >
* < / p r e >
2016-03-28 10:46:51 +00:00
*
* # Inheritance
* A scope can inherit from a parent scope , as in this example :
2016-04-18 12:34:29 +00:00
* < pre >
2016-03-28 10:46:51 +00:00
var parent = $rootScope ;
var child = parent . $new ( ) ;
parent . salutation = "Hello" ;
2016-04-18 12:34:29 +00:00
child . name = "World" ;
2016-03-28 10:46:51 +00:00
expect ( child . salutation ) . toEqual ( 'Hello' ) ;
child . salutation = "Welcome" ;
expect ( child . salutation ) . toEqual ( 'Welcome' ) ;
expect ( parent . salutation ) . toEqual ( 'Hello' ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
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 ;
2016-04-18 12:34:29 +00:00
this [ 'this' ] = this . $root = this ;
2016-03-28 10:46:51 +00:00
this . $$destroyed = false ;
2016-04-18 12:34:29 +00:00
this . $$asyncQueue = [ ] ;
this . $$postDigestQueue = [ ] ;
2016-03-28 10:46:51 +00:00
this . $$listeners = { } ;
this . $$listenerCount = { } ;
2016-04-18 12:34:29 +00:00
this . $$isolateBindings = { } ;
2016-03-28 10:46:51 +00:00
}
/ * *
* @ ngdoc property
2016-04-18 12:34:29 +00:00
* @ name ng . $rootScope . Scope # $id
* @ propertyOf ng . $rootScope . Scope
* @ returns { number } Unique scope ID ( monotonically increasing alphanumeric sequence ) useful for
* debugging .
2016-03-28 10:46:51 +00:00
* /
Scope . prototype = {
constructor : Scope ,
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $rootScope . Scope # $new
* @ methodOf ng . $rootScope . Scope
* @ function
2016-03-28 10:46:51 +00:00
*
* @ description
* Creates a new child { @ link ng . $rootScope . Scope scope } .
*
2016-04-18 12:34:29 +00:00
* The parent scope will propagate the { @ link ng . $rootScope . Scope # methods _$digest $digest ( ) } and
* { @ link ng . $rootScope . Scope # methods _$digest $digest ( ) } events . The scope can be removed from the
* scope hierarchy using { @ link ng . $rootScope . Scope # methods _$destroy $destroy ( ) } .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* { @ link ng . $rootScope . Scope # methods _$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 .
*
* @ returns { Object } The newly created child scope .
*
* /
2016-04-18 12:34:29 +00:00
$new : function ( isolate ) {
var ChildScope ,
child ;
2016-03-28 10:46:51 +00:00
if ( isolate ) {
child = new Scope ( ) ;
child . $root = this . $root ;
2016-04-18 12:34:29 +00:00
// ensure that there is just one async queue per $rootScope and its children
child . $$asyncQueue = this . $$asyncQueue ;
child . $$postDigestQueue = this . $$postDigestQueue ;
2016-03-28 10:46:51 +00:00
} else {
2016-04-18 12:34:29 +00:00
ChildScope = function ( ) { } ; // should be anonymous; This is so that when the minifier munges
// the name it does not become random set of chars. This will then show up as class
// name in the web inspector.
ChildScope . prototype = this ;
child = new ChildScope ( ) ;
child . $id = nextUid ( ) ;
}
child [ 'this' ] = child ;
child . $$listeners = { } ;
child . $$listenerCount = { } ;
child . $parent = this ;
child . $$watchers = child . $$nextSibling = child . $$childHead = child . $$childTail = null ;
child . $$prevSibling = this . $$childTail ;
if ( this . $$childHead ) {
this . $$childTail . $$nextSibling = child ;
this . $$childTail = child ;
2016-03-28 10:46:51 +00:00
} else {
2016-04-18 12:34:29 +00:00
this . $$childHead = this . $$childTail = child ;
2016-03-28 10:46:51 +00:00
}
return child ;
} ,
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $rootScope . Scope # $watch
* @ methodOf ng . $rootScope . Scope
* @ function
2016-03-28 10:46:51 +00:00
*
* @ description
* Registers a ` listener ` callback to be executed whenever the ` watchExpression ` changes .
*
2016-04-18 12:34:29 +00:00
* - The ` watchExpression ` is called on every call to { @ link ng . $rootScope . Scope # methods _$digest
* $digest ( ) } and should return the value that will be watched . ( Since
* { @ link ng . $rootScope . Scope # methods _$digest $digest ( ) } reruns when it detects changes the
* ` watchExpression ` can execute multiple times per
* { @ link ng . $rootScope . Scope # methods _$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 ,
2016-04-18 12:34:29 +00:00
* see below ) . The inequality is determined according to
* { @ link angular . equals } function . To save the value of the object for later comparison ,
* the { @ link angular . copy } function is used . It also means that watching complex options
* 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 .
*
*
2016-04-18 12:34:29 +00:00
* If you want to be notified whenever { @ link ng . $rootScope . Scope # methods _$digest $digest } is called ,
* you can register a ` watchExpression ` function with no ` listener ` . ( Since ` watchExpression `
* can execute multiple times per { @ link ng . $rootScope . Scope # methods _$digest $digest } cycle when a
* change is detected , be prepared for multiple calls to your listener . )
2016-03-28 10:46:51 +00:00
*
* After a watcher is registered with the scope , the ` listener ` fn is called asynchronously
2016-04-18 12:34:29 +00:00
* ( via { @ link ng . $rootScope . Scope # methods _$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 .
*
2016-04-18 12:34:29 +00:00
* The example below contains an illustration of using a function as your $watch listener
2016-03-28 10:46:51 +00:00
*
*
* # Example
2016-04-18 12:34:29 +00:00
* < pre >
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 ( ) ;
2016-04-18 12:34:29 +00:00
// no variable change
expect ( scope . counter ) . toEqual ( 0 ) ;
2016-03-28 10:46:51 +00:00
scope . name = 'adam' ;
scope . $digest ( ) ;
2016-04-18 12:34:29 +00:00
expect ( scope . counter ) . toEqual ( 1 ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
// Using a listener function
2016-03-28 10:46:51 +00:00
var food ;
scope . foodCounter = 0 ;
expect ( scope . foodCounter ) . toEqual ( 0 ) ;
scope . $watch (
2016-04-18 12:34:29 +00:00
// This is the listener function
2016-03-28 10:46:51 +00:00
function ( ) { return food ; } ,
2016-04-18 12:34:29 +00:00
// This is the change handler
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 ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
2016-03-28 10:46:51 +00:00
*
*
*
* @ param { ( function ( ) | string ) } watchExpression Expression that is evaluated on each
2016-04-18 12:34:29 +00:00
* { @ link ng . $rootScope . Scope # methods _$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 .
2016-04-18 12:34:29 +00:00
* @ param { ( function ( ) | string ) = } listener Callback called whenever the return value of
* the ` watchExpression ` changes .
*
* - ` string ` : Evaluated as { @ link guide / expression expression }
* - ` function(newValue, oldValue, scope) ` : called with current and previous values as
* parameters .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ param { boolean = } objectEquality Compare object for equality rather than for reference .
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 ) {
2016-03-28 10:46:51 +00:00
var scope = this ,
2016-04-18 12:34:29 +00:00
get = compileToFn ( watchExp , 'watch' ) ,
2016-03-28 10:46:51 +00:00
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 ;
2016-04-18 12:34:29 +00:00
// in the case user pass string, we need to compile it, do we really need this ?
2016-03-28 10:46:51 +00:00
if ( ! isFunction ( listener ) ) {
2016-04-18 12:34:29 +00:00
var listenFn = compileToFn ( listener || noop , 'listener' ) ;
watcher . fn = function ( newVal , oldVal , scope ) { listenFn ( scope ) ; } ;
}
if ( typeof watchExp == 'string' && get . constant ) {
var originalFn = watcher . fn ;
watcher . fn = function ( newVal , oldVal , scope ) {
originalFn . call ( this , newVal , oldVal , scope ) ;
arrayRemove ( array , watcher ) ;
} ;
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 ) ;
2016-04-18 12:34:29 +00:00
return function ( ) {
arrayRemove ( array , watcher ) ;
2016-03-28 10:46:51 +00:00
lastDirtyWatch = null ;
} ;
} ,
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 function
* @ name ng . $rootScope . Scope # $watchCollection
* @ methodOf ng . $rootScope . Scope
* @ 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
2016-04-18 12:34:29 +00:00
* < pre >
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 ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
2016-03-28 10:46:51 +00:00
*
*
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00:00
* { @ link ng . $rootScope . Scope # methods _$digest $digest } cycle . Any shallow change within the
2016-03-28 10:46:51 +00:00
* collection will trigger a call to the ` listener ` .
*
2016-04-18 12:34:29 +00:00
* @ param { function ( newCollection , oldCollection , scope ) } listener a callback function that is
* fired with both the ` newCollection ` and ` oldCollection ` as parameters .
* The ` newCollection ` object is the newly modified data obtained from the ` obj ` expression
* and the ` oldCollection ` object is a copy of the former collection data .
* The ` scope ` 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 ) {
var self = this ;
var oldValue ;
2016-04-18 12:34:29 +00:00
var newValue ;
2016-03-28 10:46:51 +00:00
var changeDetected = 0 ;
2016-04-18 12:34:29 +00:00
var objGetter = $parse ( obj ) ;
2016-03-28 10:46:51 +00:00
var internalArray = [ ] ;
var internalObject = { } ;
var oldLength = 0 ;
2016-04-18 12:34:29 +00:00
function $watchCollectionWatch ( ) {
newValue = objGetter ( self ) ;
var newLength , key ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( ! isObject ( newValue ) ) {
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 ++ ) {
2016-04-18 12:34:29 +00:00
if ( oldValue [ i ] !== newValue [ i ] ) {
2016-03-28 10:46:51 +00:00
changeDetected ++ ;
2016-04-18 12:34:29 +00:00
oldValue [ i ] = newValue [ i ] ;
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 ++ ;
2016-04-18 12:34:29 +00:00
if ( oldValue . hasOwnProperty ( key ) ) {
if ( oldValue [ key ] !== newValue [ key ] ) {
2016-03-28 10:46:51 +00:00
changeDetected ++ ;
2016-04-18 12:34:29 +00:00
oldValue [ key ] = newValue [ key ] ;
2016-03-28 10:46:51 +00:00
}
} else {
oldLength ++ ;
2016-04-18 12:34:29 +00:00
oldValue [ key ] = newValue [ key ] ;
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 ++ ;
2016-04-18 12:34:29 +00:00
for ( key in oldValue ) {
if ( oldValue . hasOwnProperty ( key ) && ! newValue . hasOwnProperty ( key ) ) {
2016-03-28 10:46:51 +00:00
oldLength -- ;
delete oldValue [ key ] ;
}
}
}
}
return changeDetected ;
}
function $watchCollectionAction ( ) {
2016-04-18 12:34:29 +00:00
listener ( newValue , oldValue , self ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
return this . $watch ( $watchCollectionWatch , $watchCollectionAction ) ;
2016-03-28 10:46:51 +00:00
} ,
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $rootScope . Scope # $digest
* @ methodOf ng . $rootScope . Scope
* @ function
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* Processes all of the { @ link ng . $rootScope . Scope # methods _$watch watchers } of the current scope and
* its children . Because a { @ link ng . $rootScope . Scope # methods _$watch watcher } ' s listener can change
* the model , the ` $ digest() ` keeps calling the { @ link ng . $rootScope . Scope # methods _$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
2016-04-18 12:34:29 +00:00
* { @ link ng . $compileProvider # methods _directive directives } .
* Instead , you should call { @ link ng . $rootScope . Scope # methods _$apply $apply ( ) } ( typically from within
* a { @ link ng . $compileProvider # methods _directive directives } ) , 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
2016-04-18 12:34:29 +00:00
* { @ link ng . $rootScope . Scope # methods _$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
2016-04-18 12:34:29 +00:00
* < pre >
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 ( ) ;
2016-04-18 12:34:29 +00:00
// no variable change
expect ( scope . counter ) . toEqual ( 0 ) ;
2016-03-28 10:46:51 +00:00
scope . name = 'adam' ;
scope . $digest ( ) ;
2016-04-18 12:34:29 +00:00
expect ( scope . counter ) . toEqual ( 1 ) ;
* < / p r e >
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 ,
2016-04-18 12:34:29 +00:00
asyncQueue = this . $$asyncQueue ,
postDigestQueue = this . $$postDigestQueue ,
2016-03-28 10:46:51 +00:00
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' ) ;
lastDirtyWatch = null ;
do { // "while dirty" loop
dirty = false ;
current = target ;
2016-04-18 12:34:29 +00:00
while ( asyncQueue . length ) {
2016-03-28 10:46:51 +00:00
try {
asyncTask = asyncQueue . shift ( ) ;
2016-04-18 12:34:29 +00:00
asyncTask . scope . $eval ( asyncTask . expression ) ;
2016-03-28 10:46:51 +00:00
} catch ( e ) {
2016-04-18 12:34:29 +00:00
clearPhase ( ) ;
2016-03-28 10:46:51 +00:00
$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 )
2016-04-18 12:34:29 +00:00
: ( typeof value == 'number' && typeof last == 'number'
2016-03-28 10:46:51 +00:00
&& isNaN ( value ) && isNaN ( last ) ) ) ) {
dirty = true ;
lastDirtyWatch = watch ;
2016-04-18 12:34:29 +00:00
watch . last = watch . eq ? copy ( value ) : value ;
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 ] = [ ] ;
2016-04-18 12:34:29 +00:00
logMsg = ( isFunction ( watch . exp ) )
? 'fn: ' + ( watch . exp . name || watch . exp . toString ( ) )
: watch . exp ;
logMsg += '; newVal: ' + toJson ( value ) + '; oldVal: ' + toJson ( last ) ;
watchLog [ logIdx ] . push ( logMsg ) ;
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 ) {
2016-04-18 12:34:29 +00:00
clearPhase ( ) ;
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 $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 ) ) ) ) {
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00: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}' ,
2016-04-18 12:34:29 +00:00
TTL , toJson ( watchLog ) ) ;
2016-03-28 10:46:51 +00:00
}
} while ( dirty || asyncQueue . length ) ;
clearPhase ( ) ;
2016-04-18 12:34:29 +00:00
while ( postDigestQueue . length ) {
2016-03-28 10:46:51 +00:00
try {
postDigestQueue . shift ( ) ( ) ;
} catch ( e ) {
$exceptionHandler ( e ) ;
}
}
} ,
/ * *
* @ ngdoc event
2016-04-18 12:34:29 +00:00
* @ name ng . $rootScope . Scope # $destroy
* @ eventOf ng . $rootScope . Scope
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 .
* /
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $rootScope . Scope # $destroy
* @ methodOf ng . $rootScope . Scope
* @ 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
2016-04-18 12:34:29 +00:00
* that calls to { @ link ng . $rootScope . Scope # methods _$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
2016-04-18 12:34:29 +00:00
forEach ( this . $$listenerCount , bind ( null , decrementListenerCount , this ) ) ;
2016-03-28 10:46:51 +00:00
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 ;
2016-04-18 12:34:29 +00:00
// This is bogus code that works around Chrome's GC leak
// see: https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
this . $parent = this . $$nextSibling = this . $$prevSibling = this . $$childHead =
this . $$childTail = null ;
2016-03-28 10:46:51 +00:00
} ,
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $rootScope . Scope # $eval
* @ methodOf ng . $rootScope . Scope
* @ 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
2016-04-18 12:34:29 +00:00
* < pre >
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 ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
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 ) ;
} ,
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $rootScope . Scope # $evalAsync
* @ methodOf ng . $rootScope . Scope
* @ 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 ) .
2016-04-18 12:34:29 +00:00
* - at least one { @ link ng . $rootScope . Scope # methods _$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 .
*
* /
2016-04-18 12:34:29 +00:00
$evalAsync : function ( expr ) {
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
2016-04-18 12:34:29 +00:00
if ( ! $rootScope . $$phase && ! $rootScope . $$asyncQueue . length ) {
2016-03-28 10:46:51 +00:00
$browser . defer ( function ( ) {
2016-04-18 12:34:29 +00:00
if ( $rootScope . $$asyncQueue . length ) {
2016-03-28 10:46:51 +00:00
$rootScope . $digest ( ) ;
}
} ) ;
}
2016-04-18 12:34:29 +00:00
this . $$asyncQueue . push ( { scope : this , expression : expr } ) ;
2016-03-28 10:46:51 +00:00
} ,
2016-04-18 12:34:29 +00:00
$$postDigest : function ( fn ) {
this . $$postDigestQueue . push ( fn ) ;
2016-03-28 10:46:51 +00:00
} ,
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $rootScope . Scope # $apply
* @ methodOf ng . $rootScope . Scope
* @ 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 } ,
2016-04-18 12:34:29 +00:00
* { @ link ng . $rootScope . Scope # methods _$digest executing watches } .
2016-03-28 10:46:51 +00:00
*
* # # Life cycle
*
* # Pseudo - Code of ` $ apply() `
2016-04-18 12:34:29 +00:00
* < pre >
2016-03-28 10:46:51 +00:00
function $apply ( expr ) {
try {
return $eval ( expr ) ;
} catch ( e ) {
$exceptionHandler ( e ) ;
} finally {
$root . $digest ( ) ;
}
}
2016-04-18 12:34:29 +00:00
* < / p r e >
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
2016-04-18 12:34:29 +00:00
* { @ link ng . $rootScope . Scope # methods _$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 .
2016-04-18 12:34:29 +00:00
* 3. The { @ link ng . $rootScope . Scope # methods _$watch watch } listeners are fired immediately after the
* expression was executed using the { @ link ng . $rootScope . Scope # methods _$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 ;
}
}
} ,
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $rootScope . Scope # $on
* @ methodOf ng . $rootScope . Scope
* @ function
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* Listens on events of a given type . See { @ link ng . $rootScope . Scope # methods _$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 .
2016-04-18 12:34:29 +00:00
* - ` currentScope ` - ` {Scope} ` : the current scope which is handling the event .
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 .
2016-04-18 12:34:29 +00: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 ( ) {
2016-04-18 12:34:29 +00:00
namedListeners [ indexOf ( namedListeners , listener ) ] = null ;
decrementListenerCount ( self , 1 , name ) ;
2016-03-28 10:46:51 +00:00
} ;
} ,
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $rootScope . Scope # $emit
* @ methodOf ng . $rootScope . Scope
* @ function
2016-03-28 10:46:51 +00:00
*
* @ description
* Dispatches an event ` name ` upwards through the scope hierarchy notifying the
2016-04-18 12:34:29 +00:00
* registered { @ link ng . $rootScope . Scope # methods _$on } listeners .
2016-03-28 10:46:51 +00:00
*
* The event life cycle starts at the scope on which ` $ emit ` was called . All
2016-04-18 12:34:29 +00:00
* { @ link ng . $rootScope . Scope # methods _$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 .
*
2016-04-18 12:34:29 +00:00
* Any exception emitted from the { @ link ng . $rootScope . Scope # methods _$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 .
2016-04-18 12:34:29 +00:00
* @ param { ... * } args Optional set of arguments which will be passed onto the event listeners .
* @ return { Object } Event object ( see { @ link ng . $rootScope . Scope # methods _$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 ;
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00:00
if ( stopPropagation ) return event ;
2016-03-28 10:46:51 +00:00
//traverse upwards
scope = scope . $parent ;
} while ( scope ) ;
return event ;
} ,
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $rootScope . Scope # $broadcast
* @ methodOf ng . $rootScope . Scope
* @ function
2016-03-28 10:46:51 +00:00
*
* @ description
* Dispatches an event ` name ` downwards to all child scopes ( and their children ) notifying the
2016-04-18 12:34:29 +00:00
* registered { @ link ng . $rootScope . Scope # methods _$on } listeners .
2016-03-28 10:46:51 +00:00
*
* The event life cycle starts at the scope on which ` $ broadcast ` was called . All
2016-04-18 12:34:29 +00:00
* { @ link ng . $rootScope . Scope # methods _$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 .
*
2016-04-18 12:34:29 +00:00
* Any exception emitted from the { @ link ng . $rootScope . Scope # methods _$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 .
2016-04-18 12:34:29 +00:00
* @ param { ... * } args Optional set of arguments which will be passed onto the event listeners .
* @ return { Object } Event object , see { @ link ng . $rootScope . Scope # methods _$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
2016-04-18 12:34:29 +00:00
} ,
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 ] || [ ] ;
2016-04-18 12:34:29 +00: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 ) ;
2016-04-18 12:34:29 +00: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 ) ) ) ) {
2016-04-18 12:34:29 +00:00
while ( current !== target && ! ( next = current . $$nextSibling ) ) {
2016-03-28 10:46:51 +00:00
current = current . $parent ;
}
}
}
return event ;
}
} ;
var $rootScope = new Scope ( ) ;
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 ;
}
2016-04-18 12:34:29 +00:00
function compileToFn ( exp , name ) {
var fn = $parse ( exp ) ;
assertArgFn ( fn , name ) ;
return fn ;
2016-03-28 10:46:51 +00:00
}
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 ( ) { }
} ] ;
}
/ * *
* @ 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):/ ,
2016-04-18 12:34:29 +00:00
imgSrcSanitizationWhitelist = /^\s*(https?|ftp|file):|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 ;
2016-04-18 12:34:29 +00:00
// NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case.
if ( ! msie || msie >= 8 ) {
normalizedVal = urlResolve ( uri ) . href ;
if ( normalizedVal !== '' && ! normalizedVal . match ( regex ) ) {
return 'unsafe:' + normalizedVal ;
}
2016-03-28 10:46:51 +00:00
}
return uri ;
} ;
} ;
}
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.
2016-04-18 12:34:29 +00:00
// Copied from:
// http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962
// Prereq: s is a string.
function escapeForRegexp ( s ) {
return s . replace ( /([-()\[\]{}+?*.$\^|,:#<!\\])/g , '\\$1' ) .
replace ( /\x08/g , '\\x08' ) ;
}
2016-03-28 10:46:51 +00:00
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
2016-04-18 12:34:29 +00:00
* @ name ng . $sceDelegate
* @ 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
2016-04-18 12:34:29 +00:00
* templates . Refer { @ link ng . $sceDelegateProvider # methods _resourceUrlWhitelist
2016-03-28 10:46:51 +00:00
* $sceDelegateProvider . resourceUrlWhitelist } and { @ link
2016-04-18 12:34:29 +00:00
* ng . $sceDelegateProvider # methods _resourceUrlBlacklist $sceDelegateProvider . resourceUrlBlacklist }
2016-03-28 10:46:51 +00:00
* /
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc object
* @ name ng . $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
2016-04-18 12:34:29 +00:00
* ng . $sceDelegateProvider # methods _resourceUrlWhitelist $sceDelegateProvider . resourceUrlWhitelist } and
* { @ link ng . $sceDelegateProvider # methods _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 :
*
2016-04-18 12:34:29 +00:00
* < pre class = "prettyprint" >
* 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**' ] ) ;
* } ) ;
* < / p r e >
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 = [ ] ;
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . sceDelegateProvider # resourceUrlWhitelist
* @ methodOf ng . $sceDelegateProvider
* @ 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 .
* /
2016-04-18 12:34:29 +00:00
this . resourceUrlWhitelist = function ( value ) {
2016-03-28 10:46:51 +00:00
if ( arguments . length ) {
resourceUrlWhitelist = adjustMatchers ( value ) ;
}
return resourceUrlWhitelist ;
} ;
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . sceDelegateProvider # resourceUrlBlacklist
* @ methodOf ng . $sceDelegateProvider
* @ 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 .
* /
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00:00
* @ name ng . $sceDelegate # trustAs
* @ methodOf ng . $sceDelegate
2016-03-28 10:46:51 +00:00
*
* @ description
* Returns an object that is trusted by angular for use in specified strict
2016-04-18 12:34:29 +00:00
* contextual escaping contexts ( such as ng - html - bind - unsafe , 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
2016-04-18 12:34:29 +00:00
* @ name ng . $sceDelegate # valueOf
* @ methodOf ng . $sceDelegate
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* If the passed parameter had been returned by a prior call to { @ link ng . $sceDelegate # methods _trustAs
2016-03-28 10:46:51 +00:00
* ` $ sceDelegate.trustAs ` } , returns the value that had been passed to { @ link
2016-04-18 12:34:29 +00:00
* ng . $sceDelegate # methods _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
2016-04-18 12:34:29 +00:00
* ng . $sceDelegate # methods _trustAs ` $ sceDelegate.trustAs ` } , returns it as - is .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ param { * } value The result of a prior { @ link ng . $sceDelegate # methods _trustAs ` $ sceDelegate.trustAs ` }
2016-03-28 10:46:51 +00:00
* call or anything else .
2016-04-18 12:34:29 +00:00
* @ returns { * } The ` value ` that was originally provided to { @ link ng . $sceDelegate # methods _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
2016-04-18 12:34:29 +00:00
* @ name ng . $sceDelegate # getTrusted
* @ methodOf ng . $sceDelegate
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* Takes the result of a { @ link ng . $sceDelegate # methods _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 .
2016-04-18 12:34:29 +00:00
* @ param { * } maybeTrusted The result of a prior { @ link ng . $sceDelegate # methods _trustAs
2016-03-28 10:46:51 +00:00
* ` $ sceDelegate.trustAs ` } call .
2016-04-18 12:34:29 +00:00
* @ returns { * } The value the was originally provided to { @ link ng . $sceDelegate # methods _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 } ;
} ] ;
}
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc object
* @ name ng . $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
2016-04-18 12:34:29 +00:00
* @ name ng . $sce
* @ 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 .
*
2016-04-18 12:34:29 +00:00
* Note : When enabled ( the default ) , IE8 in quirks mode is not supported . In this mode , IE8 allows
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 :
*
2016-04-18 12:34:29 +00:00
* < pre class = "prettyprint" >
* < input ng - model = "userHtml" >
* < div ng - bind - html = "userHtml" >
* < / p r e >
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 .
*
2016-05-18 00:10:50 +00:00
* In the case of AngularJS ' SCE service , one uses { @ link ng . $sce # methods _trustAs $sce . trustAs }
2016-04-18 12:34:29 +00:00
* ( and shorthand methods such as { @ link ng . $sce # methods _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 ?
*
2016-04-18 12:34:29 +00:00
* In privileged contexts , directives and code will bind to the result of { @ link ng . $sce # methods _getTrusted
2016-03-28 10:46:51 +00:00
* $sce . getTrusted ( context , value ) } rather than to the value directly . Directives use { @ link
2016-04-18 12:34:29 +00:00
* ng . $sce # methods _parse $sce . parseAs } rather than ` $ parse ` to watch attribute bindings , which performs the
* { @ link ng . $sce # methods _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
2016-04-18 12:34:29 +00:00
* ng . $sce # methods _parseAsHtml $sce . parseAsHtml ( binding expression ) } . Here ' s the actual code ( slightly
2016-03-28 10:46:51 +00:00
* simplified ) :
*
2016-04-18 12:34:29 +00:00
* < pre class = "prettyprint" >
* var ngBindHtmlDirective = [ '$sce' , function ( $sce ) {
* return function ( scope , element , attr ) {
* scope . $watch ( $sce . parseAsHtml ( attr . ngBindHtml ) , function ( value ) {
* element . html ( value || '' ) ;
* } ) ;
* } ;
* } ] ;
* < / p r e >
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
2016-04-18 12:34:29 +00:00
* document . This is done by calling { @ link ng . $sce # methods _getTrustedResourceUrl
2016-03-28 10:46:51 +00:00
* $sce . getTrustedResourceUrl } on the template URL . To load templates from other domains and / or
2016-04-18 12:34:29 +00:00
* protocols , you may either either { @ link ng . $sceDelegateProvider # methods _resourceUrlWhitelist whitelist
* them } or { @ link ng . $sce # methods _trustAsResourceUrl wrap it } into a trusted value .
2016-03-28 10:46:51 +00:00
*
* * Please note * :
* The browser ' s
2016-04-18 12:34:29 +00:00
* { @ link https : //code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest
* Same Origin Policy } and { @ link http : //www.w3.org/TR/cors/ Cross-Origin Resource Sharing (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 .
*
2016-04-18 12:34:29 +00:00
* # # This feels like too much overhead for the developer ?
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
2016-04-18 12:34:29 +00:00
* call ` $ sce.trustAs ` on them . ( e . g .
* ` <div ng-html-bind-unsafe="'<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
2016-04-18 12:34:29 +00:00
* through { @ link ng . $sce # methods _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
2016-04-18 12:34:29 +00:00
* ng . $sceDelegateProvider # methods _resourceUrlWhitelist whitelists } and { @ link
* ng . $sceDelegateProvider # methods _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 |
* | -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- |
2016-04-18 12:34:29 +00: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 . |
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 . |
2016-04-18 12:34:29 +00: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 consititute an SCE context . |
* | ` $ sce.RESOURCE_URL ` | For URLs that are not only safe to follow as links , but whose contens 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 . |
*
2016-04-18 12:34:29 +00:00
* # # Format of items in { @ link ng . $sceDelegateProvider # methods _resourceUrlWhitelist resourceUrlWhitelist } / { @ link ng . $sceDelegateProvider # methods _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 .
2016-04-18 12:34:29 +00:00
* - ` * ` : matches zero or more occurances of any character other than one of the following 6
* characters : '`:`' , '`/`' , '`.`' , '`?`' , '`&`' and ';' . It ' s a useful wildcard for use
2016-03-28 10:46:51 +00:00
* in a whitelist .
2016-04-18 12:34:29 +00:00
* - ` ** ` : matches zero or more occurances of * any * character . As such , it ' s not
* not appropriate to use in for 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
2016-04-18 12:34:29 +00:00
* not have been the intention . ) It ' s 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
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00:00
* if they 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 .
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00: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 .
*
2016-04-18 12:34:29 +00:00
* @ example
< example module = "mySceApp" >
< file name = "index.html" >
< div ng - controller = "myAppController 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" >
var mySceApp = angular . module ( 'mySceApp' , [ 'ngSanitize' ] ) ;
mySceApp . controller ( "myAppController" , function myAppController ( $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 = "scenario.js" >
describe ( 'SCE doc demo' , function ( ) {
it ( 'should sanitize untrusted values' , function ( ) {
expect ( element ( '.htmlComment' ) . html ( ) ) . toBe ( '<span>Is <i>anyone</i> reading this?</span>' ) ;
} ) ;
it ( 'should NOT sanitize explicitly trusted values' , function ( ) {
expect ( element ( '#explicitlyTrustedHtml' ) . html ( ) ) . 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 :
*
2016-04-18 12:34:29 +00:00
* < pre class = "prettyprint" >
* angular . module ( 'myAppWithSceDisabledmyApp' , [ ] ) . config ( function ( $sceProvider ) {
* // Completely disable SCE. For demonstration purposes only!
* // Do not use in new projects.
* $sceProvider . enabled ( false ) ;
* } ) ;
* < / p r e >
2016-03-28 10:46:51 +00:00
*
* /
/* jshint maxlen: 100 */
function $SceProvider ( ) {
var enabled = true ;
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . sceProvider # enabled
* @ methodOf ng . $sceProvider
* @ 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 .
* /
2016-04-18 12:34:29 +00: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 .
* /
2016-04-18 12:34:29 +00:00
this . $get = [ '$parse' , '$sniffer' , '$sceDelegate' , function (
$parse , $sniffer , $sceDelegate ) {
// Prereq: Ensure that we're not running in IE8 quirks mode. In that mode, IE allows
2016-03-28 10:46:51 +00:00
// the "expression(javascript expression)" syntax which is insecure.
2016-04-18 12:34:29 +00:00
if ( enabled && $sniffer . msie && $sniffer . msieDocumentMode < 8 ) {
2016-03-28 10:46:51 +00:00
throw $sceMinErr ( 'iequirks' ,
2016-04-18 12:34:29 +00:00
'Strict Contextual Escaping does not support Internet Explorer version < 9 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.' ) ;
}
2016-04-18 12:34:29 +00:00
var sce = copy ( SCE _CONTEXTS ) ;
2016-03-28 10:46:51 +00:00
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . sce # isEnabled
* @ methodOf ng . $sce
* @ 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 .
* /
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00:00
* @ name ng . $sce # parse
* @ methodOf ng . $sce
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
2016-04-18 12:34:29 +00:00
* wraps the expression in a call to { @ link ng . $sce # methods _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 {
2016-04-18 12:34:29 +00:00
return function sceParseAsTrusted ( self , locals ) {
return sce . getTrusted ( type , parsed ( self , locals ) ) ;
} ;
2016-03-28 10:46:51 +00:00
}
} ;
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name ng . $sce # trustAs
* @ methodOf ng . $sce
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* Delegates to { @ link ng . $sceDelegate # methods _trustAs ` $ sceDelegate.trustAs ` } . As such ,
* returns an objectthat is trusted by angular for use in specified strict contextual
* escaping contexts ( such as ng - html - bind - unsafe , 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
2016-04-18 12:34:29 +00:00
* @ name ng . $sce # trustAsHtml
* @ methodOf ng . $sce
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.trustAsHtml(value) ` →
2016-04-18 12:34:29 +00:00
* { @ link ng . $sceDelegate # methods _trustAs ` $ sceDelegate.trustAs( $ sce.HTML, value) ` }
2016-03-28 10:46:51 +00:00
*
* @ param { * } value The value to trustAs .
2016-04-18 12:34:29 +00:00
* @ returns { * } An object that can be passed to { @ link ng . $sce # methods _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
2016-04-18 12:34:29 +00:00
* return value of { @ link ng . $sce # methods _trustAs $sce . trustAs } . )
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name ng . $sce # trustAsUrl
* @ methodOf ng . $sce
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.trustAsUrl(value) ` →
2016-04-18 12:34:29 +00:00
* { @ link ng . $sceDelegate # methods _trustAs ` $ sceDelegate.trustAs( $ sce.URL, value) ` }
2016-03-28 10:46:51 +00:00
*
* @ param { * } value The value to trustAs .
2016-04-18 12:34:29 +00:00
* @ returns { * } An object that can be passed to { @ link ng . $sce # methods _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
2016-04-18 12:34:29 +00:00
* return value of { @ link ng . $sce # methods _trustAs $sce . trustAs } . )
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name ng . $sce # trustAsResourceUrl
* @ methodOf ng . $sce
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.trustAsResourceUrl(value) ` →
2016-04-18 12:34:29 +00:00
* { @ link ng . $sceDelegate # methods _trustAs ` $ sceDelegate.trustAs( $ sce.RESOURCE_URL, value) ` }
2016-03-28 10:46:51 +00:00
*
* @ param { * } value The value to trustAs .
2016-04-18 12:34:29 +00:00
* @ returns { * } An object that can be passed to { @ link ng . $sce # methods _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
2016-04-18 12:34:29 +00:00
* value of { @ link ng . $sce # methods _trustAs $sce . trustAs } . )
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name ng . $sce # trustAsJs
* @ methodOf ng . $sce
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.trustAsJs(value) ` →
2016-04-18 12:34:29 +00:00
* { @ link ng . $sceDelegate # methods _trustAs ` $ sceDelegate.trustAs( $ sce.JS, value) ` }
2016-03-28 10:46:51 +00:00
*
* @ param { * } value The value to trustAs .
2016-04-18 12:34:29 +00:00
* @ returns { * } An object that can be passed to { @ link ng . $sce # methods _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
2016-04-18 12:34:29 +00:00
* return value of { @ link ng . $sce # methods _trustAs $sce . trustAs } . )
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name ng . $sce # getTrusted
* @ methodOf ng . $sce
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* Delegates to { @ link ng . $sceDelegate # methods _getTrusted ` $ sceDelegate.getTrusted ` } . As such ,
* takes the result of a { @ link ng . $sce # methods _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 .
2016-04-18 12:34:29 +00:00
* @ param { * } maybeTrusted The result of a prior { @ link ng . $sce # methods _trustAs ` $ sce.trustAs ` }
2016-03-28 10:46:51 +00:00
* call .
* @ returns { * } The value the was originally provided to
2016-04-18 12:34:29 +00:00
* { @ link ng . $sce # methods _trustAs ` $ sce.trustAs ` } if valid in this context .
2016-03-28 10:46:51 +00:00
* Otherwise , throws an exception .
* /
/ * *
* @ ngdoc method
2016-04-18 12:34:29 +00:00
* @ name ng . $sce # getTrustedHtml
* @ methodOf ng . $sce
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.getTrustedHtml(value) ` →
2016-04-18 12:34:29 +00:00
* { @ link ng . $sceDelegate # methods _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
2016-04-18 12:34:29 +00:00
* @ name ng . $sce # getTrustedCss
* @ methodOf ng . $sce
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.getTrustedCss(value) ` →
2016-04-18 12:34:29 +00:00
* { @ link ng . $sceDelegate # methods _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
2016-04-18 12:34:29 +00:00
* @ name ng . $sce # getTrustedUrl
* @ methodOf ng . $sce
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.getTrustedUrl(value) ` →
2016-04-18 12:34:29 +00:00
* { @ link ng . $sceDelegate # methods _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
2016-04-18 12:34:29 +00:00
* @ name ng . $sce # getTrustedResourceUrl
* @ methodOf ng . $sce
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.getTrustedResourceUrl(value) ` →
2016-04-18 12:34:29 +00:00
* { @ link ng . $sceDelegate # methods _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
2016-04-18 12:34:29 +00:00
* @ name ng . $sce # getTrustedJs
* @ methodOf ng . $sce
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.getTrustedJs(value) ` →
2016-04-18 12:34:29 +00:00
* { @ link ng . $sceDelegate # methods _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
2016-04-18 12:34:29 +00:00
* @ name ng . $sce # parseAsHtml
* @ methodOf ng . $sce
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.parseAsHtml(expression string) ` →
2016-04-18 12:34:29 +00:00
* { @ link ng . $sce # methods _parse ` $ 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
2016-04-18 12:34:29 +00:00
* @ name ng . $sce # parseAsCss
* @ methodOf ng . $sce
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.parseAsCss(value) ` →
2016-04-18 12:34:29 +00:00
* { @ link ng . $sce # methods _parse ` $ 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
2016-04-18 12:34:29 +00:00
* @ name ng . $sce # parseAsUrl
* @ methodOf ng . $sce
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.parseAsUrl(value) ` →
2016-04-18 12:34:29 +00:00
* { @ link ng . $sce # methods _parse ` $ 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
2016-04-18 12:34:29 +00:00
* @ name ng . $sce # parseAsResourceUrl
* @ methodOf ng . $sce
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.parseAsResourceUrl(value) ` →
2016-04-18 12:34:29 +00:00
* { @ link ng . $sce # methods _parse ` $ 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
2016-04-18 12:34:29 +00:00
* @ name ng . $sce # parseAsJs
* @ methodOf ng . $sce
2016-03-28 10:46:51 +00:00
*
* @ description
* Shorthand method . ` $ sce.parseAsJs(value) ` →
2016-04-18 12:34:29 +00:00
* { @ link ng . $sce # methods _parse ` $ 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 ;
2016-04-18 12:34:29 +00:00
forEach ( SCE _CONTEXTS , function ( enumValue , name ) {
2016-03-28 10:46:51 +00:00
var lName = lowercase ( name ) ;
2016-04-18 12:34:29 +00:00
sce [ camelCase ( "parse_as_" + lName ) ] = function ( expr ) {
2016-03-28 10:46:51 +00:00
return parse ( enumValue , expr ) ;
} ;
2016-04-18 12:34:29 +00:00
sce [ camelCase ( "get_trusted_" + lName ) ] = function ( value ) {
2016-03-28 10:46:51 +00:00
return getTrusted ( enumValue , value ) ;
} ;
2016-04-18 12:34:29 +00: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 ! ! !
*
2016-04-18 12:34:29 +00:00
* @ name ng . $sniffer
2016-03-28 10:46:51 +00:00
* @ requires $window
* @ requires $document
*
* @ property { boolean } history Does the browser support html5 history api ?
2016-04-18 12:34:29 +00:00
* @ property { boolean } hashchange Does the browser support hashchange event ?
2016-03-28 10:46:51 +00:00
* @ 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 ] || { } ,
2016-04-18 12:34:29 +00:00
documentMode = document . documentMode ,
2016-03-28 10:46:51 +00:00
vendorPrefix ,
2016-04-18 12:34:29 +00:00
vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/ ,
2016-03-28 10:46:51 +00:00
bodyStyle = document . body && document . body . style ,
transitions = false ,
animations = false ,
match ;
if ( bodyStyle ) {
2016-04-18 12:34:29 +00: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 ;
}
}
2016-04-18 12:34:29 +00: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 ) ) ;
2016-04-18 12:34:29 +00:00
if ( android && ( ! transitions || ! animations ) ) {
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
2016-04-18 12:34:29 +00:00
hashchange : 'onhashchange' in $window &&
// IE8 compatible mode lies
( ! documentMode || documentMode > 7 ) ,
2016-03-28 10:46:51 +00:00
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.
2016-04-18 12:34:29 +00:00
if ( event == 'input' && msie == 9 ) 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 ,
2016-04-18 12:34:29 +00:00
transitions : transitions ,
animations : animations ,
android : android ,
msie : msie ,
msieDocumentMode : documentMode
2016-03-28 10:46:51 +00:00
} ;
} ] ;
}
2016-04-18 12:34:29 +00:00
function $TimeoutProvider ( ) {
this . $get = [ '$rootScope' , '$browser' , '$q' , '$exceptionHandler' ,
function ( $rootScope , $browser , $q , $exceptionHandler ) {
var deferreds = { } ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/ * *
* @ ngdoc function
* @ name ng . $timeout
* @ requires $browser
*
* @ 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
* will invoke ` fn ` within the { @ link ng . $rootScope . Scope # methods _$apply $apply } block .
* @ 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 ) {
var deferred = $q . defer ( ) ,
promise = deferred . promise ,
skipApply = ( isDefined ( invokeApply ) && ! invokeApply ) ,
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 ( ) ) ;
} catch ( e ) {
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
}
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $timeout # cancel
* @ methodOf ng . $timeout
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" ) ;
2016-04-18 12:34:29 +00:00
var originUrl = urlResolve ( window . location . href , true ) ;
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
* -- -- -- -- -- -- -- -- -- -- -- -- -- -
2016-04-18 12:34:29 +00:00
* IE >= 8 and <= 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 .
*
2016-04-18 12:34:29 +00:00
* IE7 does not normalize the URL when assigned to an anchor node . ( Apparently , it does , if one
* uses the inner HTML approach to assign the URL as part of an HTML snippet -
* http : //stackoverflow.com/a/472729) However, setting img[src] does normalize the URL.
* Unfortunately , setting img [ src ] to something like "javascript:foo" on IE throws an exception .
* Since the primary usage for normalizing URLs is to sanitize such URLs , we can ' t use that
* method and IE < 8 is unsupported .
*
2016-03-28 10:46:51 +00:00
* 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/
*
2016-04-18 12:34:29 +00:00
* @ 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 "/"
*
* /
2016-04-18 12:34:29 +00:00
function urlResolve ( url , base ) {
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 ) ;
}
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc object
* @ name ng . $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
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
2016-03-28 10:46:51 +00:00
< script >
2016-04-18 12:34:29 +00:00
function Ctrl ( $scope , $window ) {
$scope . greeting = 'Hello, World!' ;
$scope . doGreeting = function ( greeting ) {
2016-03-28 10:46:51 +00:00
$window . alert ( greeting ) ;
2016-04-18 12:34:29 +00:00
} ;
}
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2016-04-18 12:34:29 +00:00
< div ng - controller = "Ctrl" >
< 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 >
2016-04-18 12:34:29 +00:00
< / d o c : s o u r c e >
< doc : scenario >
2016-03-28 10:46:51 +00:00
it ( 'should display the greeting in the input box' , function ( ) {
2016-04-18 12:34:29 +00:00
input ( 'greeting' ) . enter ( '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();
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
function $WindowProvider ( ) {
2016-03-28 10:46:51 +00:00
this . $get = valueFn ( window ) ;
}
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc object
* @ name ng . $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 .
*
2016-04-18 12:34:29 +00:00
* < pre >
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 ;
* } ;
* } ) ;
* }
2016-04-18 12:34:29 +00:00
* < / p r e >
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
*
2016-04-18 12:34:29 +00:00
* < pre >
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 ) ;
* } ) ;
2016-04-18 12:34:29 +00:00
* < / p r e >
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 .
* /
2016-04-18 12:34:29 +00:00
/ * *
* @ ngdoc method
* @ name ng . $filterProvider # register
* @ methodOf ng . $filterProvider
* @ description
* Register filter factory function .
*
* @ param { String } name Name of the filter .
* @ param { function } fn The filter factory function which is injectable .
* /
2016-03-28 10:46:51 +00:00
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $filter
* @ 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
2016-04-18 12:34:29 +00:00
* /
2016-03-28 10:46:51 +00:00
$FilterProvider . $inject = [ '$provide' ] ;
function $FilterProvider ( $provide ) {
var suffix = 'Filter' ;
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . $controllerProvider # register
* @ methodOf ng . $controllerProvider
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 .
* @ 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 ) {
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00:00
* @ name ng . filter : filter
* @ 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 :
*
2016-04-18 12:34:29 +00:00
* - ` string ` : The string is evaluated as an expression and the resulting value is used for substring match against
* the contents of the ` array ` . All strings or objects with string properties in ` array ` that contain this string
* will be returned . 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
2016-04-18 12:34:29 +00:00
* property of the object . That ' s equivalent to the simple substring match with a ` string `
* as described above .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* - ` function(value) ` : 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 :
*
2016-04-18 12:34:29 +00:00
* - ` function(actual, expected) ` :
* The function will be given the object value and the predicate value to compare and
* should return true if the item should be included in filtered result .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* - ` true ` : A shorthand for ` function(actual, expected) { return angular.equals(expected, actual)} ` .
* this is essentially strict comparison of expected and actual .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
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 >
2016-04-18 12:34:29 +00:00
< tr ng - repeat = "friend in friends | filter:search:strict" >
< td > { { friend . name } } < / t d >
< td > { { friend . phone } } < / t d >
2016-03-28 10:46:51 +00:00
< / t r >
< / t a b l e >
2016-04-18 12:34:29 +00:00
< / d o c : s o u r c e >
< doc : scenario >
2016-03-28 10:46:51 +00:00
it ( 'should search across all fields when filtering with a string' , function ( ) {
2016-04-18 12:34:29 +00:00
input ( 'searchText' ) . enter ( 'm' ) ;
expect ( repeater ( '#searchTextResults tr' , 'friend in friends' ) . column ( 'friend.name' ) ) .
toEqual ( [ 'Mary' , 'Mike' , 'Adam' ] ) ;
input ( 'searchText' ) . enter ( '76' ) ;
expect ( repeater ( '#searchTextResults tr' , 'friend in friends' ) . column ( 'friend.name' ) ) .
toEqual ( [ 'John' , 'Julie' ] ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should search in specific fields when filtering with a predicate object' , function ( ) {
2016-04-18 12:34:29 +00:00
input ( 'search.$' ) . enter ( 'i' ) ;
expect ( repeater ( '#searchObjResults tr' , 'friend in friends' ) . column ( 'friend.name' ) ) .
toEqual ( [ 'Mary' , 'Mike' , 'Julie' , 'Juliette' ] ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should use a equal comparison when comparator is true' , function ( ) {
2016-04-18 12:34:29 +00:00
input ( 'search.name' ) . enter ( 'Julie' ) ;
input ( 'strict' ) . check ( ) ;
expect ( repeater ( '#searchObjResults tr' , 'friend in friends' ) . column ( 'friend.name' ) ) .
toEqual ( [ 'Julie' ] ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : s c e n a r i o >
< / d o c : 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 ;
var comparatorType = typeof ( comparator ) ,
predicates = [ ] ;
predicates . check = function ( value ) {
for ( var j = 0 ; j < predicates . length ; j ++ ) {
if ( ! predicates [ j ] ( value ) ) {
return false ;
}
}
return true ;
} ;
if ( comparatorType !== 'function' ) {
if ( comparatorType === 'boolean' && comparator ) {
comparator = function ( obj , text ) {
return angular . equals ( obj , text ) ;
} ;
2016-03-28 10:46:51 +00:00
} else {
2016-04-18 12:34:29 +00:00
comparator = function ( obj , text ) {
text = ( '' + text ) . toLowerCase ( ) ;
return ( '' + obj ) . toLowerCase ( ) . indexOf ( text ) > - 1 ;
} ;
2016-03-28 10:46:51 +00:00
}
}
2016-04-18 12:34:29 +00:00
var search = function ( obj , text ) {
if ( typeof text == 'string' && text . charAt ( 0 ) === '!' ) {
return ! search ( obj , text . substr ( 1 ) ) ;
}
switch ( typeof obj ) {
case "boolean" :
case "number" :
case "string" :
return comparator ( obj , text ) ;
case "object" :
switch ( typeof text ) {
case "object" :
return comparator ( obj , text ) ;
default :
for ( var objKey in obj ) {
if ( objKey . charAt ( 0 ) !== '$' && search ( obj [ objKey ] , text ) ) {
return true ;
}
}
break ;
}
return false ;
case "array" :
for ( var i = 0 ; i < obj . length ; i ++ ) {
if ( search ( obj [ i ] , text ) ) {
return true ;
}
}
return false ;
default :
return false ;
}
} ;
switch ( typeof expression ) {
case "boolean" :
case "number" :
case "string" :
// Set up expression object and fall through
expression = { $ : expression } ;
// jshint -W086
case "object" :
// jshint +W086
for ( var key in expression ) {
( function ( path ) {
if ( typeof expression [ path ] == 'undefined' ) return ;
predicates . push ( function ( value ) {
return search ( path == '$' ? value : getter ( value , path ) , expression [ path ] ) ;
} ) ;
} ) ( key ) ;
}
2016-03-28 10:46:51 +00:00
break ;
2016-04-18 12:34:29 +00:00
case 'function' :
predicates . push ( expression ) ;
2016-03-28 10:46:51 +00:00
break ;
default :
return array ;
}
2016-04-18 12:34:29 +00:00
var filtered = [ ] ;
for ( var j = 0 ; j < array . length ; j ++ ) {
var value = array [ j ] ;
if ( predicates . check ( value ) ) {
filtered . push ( value ) ;
2016-03-28 10:46:51 +00:00
}
}
2016-04-18 12:34:29 +00:00
return filtered ;
2016-03-28 10:46:51 +00:00
} ;
}
/ * *
* @ ngdoc filter
2016-04-18 12:34:29 +00:00
* @ name ng . filter : currency
* @ 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 .
* @ returns { string } Formatted number .
*
*
* @ example
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
2016-03-28 10:46:51 +00:00
< script >
2016-04-18 12:34:29 +00:00
function Ctrl ( $scope ) {
$scope . amount = 1234.56 ;
}
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2016-04-18 12:34:29 +00:00
< div ng - controller = "Ctrl" >
< input type = "number" ng - model = "amount" > < br >
default currency symbol ( $ ) : { { amount | currency } } < br >
custom currency identifier ( USD$ ) : { { amount | currency : "USD$" } }
2016-03-28 10:46:51 +00:00
< / d i v >
2016-04-18 12:34:29 +00:00
< / d o c : s o u r c e >
< doc : scenario >
2016-03-28 10:46:51 +00:00
it ( 'should init with 1234.56' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( binding ( 'amount | currency' ) ) . toBe ( '$1,234.56' ) ;
expect ( binding ( 'amount | currency:"USD$"' ) ) . toBe ( 'USD$1,234.56' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should update' , function ( ) {
2016-04-18 12:34:29 +00:00
input ( 'amount' ) . enter ( '-1234' ) ;
expect ( binding ( 'amount | currency' ) ) . toBe ( '($1,234.00)' ) ;
expect ( binding ( 'amount | currency:"USD$"' ) ) . toBe ( '(USD$1,234.00)' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : s c e n a r i o >
< / d o c : 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 ;
2016-04-18 12:34:29 +00:00
return function ( amount , currencySymbol ) {
if ( isUndefined ( currencySymbol ) ) currencySymbol = formats . CURRENCY _SYM ;
return formatNumber ( amount , formats . PATTERNS [ 1 ] , formats . GROUP _SEP , formats . DECIMAL _SEP , 2 ) .
replace ( /\u00A4/g , currencySymbol ) ;
2016-03-28 10:46:51 +00:00
} ;
}
/ * *
* @ ngdoc filter
2016-04-18 12:34:29 +00:00
* @ name ng . filter : number
* @ function
2016-03-28 10:46:51 +00:00
*
* @ description
* Formats a number as text .
*
* 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
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
2016-03-28 10:46:51 +00:00
< script >
2016-04-18 12:34:29 +00:00
function Ctrl ( $scope ) {
$scope . val = 1234.56789 ;
}
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2016-04-18 12:34:29 +00:00
< div ng - controller = "Ctrl" >
Enter number : < input ng - model = 'val' > < br >
Default formatting : { { val | number } } < br >
No fractions : { { val | number : 0 } } < br >
Negative number : { { - val | number : 4 } }
2016-03-28 10:46:51 +00:00
< / d i v >
2016-04-18 12:34:29 +00:00
< / d o c : s o u r c e >
< doc : scenario >
2016-03-28 10:46:51 +00:00
it ( 'should format numbers' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( binding ( 'val | number' ) ) . toBe ( '1,234.568' ) ;
expect ( binding ( 'val | number:0' ) ) . toBe ( '1,235' ) ;
expect ( binding ( '-val | number:4' ) ) . toBe ( '-1,234.5679' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should update' , function ( ) {
2016-04-18 12:34:29 +00:00
input ( 'val' ) . enter ( '3374.333' ) ;
expect ( binding ( 'val | number' ) ) . toBe ( '3,374.333' ) ;
expect ( binding ( 'val | number:0' ) ) . toBe ( '3,374' ) ;
expect ( binding ( '-val | number:4' ) ) . toBe ( '-3,374.3330' ) ;
} ) ;
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
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 ) {
2016-04-18 12:34:29 +00:00
return 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 ) {
if ( isNaN ( number ) || ! isFinite ( number ) ) return '' ;
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 ) {
numStr = '0' ;
} 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
2016-04-18 12:34:29 +00:00
var pow = Math . pow ( 10 , fractionSize ) ;
number = Math . round ( number * pow ) / pow ;
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 ++ ) {
if ( ( pos - i ) % group === 0 && i !== 0 ) {
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 ++ ) {
if ( ( whole . length - i ) % lgroup === 0 && i !== 0 ) {
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.
while ( fraction . length < fractionSize ) {
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 {
2016-04-18 12:34:29 +00:00
if ( fractionSize > 0 && number > - 1 && number < 1 ) {
formatedText = number . toFixed ( fractionSize ) ;
2016-03-28 10:46:51 +00:00
}
}
2016-04-18 12:34:29 +00:00
parts . push ( isNegative ? pattern . negPre : pattern . posPre ) ;
parts . push ( formatedText ) ;
parts . push ( isNegative ? pattern . negSuf : pattern . posSuf ) ;
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 ;
2016-04-18 12:34:29 +00:00
while ( num . length < digits ) num = '0' + num ;
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 ;
2016-04-18 12:34:29 +00:00
if ( value === 0 && offset == - 12 ) value = 12 ;
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 ;
}
function ampmGetter ( date , formats ) {
return date . getHours ( ) < 12 ? formats . AMPMS [ 0 ] : formats . AMPMS [ 1 ] ;
}
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 ,
2016-04-18 12:34:29 +00:00
Z : timeZoneGetter
2016-03-28 10:46:51 +00:00
} ;
2016-04-18 12:34:29 +00:00
var DATE _FORMATS _SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/ ,
2016-03-28 10:46:51 +00:00
NUMBER _STRING = /^\-?\d+$/ ;
/ * *
* @ ngdoc filter
2016-04-18 12:34:29 +00:00
* @ name ng . filter : date
* @ 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 )
2016-04-18 12:34:29 +00: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 )
2016-04-18 12:34:29 +00:00
* * ` '.sss' or ',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 )
*
* ` 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
2016-04-18 12:34:29 +00: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 )
2016-04-18 12:34:29 +00: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
*
2016-04-18 12:34:29 +00:00
* ` format ` string can contain literal values . These need to be quoted with single quotes ( e . g .
* ` "h 'in the morning'" ` ) . In order to output single quote , use 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
2016-04-18 12:34:29 +00: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 .
* @ returns { string } Formatted string or the input if input is not recognized as date / millis .
*
* @ example
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
2016-03-28 10:46:51 +00:00
< span ng - non - bindable > { { 1288323623006 | date : 'medium' } } < / s p a n > :
2016-04-18 12:34:29 +00:00
{ { 1288323623006 | date : 'medium' } } < br >
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 > :
2016-04-18 12:34:29 +00:00
{ { 1288323623006 | date : 'yyyy-MM-dd HH:mm:ss Z' } } < br >
2016-03-28 10:46:51 +00:00
< span ng - non - bindable > { { 1288323623006 | date : 'MM/dd/yyyy @ h:mma' } } < / s p a n > :
2016-04-18 12:34:29 +00:00
{ { '1288323623006' | date : 'MM/dd/yyyy @ h:mma' } } < br >
< / d o c : s o u r c e >
< doc : scenario >
2016-03-28 10:46:51 +00:00
it ( 'should format date' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( binding ( "1288323623006 | date:'medium'" ) ) .
2016-03-28 10:46:51 +00:00
toMatch ( /Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/ ) ;
2016-04-18 12:34:29 +00:00
expect ( binding ( "1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'" ) ) .
2016-03-28 10:46:51 +00:00
toMatch ( /2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/ ) ;
2016-04-18 12:34:29 +00:00
expect ( binding ( "'1288323623006' | date:'MM/dd/yyyy @ h:mma'" ) ) .
2016-03-28 10:46:51 +00:00
toMatch ( /10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/ ) ;
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : s c e n a r i o >
< / d o c : 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 ] ) ) ;
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 ;
}
2016-04-18 12:34:29 +00:00
return function ( date , format ) {
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 ) ) {
2016-04-18 12:34:29 +00:00
if ( NUMBER _STRING . test ( date ) ) {
date = int ( date ) ;
} else {
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 ;
}
2016-04-18 12:34:29 +00: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 ;
}
}
2016-04-18 12:34:29 +00:00
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
2016-04-18 12:34:29 +00:00
* @ name ng . filter : json
* @ 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 .
* @ returns { string } JSON string .
*
*
2016-04-18 12:34:29 +00:00
* @ example :
< doc : example >
< doc : source >
< pre > { { { 'name' : 'value' } | json } } < / p r e >
< / d o c : s o u r c e >
< doc : scenario >
2016-03-28 10:46:51 +00:00
it ( 'should jsonify filtered objects' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( binding ( "{'name':'value'}" ) ) . toMatch ( /\{\n "name": ?"value"\n}/ ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
2016-03-28 10:46:51 +00:00
*
* /
function jsonFilter ( ) {
2016-04-18 12:34:29 +00:00
return function ( object ) {
return toJson ( object , true ) ;
2016-03-28 10:46:51 +00:00
} ;
}
/ * *
* @ ngdoc filter
2016-04-18 12:34:29 +00:00
* @ name ng . filter : lowercase
* @ function
2016-03-28 10:46:51 +00:00
* @ description
* Converts string to lowercase .
* @ see angular . lowercase
* /
var lowercaseFilter = valueFn ( lowercase ) ;
/ * *
* @ ngdoc filter
2016-04-18 12:34:29 +00:00
* @ name ng . filter : uppercase
* @ function
2016-03-28 10:46:51 +00:00
* @ description
* Converts string to uppercase .
* @ see angular . uppercase
* /
var uppercaseFilter = valueFn ( uppercase ) ;
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . filter : limitTo
* @ 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
2016-04-18 12:34:29 +00:00
* are taken from either the beginning or the end of the source array or string , as specified by
* the value and sign ( positive or negative ) of ` limit ` .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ param { Array | string } input Source array or string 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
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
2016-03-28 10:46:51 +00:00
< script >
2016-04-18 12:34:29 +00:00
function Ctrl ( $scope ) {
$scope . numbers = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ] ;
$scope . letters = "abcdefghi" ;
$scope . numLimit = 3 ;
$scope . letterLimit = 3 ;
}
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2016-04-18 12:34:29 +00:00
< div ng - controller = "Ctrl" >
Limit { { numbers } } to : < input type = "integer" ng - model = "numLimit" >
2016-03-28 10:46:51 +00:00
< p > Output numbers : { { numbers | limitTo : numLimit } } < / p >
2016-04-18 12:34:29 +00:00
Limit { { letters } } to : < input type = "integer" ng - model = "letterLimit" >
2016-03-28 10:46:51 +00:00
< p > Output letters : { { letters | limitTo : letterLimit } } < / p >
< / d i v >
2016-04-18 12:34:29 +00:00
< / d o c : s o u r c e >
< doc : scenario >
2016-03-28 10:46:51 +00:00
it ( 'should limit the number array to first three items' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( element ( '.doc-example-live input[ng-model=numLimit]' ) . val ( ) ) . toBe ( '3' ) ;
expect ( element ( '.doc-example-live input[ng-model=letterLimit]' ) . val ( ) ) . toBe ( '3' ) ;
expect ( binding ( 'numbers | limitTo:numLimit' ) ) . toEqual ( '[1,2,3]' ) ;
expect ( binding ( 'letters | limitTo:letterLimit' ) ) . toEqual ( 'abc' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
it ( 'should update the output when -3 is entered' , function ( ) {
input ( 'numLimit' ) . enter ( - 3 ) ;
input ( 'letterLimit' ) . enter ( - 3 ) ;
expect ( binding ( 'numbers | limitTo:numLimit' ) ) . toEqual ( '[7,8,9]' ) ;
expect ( binding ( 'letters | limitTo:letterLimit' ) ) . toEqual ( 'ghi' ) ;
} ) ;
2016-03-28 10:46:51 +00:00
it ( 'should not exceed the maximum size of input array' , function ( ) {
2016-04-18 12:34:29 +00:00
input ( 'numLimit' ) . enter ( 100 ) ;
input ( 'letterLimit' ) . enter ( 100 ) ;
expect ( binding ( 'numbers | limitTo:numLimit' ) ) . toEqual ( '[1,2,3,4,5,6,7,8,9]' ) ;
expect ( binding ( 'letters | limitTo:letterLimit' ) ) . toEqual ( 'abcdefghi' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
* /
function limitToFilter ( ) {
return function ( input , limit ) {
if ( ! isArray ( input ) && ! isString ( input ) ) return input ;
2016-05-18 00:10:50 +00:00
2016-04-18 12:34:29 +00:00
limit = int ( limit ) ;
if ( isString ( input ) ) {
//NaN check on limit
if ( limit ) {
return limit >= 0 ? input . slice ( 0 , limit ) : input . slice ( limit , input . length ) ;
} else {
return "" ;
}
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
var out = [ ] ,
i , n ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
// if abs(limit) exceeds maximum length, trim it
if ( limit > input . length )
limit = input . length ;
else if ( limit < - input . length )
limit = - input . length ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( limit > 0 ) {
i = 0 ;
n = limit ;
2016-03-28 10:46:51 +00:00
} else {
2016-04-18 12:34:29 +00:00
i = input . length + limit ;
n = input . length ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
for ( ; i < n ; i ++ ) {
out . push ( input [ i ] ) ;
}
return out ;
2016-03-28 10:46:51 +00:00
} ;
}
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . filter : orderBy
* @ function
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* Orders a specified ` array ` by the ` expression ` predicate .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ param { Array } array The array to sort .
* @ 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
2016-04-18 12:34:29 +00:00
* ` < ` , ` = ` , ` > ` operator .
* - ` string ` : An Angular expression which evaluates to an object to order by , such as 'name'
* to sort by a property called 'name' . Optionally prefixed with ` + ` or ` - ` to control
* ascending or descending sort order ( for example , + name or - name ) .
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 .
*
2016-04-18 12:34:29 +00:00
* @ param { boolean = } reverse Reverse the order the array .
2016-03-28 10:46:51 +00:00
* @ returns { Array } Sorted copy of the source array .
*
* @ example
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
< script >
function Ctrl ( $scope ) {
2016-03-28 10:46:51 +00:00
$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 } ,
2016-04-18 12:34:29 +00:00
{ name : 'Julie' , phone : '555-8765' , age : 29 } ]
$scope . predicate = '-age' ;
}
< / s c r i p t >
< div ng - controller = "Ctrl" >
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 >
2016-04-18 12:34:29 +00:00
< / d o c : s o u r c e >
< doc : scenario >
it ( 'should be reverse ordered by aged' , function ( ) {
expect ( binding ( 'predicate' ) ) . toBe ( '-age' ) ;
expect ( repeater ( 'table.friend' , 'friend in friends' ) . column ( 'friend.age' ) ) .
toEqual ( [ '35' , '29' , '21' , '19' , '10' ] ) ;
expect ( repeater ( 'table.friend' , 'friend in friends' ) . column ( 'friend.name' ) ) .
toEqual ( [ 'Adam' , 'Julie' , 'Mike' , 'Mary' , 'John' ] ) ;
} ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
it ( 'should reorder the table when user selects different predicate' , function ( ) {
element ( '.doc-example-live a:contains("Name")' ) . click ( ) ;
expect ( repeater ( 'table.friend' , 'friend in friends' ) . column ( 'friend.name' ) ) .
toEqual ( [ 'Adam' , 'John' , 'Julie' , 'Mary' , 'Mike' ] ) ;
expect ( repeater ( 'table.friend' , 'friend in friends' ) . column ( 'friend.age' ) ) .
toEqual ( [ '35' , '10' , '29' , '19' , '21' ] ) ;
element ( '.doc-example-live a:contains("Phone")' ) . click ( ) ;
expect ( repeater ( 'table.friend' , 'friend in friends' ) . column ( 'friend.phone' ) ) .
toEqual ( [ '555-9876' , '555-8765' , '555-5678' , '555-4321' , '555-1212' ] ) ;
expect ( repeater ( 'table.friend' , 'friend in friends' ) . column ( 'friend.name' ) ) .
toEqual ( [ 'Mary' , 'Julie' , 'Adam' , 'Mike' , 'John' ] ) ;
} ) ;
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
orderByFilter . $inject = [ '$parse' ] ;
2016-04-18 12:34:29 +00:00
function orderByFilter ( $parse ) {
2016-03-28 10:46:51 +00:00
return function ( array , sortPredicate , reverseOrder ) {
2016-04-18 12:34:29 +00:00
if ( ! isArray ( array ) ) return array ;
if ( ! sortPredicate ) return array ;
sortPredicate = isArray ( sortPredicate ) ? sortPredicate : [ sortPredicate ] ;
sortPredicate = map ( sortPredicate , function ( predicate ) {
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 ) ;
}
2016-04-18 12:34:29 +00:00
get = $parse ( predicate ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
return reverseComparator ( function ( a , b ) {
return compare ( get ( a ) , get ( b ) ) ;
} , descending ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
var arrayCopy = [ ] ;
for ( var i = 0 ; i < array . length ; i ++ ) { arrayCopy . push ( array [ i ] ) ; }
return arrayCopy . sort ( reverseComparator ( comparator , reverseOrder ) ) ;
function comparator ( o1 , o2 ) {
for ( var i = 0 ; i < sortPredicate . length ; i ++ ) {
var comp = sortPredicate [ i ] ( o1 , o2 ) ;
if ( comp !== 0 ) return comp ;
}
return 0 ;
}
function reverseComparator ( comp , descending ) {
return toBoolean ( descending )
? function ( a , b ) { return comp ( b , a ) ; }
: comp ;
}
function compare ( v1 , v2 ) {
var t1 = typeof v1 ;
var t2 = typeof v2 ;
if ( t1 == t2 ) {
if ( t1 == "string" ) {
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
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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 ) {
2016-04-18 12:34:29 +00:00
if ( msie <= 8 ) {
// turn <a href ng-click="..">link</a> into a stylable link in IE
// but only if it doesn't have name attribute, in which case it's an anchor
if ( ! attr . href && ! attr . name ) {
attr . $set ( 'href' , '' ) ;
}
// add a comment node to anchors to workaround IE bug that causes element content to be reset
// to new attribute content if attribute is updated with value containing @ and element also
// contains value with @
// see issue #1949
element . append ( document . createComment ( 'IE fix' ) ) ;
}
if ( ! attr . href && ! attr . name ) {
return function ( scope , element ) {
element . on ( 'click' , function ( event ) {
2016-03-28 10:46:51 +00:00
// if we have no href url, then don't navigate anywhere.
2016-04-18 12:34:29 +00:00
if ( ! element . attr ( 'href' ) ) {
2016-03-28 10:46:51 +00:00
event . preventDefault ( ) ;
}
} ) ;
} ;
}
}
} ) ;
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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
2016-04-18 12:34:29 +00: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 :
2016-04-18 12:34:29 +00:00
* < pre >
* < a href = "http://www.gravatar.com/avatar/{{hash}}" / >
* < / p r e >
2016-03-28 10:46:51 +00:00
*
* The correct way to write it :
2016-04-18 12:34:29 +00:00
* < pre >
* < a ng - href = "http://www.gravatar.com/avatar/{{hash}}" / >
* < / p r e >
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 :
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
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 )
2016-04-18 12:34:29 +00:00
< / d o c : s o u r c e >
< doc : scenario >
2016-03-28 10:46:51 +00:00
it ( 'should execute ng-click but not reload when href without value' , function ( ) {
2016-04-18 12:34:29 +00:00
element ( '#link-1' ) . click ( ) ;
expect ( input ( 'value' ) . val ( ) ) . toEqual ( '1' ) ;
expect ( element ( '#link-1' ) . attr ( 'href' ) ) . toBe ( "" ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should execute ng-click but not reload when href empty string' , function ( ) {
2016-04-18 12:34:29 +00:00
element ( '#link-2' ) . click ( ) ;
expect ( input ( 'value' ) . val ( ) ) . toEqual ( '2' ) ;
expect ( element ( '#link-2' ) . attr ( 'href' ) ) . toBe ( "" ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should execute ng-click and change url when ng-href specified' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( element ( '#link-3' ) . attr ( 'href' ) ) . toBe ( "/123" ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
element ( '#link-3' ) . click ( ) ;
expect ( browser ( ) . window ( ) . path ( ) ) . toEqual ( '/123' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should execute ng-click but not reload when href empty string and name specified' , function ( ) {
2016-04-18 12:34:29 +00:00
element ( '#link-4' ) . click ( ) ;
expect ( input ( 'value' ) . val ( ) ) . toEqual ( '4' ) ;
expect ( element ( '#link-4' ) . attr ( '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 ( ) {
2016-04-18 12:34:29 +00:00
element ( '#link-5' ) . click ( ) ;
expect ( input ( 'value' ) . val ( ) ) . toEqual ( '5' ) ;
expect ( element ( '#link-5' ) . attr ( 'href' ) ) . toBe ( undefined ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should only change url when only ng-href' , function ( ) {
2016-04-18 12:34:29 +00:00
input ( 'value' ) . enter ( '6' ) ;
expect ( element ( '#link-6' ) . attr ( 'href' ) ) . toBe ( '6' ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
element ( '#link-6' ) . click ( ) ;
expect ( browser ( ) . location ( ) . url ( ) ) . toEqual ( '/6' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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 :
2016-04-18 12:34:29 +00:00
* < pre >
* < img src = "http://www.gravatar.com/avatar/{{hash}}" / >
* < / p r e >
2016-03-28 10:46:51 +00:00
*
* The correct way to write it :
2016-04-18 12:34:29 +00:00
* < pre >
* < img ng - src = "http://www.gravatar.com/avatar/{{hash}}" / >
* < / p r e >
2016-03-28 10:46:51 +00:00
*
* @ element IMG
* @ param { template } ngSrc any string which can contain ` {{}} ` markup .
* /
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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 :
2016-04-18 12:34:29 +00:00
* < pre >
* < img srcset = "http://www.gravatar.com/avatar/{{hash}} 2x" / >
* < / p r e >
2016-03-28 10:46:51 +00:00
*
* The correct way to write it :
2016-04-18 12:34:29 +00:00
* < pre >
* < img ng - srcset = "http://www.gravatar.com/avatar/{{hash}} 2x" / >
* < / p r e >
2016-03-28 10:46:51 +00:00
*
* @ element IMG
* @ param { template } ngSrcset any string which can contain ` {{}} ` markup .
* /
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : ngDisabled
2016-03-28 10:46:51 +00:00
* @ restrict A
* @ priority 100
*
* @ description
*
2016-04-18 12:34:29 +00:00
* The following markup will make the button enabled on Chrome / Firefox but not on IE8 and older IEs :
* < pre >
* < div ng - init = "scope = { isDisabled: false }" >
* < button disabled = "{{scope.isDisabled}}" > Disabled < / b u t t o n >
* < / d i v >
* < / p r e >
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* 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 . )
* 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 ` ngDisabled ` directive solves this problem for the ` disabled ` 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
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
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 >
2016-04-18 12:34:29 +00:00
< / d o c : s o u r c e >
< doc : scenario >
2016-03-28 10:46:51 +00:00
it ( 'should toggle button' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( element ( '.doc-example-live :button' ) . prop ( 'disabled' ) ) . toBeFalsy ( ) ;
input ( 'checked' ) . check ( ) ;
expect ( element ( '.doc-example-live :button' ) . prop ( 'disabled' ) ) . toBeTruthy ( ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : s c e n a r i o >
< / d o c : 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 ,
2016-04-18 12:34:29 +00:00
* then special attribute "disabled" will be set on the element
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
Check me to check both : < input type = "checkbox" ng - model = "master" > < br / >
< input id = "checkSlave" type = "checkbox" ng - checked = "master" >
< / d o c : s o u r c e >
< doc : scenario >
2016-03-28 10:46:51 +00:00
it ( 'should check both checkBoxes' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( element ( '.doc-example-live #checkSlave' ) . prop ( 'checked' ) ) . toBeFalsy ( ) ;
input ( 'master' ) . check ( ) ;
expect ( element ( '.doc-example-live #checkSlave' ) . prop ( 'checked' ) ) . toBeTruthy ( ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : s c e n a r i o >
< / d o c : 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
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
Check me to make text readonly : < input type = "checkbox" ng - model = "checked" > < br / >
< input type = "text" ng - readonly = "checked" value = "I'm Angular" / >
< / d o c : s o u r c e >
< doc : scenario >
2016-03-28 10:46:51 +00:00
it ( 'should toggle readonly attr' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( element ( '.doc-example-live :text' ) . prop ( 'readonly' ) ) . toBeFalsy ( ) ;
input ( 'checked' ) . check ( ) ;
expect ( element ( '.doc-example-live :text' ) . prop ( 'readonly' ) ) . toBeTruthy ( ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : s c e n a r i o >
< / d o c : 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
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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 .
* The ` ngSelected ` directive solves this problem for the ` selected ` atttribute .
* 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
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
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 >
2016-04-18 12:34:29 +00:00
< / d o c : s o u r c e >
< doc : scenario >
2016-03-28 10:46:51 +00:00
it ( 'should select Greetings!' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( element ( '.doc-example-live #greet' ) . prop ( 'selected' ) ) . toBeFalsy ( ) ;
input ( 'selected' ) . check ( ) ;
expect ( element ( '.doc-example-live #greet' ) . prop ( 'selected' ) ) . toBeTruthy ( ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : s c e n a r i o >
< / d o c : 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
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
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 >
2016-04-18 12:34:29 +00:00
< / d o c : s o u r c e >
< doc : scenario >
2016-03-28 10:46:51 +00:00
it ( 'should toggle open' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( element ( '#details' ) . prop ( 'open' ) ) . toBeFalsy ( ) ;
input ( 'open' ) . check ( ) ;
expect ( element ( '#details' ) . prop ( 'open' ) ) . toBeTruthy ( ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : s c e n a r i o >
< / d o c : 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 {
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
} ) ;
}
} ;
} ;
} ) ;
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 ) {
attr . $observe ( normalized , function ( value ) {
2016-04-18 12:34:29 +00:00
if ( ! value )
return ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
attr . $set ( attrName , 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.
2016-04-18 12:34:29 +00:00
if ( msie ) element . prop ( attrName , attr [ attrName ] ) ;
2016-03-28 10:46:51 +00:00
} ) ;
}
} ;
} ;
} ) ;
2016-04-18 12:34:29 +00:00
/* global -nullFormCtrl */
2016-03-28 10:46:51 +00:00
var nullFormCtrl = {
$addControl : noop ,
$removeControl : noop ,
$setValidity : noop ,
$setDirty : noop ,
2016-04-18 12:34:29 +00:00
$setPristine : noop
} ;
2016-03-28 10:46:51 +00:00
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc object
* @ name ng . directive : 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 .
*
2016-04-18 12:34:29 +00:00
* @ property { Object } $error Is an object hash , containing references to all invalid controls or
* forms , where :
2016-03-28 10:46:51 +00:00
*
* - keys are validation tokens ( error names ) ,
2016-04-18 12:34:29 +00:00
* - values are arrays of controls or forms that are invalid 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 `
2016-05-18 00:10:50 +00:00
*
2016-03-28 10:46:51 +00:00
* @ description
2016-04-18 12:34:29 +00:00
* ` FormController ` keeps track of all its controls and nested forms as well as 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
2016-04-18 12:34:29 +00:00
FormController . $inject = [ '$element' , '$attrs' , '$scope' ] ;
function FormController ( element , attrs ) {
2016-03-28 10:46:51 +00:00
var form = this ,
2016-04-18 12:34:29 +00:00
parentForm = element . parent ( ) . controller ( 'form' ) || nullFormCtrl ,
invalidCount = 0 , // used to easily determine if we are valid
errors = form . $error = { } ,
2016-03-28 10:46:51 +00:00
controls = [ ] ;
// init state
2016-04-18 12:34:29 +00:00
form . $name = attrs . name || attrs . ngForm ;
2016-03-28 10:46:51 +00:00
form . $dirty = false ;
form . $pristine = true ;
form . $valid = true ;
form . $invalid = false ;
2016-04-18 12:34:29 +00:00
parentForm . $addControl ( form ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
// Setup initial state of the control
element . addClass ( PRISTINE _CLASS ) ;
toggleValidCss ( true ) ;
// convenience method for easy toggling of classes
function toggleValidCss ( isValid , validationErrorKey ) {
validationErrorKey = validationErrorKey ? '-' + snake _case ( validationErrorKey , '-' ) : '' ;
element .
removeClass ( ( isValid ? INVALID _CLASS : VALID _CLASS ) + validationErrorKey ) .
addClass ( ( isValid ? VALID _CLASS : INVALID _CLASS ) + validationErrorKey ) ;
}
2016-03-28 10:46:51 +00:00
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . directive : form . FormController # $addControl
* @ methodOf ng . directive : form . FormController
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 ;
}
} ;
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . directive : form . FormController # $removeControl
* @ methodOf ng . directive : form . FormController
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 ] ;
}
2016-04-18 12:34:29 +00:00
forEach ( errors , function ( queue , validationToken ) {
form . $setValidity ( validationToken , true , control ) ;
2016-03-28 10:46:51 +00:00
} ) ;
arrayRemove ( controls , control ) ;
} ;
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . directive : form . FormController # $setValidity
* @ methodOf ng . directive : form . FormController
2016-03-28 10:46:51 +00:00
*
* @ description
* Sets the validity of a form control .
*
* This method will also propagate to parent forms .
* /
2016-04-18 12:34:29 +00:00
form . $setValidity = function ( validationToken , isValid , control ) {
var queue = errors [ validationToken ] ;
if ( isValid ) {
if ( queue ) {
arrayRemove ( queue , control ) ;
if ( ! queue . length ) {
invalidCount -- ;
if ( ! invalidCount ) {
toggleValidCss ( isValid ) ;
form . $valid = true ;
form . $invalid = false ;
}
errors [ validationToken ] = false ;
toggleValidCss ( true , validationToken ) ;
parentForm . $setValidity ( validationToken , true , form ) ;
2016-03-28 10:46:51 +00:00
}
}
2016-04-18 12:34:29 +00:00
} else {
if ( ! invalidCount ) {
toggleValidCss ( isValid ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
if ( queue ) {
if ( includes ( queue , control ) ) return ;
} else {
errors [ validationToken ] = queue = [ ] ;
invalidCount ++ ;
toggleValidCss ( false , validationToken ) ;
parentForm . $setValidity ( validationToken , false , form ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
queue . push ( control ) ;
form . $valid = false ;
form . $invalid = true ;
}
} ;
2016-03-28 10:46:51 +00:00
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . directive : form . FormController # $setDirty
* @ methodOf ng . directive : form . FormController
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 ( ) {
2016-04-18 12:34:29 +00:00
element . removeClass ( PRISTINE _CLASS ) . addClass ( 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
} ;
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc function
* @ name ng . directive : form . FormController # $setPristine
* @ methodOf ng . directive : form . FormController
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 .
* /
2016-04-18 12:34:29 +00:00
form . $setPristine = function ( ) {
element . removeClass ( DIRTY _CLASS ) . addClass ( PRISTINE _CLASS ) ;
2016-03-28 10:46:51 +00:00
form . $dirty = false ;
form . $pristine = true ;
forEach ( controls , function ( control ) {
control . $setPristine ( ) ;
} ) ;
} ;
}
2016-04-18 12:34:29 +00:00
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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 .
*
* @ param { string = } ngForm | name Name of the form . If specified , the form controller will be published into
* related scope , under this name .
*
* /
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : form
2016-03-28 10:46:51 +00:00
* @ restrict E
*
* @ description
* Directive that instantiates
2016-04-18 12:34:29 +00:00
* { @ link ng . directive : 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 ` }
*
2016-04-18 12:34:29 +00: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 .
*
*
* # 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 ` )
*
2016-04-18 12:34:29 +00:00
* @ 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
*
* @ example
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
2016-03-28 10:46:51 +00:00
< script >
2016-04-18 12:34:29 +00:00
function Ctrl ( $scope ) {
$scope . userType = 'guest' ;
}
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2016-04-18 12:34:29 +00:00
< form name = "myForm" ng - controller = "Ctrl" >
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 >
2016-04-18 12:34:29 +00:00
< tt > userType = { { userType } } < / t t > < b r >
< tt > myForm . input . $valid = { { myForm . input . $valid } } < / t t > < b r >
< tt > myForm . input . $error = { { myForm . input . $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 >
2016-03-28 10:46:51 +00:00
< / f o r m >
2016-04-18 12:34:29 +00:00
< / d o c : s o u r c e >
< doc : scenario >
2016-03-28 10:46:51 +00:00
it ( 'should initialize to model' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( binding ( 'userType' ) ) . toEqual ( 'guest' ) ;
expect ( binding ( 'myForm.input.$valid' ) ) . toEqual ( 'true' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should be invalid if empty' , function ( ) {
2016-04-18 12:34:29 +00:00
input ( 'userType' ) . enter ( '' ) ;
expect ( binding ( 'userType' ) ) . toEqual ( '' ) ;
expect ( binding ( 'myForm.input.$valid' ) ) . toEqual ( 'false' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
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 ,
2016-04-18 12:34:29 +00:00
compile : function ( ) {
2016-03-28 10:46:51 +00:00
return {
2016-04-18 12:34:29 +00:00
pre : function ( scope , formElement , attr , controller ) {
if ( ! attr . action ) {
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.
2016-04-18 12:34:29 +00:00
var preventDefaultListener = function ( event ) {
event . preventDefault
? event . preventDefault ( )
: event . returnValue = false ; // IE
2016-03-28 10:46:51 +00:00
} ;
2016-04-18 12:34:29 +00:00
addEventListenerFn ( formElement [ 0 ] , 'submit' , preventDefaultListener ) ;
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 ( ) {
2016-04-18 12:34:29 +00:00
removeEventListenerFn ( formElement [ 0 ] , 'submit' , preventDefaultListener ) ;
2016-03-28 10:46:51 +00:00
} , 0 , false ) ;
} ) ;
}
2016-04-18 12:34:29 +00:00
var parentFormCtrl = formElement . parent ( ) . controller ( 'form' ) ,
alias = attr . name || attr . ngForm ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( alias ) {
setter ( scope , alias , controller , alias ) ;
}
if ( parentFormCtrl ) {
formElement . on ( '$destroy' , function ( ) {
parentFormCtrl . $removeControl ( controller ) ;
if ( alias ) {
setter ( scope , alias , undefined , alias ) ;
}
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 ) ;
2016-04-18 12:34:29 +00:00
/ * g l o b a l
- VALID _CLASS ,
- INVALID _CLASS ,
- PRISTINE _CLASS ,
- DIRTY _CLASS
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
var URL _REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/ ;
var EMAIL _REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/ ;
var NUMBER _REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/ ;
2016-03-28 10:46:51 +00:00
var inputType = {
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc inputType
* @ name ng . directive : input . text
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* Standard HTML text input with angular data binding .
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
2016-04-18 12:34:29 +00:00
* maxlength .
* @ 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 .
* @ param { boolean = } [ ngTrim = true ] If set to false Angular will not automatically trim the input .
*
* @ example
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
2016-03-28 10:46:51 +00:00
< script >
2016-04-18 12:34:29 +00:00
function Ctrl ( $scope ) {
$scope . text = 'guest' ;
$scope . word = /^\s*\w*\s*$/ ;
}
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2016-04-18 12:34:29 +00:00
< form name = "myForm" ng - controller = "Ctrl" >
Single word : < input type = "text" name = "input" ng - model = "text"
ng - pattern = "word" required ng - trim = "false" >
< 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 >
< tt > text = { { 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 >
2016-04-18 12:34:29 +00:00
< / d o c : s o u r c e >
< doc : scenario >
2016-03-28 10:46:51 +00:00
it ( 'should initialize to model' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( binding ( 'text' ) ) . toEqual ( 'guest' ) ;
expect ( binding ( 'myForm.input.$valid' ) ) . toEqual ( 'true' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should be invalid if empty' , function ( ) {
2016-04-18 12:34:29 +00:00
input ( 'text' ) . enter ( '' ) ;
expect ( binding ( 'text' ) ) . toEqual ( '' ) ;
expect ( binding ( 'myForm.input.$valid' ) ) . toEqual ( 'false' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should be invalid if multi word' , function ( ) {
2016-04-18 12:34:29 +00:00
input ( 'text' ) . enter ( 'hello world' ) ;
expect ( binding ( 'myForm.input.$valid' ) ) . toEqual ( 'false' ) ;
} ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
it ( 'should not be trimmed' , function ( ) {
input ( 'text' ) . enter ( 'untrimmed ' ) ;
expect ( binding ( 'text' ) ) . toEqual ( 'untrimmed ' ) ;
expect ( binding ( 'myForm.input.$valid' ) ) . toEqual ( 'true' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
'text' : textInputType ,
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc inputType
* @ name ng . directive : 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
*
* @ 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
* maxlength .
* @ 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 .
*
* @ example
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
< script >
function Ctrl ( $scope ) {
$scope . value = 12 ;
}
< / s c r i p t >
< form name = "myForm" ng - controller = "Ctrl" >
Number : < input type = "number" name = "input" ng - model = "value"
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 >
< tt > value = { { value } } < /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 >
< / d o c : s o u r c e >
< doc : scenario >
it ( 'should initialize to model' , function ( ) {
expect ( binding ( 'value' ) ) . toEqual ( '12' ) ;
expect ( binding ( 'myForm.input.$valid' ) ) . toEqual ( 'true' ) ;
} ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
it ( 'should be invalid if empty' , function ( ) {
input ( 'value' ) . enter ( '' ) ;
expect ( binding ( 'value' ) ) . toEqual ( '' ) ;
expect ( binding ( 'myForm.input.$valid' ) ) . toEqual ( 'false' ) ;
} ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
it ( 'should be invalid if over max' , function ( ) {
input ( 'value' ) . enter ( '123' ) ;
expect ( binding ( 'value' ) ) . toEqual ( '' ) ;
expect ( binding ( 'myForm.input.$valid' ) ) . toEqual ( 'false' ) ;
} ) ;
< / d o c : s c e n a r i o >
< / d o c : 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
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc inputType
* @ name ng . directive : 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
*
* @ 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
2016-04-18 12:34:29 +00:00
* maxlength .
* @ 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 .
*
* @ example
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
2016-03-28 10:46:51 +00:00
< script >
2016-04-18 12:34:29 +00:00
function Ctrl ( $scope ) {
$scope . text = 'http://google.com' ;
}
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2016-04-18 12:34:29 +00:00
< form name = "myForm" ng - controller = "Ctrl" >
URL : < input type = "url" name = "input" ng - model = "text" required >
< 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 >
< tt > text = { { 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 >
2016-04-18 12:34:29 +00:00
< / d o c : s o u r c e >
< doc : scenario >
2016-03-28 10:46:51 +00:00
it ( 'should initialize to model' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( binding ( 'text' ) ) . toEqual ( 'http://google.com' ) ;
expect ( binding ( 'myForm.input.$valid' ) ) . toEqual ( 'true' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should be invalid if empty' , function ( ) {
2016-04-18 12:34:29 +00:00
input ( 'text' ) . enter ( '' ) ;
expect ( binding ( 'text' ) ) . toEqual ( '' ) ;
expect ( binding ( 'myForm.input.$valid' ) ) . toEqual ( 'false' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should be invalid if not url' , function ( ) {
2016-04-18 12:34:29 +00:00
input ( 'text' ) . enter ( 'xxx' ) ;
expect ( binding ( 'myForm.input.$valid' ) ) . toEqual ( 'false' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
'url' : urlInputType ,
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc inputType
* @ name ng . directive : 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 .
*
* @ 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
2016-04-18 12:34:29 +00:00
* maxlength .
* @ 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 .
*
* @ example
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
2016-03-28 10:46:51 +00:00
< script >
2016-04-18 12:34:29 +00:00
function Ctrl ( $scope ) {
$scope . text = 'me@example.com' ;
}
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2016-04-18 12:34:29 +00:00
< form name = "myForm" ng - controller = "Ctrl" >
Email : < input type = "email" name = "input" ng - model = "text" required >
< 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 >
< tt > text = { { 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 >
2016-04-18 12:34:29 +00:00
< / d o c : s o u r c e >
< doc : scenario >
2016-03-28 10:46:51 +00:00
it ( 'should initialize to model' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( binding ( 'text' ) ) . toEqual ( 'me@example.com' ) ;
expect ( binding ( 'myForm.input.$valid' ) ) . toEqual ( 'true' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should be invalid if empty' , function ( ) {
2016-04-18 12:34:29 +00:00
input ( 'text' ) . enter ( '' ) ;
expect ( binding ( 'text' ) ) . toEqual ( '' ) ;
expect ( binding ( 'myForm.input.$valid' ) ) . toEqual ( 'false' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should be invalid if not email' , function ( ) {
2016-04-18 12:34:29 +00:00
input ( 'text' ) . enter ( 'xxx' ) ;
expect ( binding ( 'myForm.input.$valid' ) ) . toEqual ( 'false' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
'email' : emailInputType ,
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc inputType
* @ name ng . directive : 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 .
*
* @ example
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
2016-03-28 10:46:51 +00:00
< script >
2016-04-18 12:34:29 +00:00
function Ctrl ( $scope ) {
$scope . color = 'blue' ;
}
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2016-04-18 12:34:29 +00:00
< form name = "myForm" ng - controller = "Ctrl" >
< input type = "radio" ng - model = "color" value = "red" > Red < br / >
< input type = "radio" ng - model = "color" value = "green" > Green < br / >
< input type = "radio" ng - model = "color" value = "blue" > Blue < br / >
< tt > color = { { color } } < /tt><br/ >
2016-03-28 10:46:51 +00:00
< / f o r m >
2016-04-18 12:34:29 +00:00
< / d o c : s o u r c e >
< doc : scenario >
2016-03-28 10:46:51 +00:00
it ( 'should change state' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( binding ( 'color' ) ) . toEqual ( 'blue' ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
input ( 'color' ) . select ( 'red' ) ;
expect ( binding ( 'color' ) ) . toEqual ( 'red' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
'radio' : radioInputType ,
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc inputType
* @ name ng . directive : 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 .
2016-04-18 12:34:29 +00:00
* @ param { string = } ngTrueValue The value to which the expression should be set when selected .
* @ param { string = } 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
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
2016-03-28 10:46:51 +00:00
< script >
2016-04-18 12:34:29 +00:00
function Ctrl ( $scope ) {
$scope . value1 = true ;
$scope . value2 = 'YES'
}
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2016-04-18 12:34:29 +00:00
< form name = "myForm" ng - controller = "Ctrl" >
Value1 : < input type = "checkbox" ng - model = "value1" > < br / >
Value2 : < input type = "checkbox" ng - model = "value2"
ng - true - value = "YES" ng - false - value = "NO" > < br / >
< tt > value1 = { { value1 } } < /tt><br/ >
< tt > value2 = { { value2 } } < /tt><br/ >
2016-03-28 10:46:51 +00:00
< / f o r m >
2016-04-18 12:34:29 +00:00
< / d o c : s o u r c e >
< doc : scenario >
2016-03-28 10:46:51 +00:00
it ( 'should change state' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( binding ( 'value1' ) ) . toEqual ( 'true' ) ;
expect ( binding ( 'value2' ) ) . toEqual ( 'YES' ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
input ( 'value1' ) . check ( ) ;
input ( 'value2' ) . check ( ) ;
expect ( binding ( 'value1' ) ) . toEqual ( 'false' ) ;
expect ( binding ( 'value2' ) ) . toEqual ( 'NO' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
'checkbox' : checkboxInputType ,
'hidden' : noop ,
'button' : noop ,
'submit' : noop ,
2016-04-18 12:34:29 +00:00
'reset' : noop
2016-03-28 10:46:51 +00:00
} ;
2016-04-18 12:34:29 +00:00
// A helper function to call $setValidity and return the value / undefined,
// a pattern that is repeated a lot in the input validation logic.
function validate ( ctrl , validatorName , validity , value ) {
ctrl . $setValidity ( validatorName , validity ) ;
return validity ? value : undefined ;
2016-03-28 10:46:51 +00:00
}
function textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
// 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 ;
} ) ;
}
2016-04-18 12:34:29 +00:00
var listener = function ( ) {
2016-03-28 10:46:51 +00:00
if ( composing ) return ;
2016-04-18 12:34:29 +00:00
var value = element . val ( ) ;
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
2016-04-18 12:34:29 +00:00
// e.g. <input ng-model="foo" ng-trim="false">
if ( toBoolean ( attr . ngTrim || 'T' ) ) {
2016-03-28 10:46:51 +00:00
value = trim ( value ) ;
}
2016-04-18 12:34:29 +00:00
if ( ctrl . $viewValue !== value ) {
if ( scope . $$phase ) {
ctrl . $setViewValue ( value ) ;
} else {
scope . $apply ( function ( ) {
ctrl . $setViewValue ( value ) ;
} ) ;
}
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 ;
var deferListener = function ( ) {
2016-03-28 10:46:51 +00:00
if ( ! timeout ) {
timeout = $browser . defer ( function ( ) {
2016-04-18 12:34:29 +00:00
listener ( ) ;
2016-03-28 10:46:51 +00:00
timeout = null ;
} ) ;
}
} ;
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 ;
2016-04-18 12:34:29 +00:00
deferListener ( ) ;
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
} ;
2016-04-18 12:34:29 +00:00
// pattern validator
var pattern = attr . ngPattern ,
patternValidator ,
match ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( pattern ) {
var validateRegex = function ( regexp , value ) {
return validate ( ctrl , 'pattern' , ctrl . $isEmpty ( value ) || regexp . test ( value ) , value ) ;
} ;
match = pattern . match ( /^\/(.*)\/([gim]*)$/ ) ;
if ( match ) {
pattern = new RegExp ( match [ 1 ] , match [ 2 ] ) ;
patternValidator = function ( value ) {
return validateRegex ( pattern , value ) ;
} ;
} else {
patternValidator = function ( value ) {
var patternObj = scope . $eval ( pattern ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( ! patternObj || ! patternObj . test ) {
throw minErr ( 'ngPattern' ) ( 'noregexp' ,
'Expected {0} to be a RegExp but was {1}. Element: {2}' , pattern ,
patternObj , startingTag ( element ) ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
return validateRegex ( patternObj , value ) ;
2016-03-28 10:46:51 +00:00
} ;
}
2016-04-18 12:34:29 +00:00
ctrl . $formatters . push ( patternValidator ) ;
ctrl . $parsers . push ( patternValidator ) ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
// min length validator
if ( attr . ngMinlength ) {
var minlength = int ( attr . ngMinlength ) ;
var minLengthValidator = function ( value ) {
return validate ( ctrl , 'minlength' , ctrl . $isEmpty ( value ) || value . length >= minlength , value ) ;
} ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
ctrl . $parsers . push ( minLengthValidator ) ;
ctrl . $formatters . push ( minLengthValidator ) ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
// max length validator
if ( attr . ngMaxlength ) {
var maxlength = int ( attr . ngMaxlength ) ;
var maxLengthValidator = function ( value ) {
return validate ( ctrl , 'maxlength' , ctrl . $isEmpty ( value ) || value . length <= maxlength , value ) ;
} ;
ctrl . $parsers . push ( maxLengthValidator ) ;
ctrl . $formatters . push ( maxLengthValidator ) ;
2016-03-28 10:46:51 +00:00
}
}
function numberInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
2016-04-18 12:34:29 +00:00
textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
2016-03-28 10:46:51 +00:00
ctrl . $parsers . push ( function ( value ) {
2016-04-18 12:34:29 +00:00
var empty = ctrl . $isEmpty ( value ) ;
if ( empty || NUMBER _REGEXP . test ( value ) ) {
ctrl . $setValidity ( 'number' , true ) ;
return value === '' ? null : ( empty ? value : parseFloat ( value ) ) ;
} else {
ctrl . $setValidity ( 'number' , false ) ;
return undefined ;
}
2016-03-28 10:46:51 +00:00
} ) ;
ctrl . $formatters . push ( function ( value ) {
2016-04-18 12:34:29 +00:00
return ctrl . $isEmpty ( value ) ? '' : '' + value ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
if ( attr . min ) {
var minValidator = function ( value ) {
var min = parseFloat ( attr . min ) ;
return validate ( ctrl , 'min' , ctrl . $isEmpty ( value ) || value >= min , value ) ;
2016-03-28 10:46:51 +00:00
} ;
2016-04-18 12:34:29 +00:00
ctrl . $parsers . push ( minValidator ) ;
ctrl . $formatters . push ( minValidator ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
if ( attr . max ) {
var maxValidator = function ( value ) {
var max = parseFloat ( attr . max ) ;
return validate ( ctrl , 'max' , ctrl . $isEmpty ( value ) || value <= max , value ) ;
2016-03-28 10:46:51 +00:00
} ;
2016-04-18 12:34:29 +00:00
ctrl . $parsers . push ( maxValidator ) ;
ctrl . $formatters . push ( maxValidator ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
ctrl . $formatters . push ( function ( value ) {
return validate ( ctrl , 'number' , ctrl . $isEmpty ( value ) || isNumber ( value ) , value ) ;
} ) ;
2016-03-28 10:46:51 +00:00
}
function urlInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
2016-04-18 12:34:29 +00:00
textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
var urlValidator = function ( value ) {
return validate ( ctrl , 'url' , ctrl . $isEmpty ( value ) || URL _REGEXP . test ( value ) , value ) ;
2016-03-28 10:46:51 +00:00
} ;
2016-04-18 12:34:29 +00:00
ctrl . $formatters . push ( urlValidator ) ;
ctrl . $parsers . push ( urlValidator ) ;
2016-03-28 10:46:51 +00:00
}
function emailInputType ( scope , element , attr , ctrl , $sniffer , $browser ) {
2016-04-18 12:34:29 +00:00
textInputType ( scope , element , attr , ctrl , $sniffer , $browser ) ;
var emailValidator = function ( value ) {
return validate ( ctrl , 'email' , ctrl . $isEmpty ( value ) || EMAIL _REGEXP . test ( value ) , value ) ;
2016-03-28 10:46:51 +00:00
} ;
2016-04-18 12:34:29 +00:00
ctrl . $formatters . push ( emailValidator ) ;
ctrl . $parsers . push ( emailValidator ) ;
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 ( ) ) ;
}
2016-04-18 12:34:29 +00:00
element . on ( 'click' , function ( ) {
2016-03-28 10:46:51 +00:00
if ( element [ 0 ] . checked ) {
2016-04-18 12:34:29 +00:00
scope . $apply ( function ( ) {
ctrl . $setViewValue ( attr . 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
ctrl . $render = function ( ) {
var value = attr . value ;
element [ 0 ] . checked = ( value == ctrl . $viewValue ) ;
} ;
attr . $observe ( 'value' , ctrl . $render ) ;
}
2016-04-18 12:34:29 +00:00
function checkboxInputType ( scope , element , attr , ctrl ) {
var trueValue = attr . ngTrueValue ,
falseValue = attr . ngFalseValue ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( ! isString ( trueValue ) ) trueValue = true ;
if ( ! isString ( falseValue ) ) falseValue = false ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
element . on ( 'click' , function ( ) {
scope . $apply ( function ( ) {
ctrl . $setViewValue ( element [ 0 ] . checked ) ;
} ) ;
} ) ;
2016-03-28 10:46:51 +00:00
ctrl . $render = function ( ) {
element [ 0 ] . checked = ctrl . $viewValue ;
} ;
2016-04-18 12:34:29 +00:00
// Override the standard `$isEmpty` because a value of `false` means empty in a checkbox.
2016-03-28 10:46:51 +00:00
ctrl . $isEmpty = function ( value ) {
2016-04-18 12:34:29 +00:00
return value !== trueValue ;
2016-03-28 10:46:51 +00:00
} ;
ctrl . $formatters . push ( function ( value ) {
2016-04-18 12:34:29 +00:00
return value === trueValue ;
2016-03-28 10:46:51 +00:00
} ) ;
ctrl . $parsers . push ( function ( value ) {
return value ? trueValue : falseValue ;
} ) ;
}
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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
2016-04-18 12:34:29 +00:00
* maxlength .
* @ 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 .
* /
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : input
2016-03-28 10:46:51 +00:00
* @ restrict E
*
* @ description
2016-04-18 12:34:29 +00:00
* HTML input element control with angular data - binding . Input control follows HTML5 input types
* and polyfills the HTML5 validation behavior for older browsers .
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
2016-04-18 12:34:29 +00:00
* maxlength .
* @ 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 .
*
* @ example
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
2016-03-28 10:46:51 +00:00
< script >
2016-04-18 12:34:29 +00:00
function Ctrl ( $scope ) {
$scope . user = { name : 'guest' , last : 'visitor' } ;
}
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2016-04-18 12:34:29 +00:00
< div ng - controller = "Ctrl" >
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 >
2016-04-18 12:34:29 +00:00
< / d o c : s o u r c e >
< doc : scenario >
2016-03-28 10:46:51 +00:00
it ( 'should initialize to model' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( binding ( 'user' ) ) . toEqual ( '{"name":"guest","last":"visitor"}' ) ;
expect ( binding ( 'myForm.userName.$valid' ) ) . toEqual ( 'true' ) ;
expect ( binding ( 'myForm.$valid' ) ) . toEqual ( 'true' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should be invalid if empty when required' , function ( ) {
2016-04-18 12:34:29 +00:00
input ( 'user.name' ) . enter ( '' ) ;
expect ( binding ( 'user' ) ) . toEqual ( '{"last":"visitor"}' ) ;
expect ( binding ( 'myForm.userName.$valid' ) ) . toEqual ( 'false' ) ;
expect ( binding ( 'myForm.$valid' ) ) . toEqual ( 'false' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should be valid if empty when min length is set' , function ( ) {
2016-04-18 12:34:29 +00:00
input ( 'user.last' ) . enter ( '' ) ;
expect ( binding ( 'user' ) ) . toEqual ( '{"name":"guest","last":""}' ) ;
expect ( binding ( 'myForm.lastName.$valid' ) ) . toEqual ( 'true' ) ;
expect ( binding ( 'myForm.$valid' ) ) . toEqual ( 'true' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should be invalid if less than required min length' , function ( ) {
2016-04-18 12:34:29 +00:00
input ( 'user.last' ) . enter ( 'xx' ) ;
expect ( binding ( 'user' ) ) . toEqual ( '{"name":"guest"}' ) ;
expect ( binding ( 'myForm.lastName.$valid' ) ) . toEqual ( 'false' ) ;
expect ( binding ( 'myForm.lastName.$error' ) ) . toMatch ( /minlength/ ) ;
expect ( binding ( 'myForm.$valid' ) ) . toEqual ( 'false' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should be invalid if longer than max length' , function ( ) {
2016-04-18 12:34:29 +00:00
input ( 'user.last' ) . enter ( 'some ridiculously long name' ) ;
expect ( binding ( 'user' ) )
. toEqual ( '{"name":"guest"}' ) ;
expect ( binding ( 'myForm.lastName.$valid' ) ) . toEqual ( 'false' ) ;
expect ( binding ( 'myForm.lastName.$error' ) ) . toMatch ( /maxlength/ ) ;
expect ( binding ( 'myForm.$valid' ) ) . toEqual ( 'false' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
var inputDirective = [ '$browser' , '$sniffer' , function ( $browser , $sniffer ) {
2016-03-28 10:46:51 +00:00
return {
restrict : 'E' ,
2016-04-18 12:34:29 +00:00
require : '?ngModel' ,
link : function ( scope , element , attr , ctrl ) {
if ( ctrl ) {
( inputType [ lowercase ( attr . type ) ] || inputType . text ) ( scope , element , attr , ctrl , $sniffer ,
$browser ) ;
2016-03-28 10:46:51 +00:00
}
}
} ;
} ] ;
2016-04-18 12:34:29 +00:00
var VALID _CLASS = 'ng-valid' ,
INVALID _CLASS = 'ng-invalid' ,
PRISTINE _CLASS = 'ng-pristine' ,
DIRTY _CLASS = 'ng-dirty' ;
2016-03-28 10:46:51 +00:00
/ * *
2016-04-18 12:34:29 +00:00
* @ ngdoc object
* @ name ng . directive : 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 . Each function is called , in turn , passing the value
through to the next . Used to sanitize / convert the value as well as validation .
For validation , the parsers should update the validity state using
{ @ link ng . directive : ngModel . NgModelController # methods _$setValidity $setValidity ( ) } ,
and return ` undefined ` for invalid values .
*
* @ property { Array . < Function > } $formatters Array of functions to execute , as a pipeline , whenever
the model value changes . Each function is called , in turn , passing the value through to the
next . Used to format / convert values for display in the control and validation .
* < pre >
* function formatter ( value ) {
* if ( value ) {
* return value . toUpperCase ( ) ;
* }
* }
* ngModel . $formatters . push ( formatter ) ;
* < / p r e >
*
* @ 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 errors as keys .
*
* @ 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 .
2016-03-28 10:46:51 +00:00
*
* @ description
*
2016-04-18 12:34:29 +00:00
* ` NgModelController ` provides API for the ` ng-model ` 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 .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* # # 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 .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* Note that ` contenteditable ` is an HTML5 attribute , which tells the browser to let the element
* contents be edited in place by the user . This will not work on older browsers .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* < example module = "customControl" >
< file name = "style.css" >
[ contenteditable ] {
border : 1 px solid black ;
background - color : white ;
min - height : 20 px ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
. ng - invalid {
border : 1 px solid red ;
}
< / f i l e >
< file name = "script.js" >
angular . module ( 'customControl' , [ ] ) .
directive ( 'contenteditable' , function ( ) {
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 ( ngModel . $viewValue || '' ) ;
} ;
// Listen for change events to enable binding
element . on ( 'blur keyup change' , function ( ) {
scope . $apply ( 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 ) ;
}
}
} ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
< / 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 = "scenario.js" >
it ( 'should data-bind and become invalid' , function ( ) {
var contentEditable = element ( '[contenteditable]' ) ;
expect ( contentEditable . text ( ) ) . toEqual ( 'Change me!' ) ;
input ( 'userContent' ) . enter ( '' ) ;
expect ( contentEditable . text ( ) ) . toEqual ( '' ) ;
expect ( contentEditable . prop ( 'className' ) ) . toMatch ( /ng-invalid-required/ ) ;
} ) ;
< / 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 NgModelController = [ '$scope' , '$exceptionHandler' , '$attrs' , '$element' , '$parse' ,
function ( $scope , $exceptionHandler , $attr , $element , $parse ) {
this . $viewValue = Number . NaN ;
this . $modelValue = Number . NaN ;
this . $parsers = [ ] ;
this . $formatters = [ ] ;
this . $viewChangeListeners = [ ] ;
this . $pristine = true ;
this . $dirty = false ;
this . $valid = true ;
this . $invalid = false ;
this . $name = $attr . name ;
var ngModelGet = $parse ( $attr . ngModel ) ,
ngModelSet = ngModelGet . assign ;
if ( ! ngModelSet ) {
throw minErr ( 'ngModel' ) ( 'nonassign' , "Expression '{0}' is non-assignable. Element: {1}" ,
$attr . ngModel , startingTag ( $element ) ) ;
}
/ * *
* @ ngdoc function
* @ name ng . directive : ngModel . NgModelController # $render
* @ methodOf ng . directive : ngModel . NgModelController
*
* @ description
* Called when the view needs to be updated . It is expected that the user of the ng - model
* directive will implement this method .
* /
this . $render = noop ;
/ * *
* @ ngdoc function
* @ name { ng . directive : ngModel . NgModelController # $isEmpty
* @ methodOf ng . directive : ngModel . NgModelController
*
* @ description
* This is called when we need to determine if the value of the 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 .
* /
this . $isEmpty = function ( value ) {
return isUndefined ( value ) || value === '' || value === null || value !== value ;
} ;
var parentForm = $element . inheritedData ( '$formController' ) || nullFormCtrl ,
invalidCount = 0 , // used to easily determine if we are valid
$error = this . $error = { } ; // keep invalid keys here
// Setup initial state of the control
$element . addClass ( PRISTINE _CLASS ) ;
toggleValidCss ( true ) ;
// convenience method for easy toggling of classes
function toggleValidCss ( isValid , validationErrorKey ) {
validationErrorKey = validationErrorKey ? '-' + snake _case ( validationErrorKey , '-' ) : '' ;
$element .
removeClass ( ( isValid ? INVALID _CLASS : VALID _CLASS ) + validationErrorKey ) .
addClass ( ( isValid ? VALID _CLASS : INVALID _CLASS ) + validationErrorKey ) ;
}
/ * *
* @ ngdoc function
* @ name ng . directive : ngModel . NgModelController # $setValidity
* @ methodOf ng . directive : ngModel . NgModelController
*
* @ description
* Change the validity state , and notifies the form when the control changes validity . ( i . e . it
* does not notify form if given validator is already marked as invalid ) .
*
* This method should be called by validators - i . e . the parser or formatter functions .
*
* @ param { string } validationErrorKey Name of the validator . the ` validationErrorKey ` will assign
* to ` $ error[validationErrorKey]=isValid ` 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 ) or invalid ( false ) .
* /
this . $setValidity = function ( validationErrorKey , isValid ) {
// Purposeful use of ! here to cast isValid to boolean in case it is undefined
// jshint -W018
if ( $error [ validationErrorKey ] === ! isValid ) return ;
// jshint +W018
if ( isValid ) {
if ( $error [ validationErrorKey ] ) invalidCount -- ;
if ( ! invalidCount ) {
toggleValidCss ( true ) ;
this . $valid = true ;
this . $invalid = false ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
} else {
toggleValidCss ( false ) ;
this . $invalid = true ;
this . $valid = false ;
invalidCount ++ ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
$error [ validationErrorKey ] = ! isValid ;
toggleValidCss ( isValid , validationErrorKey ) ;
parentForm . $setValidity ( validationErrorKey , isValid , this ) ;
2016-03-28 10:46:51 +00:00
} ;
2016-04-18 12:34:29 +00:00
/ * *
* @ ngdoc function
* @ name ng . directive : ngModel . NgModelController # $setPristine
* @ methodOf ng . directive : ngModel . NgModelController
*
* @ 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 ) .
* /
this . $setPristine = function ( ) {
this . $dirty = false ;
this . $pristine = true ;
$element . removeClass ( DIRTY _CLASS ) . addClass ( PRISTINE _CLASS ) ;
} ;
/ * *
* @ ngdoc function
* @ name ng . directive : ngModel . NgModelController # $setViewValue
* @ methodOf ng . directive : ngModel . NgModelController
*
* @ description
* Update the view value .
*
* This method should be called when the view value changes , typically from within a DOM event handler .
* For example { @ link ng . directive : input input } and
* { @ link ng . directive : select select } directives call it .
*
* It will update the $viewValue , then pass this value through each of the functions in ` $ parsers ` ,
* which includes any validators . The value that comes out of this ` $ parsers ` pipeline , be applied to
* ` $ modelValue ` and the * * expression * * specified in the ` ng-model ` attribute .
*
* Lastly , all the registered change listeners , in the ` $ viewChangeListeners ` list , are called .
*
* Note that calling this function does not trigger a ` $ digest ` .
*
* @ param { string } value Value from the view .
* /
this . $setViewValue = function ( value ) {
this . $viewValue = value ;
// change to dirty
if ( this . $pristine ) {
this . $dirty = true ;
this . $pristine = false ;
$element . removeClass ( PRISTINE _CLASS ) . addClass ( DIRTY _CLASS ) ;
parentForm . $setDirty ( ) ;
}
forEach ( this . $parsers , function ( fn ) {
value = fn ( value ) ;
} ) ;
if ( this . $modelValue !== value ) {
this . $modelValue = value ;
ngModelSet ( $scope , value ) ;
forEach ( this . $viewChangeListeners , function ( listener ) {
try {
listener ( ) ;
} catch ( e ) {
$exceptionHandler ( e ) ;
}
} ) ;
}
} ;
// model -> value
var ctrl = this ;
$scope . $watch ( function ngModelWatch ( ) {
var value = ngModelGet ( $scope ) ;
// if scope model value and ngModel value are out of sync
if ( ctrl . $modelValue !== value ) {
var formatters = ctrl . $formatters ,
idx = formatters . length ;
ctrl . $modelValue = value ;
while ( idx -- ) {
value = formatters [ idx ] ( value ) ;
}
if ( ctrl . $viewValue !== value ) {
ctrl . $viewValue = value ;
ctrl . $render ( ) ;
}
}
return value ;
} ) ;
} ] ;
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : ngModel
*
* @ element input
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* The ` ngModel ` directive binds an ` input ` , ` select ` , ` textarea ` ( or custom form control ) to a
* property on the scope using { @ link ng . directive : ngModel . NgModelController NgModelController } ,
* which is created and exposed by this directive .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* ` ngModel ` is responsible for :
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* - 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 , validation errors ) .
* - Setting related css classes on the element ( ` ng-valid ` , ` ng-invalid ` , ` ng-dirty ` , ` ng-pristine ` ) .
* - Registering the control with its parent { @ link ng . directive : form form } .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* 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 .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* For best practices on using ` ngModel ` , see :
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* - { @ link https : //github.com/angular/angular.js/wiki/Understanding-Scopes}
*
* For basic examples , how to use ` ngModel ` , see :
*
* - { @ link ng . directive : input input }
* - { @ link ng . directive : input . text text }
* - { @ link ng . directive : input . checkbox checkbox }
* - { @ link ng . directive : input . radio radio }
* - { @ link ng . directive : input . number number }
* - { @ link ng . directive : input . email email }
* - { @ link ng . directive : input . url url }
* - { @ link ng . directive : select select }
* - { @ link ng . directive : textarea textarea }
2016-03-28 10:46:51 +00:00
*
* /
2016-04-18 12:34:29 +00:00
var ngModelDirective = function ( ) {
2016-03-28 10:46:51 +00:00
return {
2016-04-18 12:34:29 +00:00
require : [ 'ngModel' , '^?form' ] ,
controller : NgModelController ,
link : function ( scope , element , attr , ctrls ) {
// notify others, especially parent forms
var modelCtrl = ctrls [ 0 ] ,
formCtrl = ctrls [ 1 ] || nullFormCtrl ;
formCtrl . $addControl ( modelCtrl ) ;
scope . $on ( '$destroy' , function ( ) {
formCtrl . $removeControl ( modelCtrl ) ;
} ) ;
2016-03-28 10:46:51 +00:00
}
} ;
2016-04-18 12:34:29 +00:00
} ;
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : ngChange
2016-03-28 10:46:51 +00:00
*
* @ description
2016-04-18 12:34:29 +00:00
* Evaluate given expression when user changes the input .
* The expression is not evaluated when the value change is coming from the model .
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
2016-04-18 12:34:29 +00:00
* < doc : example >
* < doc : source >
2016-03-28 10:46:51 +00:00
* < script >
2016-04-18 12:34:29 +00:00
* function Controller ( $scope ) {
* $scope . counter = 0 ;
* $scope . change = function ( ) {
* $scope . counter ++ ;
* } ;
* }
2016-03-28 10:46:51 +00:00
* < / s c r i p t >
2016-04-18 12:34:29 +00:00
* < div ng - controller = "Controller" >
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 / >
2016-04-18 12:34:29 +00:00
* debug = { { confirmed } } < br / >
* counter = { { counter } }
2016-03-28 10:46:51 +00:00
* < / d i v >
2016-04-18 12:34:29 +00:00
* < / d o c : s o u r c e >
* < doc : scenario >
2016-03-28 10:46:51 +00:00
* it ( 'should evaluate the expression if changing from view' , function ( ) {
2016-04-18 12:34:29 +00:00
* expect ( binding ( 'counter' ) ) . toEqual ( '0' ) ;
* element ( '#ng-change-example1' ) . click ( ) ;
* expect ( binding ( 'counter' ) ) . toEqual ( '1' ) ;
* expect ( binding ( 'confirmed' ) ) . toEqual ( 'true' ) ;
2016-03-28 10:46:51 +00:00
* } ) ;
*
* it ( 'should not evaluate the expression if changing from model' , function ( ) {
2016-04-18 12:34:29 +00:00
* element ( '#ng-change-example2' ) . click ( ) ;
* expect ( binding ( 'counter' ) ) . toEqual ( '0' ) ;
* expect ( binding ( 'confirmed' ) ) . toEqual ( 'true' ) ;
2016-03-28 10:46:51 +00:00
* } ) ;
2016-04-18 12:34:29 +00:00
* < / d o c : s c e n a r i o >
* < / d o c : e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
var ngChangeDirective = valueFn ( {
require : 'ngModel' ,
link : function ( scope , element , attr , ctrl ) {
ctrl . $viewChangeListeners . push ( function ( ) {
scope . $eval ( attr . ngChange ) ;
} ) ;
}
} ) ;
2016-04-18 12:34:29 +00:00
var requiredDirective = function ( ) {
return {
require : '?ngModel' ,
link : function ( scope , elm , attr , ctrl ) {
if ( ! ctrl ) return ;
attr . required = true ; // force truthy in case we are on non input element
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
var validator = function ( value ) {
if ( attr . required && ctrl . $isEmpty ( value ) ) {
ctrl . $setValidity ( 'required' , false ) ;
return ;
} else {
ctrl . $setValidity ( 'required' , true ) ;
return value ;
}
} ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
ctrl . $formatters . push ( validator ) ;
ctrl . $parsers . unshift ( validator ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
attr . $observe ( 'required' , function ( ) {
validator ( ctrl . $viewValue ) ;
} ) ;
}
} ;
} ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/ * *
* @ ngdoc directive
* @ name ng . directive : ngList
*
* @ description
* Text input that converts between a delimited string and an array of strings . The delimiter
* can be a fixed string ( by default a comma ) or a regular expression .
*
* @ element input
* @ param { string = } ngList optional delimiter that should be used to split the value . If
* specified in form ` /something/ ` then the value will be converted into a regular expression .
*
* @ example
< doc : example >
< doc : source >
< script >
function Ctrl ( $scope ) {
$scope . names = [ 'igor' , 'misko' , 'vojta' ] ;
}
< / s c r i p t >
< form name = "myForm" ng - controller = "Ctrl" >
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 >
< / d o c : s o u r c e >
< doc : scenario >
it ( 'should initialize to model' , function ( ) {
expect ( binding ( 'names' ) ) . toEqual ( '["igor","misko","vojta"]' ) ;
expect ( binding ( 'myForm.namesInput.$valid' ) ) . toEqual ( 'true' ) ;
expect ( element ( 'span.error' ) . css ( 'display' ) ) . toBe ( 'none' ) ;
} ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
it ( 'should be invalid if empty' , function ( ) {
input ( 'names' ) . enter ( '' ) ;
expect ( binding ( 'names' ) ) . toEqual ( '' ) ;
expect ( binding ( 'myForm.namesInput.$valid' ) ) . toEqual ( 'false' ) ;
expect ( element ( 'span.error' ) . css ( 'display' ) ) . not ( ) . toBe ( 'none' ) ;
} ) ;
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
* /
var ngListDirective = function ( ) {
return {
require : 'ngModel' ,
link : function ( scope , element , attr , ctrl ) {
var match = /\/(.*)\// . exec ( attr . ngList ) ,
separator = match && new RegExp ( match [ 1 ] ) || attr . 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 ( trim ( value ) ) ;
2016-03-28 10:46:51 +00:00
} ) ;
}
2016-04-18 12:34:29 +00:00
return list ;
} ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
ctrl . $parsers . push ( parse ) ;
ctrl . $formatters . push ( function ( value ) {
if ( isArray ( value ) ) {
return value . join ( ', ' ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
return undefined ;
} ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
// Override the standard $isEmpty because an empty array means the input is empty.
ctrl . $isEmpty = function ( value ) {
return ! value || ! value . length ;
} ;
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
var CONSTANT _VALUE _REGEXP = /^(true|false|\d+)$/ ;
/ * *
* @ ngdoc directive
* @ name ng . directive : ngValue
*
* @ description
* Binds the given expression to the value of ` input[select] ` or ` input[radio] ` , so
* that when the element is selected , the ` ngModel ` of that element is set to the
* bound value .
*
* ` ngValue ` is useful when dynamically generating lists of radio buttons using ` ng-repeat ` , as
* shown below .
*
* @ element input
* @ param { string = } ngValue angular expression , whose value will be bound to the ` value ` attribute
* of the ` input ` element
*
* @ example
< doc : example >
< doc : source >
< script >
function Ctrl ( $scope ) {
$scope . names = [ 'pizza' , 'unicorns' , 'robots' ] ;
$scope . my = { favorite : 'unicorns' } ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
< / s c r i p t >
< form ng - controller = "Ctrl" >
< 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 >
< / d o c : s o u r c e >
< doc : scenario >
it ( 'should initialize to model' , function ( ) {
expect ( binding ( 'my.favorite' ) ) . toEqual ( 'unicorns' ) ;
} ) ;
it ( 'should bind the values to the inputs' , function ( ) {
input ( 'my.favorite' ) . select ( 'pizza' ) ;
expect ( binding ( 'my.favorite' ) ) . toEqual ( 'pizza' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
* /
var ngValueDirective = function ( ) {
return {
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 ) ;
} ) ;
} ;
2016-03-28 10:46:51 +00:00
}
}
2016-04-18 12:34:29 +00:00
} ;
} ;
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : ngBind
2016-03-28 10:46:51 +00:00
* @ restrict AC
*
* @ description
2016-04-18 12:34:29 +00: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
*
2016-04-18 12:34:29 +00: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
*
2016-04-18 12:34:29 +00:00
* It is preferrable to use ` ngBind ` instead of ` {{ expression }} ` when 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
*
2016-04-18 12:34:29 +00: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
*
*
* @ element ANY
2016-04-18 12:34:29 +00:00
* @ param { expression } ngBind { @ link guide / expression Expression } to evaluate .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ example
* Enter a name in the Live Preview text box ; the greeting below the text box changes instantly .
< doc : example >
< doc : source >
< script >
function Ctrl ( $scope ) {
$scope . name = 'Whirled' ;
}
< / s c r i p t >
< div ng - controller = "Ctrl" >
Enter name : < input type = "text" ng - model = "name" > < br >
Hello < span ng - bind = "name" > < / s p a n > !
< / d i v >
< / d o c : s o u r c e >
< doc : scenario >
it ( 'should check ng-bind' , function ( ) {
expect ( using ( '.doc-example-live' ) . binding ( 'name' ) ) . toBe ( 'Whirled' ) ;
using ( '.doc-example-live' ) . input ( 'name' ) . enter ( 'world' ) ;
expect ( using ( '.doc-example-live' ) . binding ( 'name' ) ) . toBe ( 'world' ) ;
} ) ;
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
* /
var ngBindDirective = ngDirective ( function ( scope , element , attr ) {
element . addClass ( 'ng-binding' ) . data ( '$binding' , attr . ngBind ) ;
scope . $watch ( attr . ngBind , function ngBindWatchAction ( value ) {
// We are purposefully using == here rather than === because we want to
// catch when value is "null or undefined"
// jshint -W041
element . text ( value == undefined ? '' : value ) ;
} ) ;
} ) ;
/ * *
* @ ngdoc directive
* @ name ng . directive : ngBindTemplate
*
* @ 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 .
*
* @ 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 .
< doc : example >
< doc : source >
< script >
function Ctrl ( $scope ) {
$scope . salutation = 'Hello' ;
$scope . name = 'World' ;
}
< / s c r i p t >
< div ng - controller = "Ctrl" >
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 >
< / d o c : s o u r c e >
< doc : scenario >
it ( 'should check ng-bind' , function ( ) {
expect ( using ( '.doc-example-live' ) . binding ( 'salutation' ) ) .
toBe ( 'Hello' ) ;
expect ( using ( '.doc-example-live' ) . binding ( 'name' ) ) .
toBe ( 'World' ) ;
using ( '.doc-example-live' ) . input ( 'salutation' ) . enter ( 'Greetings' ) ;
using ( '.doc-example-live' ) . input ( 'name' ) . enter ( 'user' ) ;
expect ( using ( '.doc-example-live' ) . binding ( 'salutation' ) ) .
toBe ( 'Greetings' ) ;
expect ( using ( '.doc-example-live' ) . binding ( 'name' ) ) .
toBe ( 'user' ) ;
} ) ;
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
* /
var ngBindTemplateDirective = [ '$interpolate' , function ( $interpolate ) {
return function ( scope , element , attr ) {
// TODO: move this to scenario runner
var interpolateFn = $interpolate ( element . attr ( attr . $attr . ngBindTemplate ) ) ;
element . addClass ( 'ng-binding' ) . data ( '$binding' , interpolateFn ) ;
attr . $observe ( 'ngBindTemplate' , function ( value ) {
element . text ( value ) ;
} ) ;
} ;
} ] ;
/ * *
* @ ngdoc directive
* @ name ng . directive : ngBindHtml
*
* @ description
* Creates a binding that will innerHTML the result of evaluating the ` expression ` into the current
* element in a secure way . By default , the innerHTML - ed 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 . ) You may also bypass sanitization for values you know are safe . To do so , bind to
* an explicitly trusted value via { @ link ng . $sce # methods _trustAsHtml $sce . trustAsHtml } . See the example
* under { @ link ng . $sce # Example 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
Try it here : enter text in text box and watch the greeting change .
2016-05-18 00:10:50 +00:00
2016-04-18 12:34:29 +00:00
< example module = "ngBindHtmlExample" deps = "angular-sanitize.js" >
< file name = "index.html" >
< div ng - controller = "ngBindHtmlCtrl" >
< p ng - bind - html = "myHTML" > < / p >
< / d i v >
< / f i l e >
2016-05-18 00:10:50 +00:00
2016-04-18 12:34:29 +00:00
< file name = "script.js" >
angular . module ( 'ngBindHtmlExample' , [ 'ngSanitize' ] )
. controller ( 'ngBindHtmlCtrl' , [ '$scope' , function ngBindHtmlCtrl ( $scope ) {
$scope . myHTML =
'I am an <code>HTML</code>string with <a href="#">links!</a> and other <em>stuff</em>' ;
} ] ) ;
< / f i l e >
< file name = "scenario.js" >
it ( 'should check ng-bind-html' , function ( ) {
expect ( using ( '.doc-example-live' ) . binding ( 'myHTML' ) ) .
toBe (
'I am an <code>HTML</code>string with <a href="#">links!</a> and other <em>stuff</em>'
) ;
} ) ;
< / f i l e >
< / e x a m p l e >
* /
var ngBindHtmlDirective = [ '$sce' , '$parse' , function ( $sce , $parse ) {
return function ( scope , element , attr ) {
element . addClass ( 'ng-binding' ) . data ( '$binding' , attr . ngBindHtml ) ;
var parsed = $parse ( attr . ngBindHtml ) ;
function getStringValue ( ) { return ( parsed ( scope ) || '' ) . toString ( ) ; }
scope . $watch ( getStringValue , function ngBindHtmlWatchAction ( value ) {
element . html ( $sce . getTrustedHtml ( parsed ( scope ) ) || '' ) ;
} ) ;
} ;
} ] ;
function classDirective ( name , selector ) {
name = 'ngClass' + name ;
return function ( ) {
return {
restrict : 'AC' ,
link : function ( scope , element , attr ) {
var oldVal ;
scope . $watch ( attr [ name ] , ngClassWatchAction , true ) ;
attr . $observe ( 'class' , function ( value ) {
ngClassWatchAction ( scope . $eval ( attr [ name ] ) ) ;
} ) ;
if ( name !== 'ngClass' ) {
scope . $watch ( '$index' , function ( $index , old$index ) {
// jshint bitwise: false
var mod = $index & 1 ;
if ( mod !== old$index & 1 ) {
var classes = flattenClasses ( scope . $eval ( attr [ name ] ) ) ;
mod === selector ?
attr . $addClass ( classes ) :
attr . $removeClass ( classes ) ;
}
} ) ;
}
function ngClassWatchAction ( newVal ) {
if ( selector === true || scope . $index % 2 === selector ) {
var newClasses = flattenClasses ( newVal || '' ) ;
if ( ! oldVal ) {
attr . $addClass ( newClasses ) ;
} else if ( ! equals ( newVal , oldVal ) ) {
attr . $updateClass ( newClasses , flattenClasses ( oldVal ) ) ;
}
}
oldVal = copy ( newVal ) ;
}
function flattenClasses ( classVal ) {
if ( isArray ( classVal ) ) {
return classVal . join ( ' ' ) ;
} else if ( isObject ( classVal ) ) {
var classes = [ ] , i = 0 ;
forEach ( classVal , function ( v , k ) {
if ( v ) {
classes . push ( k ) ;
}
} ) ;
return classes . join ( ' ' ) ;
}
return classVal ;
}
}
} ;
} ;
}
/ * *
* @ ngdoc directive
* @ name ng . directive : ngClass
* @ restrict AC
*
* @ description
* 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 .
*
* 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
* add - happens just before the class is applied to the element
* remove - happens just before the class is removed from the element
*
* @ 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 >
2016-04-18 12:34:29 +00:00
< file name = "scenario.js" >
2016-03-28 10:46:51 +00:00
it ( 'should let you toggle the class' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( element ( '.doc-example-live p:first' ) . prop ( 'className' ) ) . not ( ) . toMatch ( /bold/ ) ;
expect ( element ( '.doc-example-live p:first' ) . prop ( 'className' ) ) . not ( ) . toMatch ( /red/ ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
input ( 'important' ) . check ( ) ;
expect ( element ( '.doc-example-live p:first' ) . prop ( 'className' ) ) . toMatch ( /bold/ ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
input ( 'error' ) . check ( ) ;
expect ( element ( '.doc-example-live p:first' ) . prop ( 'className' ) ) . toMatch ( /red/ ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should let you toggle string example' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( element ( '.doc-example-live p:nth-of-type(2)' ) . prop ( 'className' ) ) . toBe ( '' ) ;
input ( 'style' ) . enter ( 'red' ) ;
expect ( element ( '.doc-example-live p:nth-of-type(2)' ) . prop ( 'className' ) ) . toBe ( 'red' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'array example should have 3 classes' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( element ( '.doc-example-live p:last' ) . prop ( 'className' ) ) . toBe ( '' ) ;
input ( 'style1' ) . enter ( 'bold' ) ;
input ( 'style2' ) . enter ( 'strike' ) ;
input ( 'style3' ) . enter ( 'red' ) ;
expect ( element ( '.doc-example-live p:last' ) . prop ( 'className' ) ) . 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 .
2016-04-18 12:34:29 +00:00
< example animations = "true" >
2016-03-28 10:46:51 +00:00
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
< input type = "button" value = "set" ng - click = "myVar='my-class'" >
< input 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 >
2016-04-18 12:34:29 +00:00
< file name = "scenario.js" >
2016-03-28 10:46:51 +00:00
it ( 'should check ng-class' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( element ( '.doc-example-live span' ) . prop ( 'className' ) ) . not ( ) .
2016-03-28 10:46:51 +00:00
toMatch ( /my-class/ ) ;
2016-04-18 12:34:29 +00:00
using ( '.doc-example-live' ) . element ( ':button:first' ) . click ( ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
expect ( element ( '.doc-example-live span' ) . prop ( 'className' ) ) .
2016-03-28 10:46:51 +00:00
toMatch ( /my-class/ ) ;
2016-04-18 12:34:29 +00:00
using ( '.doc-example-live' ) . element ( ':button:last' ) . click ( ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
expect ( element ( '.doc-example-live span' ) . prop ( 'className' ) ) . 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
2016-04-18 12:34:29 +00:00
to view the step by step details of { @ link ngAnimate . $animate # methods _addclass $animate . addClass } and
{ @ link ngAnimate . $animate # methods _removeclass $animate . removeClass } .
2016-03-28 10:46:51 +00:00
* /
var ngClassDirective = classDirective ( '' , true ) ;
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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 >
2016-04-18 12:34:29 +00:00
< file name = "scenario.js" >
2016-03-28 10:46:51 +00:00
it ( 'should check ng-class-odd and ng-class-even' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( element ( '.doc-example-live li:first span' ) . prop ( 'className' ) ) .
2016-03-28 10:46:51 +00:00
toMatch ( /odd/ ) ;
2016-04-18 12:34:29 +00:00
expect ( element ( '.doc-example-live li:last span' ) . prop ( 'className' ) ) .
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
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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 >
2016-04-18 12:34:29 +00:00
< file name = "scenario.js" >
2016-03-28 10:46:51 +00:00
it ( 'should check ng-class-odd and ng-class-even' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( element ( '.doc-example-live li:first span' ) . prop ( 'className' ) ) .
2016-03-28 10:46:51 +00:00
toMatch ( /odd/ ) ;
2016-04-18 12:34:29 +00:00
expect ( element ( '.doc-example-live li:last span' ) . prop ( 'className' ) ) .
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
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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 } ) .
*
2016-04-18 12:34:29 +00:00
* < pre >
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 ;
* }
2016-04-18 12:34:29 +00:00
* < / p r e >
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 .
*
2016-04-18 12:34:29 +00:00
* Legacy browsers , like IE7 , do not provide attribute selector support ( added in CSS 2.1 ) so they
* cannot match the ` [ng \: cloak] ` selector . To work around this limitation , you must add the css
* class ` ng-cloak ` in addition to the ` ngCloak ` directive as shown in the example below .
*
2016-03-28 10:46:51 +00:00
* @ element ANY
*
* @ example
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
2016-03-28 10:46:51 +00:00
< div id = "template1" ng - cloak > { { 'hello' } } < / d i v >
2016-04-18 12:34:29 +00:00
< div id = "template2" ng - cloak class = "ng-cloak" > { { 'hello IE7' } } < / d i v >
< / d o c : s o u r c e >
< doc : scenario >
2016-03-28 10:46:51 +00:00
it ( 'should remove the template directive and css class' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( element ( '.doc-example-live #template1' ) . attr ( 'ng-cloak' ) ) .
not ( ) . toBeDefined ( ) ;
expect ( element ( '.doc-example-live #template2' ) . attr ( 'ng-cloak' ) ) .
not ( ) . toBeDefined ( ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : s c e n a r i o >
< / d o c : 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
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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 :
*
2016-04-18 12:34:29 +00:00
* * Model — The Model is scope properties ; 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
2016-04-18 12:34:29 +00:00
* @ param { expression } ngController Name of a globally accessible constructor function 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 ` as propertyName ` .
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
2016-04-18 12:34:29 +00:00
* easily be called from the angular markup . Notice that the scope becomes the ` this ` for the
* controller ' s instance . This allows for easy access to the view data from the controller . Also
* notice that any changes to the data are automatically reflected in the View without the need
* for a manual update . The example is shown in two different declaration styles you may use
* according to preference .
< doc : example >
< doc : source >
< script >
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 = '' ;
} ;
< / s c r i p t >
< 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 >
< / d o c : s o u r c e >
< doc : scenario >
it ( 'should check controller as' , function ( ) {
expect ( element ( '#ctrl-as-exmpl>:input' ) . val ( ) ) . toBe ( 'John Smith' ) ;
expect ( element ( '#ctrl-as-exmpl li:nth-child(1) input' ) . val ( ) )
. toBe ( '408 555 1212' ) ;
expect ( element ( '#ctrl-as-exmpl li:nth-child(2) input' ) . val ( ) )
. toBe ( 'john.smith@example.org' ) ;
element ( '#ctrl-as-exmpl li:first a:contains("clear")' ) . click ( ) ;
expect ( element ( '#ctrl-as-exmpl li:first input' ) . val ( ) ) . toBe ( '' ) ;
element ( '#ctrl-as-exmpl li:last a:contains("add")' ) . click ( ) ;
expect ( element ( '#ctrl-as-exmpl li:nth-child(3) input' ) . val ( ) )
. toBe ( 'yourname@example.org' ) ;
} ) ;
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
< doc : example >
< doc : source >
< script >
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 ( this . name ) ;
} ;
$scope . addContact = function ( ) {
this . contacts . push ( { type : 'email' , value : 'yourname@example.org' } ) ;
} ;
$scope . removeContact = function ( contactToRemove ) {
var index = this . contacts . indexOf ( contactToRemove ) ;
this . contacts . splice ( index , 1 ) ;
} ;
$scope . clearContact = function ( contact ) {
contact . type = 'phone' ;
contact . value = '' ;
} ;
}
< / s c r i p t >
< 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 >
< / d o c : s o u r c e >
< doc : scenario >
it ( 'should check controller' , function ( ) {
expect ( element ( '#ctrl-exmpl>:input' ) . val ( ) ) . toBe ( 'John Smith' ) ;
expect ( element ( '#ctrl-exmpl li:nth-child(1) input' ) . val ( ) )
. toBe ( '408 555 1212' ) ;
expect ( element ( '#ctrl-exmpl li:nth-child(2) input' ) . val ( ) )
. toBe ( 'john.smith@example.org' ) ;
element ( '#ctrl-exmpl li:first a:contains("clear")' ) . click ( ) ;
expect ( element ( '#ctrl-exmpl li:first input' ) . val ( ) ) . toBe ( '' ) ;
element ( '#ctrl-exmpl li:last a:contains("add")' ) . click ( ) ;
expect ( element ( '#ctrl-exmpl li:nth-child(3) input' ) . val ( ) )
. toBe ( 'yourname@example.org' ) ;
} ) ;
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
var ngControllerDirective = [ function ( ) {
return {
scope : true ,
controller : '@' ,
priority : 500
} ;
} ] ;
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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
*
2016-04-18 12:34:29 +00:00
* This is necessary when developing things like Google Chrome Extensions .
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 ) .
* For us to be compatible , we just need to implement the "getterFn" in $parse without violating
* any of these restrictions .
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
*
2016-04-18 12:34:29 +00:00
* In order to use this feature put the ` ngCsp ` directive on the root element of the application .
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 .
2016-04-18 12:34:29 +00:00
< pre >
2016-03-28 10:46:51 +00:00
< ! doctype html >
< html ng - app ng - csp >
...
...
< / h t m l >
2016-04-18 12:34:29 +00:00
< / p r e >
* /
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
// 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 a csp() fn that looks for ng-csp attribute
// anywhere in the current doc
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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
* @ param { expression } ngClick { @ link guide / expression Expression } to evaluate upon
2016-04-18 12:34:29 +00:00
* click . ( Event object is available as ` $ event ` )
2016-03-28 10:46:51 +00:00
*
* @ example
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
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 >
2016-04-18 12:34:29 +00:00
count : { { count } }
< / d o c : s o u r c e >
< doc : protractor >
2016-03-28 10:46:51 +00:00
it ( 'should check ng-click' , function ( ) {
expect ( element ( by . binding ( 'count' ) ) . getText ( ) ) . toMatch ( '0' ) ;
2016-04-18 12:34:29 +00:00
element ( by . css ( '.doc-example-live button' ) ) . click ( ) ;
2016-03-28 10:46:51 +00:00
expect ( element ( by . binding ( 'count' ) ) . getText ( ) ) . toMatch ( '1' ) ;
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : p r o t r a c t o r >
< / d o c : e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
/ *
2016-04-18 12:34:29 +00:00
* A directive that allows creation of custom onclick handlers that are defined as angular
* expressions and are compiled and executed within the current scope .
*
* Events that are handled via these handler are always configured not to propagate further .
2016-03-28 10:46:51 +00:00
* /
var ngEventDirectives = { } ;
forEach (
'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste' . split ( ' ' ) ,
2016-04-18 12:34:29 +00:00
function ( name ) {
var directiveName = directiveNormalize ( 'ng-' + name ) ;
ngEventDirectives [ directiveName ] = [ '$parse' , function ( $parse ) {
2016-03-28 10:46:51 +00:00
return {
compile : function ( $element , attr ) {
2016-04-18 12:34:29 +00:00
var fn = $parse ( attr [ directiveName ] ) ;
return function ( scope , element , attr ) {
element . on ( lowercase ( name ) , function ( event ) {
scope . $apply ( function ( ) {
2016-03-28 10:46:51 +00:00
fn ( scope , { $event : event } ) ;
2016-04-18 12:34:29 +00:00
} ) ;
2016-03-28 10:46:51 +00:00
} ) ;
} ;
}
} ;
} ] ;
}
) ;
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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
* @ 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
< doc : example >
< doc : source >
< button ng - dblclick = "count = count + 1" ng - init = "count=0" >
Increment ( on double click )
< / b u t t o n >
count : { { count } }
< / d o c : s o u r c e >
< / d o c : e x a m p l e >
* /
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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
* @ param { expression } ngMousedown { @ link guide / expression Expression } to evaluate upon
* mousedown . ( Event object is available as ` $ event ` )
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ example
< doc : example >
< doc : source >
< button ng - mousedown = "count = count + 1" ng - init = "count=0" >
Increment ( on mouse down )
< / b u t t o n >
count : { { count } }
< / d o c : s o u r c e >
< / d o c : e x a m p l e >
* /
/ * *
* @ ngdoc directive
* @ name ng . directive : 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
* @ param { expression } ngMouseup { @ link guide / expression Expression } to evaluate upon
* mouseup . ( Event object is available as ` $ event ` )
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ example
< doc : example >
< doc : source >
< button ng - mouseup = "count = count + 1" ng - init = "count=0" >
Increment ( on mouse up )
< / b u t t o n >
count : { { count } }
< / d o c : s o u r c e >
< / d o c : e x a m p l e >
* /
/ * *
* @ ngdoc directive
* @ name ng . directive : 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
* @ param { expression } ngMouseover { @ link guide / expression Expression } to evaluate upon
* mouseover . ( Event object is available as ` $ event ` )
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ example
< doc : example >
< doc : source >
< button ng - mouseover = "count = count + 1" ng - init = "count=0" >
Increment ( when mouse is over )
< / b u t t o n >
count : { { count } }
< / d o c : s o u r c e >
< / d o c : e x a m p l e >
* /
/ * *
* @ ngdoc directive
* @ name ng . directive : 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
* @ param { expression } ngMouseenter { @ link guide / expression Expression } to evaluate upon
* mouseenter . ( Event object is available as ` $ event ` )
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ example
< doc : example >
< doc : source >
< button ng - mouseenter = "count = count + 1" ng - init = "count=0" >
Increment ( when mouse enters )
< / b u t t o n >
count : { { count } }
< / d o c : s o u r c e >
< / d o c : e x a m p l e >
* /
/ * *
* @ ngdoc directive
* @ name ng . directive : 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
* @ param { expression } ngMouseleave { @ link guide / expression Expression } to evaluate upon
* mouseleave . ( Event object is available as ` $ event ` )
2016-03-28 10:46:51 +00:00
*
* @ example
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
< button ng - mouseleave = "count = count + 1" ng - init = "count=0" >
Increment ( when mouse leaves )
< / b u t t o n >
count : { { count } }
< / d o c : s o u r c e >
< / d o c : e x a m p l e >
* /
/ * *
* @ ngdoc directive
* @ name ng . directive : 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
* @ param { expression } ngMousemove { @ link guide / expression Expression } to evaluate upon
* mousemove . ( Event object is available as ` $ event ` )
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ example
< doc : example >
< doc : source >
< button ng - mousemove = "count = count + 1" ng - init = "count=0" >
Increment ( when mouse moves )
< / b u t t o n >
count : { { count } }
< / d o c : s o u r c e >
< / d o c : e x a m p l e >
* /
/ * *
* @ ngdoc directive
* @ name ng . directive : 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
* @ 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
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
< input ng - keydown = "count = count + 1" ng - init = "count=0" >
key down count : { { count } }
< / d o c : s o u r c e >
< / d o c : e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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
* @ 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
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
< input ng - keyup = "count = count + 1" ng - init = "count=0" >
key up count : { { count } }
< / d o c : s o u r c e >
< / d o c : 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
* @ name ng . directive : ngKeypress
*
* @ description
* Specify custom behavior on keypress event .
*
* @ element ANY
* @ param { expression } ngKeypress { @ link guide / expression Expression } to evaluate upon
* keypress . ( Event object is available as ` $ event ` and can be interrogated for keyCode , altKey , etc . )
*
* @ example
< doc : example >
< doc : source >
< input ng - keypress = "count = count + 1" ng - init = "count=0" >
key press count : { { count } }
< / d o c : s o u r c e >
< / d o c : e x a m p l e >
* /
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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
* server and reloading the current page ) * * but only if the form does not contain an ` action `
* attribute * * .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ element form
* @ param { expression } ngSubmit { @ link guide / expression Expression } to eval . ( Event object is available as ` $ event ` )
2016-03-28 10:46:51 +00:00
*
* @ example
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
< script >
function Ctrl ( $scope ) {
$scope . list = [ ] ;
$scope . text = 'hello' ;
$scope . submit = function ( ) {
if ( this . text ) {
this . list . push ( this . text ) ;
this . text = '' ;
}
} ;
}
< / s c r i p t >
< form ng - submit = "submit()" ng - controller = "Ctrl" >
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 >
< / d o c : s o u r c e >
< doc : scenario >
it ( 'should check ng-submit' , function ( ) {
expect ( binding ( 'list' ) ) . toBe ( '[]' ) ;
element ( '.doc-example-live #submit' ) . click ( ) ;
expect ( binding ( 'list' ) ) . toBe ( '["hello"]' ) ;
expect ( input ( 'text' ) . val ( ) ) . toBe ( '' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
it ( 'should ignore empty strings' , function ( ) {
expect ( binding ( 'list' ) ) . toBe ( '[]' ) ;
element ( '.doc-example-live #submit' ) . click ( ) ;
element ( '.doc-example-live #submit' ) . click ( ) ;
expect ( binding ( 'list' ) ) . toBe ( '["hello"]' ) ;
} ) ;
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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
*
2016-04-18 12:34:29 +00:00
* @ element window , input , select , textarea , a
* @ param { expression } ngFocus { @ link guide / expression Expression } to evaluate upon
* focus . ( 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
* @ name ng . directive : 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
*
2016-04-18 12:34:29 +00:00
* @ element window , input , select , textarea , a
* @ param { expression } ngBlur { @ link guide / expression Expression } to evaluate upon
* blur . ( 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
* @ name ng . directive : 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
* @ param { expression } ngCopy { @ link guide / expression Expression } to evaluate upon
* copy . ( Event object is available as ` $ event ` )
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ example
< doc : example >
< doc : source >
< input ng - copy = "copied=true" ng - init = "copied=false; value='copy me'" ng - model = "value" >
copied : { { copied } }
< / d o c : s o u r c e >
< / d o c : e x a m p l e >
* /
/ * *
* @ ngdoc directive
* @ name ng . directive : 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
* @ param { expression } ngCut { @ link guide / expression Expression } to evaluate upon
* cut . ( Event object is available as ` $ event ` )
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ example
< doc : example >
< doc : source >
< input ng - cut = "cut=true" ng - init = "cut=false; value='cut me'" ng - model = "value" >
cut : { { cut } }
< / d o c : s o u r c e >
< / d o c : e x a m p l e >
* /
/ * *
* @ ngdoc directive
* @ name ng . directive : 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
* @ param { expression } ngPaste { @ link guide / expression Expression } to evaluate upon
* paste . ( Event object is available as ` $ event ` )
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ example
< doc : example >
< doc : source >
< input ng - paste = "paste=true" ng - init = "paste=false" placeholder = 'paste here' >
pasted : { { paste } }
< / d o c : s o u r c e >
< / d o c : e x a m p l e >
* /
/ * *
* @ ngdoc directive
* @ name ng . directive : ngIf
* @ 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
* { @ link https : //github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance prototypal inheritance}.
* 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
* 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
2016-04-18 12:34:29 +00:00
< example animations = "true" >
< 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" >
I ' m removed when the checkbox is unchecked .
< / 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 {
transclude : 'element' ,
priority : 600 ,
terminal : true ,
restrict : 'A' ,
$$tlb : true ,
link : function ( $scope , $element , $attr , ctrl , $transclude ) {
var block , childScope ;
$scope . $watch ( $attr . ngIf , function ngIfWatchAction ( value ) {
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( toBoolean ( value ) ) {
if ( ! childScope ) {
childScope = $scope . $new ( ) ;
$transclude ( childScope , function ( clone ) {
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
// by a directive with templateUrl when it's template arrives.
block = {
clone : clone
} ;
$animate . enter ( clone , $element . parent ( ) , $element ) ;
} ) ;
2016-03-28 10:46:51 +00:00
}
} else {
2016-04-18 12:34:29 +00:00
if ( childScope ) {
childScope . $destroy ( ) ;
childScope = null ;
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( block ) {
$animate . leave ( getBlockElements ( block . clone ) ) ;
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
* @ name ng . directive : ngInclude
* @ 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
* application document . This is done by calling { @ link ng . $sce # methods _getTrustedResourceUrl
* $sce . getTrustedResourceUrl } on it . To load templates from other domains or protocols
* you may either { @ link ng . $sceDelegateProvider # methods _resourceUrlWhitelist whitelist them } or
* { @ link ng . $sce # methods _trustAsResourceUrl wrap them } as trusted values . Refer to Angular ' s { @ link
* ng . $sce Strict Contextual Escaping } .
*
* In addition , the browser ' s
* { @ link https : //code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest
* Same Origin Policy } and { @ link http : //www.w3.org/TR/cors/ Cross-Origin Resource Sharing
* ( CORS ) } policy may further restrict whether the template is successfully loaded .
* 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 ,
* make sure you wrap it in quotes , e . g . ` src="'myPartialTemplate.html'" ` .
* @ 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
< example animations = "true" >
< file name = "index.html" >
< div ng - controller = "Ctrl" >
< 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 >
url of the template : < tt > { { template . url } } < / t t >
< 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" >
function Ctrl ( $scope ) {
$scope . templates =
[ { name : 'template1.html' , url : 'template1.html' }
, { name : 'template2.html' , url : 'template2.html' } ] ;
$scope . template = $scope . templates [ 0 ] ;
2016-03-28 10:46:51 +00:00
}
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 >
< file name = "scenario.js" >
it ( 'should load template1.html' , function ( ) {
expect ( element ( '.doc-example-live [ng-include]' ) . text ( ) ) .
toMatch ( /Content of template1.html/ ) ;
} ) ;
it ( 'should load template2.html' , function ( ) {
select ( 'template' ) . option ( '1' ) ;
expect ( element ( '.doc-example-live [ng-include]' ) . text ( ) ) .
toMatch ( /Content of template2.html/ ) ;
} ) ;
it ( 'should change to blank' , function ( ) {
select ( 'template' ) . option ( '' ) ;
expect ( element ( '.doc-example-live [ng-include]' ) ) . toBe ( undefined ) ;
} ) ;
< / 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
* @ name ng . directive : ngInclude # $includeContentRequested
* @ eventOf ng . directive : ngInclude
* @ eventType emit on the scope ngInclude was declared in
* @ description
* Emitted every time the ngInclude content is requested .
* /
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/ * *
* @ ngdoc event
* @ name ng . directive : ngInclude # $includeContentLoaded
* @ eventOf ng . directive : ngInclude
* @ eventType emit on the current ngInclude scope
* @ description
* Emitted every time the ngInclude content is reloaded .
* /
var ngIncludeDirective = [ '$http' , '$templateCache' , '$anchorScroll' , '$animate' , '$sce' ,
function ( $http , $templateCache , $anchorScroll , $animate , $sce ) {
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 ,
currentElement ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
var cleanupLastIncludeContent = function ( ) {
if ( currentScope ) {
currentScope . $destroy ( ) ;
currentScope = null ;
}
if ( currentElement ) {
$animate . leave ( currentElement ) ;
currentElement = null ;
}
} ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
scope . $watch ( $sce . parseAsResourceUrl ( srcExp ) , function ngIncludeWatchAction ( src ) {
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 ) {
$http . get ( src , { cache : $templateCache } ) . success ( function ( response ) {
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 ( ) ;
$animate . enter ( clone , null , $element , afterAnimation ) ;
} ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
currentScope = newScope ;
currentElement = clone ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
currentScope . $emit ( '$includeContentLoaded' ) ;
scope . $eval ( onloadExp ) ;
} ) . error ( function ( ) {
if ( thisChangeId === changeCounter ) cleanupLastIncludeContent ( ) ;
} ) ;
scope . $emit ( '$includeContentRequested' ) ;
2016-03-28 10:46:51 +00:00
} else {
2016-04-18 12:34:29 +00:00
cleanupLastIncludeContent ( ) ;
ctrl . template = 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
// 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 ) {
$element . html ( ctrl . template ) ;
$compile ( $element . contents ( ) ) ( scope ) ;
}
} ;
} ] ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/ * *
* @ ngdoc directive
* @ name ng . directive : 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 api / 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 >
*
* @ priority 450
*
* @ element ANY
* @ param { expression } ngInit { @ link guide / expression Expression } to eval .
*
* @ example
< doc : example >
< doc : source >
< script >
function Ctrl ( $scope ) {
$scope . list = [ [ 'a' , 'b' ] , [ 'c' , 'd' ] ] ;
}
< / s c r i p t >
< div ng - controller = "Ctrl" >
< 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 >
< / d o c : s o u r c e >
< doc : scenario >
it ( 'should alias index positions' , function ( ) {
expect ( element ( '.example-init' ) . text ( ) )
. toBe ( 'list[ 0 ][ 0 ] = a;' +
'list[ 0 ][ 1 ] = b;' +
'list[ 1 ][ 0 ] = c;' +
'list[ 1 ][ 1 ] = d;' ) ;
} ) ;
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
* /
var ngInitDirective = ngDirective ( {
priority : 450 ,
compile : function ( ) {
return {
pre : function ( scope , element , attrs ) {
scope . $eval ( attrs . ngInit ) ;
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
} ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
/ * *
* @ ngdoc directive
* @ name ng . directive : ngNonBindable
* @ 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
< doc : example >
< doc : source >
< div > Normal : { { 1 + 2 } } < / d i v >
< div ng - non - bindable > Ignored : { { 1 + 2 } } < / d i v >
< / d o c : s o u r c e >
< doc : scenario >
it ( 'should check ng-non-bindable' , function ( ) {
expect ( using ( '.doc-example-live' ) . binding ( '1 + 2' ) ) . toBe ( '3' ) ;
expect ( using ( '.doc-example-live' ) . element ( 'div:last' ) . text ( ) ) .
toMatch ( /1 \+ 2/ ) ;
} ) ;
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
* /
var ngNonBindableDirective = ngDirective ( { terminal : true , priority : 1000 } ) ;
2016-03-28 10:46:51 +00:00
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : ngPluralize
2016-03-28 10:46:51 +00:00
* @ restrict EA
*
* @ description
2016-04-18 12:34:29 +00:00
* # Overview
2016-03-28 10:46:51 +00:00
* ` 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
2016-04-18 12:34:29 +00:00
* { @ link http : //unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
* plural categories } and the strings to be displayed .
2016-03-28 10:46:51 +00:00
*
* # Plural categories and explicit number rules
* There are two
2016-04-18 12:34:29 +00:00
* { @ link http : //unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
* plural categories } 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 :
*
2016-04-18 12:34:29 +00:00
* < pre >
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 >
2016-04-18 12:34:29 +00:00
* < / p r e >
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 :
*
2016-04-18 12:34:29 +00:00
* < pre >
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 >
2016-04-18 12:34:29 +00:00
* < / p r e >
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 .
2016-04-18 12:34:29 +00:00
* In this case , plural category 'one' is matched and "John, Marry 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" .
*
2016-04-18 12:34:29 +00:00
* @ param { string | expression } count The variable to be bounded 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
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
2016-03-28 10:46:51 +00:00
< script >
2016-04-18 12:34:29 +00:00
function Ctrl ( $scope ) {
$scope . person1 = 'Igor' ;
$scope . person2 = 'Misko' ;
$scope . personCount = 1 ;
}
2016-03-28 10:46:51 +00:00
< / s c r i p t >
2016-04-18 12:34:29 +00:00
< div ng - controller = "Ctrl" >
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 >
2016-04-18 12:34:29 +00:00
< / d o c : s o u r c e >
< doc : scenario >
2016-03-28 10:46:51 +00:00
it ( 'should show correct pluralized string' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( element ( '.doc-example-live ng-pluralize:first' ) . text ( ) ) .
toBe ( '1 person is viewing.' ) ;
expect ( element ( '.doc-example-live ng-pluralize:last' ) . text ( ) ) .
toBe ( 'Igor is viewing.' ) ;
using ( '.doc-example-live' ) . input ( 'personCount' ) . enter ( '0' ) ;
expect ( element ( '.doc-example-live ng-pluralize:first' ) . text ( ) ) .
toBe ( 'Nobody is viewing.' ) ;
expect ( element ( '.doc-example-live ng-pluralize:last' ) . text ( ) ) .
toBe ( 'Nobody is viewing.' ) ;
using ( '.doc-example-live' ) . input ( 'personCount' ) . enter ( '2' ) ;
expect ( element ( '.doc-example-live ng-pluralize:first' ) . text ( ) ) .
toBe ( '2 people are viewing.' ) ;
expect ( element ( '.doc-example-live ng-pluralize:last' ) . text ( ) ) .
toBe ( 'Igor and Misko are viewing.' ) ;
using ( '.doc-example-live' ) . input ( 'personCount' ) . enter ( '3' ) ;
expect ( element ( '.doc-example-live ng-pluralize:first' ) . text ( ) ) .
toBe ( '3 people are viewing.' ) ;
expect ( element ( '.doc-example-live ng-pluralize:last' ) . text ( ) ) .
toBe ( 'Igor, Misko and one other person are viewing.' ) ;
using ( '.doc-example-live' ) . input ( 'personCount' ) . enter ( '4' ) ;
expect ( element ( '.doc-example-live ng-pluralize:first' ) . text ( ) ) .
toBe ( '4 people are viewing.' ) ;
expect ( element ( '.doc-example-live ng-pluralize:last' ) . text ( ) ) .
toBe ( 'Igor, Misko and 2 other people are viewing.' ) ;
} ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
it ( 'should show data-binded names' , function ( ) {
using ( '.doc-example-live' ) . input ( 'personCount' ) . enter ( '4' ) ;
expect ( element ( '.doc-example-live ng-pluralize:last' ) . text ( ) ) .
toBe ( 'Igor, Misko and 2 other people are viewing.' ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
using ( '.doc-example-live' ) . input ( 'person1' ) . enter ( 'Di' ) ;
using ( '.doc-example-live' ) . input ( 'person2' ) . enter ( 'Vojta' ) ;
expect ( element ( '.doc-example-live ng-pluralize:last' ) . text ( ) ) .
toBe ( 'Di, Vojta and 2 other people are viewing.' ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : s c e n a r i o >
< / d o c : 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 ) {
var BRACE = /{}/g ;
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 ( ) ,
2016-04-18 12:34:29 +00:00
isWhen = /^when(Minus)?(.+)$/ ;
2016-03-28 10:46:51 +00:00
forEach ( attr , function ( expression , attributeName ) {
2016-04-18 12:34:29 +00:00
if ( isWhen . test ( attributeName ) ) {
whens [ lowercase ( attributeName . replace ( 'when' , '' ) . replace ( 'Minus' , '-' ) ) ] =
element . attr ( attr . $attr [ attributeName ] ) ;
2016-03-28 10:46:51 +00:00
}
} ) ;
forEach ( whens , function ( expression , key ) {
2016-04-18 12:34:29 +00:00
whensExpFns [ key ] =
$interpolate ( expression . replace ( BRACE , startSymbol + numberExp + '-' +
offset + endSymbol ) ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
scope . $watch ( function ngPluralizeWatch ( ) {
var value = parseFloat ( scope . $eval ( numberExp ) ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( ! isNaN ( value ) ) {
//if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise,
//check it against pluralization rules in $locale service
if ( ! ( value in whens ) ) value = $locale . pluralCat ( value - offset ) ;
return whensExpFns [ value ] ( scope , element , true ) ;
} else {
return '' ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
} , function ngPluralizeWatchAction ( newVal ) {
element . text ( newVal ) ;
2016-03-28 10:46:51 +00:00
} ) ;
}
} ;
} ] ;
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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 ) . |
*
2016-04-18 12:34:29 +00:00
* Creating aliases for these properties is possible with { @ link api / ng . directive : ngInit ` ngInit ` } .
* This may be useful when , for instance , nesting ngRepeats .
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 :
2016-04-18 12:34:29 +00:00
* < pre >
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 >
2016-04-18 12:34:29 +00:00
* < / p r e >
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 :
2016-04-18 12:34:29 +00:00
* < pre >
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 >
2016-04-18 12:34:29 +00:00
* < / p r e >
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
2016-04-18 12:34:29 +00: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} ` .
*
2016-04-18 12:34:29 +00:00
* * ` variable in expression track by tracking_expression ` – You can also provide an optional tracking function
* which can be used to associate the objects in the collection with the DOM elements . If no tracking function
* is specified the ng - repeat associates elements by identity in the collection . It is an error to have
* more than one tracking function to resolve to the same key . ( This would mean that two distinct objects are
* mapped to the same DOM element , which is not possible . ) Filters should be applied to the expression ,
* before specifying a tracking expression .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00: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 .
*
* @ 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 :
< example 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" >
2016-04-18 12:34:29 +00:00
< li class = "animate-repeat" ng - repeat = "friend in friends | filter:q" >
2016-03-28 10:46:51 +00:00
[ { { $index + 1 } } ] { { friend . name } } who is { { friend . age } } years old .
< / l i >
< / 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 >
2016-04-18 12:34:29 +00:00
< file name = "scenario.js" >
it ( 'should render initial data set' , function ( ) {
var r = using ( '.doc-example-live' ) . repeater ( 'ul li' ) ;
expect ( r . count ( ) ) . toBe ( 10 ) ;
expect ( r . row ( 0 ) ) . toEqual ( [ "1" , "John" , "25" ] ) ;
expect ( r . row ( 1 ) ) . toEqual ( [ "2" , "Jessie" , "30" ] ) ;
expect ( r . row ( 9 ) ) . toEqual ( [ "10" , "Samantha" , "60" ] ) ;
expect ( binding ( 'friends.length' ) ) . toBe ( "10" ) ;
} ) ;
2016-03-28 10:46:51 +00:00
it ( 'should update repeater when filter predicate changes' , function ( ) {
2016-04-18 12:34:29 +00:00
var r = using ( '.doc-example-live' ) . repeater ( 'ul li' ) ;
expect ( r . count ( ) ) . toBe ( 10 ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
input ( 'q' ) . enter ( 'ma' ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
expect ( r . count ( ) ) . toBe ( 2 ) ;
expect ( r . row ( 0 ) ) . toEqual ( [ "1" , "Mary" , "28" ] ) ;
expect ( r . row ( 1 ) ) . toEqual ( [ "2" , "Samantha" , "60" ] ) ;
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' ) ;
return {
transclude : 'element' ,
priority : 1000 ,
terminal : true ,
$$tlb : true ,
2016-04-18 12:34:29 +00:00
link : function ( $scope , $element , $attr , ctrl , $transclude ) {
var expression = $attr . ngRepeat ;
var match = expression . match ( /^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/ ) ,
trackByExp , trackByExpGetter , trackByIdExpFn , trackByIdArrayFn , trackByIdObjFn ,
lhs , rhs , valueIdentifier , keyIdentifier ,
hashFnLocals = { $id : hashKey } ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
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 ) ;
2016-04-18 12:34:29 +00:00
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
lhs = match [ 1 ] ;
rhs = match [ 2 ] ;
trackByExp = match [ 3 ] ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
if ( trackByExp ) {
trackByExpGetter = $parse ( trackByExp ) ;
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 ) ;
} ;
2016-04-18 12:34:29 +00:00
} else {
trackByIdArrayFn = function ( key , value ) {
return hashKey ( value ) ;
} ;
trackByIdObjFn = function ( key ) {
return key ;
} ;
}
match = lhs . match ( /^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/ ) ;
if ( ! match ) {
throw ngRepeatMinErr ( 'iidexp' , "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'." ,
lhs ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
valueIdentifier = match [ 3 ] || match [ 1 ] ;
keyIdentifier = match [ 2 ] ;
2016-03-28 10:46:51 +00:00
// 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
2016-04-18 12:34:29 +00:00
var lastBlockMap = { } ;
2016-03-28 10:46:51 +00:00
//watch props
2016-04-18 12:34:29 +00:00
$scope . $watchCollection ( rhs , function ngRepeatAction ( collection ) {
2016-03-28 10:46:51 +00:00
var index , length ,
2016-04-18 12:34:29 +00:00
previousNode = $element [ 0 ] , // current position of the node
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.
2016-04-18 12:34:29 +00:00
nextBlockMap = { } ,
arrayLength ,
childScope ,
2016-03-28 10:46:51 +00:00
key , value , // key/value of iteration
trackById ,
trackByIdFn ,
collectionKeys ,
block , // last object information {scope, element, id}
2016-04-18 12:34:29 +00:00
nextBlockOrder = [ ] ,
2016-03-28 10:46:51 +00:00
elementsToRemove ;
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 = [ ] ;
2016-04-18 12:34:29 +00:00
for ( key in collection ) {
if ( collection . hasOwnProperty ( key ) && key . charAt ( 0 ) != '$' ) {
collectionKeys . push ( key ) ;
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
}
2016-04-18 12:34:29 +00:00
arrayLength = collectionKeys . length ;
2016-03-28 10:46:51 +00:00
// locate existing items
2016-04-18 12:34:29 +00:00
length = nextBlockOrder . length = collectionKeys . length ;
for ( index = 0 ; index < length ; index ++ ) {
key = ( collection === collectionKeys ) ? index : collectionKeys [ index ] ;
value = collection [ key ] ;
trackById = trackByIdFn ( key , value , index ) ;
assertNotHasOwnProperty ( trackById , '`track by` id' ) ;
if ( lastBlockMap . hasOwnProperty ( trackById ) ) {
block = lastBlockMap [ trackById ] ;
delete lastBlockMap [ trackById ] ;
nextBlockMap [ trackById ] = block ;
nextBlockOrder [ index ] = block ;
} else if ( nextBlockMap . hasOwnProperty ( trackById ) ) {
// restore lastBlockMap
forEach ( nextBlockOrder , function ( block ) {
if ( block && block . scope ) lastBlockMap [ block . id ] = block ;
} ) ;
// This is a duplicate and we need to throw an error
throw ngRepeatMinErr ( 'dupes' , "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}" ,
expression , trackById ) ;
} else {
// new never before seen block
nextBlockOrder [ index ] = { id : trackById } ;
nextBlockMap [ trackById ] = false ;
}
}
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
// remove existing items
for ( key in lastBlockMap ) {
// lastBlockMap is our own object so we don't need to use special hasOwnPropertyFn
if ( lastBlockMap . hasOwnProperty ( key ) ) {
block = lastBlockMap [ key ] ;
elementsToRemove = getBlockElements ( block . clone ) ;
$animate . leave ( elementsToRemove ) ;
forEach ( elementsToRemove , function ( element ) { element [ NG _REMOVED ] = true ; } ) ;
block . scope . $destroy ( ) ;
2016-03-28 10:46:51 +00:00
}
}
// we are not using forEach for perf reasons (trying to avoid #call)
2016-04-18 12:34:29 +00:00
for ( index = 0 , length = collectionKeys . length ; index < length ; index ++ ) {
2016-03-28 10:46:51 +00:00
key = ( collection === collectionKeys ) ? index : collectionKeys [ index ] ;
value = collection [ key ] ;
block = nextBlockOrder [ index ] ;
2016-04-18 12:34:29 +00:00
if ( nextBlockOrder [ index - 1 ] ) previousNode = getBlockEnd ( nextBlockOrder [ index - 1 ] ) ;
2016-03-28 10:46:51 +00:00
if ( block . scope ) {
// if we have already seen this object, then we need to reuse the
// associated scope/element
2016-04-18 12:34:29 +00:00
childScope = block . scope ;
2016-03-28 10:46:51 +00:00
nextNode = previousNode ;
do {
nextNode = nextNode . nextSibling ;
2016-04-18 12:34:29 +00:00
} while ( nextNode && nextNode [ NG _REMOVED ] ) ;
2016-03-28 10:46:51 +00:00
if ( getBlockStart ( block ) != nextNode ) {
// existing item which got moved
2016-04-18 12:34:29 +00:00
$animate . move ( getBlockElements ( block . clone ) , null , jqLite ( previousNode ) ) ;
2016-03-28 10:46:51 +00:00
}
previousNode = getBlockEnd ( block ) ;
} else {
// new item which we don't know about
2016-04-18 12:34:29 +00:00
childScope = $scope . $new ( ) ;
}
childScope [ valueIdentifier ] = value ;
if ( keyIdentifier ) childScope [ keyIdentifier ] = key ;
childScope . $index = index ;
childScope . $first = ( index === 0 ) ;
childScope . $last = ( index === ( arrayLength - 1 ) ) ;
childScope . $middle = ! ( childScope . $first || childScope . $last ) ;
// jshint bitwise: false
childScope . $odd = ! ( childScope . $even = ( index & 1 ) === 0 ) ;
// jshint bitwise: true
if ( ! block . scope ) {
$transclude ( childScope , function ( clone ) {
clone [ clone . length ++ ] = document . createComment ( ' end ngRepeat: ' + expression + ' ' ) ;
$animate . enter ( clone , null , jqLite ( previousNode ) ) ;
previousNode = clone ;
block . scope = childScope ;
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
2016-04-18 12:34:29 +00:00
// by a directive with templateUrl when it's template arrives.
2016-03-28 10:46:51 +00:00
block . clone = clone ;
nextBlockMap [ block . id ] = block ;
} ) ;
}
}
lastBlockMap = nextBlockMap ;
} ) ;
}
} ;
2016-04-18 12:34:29 +00:00
function getBlockStart ( block ) {
return block . clone [ 0 ] ;
}
function getBlockEnd ( block ) {
return block . clone [ block . clone . length - 1 ] ;
}
2016-03-28 10:46:51 +00:00
} ] ;
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : ngShow
2016-03-28 10:46:51 +00:00
*
* @ description
* The ` ngShow ` directive shows or hides the given HTML element based on the expression
2016-04-18 12:34:29 +00: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 } ) .
*
2016-04-18 12:34:29 +00:00
* < pre >
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 >
2016-04-18 12:34:29 +00:00
* < / p r e >
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* When the ngShow expression evaluates to false then the ng - hide CSS class is added to the class attribute
* on the element causing it to become hidden . When true , 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 ?
*
2016-04-18 12:34:29 +00: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 .
*
2016-04-18 12:34:29 +00:00
* # # # Overriding . ng - hide
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* 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 :
* < pre >
* . ng - hide {
* //!annotate CSS Specificity|Not to worry, this will override the AngularJS default...
* display : block ! important ;
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* //this is just another form of hiding an element
* position : absolute ;
* top : - 9999 px ;
* left : - 9999 px ;
2016-03-28 10:46:51 +00:00
* }
2016-04-18 12:34:29 +00:00
* < / p r e >
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* Just remember to include the important flag so the CSS override will function .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00: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 .
*
2016-04-18 12:34:29 +00:00
* < pre >
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 {
2016-04-18 12:34:29 +00:00
* transition : 0.5 s linear all ;
* display : block ! important ;
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 { ... }
2016-04-18 12:34:29 +00:00
* < / p r e >
2016-03-28 10:46:51 +00:00
*
* @ animations
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00:00
< example 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" >
2016-04-18 12:34:29 +00:00
< span class = "icon-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" >
2016-04-18 12:34:29 +00:00
< span class = "icon-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 >
< file name = "animations.css" >
. animate - show {
2016-04-18 12:34:29 +00: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
}
2016-04-18 12:34:29 +00:00
. animate - show . ng - hide - add ,
. animate - show . ng - hide - remove {
display : block ! important ;
2016-03-28 10:46:51 +00:00
}
. animate - show . ng - hide {
2016-04-18 12:34:29 +00:00
line - height : 0 ;
opacity : 0 ;
padding : 0 10 px ;
2016-03-28 10:46:51 +00:00
}
. check - element {
2016-04-18 12:34:29 +00:00
padding : 10 px ;
border : 1 px solid black ;
background : white ;
2016-03-28 10:46:51 +00:00
}
< / f i l e >
2016-04-18 12:34:29 +00:00
< file name = "scenario.js" >
it ( 'should check ng-show / ng-hide' , function ( ) {
expect ( element ( '.doc-example-live span:first:hidden' ) . count ( ) ) . toEqual ( 1 ) ;
expect ( element ( '.doc-example-live span:last:visible' ) . count ( ) ) . toEqual ( 1 ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
input ( 'checked' ) . check ( ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
expect ( element ( '.doc-example-live span:first:visible' ) . count ( ) ) . toEqual ( 1 ) ;
expect ( element ( '.doc-example-live span:last:hidden' ) . count ( ) ) . toEqual ( 1 ) ;
} ) ;
2016-03-28 10:46:51 +00:00
< / f i l e >
< / e x a m p l e >
* /
var ngShowDirective = [ '$animate' , function ( $animate ) {
2016-04-18 12:34:29 +00:00
return function ( scope , element , attr ) {
scope . $watch ( attr . ngShow , function ngShowWatchAction ( value ) {
$animate [ toBoolean ( value ) ? 'removeClass' : 'addClass' ] ( element , 'ng-hide' ) ;
} ) ;
2016-03-28 10:46:51 +00:00
} ;
} ] ;
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : ngHide
2016-03-28 10:46:51 +00:00
*
* @ description
* The ` ngHide ` directive shows or hides the given HTML element based on the expression
2016-04-18 12:34:29 +00: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 } ) .
*
2016-04-18 12:34:29 +00:00
* < pre >
2016-03-28 10:46:51 +00:00
* <!-- when $scope . myValue is truthy ( element is hidden ) -- >
2016-04-18 12:34:29 +00:00
* < div ng - hide = "myValue" > < / d i v >
2016-03-28 10:46:51 +00:00
*
* <!-- when $scope . myValue is falsy ( element is visible ) -- >
2016-04-18 12:34:29 +00:00
* < div ng - hide = "myValue" class = "ng-hide" > < / d i v >
* < / p r e >
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* When the ngHide expression evaluates to true then the . ng - hide CSS class is added to the class attribute
* on the element causing it to become hidden . When false , 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 ?
*
2016-04-18 12:34:29 +00: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 .
*
2016-04-18 12:34:29 +00:00
* # # # Overriding . ng - hide
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* 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 :
* < pre >
2016-03-28 10:46:51 +00:00
* . ng - hide {
2016-04-18 12:34:29 +00:00
* //!annotate CSS Specificity|Not to worry, this will override the AngularJS default...
* display : block ! important ;
*
* //this is just another form of hiding an element
* position : absolute ;
* top : - 9999 px ;
* left : - 9999 px ;
2016-03-28 10:46:51 +00:00
* }
2016-04-18 12:34:29 +00:00
* < / p r e >
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* Just remember to include the important flag so the CSS override will function .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00:00
* 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 .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* < pre >
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 {
2016-04-18 12:34:29 +00:00
* transition : 0.5 s linear all ;
* display : block ! important ;
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 { ... }
2016-04-18 12:34:29 +00:00
* < / p r e >
2016-03-28 10:46:51 +00:00
*
* @ animations
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00:00
< example 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" >
2016-04-18 12:34:29 +00:00
< span class = "icon-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" >
2016-04-18 12:34:29 +00:00
< span class = "icon-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 >
< file name = "animations.css" >
. animate - hide {
2016-04-18 12:34:29 +00: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 ;
}
. animate - hide . ng - hide - add ,
. animate - hide . ng - hide - remove {
display : block ! important ;
2016-03-28 10:46:51 +00:00
}
. animate - hide . ng - hide {
2016-04-18 12:34:29 +00:00
line - height : 0 ;
opacity : 0 ;
padding : 0 10 px ;
2016-03-28 10:46:51 +00:00
}
. check - element {
2016-04-18 12:34:29 +00:00
padding : 10 px ;
border : 1 px solid black ;
background : white ;
2016-03-28 10:46:51 +00:00
}
< / f i l e >
2016-04-18 12:34:29 +00:00
< file name = "scenario.js" >
it ( 'should check ng-show / ng-hide' , function ( ) {
expect ( element ( '.doc-example-live .check-element:first:hidden' ) . count ( ) ) . toEqual ( 1 ) ;
expect ( element ( '.doc-example-live .check-element:last:visible' ) . count ( ) ) . toEqual ( 1 ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
input ( 'checked' ) . check ( ) ;
2016-03-28 10:46:51 +00:00
2016-04-18 12:34:29 +00:00
expect ( element ( '.doc-example-live .check-element:first:visible' ) . count ( ) ) . toEqual ( 1 ) ;
expect ( element ( '.doc-example-live .check-element:last:hidden' ) . count ( ) ) . toEqual ( 1 ) ;
} ) ;
2016-03-28 10:46:51 +00:00
< / f i l e >
< / e x a m p l e >
* /
var ngHideDirective = [ '$animate' , function ( $animate ) {
2016-04-18 12:34:29 +00:00
return function ( scope , element , attr ) {
scope . $watch ( attr . ngHide , function ngHideWatchAction ( value ) {
$animate [ toBoolean ( value ) ? 'addClass' : 'removeClass' ] ( element , 'ng-hide' ) ;
} ) ;
2016-03-28 10:46:51 +00:00
} ;
} ] ;
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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
2016-04-18 12:34:29 +00: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 .
2016-03-28 10:46:51 +00:00
*
* @ example
< example >
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
< input type = "button" value = "set" ng - click = "myStyle={color:'red'}" >
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 >
2016-04-18 12:34:29 +00:00
< file name = "scenario.js" >
2016-03-28 10:46:51 +00:00
it ( 'should check ng-style' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( element ( '.doc-example-live span' ) . css ( 'color' ) ) . toBe ( 'rgb(0, 0, 0)' ) ;
element ( '.doc-example-live :button[value=set]' ) . click ( ) ;
expect ( element ( '.doc-example-live span' ) . css ( 'color' ) ) . toBe ( 'rgb(255, 0, 0)' ) ;
element ( '.doc-example-live :button[value=clear]' ) . click ( ) ;
expect ( element ( '.doc-example-live span' ) . css ( 'color' ) ) . toBe ( 'rgb(0, 0, 0)' ) ;
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
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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
2016-04-18 12:34:29 +00:00
* from the template cache ) , ` ngSwitch ` simply choses 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
* < 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 >
*
*
* @ scope
2016-04-18 12:34:29 +00:00
* @ priority 800
* @ param { * } ngSwitch | on expression to match against < tt > ng - switch - when < / t t > .
* @ paramDescription
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
2016-04-18 12:34:29 +00:00
< example animations = "true" >
2016-03-28 10:46:51 +00:00
< file name = "index.html" >
2016-04-18 12:34:29 +00:00
< div ng - controller = "Ctrl" >
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 >
2016-04-18 12:34:29 +00:00
< tt > selection = { { selection } } < / t t >
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" >
2016-04-18 12:34:29 +00:00
function Ctrl ( $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 >
2016-04-18 12:34:29 +00:00
< file name = "scenario.js" >
2016-03-28 10:46:51 +00:00
it ( 'should start in settings' , function ( ) {
2016-04-18 12:34:29 +00:00
expect ( element ( '.doc-example-live [ng-switch]' ) . text ( ) ) . toMatch ( /Settings Div/ ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should change to home' , function ( ) {
2016-04-18 12:34:29 +00:00
select ( 'selection' ) . option ( 'home' ) ;
expect ( element ( '.doc-example-live [ng-switch]' ) . text ( ) ) . toMatch ( /Home Span/ ) ;
2016-03-28 10:46:51 +00:00
} ) ;
it ( 'should select default' , function ( ) {
2016-04-18 12:34:29 +00:00
select ( 'selection' ) . option ( 'other' ) ;
expect ( element ( '.doc-example-live [ng-switch]' ) . text ( ) ) . 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 ,
2016-04-18 12:34:29 +00:00
selectedTranscludes ,
selectedElements ,
2016-03-28 10:46:51 +00:00
selectedScopes = [ ] ;
scope . $watch ( watchExpr , function ngSwitchWatchAction ( value ) {
2016-04-18 12:34:29 +00:00
for ( var i = 0 , ii = selectedScopes . length ; i < ii ; i ++ ) {
2016-03-28 10:46:51 +00:00
selectedScopes [ i ] . $destroy ( ) ;
2016-04-18 12:34:29 +00:00
$animate . leave ( selectedElements [ i ] ) ;
2016-03-28 10:46:51 +00:00
}
2016-04-18 12:34:29 +00:00
selectedElements = [ ] ;
selectedScopes = [ ] ;
2016-03-28 10:46:51 +00:00
if ( ( selectedTranscludes = ngSwitchController . cases [ '!' + value ] || ngSwitchController . cases [ '?' ] ) ) {
2016-04-18 12:34:29 +00:00
scope . $eval ( attr . change ) ;
2016-03-28 10:46:51 +00:00
forEach ( selectedTranscludes , function ( selectedTransclude ) {
2016-04-18 12:34:29 +00:00
var selectedScope = scope . $new ( ) ;
selectedScopes . push ( selectedScope ) ;
selectedTransclude . transclude ( selectedScope , function ( caseElement ) {
2016-03-28 10:46:51 +00:00
var anchor = selectedTransclude . element ;
2016-04-18 12:34:29 +00:00
selectedElements . push ( caseElement ) ;
2016-03-28 10:46:51 +00:00
$animate . enter ( caseElement , anchor . parent ( ) , anchor ) ;
} ) ;
} ) ;
}
} ) ;
}
} ;
} ] ;
var ngSwitchWhenDirective = ngDirective ( {
transclude : 'element' ,
2016-04-18 12:34:29 +00:00
priority : 800 ,
2016-03-28 10:46:51 +00:00
require : '^ngSwitch' ,
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' ,
2016-04-18 12:34:29 +00:00
priority : 800 ,
2016-03-28 10:46:51 +00:00
require : '^ngSwitch' ,
link : function ( scope , element , attr , ctrl , $transclude ) {
ctrl . cases [ '?' ] = ( ctrl . cases [ '?' ] || [ ] ) ;
ctrl . cases [ '?' ] . push ( { transclude : $transclude , element : element } ) ;
}
} ) ;
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : ngTransclude
* @ restrict AC
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
2016-04-18 12:34:29 +00:00
< doc : example module = "transclude" >
< doc : source >
< script >
function Ctrl ( $scope ) {
$scope . title = 'Lorem Ipsum' ;
$scope . text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...' ;
}
angular . module ( 'transclude' , [ ] )
. directive ( 'pane' , function ( ) {
return {
restrict : 'E' ,
transclude : true ,
scope : { title : '@' } ,
template : '<div style="border: 1px solid black;">' +
'<div style="background-color: gray">{{title}}</div>' +
'<div ng-transclude></div>' +
'</div>'
} ;
} ) ;
< / s c r i p t >
< div ng - controller = "Ctrl" >
< input ng - model = "title" > < br >
< textarea ng - model = "text" > < /textarea> <br/ >
< pane title = "{{title}}" > { { text } } < / p a n e >
< / d i v >
< / d o c : s o u r c e >
< doc : scenario >
it ( 'should have transcluded' , function ( ) {
input ( 'title' ) . enter ( 'TITLE' ) ;
input ( 'text' ) . enter ( 'TEXT' ) ;
expect ( binding ( 'title' ) ) . toEqual ( 'TITLE' ) ;
expect ( binding ( 'text' ) ) . toEqual ( 'TEXT' ) ;
} ) ;
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
2016-03-28 10:46:51 +00:00
*
* /
var ngTranscludeDirective = ngDirective ( {
2016-04-18 12:34:29 +00:00
controller : [ '$element' , '$transclude' , function ( $element , $transclude ) {
2016-03-28 10:46:51 +00:00
if ( ! $transclude ) {
2016-04-18 12:34:29 +00:00
throw minErr ( 'ngTransclude' ) ( 'orphan' ,
'Illegal use of ngTransclude directive in the template! ' +
'No parent directive that requires a transclusion found. ' +
'Element: {0}' ,
startingTag ( $element ) ) ;
}
// remember the transclusion fn but call it during linking so that we don't process transclusion before directives on
// the parent element even when the transclusion replaces the current element. (we can't use priority here because
// that applies only to compile fns and not controllers
this . $transclude = $transclude ;
} ] ,
link : function ( $scope , $element , $attrs , controller ) {
controller . $transclude ( function ( clone ) {
$element . empty ( ) ;
$element . append ( clone ) ;
} ) ;
2016-03-28 10:46:51 +00:00
}
} ) ;
/ * *
* @ ngdoc directive
2016-04-18 12:34:29 +00:00
* @ name ng . directive : script
2016-03-28 10:46:51 +00:00
* @ restrict E
*
* @ description
2016-04-18 12:34:29 +00:00
* Load the content of a ` <script> ` element into { @ link api / ng . $templateCache ` $ templateCache ` } , so that the
* template can be used by { @ link api / ng . directive : ngInclude ` ngInclude ` } ,
* { @ link api / 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 ` .
*
2016-04-18 12:34:29 +00:00
* @ param { 'text/ng-template' } 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
2016-04-18 12:34:29 +00:00
< doc : example >
< doc : source >
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 >
2016-04-18 12:34:29 +00:00
< / d o c : s o u r c e >
< doc : scenario >
2016-03-28 10:46:51 +00:00
it ( 'should load template defined inside script tag' , function ( ) {
2016-04-18 12:34:29 +00:00
element ( '#tpl-link' ) . click ( ) ;
expect ( element ( '#tpl-content' ) . text ( ) ) . toMatch ( /Content of the template/ ) ;
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
< / d o c : s c e n a r i o >
< / d o c : 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 ,
2016-04-18 12:34:29 +00:00
// IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent
2016-03-28 10:46:51 +00:00
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
2016-04-18 12:34:29 +00:00
* @ name ng . directive : 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
* ` ngOptions ` comprehension _expression .
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 .
*
2016-04-18 12:34:29 +00:00
* Note : ` ngOptions ` provides iterator facility for ` <option> ` element which should be used instead
* of { @ link ng . directive : ngRepeat ngRepeat } when you want the
* ` select ` model 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
*
* @ 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 `
* * ` label ` * * ` group by ` * * ` group ` * * ` for ` * * ` value ` * * ` in ` * * ` array `
* * ` select ` * * ` as ` * * ` label ` * * ` group by ` * * ` group ` * * ` for ` * * ` value ` * * ` in ` * * ` array ` * * ` track by ` * * ` trackexpr `
* * 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
* ` value ` variable ( e . g . ` value.propertyName ` ) .
2016-03-28 10:46:51 +00:00
*
2016-04-18 12:34:29 +00:00
* @ example
< doc : example >
< doc : source >
< script >
function MyCntrl ( $scope ) {
$scope . colors = [
{ name : 'black' , shade : 'dark' } ,
{ name : 'white' , shade : 'light' } ,
{ name : 'red' , shade : 'dark' } ,
{ name : 'blue' , shade : 'dark' } ,
{ name : 'yellow' , shade : 'light' }
] ;
$scope . color = $scope . colors [ 2 ] ; // red
}
< / s c r i p t >
< div ng - controller = "MyCntrl" >
< 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 ) :
< select ng - model = "color" ng - options = "c.name for c in colors" > < / s e l e c t > < b r >
Color ( null allowed ) :
< span class = "nullable" >
< select ng - model = "color" ng - options = "c.name for c in colors" >
< option value = "" > -- choose color -- < / o p t i o n >
< / s e l e c t >
< /span><br/ >
Color grouped by shade :
< select ng - model = "color" ng - options = "c.name group by c.shade for c in colors" >
< /select><br/ >
Select < a href ng - click = "color={name:'not in list'}" > bogus < / a > . < b r >
< hr / >
Currently selected : { { { selected _color : color } } }
< div style = "border:solid 1px black; height:20px"
ng - style = "{'background-color':color.name}" >
< / d i v >
< / d i v >
< / d o c : s o u r c e >
< doc : scenario >
it ( 'should check ng-options' , function ( ) {
expect ( binding ( '{selected_color:color}' ) ) . toMatch ( 'red' ) ;
select ( 'color' ) . option ( '0' ) ;
expect ( binding ( '{selected_color:color}' ) ) . toMatch ( 'black' ) ;
using ( '.nullable' ) . select ( 'color' ) . option ( '' ) ;
expect ( binding ( '{selected_color:color}' ) ) . toMatch ( 'null' ) ;
} ) ;
< / d o c : s c e n a r i o >
< / d o c : e x a m p l e >
2016-03-28 10:46:51 +00:00
* /
2016-04-18 12:34:29 +00:00
var ngOptionsDirective = valueFn ( { terminal : true } ) ;
// 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 _ ;
} ;
self . addOption = function ( value ) {
assertNotHasOwnProperty ( value , '"option value"' ) ;
optionsMap [ value ] = true ;
if ( ngModelCtrl . $viewValue == value ) {
$element . val ( value ) ;
if ( unknownOption . parent ( ) ) unknownOption . remove ( ) ;
}
} ;
self . removeOption = function ( value ) {
if ( this . hasOption ( value ) ) {
delete optionsMap [ value ] ;
if ( ngModelCtrl . $viewValue == value ) {
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 ,
// 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
for ( var i = 0 , children = element . children ( ) , ii = children . length ; i < ii ; i ++ ) {
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 {
if ( isUndefined ( viewValue ) && emptyOption ) {
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 ) ) {
lastView = copy ( ctrl . $viewValue ) ;
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 ;
if ( ! ( match = optionsExp . match ( NG _OPTIONS _REGEXP ) ) ) {
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 ] ,
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 ,
// 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
optionGroupsCache = [ [ { element : selectElement , label : '' } ] ] ;
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 ( ) ;
selectElement . on ( 'change' , function ( ) {
scope . $apply ( function ( ) {
var optionGroup ,
collection = valuesFn ( scope ) || [ ] ,
locals = { } ,
key , value , optionElement , index , groupIndex , length , groupLength , trackIndex ;
if ( multiple ) {
value = [ ] ;
for ( groupIndex = 0 , groupLength = optionGroupsCache . length ;
groupIndex < groupLength ;
groupIndex ++ ) {
// list of options for that group. (first item has the parent)
optionGroup = optionGroupsCache [ groupIndex ] ;
for ( index = 1 , length = optionGroup . length ; index < length ; index ++ ) {
if ( ( optionElement = optionGroup [ index ] . element ) [ 0 ] . selected ) {
key = optionElement . val ( ) ;
if ( keyName ) locals [ keyName ] = key ;
if ( trackFn ) {
for ( trackIndex = 0 ; trackIndex < collection . length ; trackIndex ++ ) {
locals [ valueName ] = collection [ trackIndex ] ;
if ( trackFn ( scope , locals ) == key ) break ;
}
} else {
locals [ valueName ] = collection [ key ] ;
}
value . push ( valueFn ( scope , locals ) ) ;
}
}
}
} else {
key = selectElement . val ( ) ;
if ( key == '?' ) {
value = undefined ;
} else if ( key === '' ) {
value = null ;
} else {
if ( trackFn ) {
for ( trackIndex = 0 ; trackIndex < collection . length ; trackIndex ++ ) {
locals [ valueName ] = collection [ trackIndex ] ;
if ( trackFn ( scope , locals ) == key ) {
value = valueFn ( scope , locals ) ;
break ;
}
}
} else {
locals [ valueName ] = collection [ key ] ;
if ( keyName ) locals [ keyName ] = key ;
value = valueFn ( scope , locals ) ;
}
}
}
ctrl . $setViewValue ( value ) ;
} ) ;
} ) ;
ctrl . $render = render ;
// TODO(vojta): can't we optimize this ?
scope . $watch ( render ) ;
function render ( ) {
// Temporary location for the option groups before we render them
var optionGroups = { '' : [ ] } ,
optionGroupNames = [ '' ] ,
optionGroupName ,
optionGroup ,
option ,
existingParent , existingOptions , existingOption ,
modelValue = ctrl . $modelValue ,
values = valuesFn ( scope ) || [ ] ,
keys = keyName ? sortedKeys ( values ) : values ,
key ,
groupLength , length ,
groupIndex , index ,
locals = { } ,
selected ,
selectedSet = false , // nothing is selected yet
lastElement ,
element ,
label ;
if ( multiple ) {
if ( trackFn && isArray ( modelValue ) ) {
selectedSet = new HashMap ( [ ] ) ;
for ( var trackIndex = 0 ; trackIndex < modelValue . length ; trackIndex ++ ) {
locals [ valueName ] = modelValue [ trackIndex ] ;
selectedSet . put ( trackFn ( scope , locals ) , modelValue [ trackIndex ] ) ;
}
} else {
selectedSet = new HashMap ( modelValue ) ;
}
}
// We now build up the list of options we need (we merge later)
for ( index = 0 ; length = keys . length , index < length ; index ++ ) {
2016-05-18 00:10:50 +00:00
2016-04-18 12:34:29 +00:00
key = index ;
if ( keyName ) {
key = keys [ index ] ;
if ( key . charAt ( 0 ) === '$' ) continue ;
locals [ keyName ] = key ;
}
locals [ valueName ] = values [ key ] ;
optionGroupName = groupByFn ( scope , locals ) || '' ;
if ( ! ( optionGroup = optionGroups [ optionGroupName ] ) ) {
optionGroup = optionGroups [ optionGroupName ] = [ ] ;
optionGroupNames . push ( optionGroupName ) ;
}
if ( multiple ) {
selected = isDefined (
selectedSet . remove ( trackFn ? trackFn ( scope , locals ) : valueFn ( scope , locals ) )
) ;
} else {
if ( trackFn ) {
var modelCast = { } ;
modelCast [ valueName ] = modelValue ;
selected = trackFn ( scope , modelCast ) === trackFn ( scope , locals ) ;
} else {
selected = modelValue === valueFn ( scope , locals ) ;
}
selectedSet = selectedSet || selected ; // see if at least one item is selected
}
label = displayFn ( scope , locals ) ; // what will be seen by the user
// doing displayFn(scope, locals) || '' overwrites zero values
label = isDefined ( label ) ? label : '' ;
optionGroup . push ( {
// either the index into array or key from object
id : trackFn ? trackFn ( scope , locals ) : ( keyName ? keys [ index ] : index ) ,
label : label ,
selected : selected // determine if we should be selected
} ) ;
}
if ( ! multiple ) {
if ( nullOption || modelValue === null ) {
// insert null option if we have a placeholder, or the model is null
optionGroups [ '' ] . unshift ( { id : '' , label : '' , selected : ! selectedSet } ) ;
} else if ( ! selectedSet ) {
// 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
for ( index = 0 , length = optionGroup . length ; index < length ; index ++ ) {
option = optionGroup [ index ] ;
if ( ( existingOption = existingOptions [ index + 1 ] ) ) {
// reuse elements
lastElement = existingOption . element ;
if ( existingOption . label !== option . label ) {
lastElement . text ( existingOption . label = option . label ) ;
}
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 ) ) ;
}
} 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 )
. attr ( 'selected' , option . selected )
. 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
} ) ;
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
while ( existingOptions . length > index ) {
existingOptions . pop ( ) . element . remove ( ) ;
}
}
// remove any excessive OPTGROUPs from select
while ( optionGroupsCache . length > groupIndex ) {
optionGroupsCache . pop ( ) [ 0 ] . element . remove ( ) ;
}
}
}
}
} ;
} ] ;
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 ( ) ) ;
}
}
2016-04-18 12:34:29 +00: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
2016-04-18 12:34:29 +00:00
if ( selectCtrl && selectCtrl . databound ) {
// For some reason Opera defaults to true and if not overridden this messes up the repeater.
// We don't want the view to drive the initialization of the model anyway.
element . prop ( 'selected' , false ) ;
} else {
selectCtrl = nullSelectCtrl ;
}
if ( interpolateFn ) {
scope . $watch ( interpolateFn , function interpolateWatchAction ( newVal , oldVal ) {
attr . $set ( 'value' , newVal ) ;
if ( newVal !== oldVal ) selectCtrl . removeOption ( oldVal ) ;
selectCtrl . addOption ( newVal ) ;
} ) ;
} else {
selectCtrl . addOption ( attr . value ) ;
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' ,
2016-04-18 12:34:29 +00:00
terminal : true
2016-03-28 10:46:51 +00:00
} ) ;
2016-04-18 12:34:29 +00:00
//try to bind to jquery now so that one can write angular.element().read()
//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 ) ;
2016-05-18 00:10:50 +00:00
! angular . $$csp ( ) && angular . element ( document ) . find ( '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{display:none !important;}ng\\:form{display:block;}</style>' ) ;