Update XStatic-Angular to 1.4.10
Change-Id: Ie61a6282f792a8721674014dc11f2e2a2ec1245b
This commit is contained in:
parent
aee8cd393d
commit
65cde9c212
|
@ -11,9 +11,9 @@ NAME = __name__.split('.')[-1] # package name (e.g. 'foo' or 'foo_bar')
|
|||
# please use a all-lowercase valid python
|
||||
# package name
|
||||
|
||||
VERSION = '1.3.18' # version of the packaged files, please use the upstream
|
||||
VERSION = '1.4.10' # version of the packaged files, please use the upstream
|
||||
# version number
|
||||
BUILD = '1' # our package build number, so we can release new builds
|
||||
BUILD = '0' # our package build number, so we can release new builds
|
||||
# with fixes for xstatic stuff.
|
||||
PACKAGE_VERSION = VERSION + '.' + BUILD # version used for PyPi
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* @license AngularJS v1.3.18
|
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* @license AngularJS v1.4.10
|
||||
* (c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, angular, undefined) {'use strict';
|
||||
|
@ -19,8 +19,8 @@
|
|||
*
|
||||
* ## Usage
|
||||
*
|
||||
* For ngAria to do its magic, simply include the module as a dependency. The directives supported
|
||||
* by ngAria are:
|
||||
* For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following
|
||||
* directives are supported:
|
||||
* `ngModel`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`, `ngDblClick`, and `ngMessages`.
|
||||
*
|
||||
* Below is a more detailed breakdown of the attributes handled by ngAria:
|
||||
|
@ -57,6 +57,16 @@
|
|||
var ngAriaModule = angular.module('ngAria', ['ng']).
|
||||
provider('$aria', $AriaProvider);
|
||||
|
||||
/**
|
||||
* Internal Utilities
|
||||
*/
|
||||
var nodeBlackList = ['BUTTON', 'A', 'INPUT', 'TEXTAREA', 'SELECT', 'DETAILS', 'SUMMARY'];
|
||||
|
||||
var isNodeOneOf = function(elem, nodeTypeArray) {
|
||||
if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @ngdoc provider
|
||||
* @name $ariaProvider
|
||||
|
@ -88,7 +98,8 @@ function $AriaProvider() {
|
|||
ariaMultiline: true,
|
||||
ariaValue: true,
|
||||
tabindex: true,
|
||||
bindKeypress: true
|
||||
bindKeypress: true,
|
||||
bindRoleForClick: true
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -107,6 +118,8 @@ function $AriaProvider() {
|
|||
* - **tabindex** – `{boolean}` – Enables/disables tabindex tags
|
||||
* - **bindKeypress** – `{boolean}` – Enables/disables keypress event binding on `<div>` and
|
||||
* `<li>` elements with ng-click
|
||||
* - **bindRoleForClick** – `{boolean}` – Adds role=button to non-interactive elements like `div`
|
||||
* using ng-click, making them more accessible to users of assistive technologies
|
||||
*
|
||||
* @description
|
||||
* Enables/disables various ARIA attributes
|
||||
|
@ -115,20 +128,18 @@ function $AriaProvider() {
|
|||
config = angular.extend(config, newConfig);
|
||||
};
|
||||
|
||||
function watchExpr(attrName, ariaAttr, negate) {
|
||||
function watchExpr(attrName, ariaAttr, nodeBlackList, negate) {
|
||||
return function(scope, elem, attr) {
|
||||
var ariaCamelName = attr.$normalize(ariaAttr);
|
||||
if (config[ariaCamelName] && !attr[ariaCamelName]) {
|
||||
if (config[ariaCamelName] && !isNodeOneOf(elem, nodeBlackList) && !attr[ariaCamelName]) {
|
||||
scope.$watch(attr[attrName], function(boolVal) {
|
||||
if (negate) {
|
||||
boolVal = !boolVal;
|
||||
}
|
||||
// ensure boolean value
|
||||
boolVal = negate ? !boolVal : !!boolVal;
|
||||
elem.attr(ariaAttr, boolVal);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $aria
|
||||
|
@ -187,10 +198,10 @@ function $AriaProvider() {
|
|||
|
||||
|
||||
ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
return $aria.$$watchExpr('ngShow', 'aria-hidden', true);
|
||||
return $aria.$$watchExpr('ngShow', 'aria-hidden', [], true);
|
||||
}])
|
||||
.directive('ngHide', ['$aria', function($aria) {
|
||||
return $aria.$$watchExpr('ngHide', 'aria-hidden', false);
|
||||
return $aria.$$watchExpr('ngHide', 'aria-hidden', [], false);
|
||||
}])
|
||||
.directive('ngModel', ['$aria', function($aria) {
|
||||
|
||||
|
@ -229,7 +240,8 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
|||
}
|
||||
},
|
||||
post: function(scope, elem, attr, ngModel) {
|
||||
var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem);
|
||||
var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem)
|
||||
&& !isNodeOneOf(elem, nodeBlackList);
|
||||
|
||||
function ngAriaWatchModelValue() {
|
||||
return ngModel.$modelValue;
|
||||
|
@ -264,24 +276,40 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
|||
scope.$watch(ngAriaWatchModelValue, shape === 'radio' ?
|
||||
getRadioReaction() : ngAriaCheckboxReaction);
|
||||
}
|
||||
if (needsTabIndex) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
break;
|
||||
case 'range':
|
||||
if (shouldAttachRole(shape, elem)) {
|
||||
elem.attr('role', 'slider');
|
||||
}
|
||||
if ($aria.config('ariaValue')) {
|
||||
if (attr.min && !elem.attr('aria-valuemin')) {
|
||||
elem.attr('aria-valuemin', attr.min);
|
||||
var needsAriaValuemin = !elem.attr('aria-valuemin') &&
|
||||
(attr.hasOwnProperty('min') || attr.hasOwnProperty('ngMin'));
|
||||
var needsAriaValuemax = !elem.attr('aria-valuemax') &&
|
||||
(attr.hasOwnProperty('max') || attr.hasOwnProperty('ngMax'));
|
||||
var needsAriaValuenow = !elem.attr('aria-valuenow');
|
||||
|
||||
if (needsAriaValuemin) {
|
||||
attr.$observe('min', function ngAriaValueMinReaction(newVal) {
|
||||
elem.attr('aria-valuemin', newVal);
|
||||
});
|
||||
}
|
||||
if (attr.max && !elem.attr('aria-valuemax')) {
|
||||
elem.attr('aria-valuemax', attr.max);
|
||||
if (needsAriaValuemax) {
|
||||
attr.$observe('max', function ngAriaValueMinReaction(newVal) {
|
||||
elem.attr('aria-valuemax', newVal);
|
||||
});
|
||||
}
|
||||
if (!elem.attr('aria-valuenow')) {
|
||||
if (needsAriaValuenow) {
|
||||
scope.$watch(ngAriaWatchModelValue, function ngAriaValueNowReaction(newVal) {
|
||||
elem.attr('aria-valuenow', newVal);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (needsTabIndex) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
break;
|
||||
case 'multiline':
|
||||
if (shouldAttachAttr('aria-multiline', 'ariaMultiline', elem)) {
|
||||
|
@ -290,10 +318,6 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
|||
break;
|
||||
}
|
||||
|
||||
if (needsTabIndex) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
|
||||
if (ngModel.$validators.required && shouldAttachAttr('aria-required', 'ariaRequired', elem)) {
|
||||
scope.$watch(function ngAriaRequiredWatch() {
|
||||
return ngModel.$error.required;
|
||||
|
@ -315,7 +339,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
|||
};
|
||||
}])
|
||||
.directive('ngDisabled', ['$aria', function($aria) {
|
||||
return $aria.$$watchExpr('ngDisabled', 'aria-disabled');
|
||||
return $aria.$$watchExpr('ngDisabled', 'aria-disabled', []);
|
||||
}])
|
||||
.directive('ngMessages', function() {
|
||||
return {
|
||||
|
@ -335,31 +359,28 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
|||
var fn = $parse(attr.ngClick, /* interceptorFn */ null, /* expensiveChecks */ true);
|
||||
return function(scope, elem, attr) {
|
||||
|
||||
var nodeBlackList = ['BUTTON', 'A', 'INPUT', 'TEXTAREA'];
|
||||
if (!isNodeOneOf(elem, nodeBlackList)) {
|
||||
|
||||
function isNodeOneOf(elem, nodeTypeArray) {
|
||||
if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) {
|
||||
return true;
|
||||
if ($aria.config('bindRoleForClick') && !elem.attr('role')) {
|
||||
elem.attr('role', 'button');
|
||||
}
|
||||
}
|
||||
if (!elem.attr('role') && !isNodeOneOf(elem, nodeBlackList)) {
|
||||
elem.attr('role', 'button');
|
||||
}
|
||||
|
||||
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
|
||||
if ($aria.config('bindKeypress') && !attr.ngKeypress && !isNodeOneOf(elem, nodeBlackList)) {
|
||||
elem.on('keypress', function(event) {
|
||||
if (event.keyCode === 32 || event.keyCode === 13) {
|
||||
scope.$apply(callback);
|
||||
}
|
||||
if ($aria.config('bindKeypress') && !attr.ngKeypress) {
|
||||
elem.on('keypress', function(event) {
|
||||
var keyCode = event.which || event.keyCode;
|
||||
if (keyCode === 32 || keyCode === 13) {
|
||||
scope.$apply(callback);
|
||||
}
|
||||
|
||||
function callback() {
|
||||
fn(scope, { $event: event });
|
||||
}
|
||||
});
|
||||
function callback() {
|
||||
fn(scope, { $event: event });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -367,7 +388,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
|||
}])
|
||||
.directive('ngDblclick', ['$aria', function($aria) {
|
||||
return function(scope, elem, attr) {
|
||||
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
|
||||
if ($aria.config('tabindex') && !elem.attr('tabindex') && !isNodeOneOf(elem, nodeBlackList)) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* @license AngularJS v1.3.18
|
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* @license AngularJS v1.4.10
|
||||
* (c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, angular, undefined) {'use strict';
|
||||
|
@ -17,191 +17,306 @@
|
|||
*
|
||||
* <div doc-module-components="ngCookies"></div>
|
||||
*
|
||||
* See {@link ngCookies.$cookies `$cookies`} and
|
||||
* {@link ngCookies.$cookieStore `$cookieStore`} for usage.
|
||||
* See {@link ngCookies.$cookies `$cookies`} for usage.
|
||||
*/
|
||||
|
||||
|
||||
angular.module('ngCookies', ['ng']).
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $cookies
|
||||
*
|
||||
* @ngdoc provider
|
||||
* @name $cookiesProvider
|
||||
* @description
|
||||
* Provides read/write access to browser's cookies.
|
||||
*
|
||||
* Only a simple Object is exposed and by adding or removing properties to/from this object, new
|
||||
* cookies are created/deleted at the end of current $eval.
|
||||
* The object's properties can only be strings.
|
||||
*
|
||||
* Requires the {@link ngCookies `ngCookies`} module to be installed.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```js
|
||||
* angular.module('cookiesExample', ['ngCookies'])
|
||||
* .controller('ExampleController', ['$cookies', function($cookies) {
|
||||
* // Retrieving a cookie
|
||||
* var favoriteCookie = $cookies.myFavorite;
|
||||
* // Setting a cookie
|
||||
* $cookies.myFavorite = 'oatmeal';
|
||||
* }]);
|
||||
* ```
|
||||
*/
|
||||
factory('$cookies', ['$rootScope', '$browser', function($rootScope, $browser) {
|
||||
var cookies = {},
|
||||
lastCookies = {},
|
||||
lastBrowserCookies,
|
||||
runEval = false,
|
||||
copy = angular.copy,
|
||||
isUndefined = angular.isUndefined;
|
||||
* Use `$cookiesProvider` to change the default behavior of the {@link ngCookies.$cookies $cookies} service.
|
||||
* */
|
||||
provider('$cookies', [function $CookiesProvider() {
|
||||
/**
|
||||
* @ngdoc property
|
||||
* @name $cookiesProvider#defaults
|
||||
* @description
|
||||
*
|
||||
* Object containing default options to pass when setting cookies.
|
||||
*
|
||||
* The object may have following properties:
|
||||
*
|
||||
* - **path** - `{string}` - The cookie will be available only for this path and its
|
||||
* sub-paths. By default, this is the URL that appears in your `<base>` tag.
|
||||
* - **domain** - `{string}` - The cookie will be available only for this domain and
|
||||
* its sub-domains. For security reasons the user agent will not accept the cookie
|
||||
* if the current domain is not a sub-domain of this domain or equal to it.
|
||||
* - **expires** - `{string|Date}` - String of the form "Wdy, DD Mon YYYY HH:MM:SS GMT"
|
||||
* or a Date object indicating the exact date/time this cookie will expire.
|
||||
* - **secure** - `{boolean}` - If `true`, then the cookie will only be available through a
|
||||
* secured connection.
|
||||
*
|
||||
* Note: By default, the address that appears in your `<base>` tag will be used as the path.
|
||||
* This is important so that cookies will be visible for all routes when html5mode is enabled.
|
||||
*
|
||||
**/
|
||||
var defaults = this.defaults = {};
|
||||
|
||||
//creates a poller fn that copies all cookies from the $browser to service & inits the service
|
||||
$browser.addPollFn(function() {
|
||||
var currentCookies = $browser.cookies();
|
||||
if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl
|
||||
lastBrowserCookies = currentCookies;
|
||||
copy(currentCookies, lastCookies);
|
||||
copy(currentCookies, cookies);
|
||||
if (runEval) $rootScope.$apply();
|
||||
}
|
||||
})();
|
||||
|
||||
runEval = true;
|
||||
|
||||
//at the end of each eval, push cookies
|
||||
//TODO: this should happen before the "delayed" watches fire, because if some cookies are not
|
||||
// strings or browser refuses to store some cookies, we update the model in the push fn.
|
||||
$rootScope.$watch(push);
|
||||
|
||||
return cookies;
|
||||
|
||||
|
||||
/**
|
||||
* Pushes all the cookies from the service to the browser and verifies if all cookies were
|
||||
* stored.
|
||||
*/
|
||||
function push() {
|
||||
var name,
|
||||
value,
|
||||
browserCookies,
|
||||
updated;
|
||||
|
||||
//delete any cookies deleted in $cookies
|
||||
for (name in lastCookies) {
|
||||
if (isUndefined(cookies[name])) {
|
||||
$browser.cookies(name, undefined);
|
||||
delete lastCookies[name];
|
||||
}
|
||||
}
|
||||
|
||||
//update all cookies updated in $cookies
|
||||
for (name in cookies) {
|
||||
value = cookies[name];
|
||||
if (!angular.isString(value)) {
|
||||
value = '' + value;
|
||||
cookies[name] = value;
|
||||
}
|
||||
if (value !== lastCookies[name]) {
|
||||
$browser.cookies(name, value);
|
||||
lastCookies[name] = value;
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
//verify what was actually stored
|
||||
if (updated) {
|
||||
browserCookies = $browser.cookies();
|
||||
|
||||
for (name in cookies) {
|
||||
if (cookies[name] !== browserCookies[name]) {
|
||||
//delete or reset all cookies that the browser dropped from $cookies
|
||||
if (isUndefined(browserCookies[name])) {
|
||||
delete cookies[name];
|
||||
delete lastCookies[name];
|
||||
} else {
|
||||
cookies[name] = lastCookies[name] = browserCookies[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}]).
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $cookieStore
|
||||
* @requires $cookies
|
||||
*
|
||||
* @description
|
||||
* Provides a key-value (string-object) storage, that is backed by session cookies.
|
||||
* Objects put or retrieved from this storage are automatically serialized or
|
||||
* deserialized by angular's toJson/fromJson.
|
||||
*
|
||||
* Requires the {@link ngCookies `ngCookies`} module to be installed.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```js
|
||||
* angular.module('cookieStoreExample', ['ngCookies'])
|
||||
* .controller('ExampleController', ['$cookieStore', function($cookieStore) {
|
||||
* // Put cookie
|
||||
* $cookieStore.put('myFavorite','oatmeal');
|
||||
* // Get cookie
|
||||
* var favoriteCookie = $cookieStore.get('myFavorite');
|
||||
* // Removing a cookie
|
||||
* $cookieStore.remove('myFavorite');
|
||||
* }]);
|
||||
* ```
|
||||
*/
|
||||
factory('$cookieStore', ['$cookies', function($cookies) {
|
||||
function calcOptions(options) {
|
||||
return options ? angular.extend({}, defaults, options) : defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $cookies
|
||||
*
|
||||
* @description
|
||||
* Provides read/write access to browser's cookies.
|
||||
*
|
||||
* <div class="alert alert-info">
|
||||
* Up until Angular 1.3, `$cookies` exposed properties that represented the
|
||||
* current browser cookie values. In version 1.4, this behavior has changed, and
|
||||
* `$cookies` now provides a standard api of getters, setters etc.
|
||||
* </div>
|
||||
*
|
||||
* Requires the {@link ngCookies `ngCookies`} module to be installed.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```js
|
||||
* angular.module('cookiesExample', ['ngCookies'])
|
||||
* .controller('ExampleController', ['$cookies', function($cookies) {
|
||||
* // Retrieving a cookie
|
||||
* var favoriteCookie = $cookies.get('myFavorite');
|
||||
* // Setting a cookie
|
||||
* $cookies.put('myFavorite', 'oatmeal');
|
||||
* }]);
|
||||
* ```
|
||||
*/
|
||||
this.$get = ['$$cookieReader', '$$cookieWriter', function($$cookieReader, $$cookieWriter) {
|
||||
return {
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $cookieStore#get
|
||||
* @name $cookies#get
|
||||
*
|
||||
* @description
|
||||
* Returns the value of given cookie key
|
||||
*
|
||||
* @param {string} key Id to use for lookup.
|
||||
* @returns {Object} Deserialized cookie value.
|
||||
* @returns {string} Raw cookie value.
|
||||
*/
|
||||
get: function(key) {
|
||||
var value = $cookies[key];
|
||||
return $$cookieReader()[key];
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $cookies#getObject
|
||||
*
|
||||
* @description
|
||||
* Returns the deserialized value of given cookie key
|
||||
*
|
||||
* @param {string} key Id to use for lookup.
|
||||
* @returns {Object} Deserialized cookie value.
|
||||
*/
|
||||
getObject: function(key) {
|
||||
var value = this.get(key);
|
||||
return value ? angular.fromJson(value) : value;
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $cookieStore#put
|
||||
* @name $cookies#getAll
|
||||
*
|
||||
* @description
|
||||
* Returns a key value object with all the cookies
|
||||
*
|
||||
* @returns {Object} All cookies
|
||||
*/
|
||||
getAll: function() {
|
||||
return $$cookieReader();
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $cookies#put
|
||||
*
|
||||
* @description
|
||||
* Sets a value for given cookie key
|
||||
*
|
||||
* @param {string} key Id for the `value`.
|
||||
* @param {Object} value Value to be stored.
|
||||
* @param {string} value Raw value to be stored.
|
||||
* @param {Object=} options Options object.
|
||||
* See {@link ngCookies.$cookiesProvider#defaults $cookiesProvider.defaults}
|
||||
*/
|
||||
put: function(key, value) {
|
||||
$cookies[key] = angular.toJson(value);
|
||||
put: function(key, value, options) {
|
||||
$$cookieWriter(key, value, calcOptions(options));
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $cookieStore#remove
|
||||
* @name $cookies#putObject
|
||||
*
|
||||
* @description
|
||||
* Serializes and sets a value for given cookie key
|
||||
*
|
||||
* @param {string} key Id for the `value`.
|
||||
* @param {Object} value Value to be stored.
|
||||
* @param {Object=} options Options object.
|
||||
* See {@link ngCookies.$cookiesProvider#defaults $cookiesProvider.defaults}
|
||||
*/
|
||||
putObject: function(key, value, options) {
|
||||
this.put(key, angular.toJson(value), options);
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $cookies#remove
|
||||
*
|
||||
* @description
|
||||
* Remove given cookie
|
||||
*
|
||||
* @param {string} key Id of the key-value pair to delete.
|
||||
* @param {Object=} options Options object.
|
||||
* See {@link ngCookies.$cookiesProvider#defaults $cookiesProvider.defaults}
|
||||
*/
|
||||
remove: function(key) {
|
||||
delete $cookies[key];
|
||||
remove: function(key, options) {
|
||||
$$cookieWriter(key, undefined, calcOptions(options));
|
||||
}
|
||||
};
|
||||
}];
|
||||
}]);
|
||||
|
||||
}]);
|
||||
angular.module('ngCookies').
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $cookieStore
|
||||
* @deprecated
|
||||
* @requires $cookies
|
||||
*
|
||||
* @description
|
||||
* Provides a key-value (string-object) storage, that is backed by session cookies.
|
||||
* Objects put or retrieved from this storage are automatically serialized or
|
||||
* deserialized by angular's toJson/fromJson.
|
||||
*
|
||||
* Requires the {@link ngCookies `ngCookies`} module to be installed.
|
||||
*
|
||||
* <div class="alert alert-danger">
|
||||
* **Note:** The $cookieStore service is **deprecated**.
|
||||
* Please use the {@link ngCookies.$cookies `$cookies`} service instead.
|
||||
* </div>
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```js
|
||||
* angular.module('cookieStoreExample', ['ngCookies'])
|
||||
* .controller('ExampleController', ['$cookieStore', function($cookieStore) {
|
||||
* // Put cookie
|
||||
* $cookieStore.put('myFavorite','oatmeal');
|
||||
* // Get cookie
|
||||
* var favoriteCookie = $cookieStore.get('myFavorite');
|
||||
* // Removing a cookie
|
||||
* $cookieStore.remove('myFavorite');
|
||||
* }]);
|
||||
* ```
|
||||
*/
|
||||
factory('$cookieStore', ['$cookies', function($cookies) {
|
||||
|
||||
return {
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $cookieStore#get
|
||||
*
|
||||
* @description
|
||||
* Returns the value of given cookie key
|
||||
*
|
||||
* @param {string} key Id to use for lookup.
|
||||
* @returns {Object} Deserialized cookie value, undefined if the cookie does not exist.
|
||||
*/
|
||||
get: function(key) {
|
||||
return $cookies.getObject(key);
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $cookieStore#put
|
||||
*
|
||||
* @description
|
||||
* Sets a value for given cookie key
|
||||
*
|
||||
* @param {string} key Id for the `value`.
|
||||
* @param {Object} value Value to be stored.
|
||||
*/
|
||||
put: function(key, value) {
|
||||
$cookies.putObject(key, value);
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $cookieStore#remove
|
||||
*
|
||||
* @description
|
||||
* Remove given cookie
|
||||
*
|
||||
* @param {string} key Id of the key-value pair to delete.
|
||||
*/
|
||||
remove: function(key) {
|
||||
$cookies.remove(key);
|
||||
}
|
||||
};
|
||||
|
||||
}]);
|
||||
|
||||
/**
|
||||
* @name $$cookieWriter
|
||||
* @requires $document
|
||||
*
|
||||
* @description
|
||||
* This is a private service for writing cookies
|
||||
*
|
||||
* @param {string} name Cookie name
|
||||
* @param {string=} value Cookie value (if undefined, cookie will be deleted)
|
||||
* @param {Object=} options Object with options that need to be stored for the cookie.
|
||||
*/
|
||||
function $$CookieWriter($document, $log, $browser) {
|
||||
var cookiePath = $browser.baseHref();
|
||||
var rawDocument = $document[0];
|
||||
|
||||
function buildCookieString(name, value, options) {
|
||||
var path, expires;
|
||||
options = options || {};
|
||||
expires = options.expires;
|
||||
path = angular.isDefined(options.path) ? options.path : cookiePath;
|
||||
if (angular.isUndefined(value)) {
|
||||
expires = 'Thu, 01 Jan 1970 00:00:00 GMT';
|
||||
value = '';
|
||||
}
|
||||
if (angular.isString(expires)) {
|
||||
expires = new Date(expires);
|
||||
}
|
||||
|
||||
var str = encodeURIComponent(name) + '=' + encodeURIComponent(value);
|
||||
str += path ? ';path=' + path : '';
|
||||
str += options.domain ? ';domain=' + options.domain : '';
|
||||
str += expires ? ';expires=' + expires.toUTCString() : '';
|
||||
str += options.secure ? ';secure' : '';
|
||||
|
||||
// per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum:
|
||||
// - 300 cookies
|
||||
// - 20 cookies per unique domain
|
||||
// - 4096 bytes per cookie
|
||||
var cookieLength = str.length + 1;
|
||||
if (cookieLength > 4096) {
|
||||
$log.warn("Cookie '" + name +
|
||||
"' possibly not set or overflowed because it was too large (" +
|
||||
cookieLength + " > 4096 bytes)!");
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
return function(name, value, options) {
|
||||
rawDocument.cookie = buildCookieString(name, value, options);
|
||||
};
|
||||
}
|
||||
|
||||
$$CookieWriter.$inject = ['$document', '$log', '$browser'];
|
||||
|
||||
angular.module('ngCookies').provider('$$cookieWriter', function $$CookieWriterProvider() {
|
||||
this.$get = $$CookieWriter;
|
||||
});
|
||||
|
||||
|
||||
})(window, window.angular);
|
||||
|
|
|
@ -11,3 +11,11 @@
|
|||
ng\:form {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ng-animate-shim {
|
||||
visibility:hidden;
|
||||
}
|
||||
|
||||
.ng-anchor {
|
||||
position:absolute;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,39 @@
|
|||
/**
|
||||
* @license AngularJS v1.3.18
|
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* @license AngularJS v1.4.10
|
||||
* (c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
(function() {'use strict';
|
||||
function isFunction(value) {return typeof value === 'function';};
|
||||
|
||||
/* global: toDebugString: true */
|
||||
|
||||
function serializeObject(obj) {
|
||||
var seen = [];
|
||||
|
||||
return JSON.stringify(obj, function(key, val) {
|
||||
val = toJsonReplacer(key, val);
|
||||
if (isObject(val)) {
|
||||
|
||||
if (seen.indexOf(val) >= 0) return '...';
|
||||
|
||||
seen.push(val);
|
||||
}
|
||||
return val;
|
||||
});
|
||||
}
|
||||
|
||||
function toDebugString(obj) {
|
||||
if (typeof obj === 'function') {
|
||||
return obj.toString().replace(/ \{[\s\S]*$/, '');
|
||||
} else if (isUndefined(obj)) {
|
||||
return 'undefined';
|
||||
} else if (typeof obj !== 'string') {
|
||||
return serializeObject(obj);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description
|
||||
|
@ -39,28 +68,33 @@
|
|||
function minErr(module, ErrorConstructor) {
|
||||
ErrorConstructor = ErrorConstructor || Error;
|
||||
return function() {
|
||||
var code = arguments[0],
|
||||
prefix = '[' + (module ? module + ':' : '') + code + '] ',
|
||||
template = arguments[1],
|
||||
templateArgs = arguments,
|
||||
var SKIP_INDEXES = 2;
|
||||
|
||||
message, i;
|
||||
var templateArgs = arguments,
|
||||
code = templateArgs[0],
|
||||
message = '[' + (module ? module + ':' : '') + code + '] ',
|
||||
template = templateArgs[1],
|
||||
paramPrefix, i;
|
||||
|
||||
message = prefix + template.replace(/\{\d+\}/g, function(match) {
|
||||
var index = +match.slice(1, -1), arg;
|
||||
message += template.replace(/\{\d+\}/g, function(match) {
|
||||
var index = +match.slice(1, -1),
|
||||
shiftedIndex = index + SKIP_INDEXES;
|
||||
|
||||
if (index + 2 < templateArgs.length) {
|
||||
return toDebugString(templateArgs[index + 2]);
|
||||
if (shiftedIndex < templateArgs.length) {
|
||||
return toDebugString(templateArgs[shiftedIndex]);
|
||||
}
|
||||
|
||||
return match;
|
||||
});
|
||||
|
||||
message = message + '\nhttp://errors.angularjs.org/1.3.18/' +
|
||||
message += '\nhttp://errors.angularjs.org/1.4.10/' +
|
||||
(module ? module + '/' : '') + code;
|
||||
for (i = 2; i < arguments.length; i++) {
|
||||
message = message + (i == 2 ? '?' : '&') + 'p' + (i - 2) + '=' +
|
||||
encodeURIComponent(toDebugString(arguments[i]));
|
||||
|
||||
for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
|
||||
message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' +
|
||||
encodeURIComponent(toDebugString(templateArgs[i]));
|
||||
}
|
||||
|
||||
return new ErrorConstructor(message);
|
||||
};
|
||||
}
|
||||
|
@ -103,8 +137,8 @@ function setupModuleLoader(window) {
|
|||
* All modules (angular core or 3rd party) that should be available to an application must be
|
||||
* registered using this mechanism.
|
||||
*
|
||||
* 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.
|
||||
* Passing one argument retrieves an existing {@link angular.Module},
|
||||
* whereas passing more than one argument creates a new {@link angular.Module}
|
||||
*
|
||||
*
|
||||
* # Module
|
||||
|
@ -141,7 +175,7 @@ function setupModuleLoader(window) {
|
|||
* unspecified then the module is being retrieved for further configuration.
|
||||
* @param {Function=} configFn Optional configuration function for the module. Same as
|
||||
* {@link angular.Module#config Module#config()}.
|
||||
* @returns {module} new module with the {@link angular.Module} api.
|
||||
* @returns {angular.Module} new module with the {@link angular.Module} api.
|
||||
*/
|
||||
return function module(name, requires, configFn) {
|
||||
var assertNotHasOwnProperty = function(name, context) {
|
||||
|
@ -211,7 +245,7 @@ function setupModuleLoader(window) {
|
|||
* @description
|
||||
* See {@link auto.$provide#provider $provide.provider()}.
|
||||
*/
|
||||
provider: invokeLater('$provide', 'provider'),
|
||||
provider: invokeLaterAndSetModuleName('$provide', 'provider'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
|
@ -222,7 +256,7 @@ function setupModuleLoader(window) {
|
|||
* @description
|
||||
* See {@link auto.$provide#factory $provide.factory()}.
|
||||
*/
|
||||
factory: invokeLater('$provide', 'factory'),
|
||||
factory: invokeLaterAndSetModuleName('$provide', 'factory'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
|
@ -233,7 +267,7 @@ function setupModuleLoader(window) {
|
|||
* @description
|
||||
* See {@link auto.$provide#service $provide.service()}.
|
||||
*/
|
||||
service: invokeLater('$provide', 'service'),
|
||||
service: invokeLaterAndSetModuleName('$provide', 'service'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
|
@ -253,11 +287,23 @@ function setupModuleLoader(window) {
|
|||
* @param {string} name constant name
|
||||
* @param {*} object Constant value.
|
||||
* @description
|
||||
* Because the constant are fixed, they get applied before other provide methods.
|
||||
* Because the constants are fixed, they get applied before other provide methods.
|
||||
* See {@link auto.$provide#constant $provide.constant()}.
|
||||
*/
|
||||
constant: invokeLater('$provide', 'constant', 'unshift'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.Module#decorator
|
||||
* @module ng
|
||||
* @param {string} The name of the service to decorate.
|
||||
* @param {Function} This function will be invoked when the service needs to be
|
||||
* instantiated and should return the decorated service instance.
|
||||
* @description
|
||||
* See {@link auto.$provide#decorator $provide.decorator()}.
|
||||
*/
|
||||
decorator: invokeLaterAndSetModuleName('$provide', 'decorator'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.Module#animation
|
||||
|
@ -271,7 +317,7 @@ function setupModuleLoader(window) {
|
|||
*
|
||||
*
|
||||
* Defines an animation hook that can be later used with
|
||||
* {@link ngAnimate.$animate $animate} service and directives that use this service.
|
||||
* {@link $animate $animate} service and directives that use this service.
|
||||
*
|
||||
* ```js
|
||||
* module.animation('.animation-name', function($inject1, $inject2) {
|
||||
|
@ -290,7 +336,7 @@ function setupModuleLoader(window) {
|
|||
* See {@link ng.$animateProvider#register $animateProvider.register()} and
|
||||
* {@link ngAnimate ngAnimate module} for more information.
|
||||
*/
|
||||
animation: invokeLater('$animateProvider', 'register'),
|
||||
animation: invokeLaterAndSetModuleName('$animateProvider', 'register'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
|
@ -308,7 +354,7 @@ function setupModuleLoader(window) {
|
|||
* (`myapp_subsection_filterx`).
|
||||
* </div>
|
||||
*/
|
||||
filter: invokeLater('$filterProvider', 'register'),
|
||||
filter: invokeLaterAndSetModuleName('$filterProvider', 'register'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
|
@ -320,7 +366,7 @@ function setupModuleLoader(window) {
|
|||
* @description
|
||||
* See {@link ng.$controllerProvider#register $controllerProvider.register()}.
|
||||
*/
|
||||
controller: invokeLater('$controllerProvider', 'register'),
|
||||
controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
|
@ -333,7 +379,7 @@ function setupModuleLoader(window) {
|
|||
* @description
|
||||
* See {@link ng.$compileProvider#directive $compileProvider.directive()}.
|
||||
*/
|
||||
directive: invokeLater('$compileProvider', 'directive'),
|
||||
directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
|
@ -383,6 +429,19 @@ function setupModuleLoader(window) {
|
|||
return moduleInstance;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} provider
|
||||
* @param {string} method
|
||||
* @returns {angular.Module}
|
||||
*/
|
||||
function invokeLaterAndSetModuleName(provider, method) {
|
||||
return function(recipeName, factoryFunction) {
|
||||
if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;
|
||||
invokeQueue.push([provider, method, arguments]);
|
||||
return moduleInstance;
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
|
|
@ -0,0 +1,980 @@
|
|||
/**
|
||||
* @license AngularJS v1.4.10
|
||||
* (c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, angular, undefined) {'use strict';
|
||||
|
||||
// NOTE: ADVANCED_OPTIMIZATIONS mode.
|
||||
//
|
||||
// This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
|
||||
// constructs incompatible with that mode.
|
||||
|
||||
var $interpolateMinErr = window['angular']['$interpolateMinErr'];
|
||||
|
||||
var noop = window['angular']['noop'],
|
||||
isFunction = window['angular']['isFunction'],
|
||||
toJson = window['angular']['toJson'];
|
||||
|
||||
function stringify(value) {
|
||||
if (value == null /* null/undefined */) { return ''; }
|
||||
switch (typeof value) {
|
||||
case 'string': return value;
|
||||
case 'number': return '' + value;
|
||||
default: return toJson(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert an index into the string into line/column for use in error messages
|
||||
// As such, this doesn't have to be efficient.
|
||||
function indexToLineAndColumn(text, index) {
|
||||
var lines = text.split(/\n/g);
|
||||
for (var i=0; i < lines.length; i++) {
|
||||
var line=lines[i];
|
||||
if (index >= line.length) {
|
||||
index -= line.length;
|
||||
} else {
|
||||
return { line: i + 1, column: index + 1 };
|
||||
}
|
||||
}
|
||||
}
|
||||
var PARSE_CACHE_FOR_TEXT_LITERALS = Object.create(null);
|
||||
|
||||
function parseTextLiteral(text) {
|
||||
var cachedFn = PARSE_CACHE_FOR_TEXT_LITERALS[text];
|
||||
if (cachedFn != null) {
|
||||
return cachedFn;
|
||||
}
|
||||
function parsedFn(context) { return text; }
|
||||
parsedFn['$$watchDelegate'] = function watchDelegate(scope, listener, objectEquality) {
|
||||
var unwatch = scope['$watch'](noop,
|
||||
function textLiteralWatcher() {
|
||||
if (isFunction(listener)) { listener.call(null, text, text, scope); }
|
||||
unwatch();
|
||||
},
|
||||
objectEquality);
|
||||
return unwatch;
|
||||
};
|
||||
PARSE_CACHE_FOR_TEXT_LITERALS[text] = parsedFn;
|
||||
parsedFn['exp'] = text; // Needed to pretend to be $interpolate for tests copied from interpolateSpec.js
|
||||
parsedFn['expressions'] = []; // Require this to call $compile.$$addBindingInfo() which allows Protractor to find elements by binding.
|
||||
return parsedFn;
|
||||
}
|
||||
|
||||
function subtractOffset(expressionFn, offset) {
|
||||
if (offset === 0) {
|
||||
return expressionFn;
|
||||
}
|
||||
function minusOffset(value) {
|
||||
return (value == void 0) ? value : value - offset;
|
||||
}
|
||||
function parsedFn(context) { return minusOffset(expressionFn(context)); }
|
||||
var unwatch;
|
||||
parsedFn['$$watchDelegate'] = function watchDelegate(scope, listener, objectEquality) {
|
||||
unwatch = scope['$watch'](expressionFn,
|
||||
function pluralExpressionWatchListener(newValue, oldValue) {
|
||||
if (isFunction(listener)) { listener.call(null, minusOffset(newValue), minusOffset(oldValue), scope); }
|
||||
},
|
||||
objectEquality);
|
||||
return unwatch;
|
||||
};
|
||||
return parsedFn;
|
||||
}
|
||||
|
||||
// NOTE: ADVANCED_OPTIMIZATIONS mode.
|
||||
//
|
||||
// This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
|
||||
// constructs incompatible with that mode.
|
||||
|
||||
/* global $interpolateMinErr: false */
|
||||
/* global isFunction: false */
|
||||
/* global noop: false */
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
function MessageSelectorBase(expressionFn, choices) {
|
||||
var self = this;
|
||||
this.expressionFn = expressionFn;
|
||||
this.choices = choices;
|
||||
if (choices["other"] === void 0) {
|
||||
throw $interpolateMinErr('reqother', '“other” is a required option.');
|
||||
}
|
||||
this.parsedFn = function(context) { return self.getResult(context); };
|
||||
this.parsedFn['$$watchDelegate'] = function $$watchDelegate(scope, listener, objectEquality) {
|
||||
return self.watchDelegate(scope, listener, objectEquality);
|
||||
};
|
||||
this.parsedFn['exp'] = expressionFn['exp'];
|
||||
this.parsedFn['expressions'] = expressionFn['expressions'];
|
||||
}
|
||||
|
||||
MessageSelectorBase.prototype.getMessageFn = function getMessageFn(value) {
|
||||
return this.choices[this.categorizeValue(value)];
|
||||
};
|
||||
|
||||
MessageSelectorBase.prototype.getResult = function getResult(context) {
|
||||
return this.getMessageFn(this.expressionFn(context))(context);
|
||||
};
|
||||
|
||||
MessageSelectorBase.prototype.watchDelegate = function watchDelegate(scope, listener, objectEquality) {
|
||||
var watchers = new MessageSelectorWatchers(this, scope, listener, objectEquality);
|
||||
return function() { watchers.cancelWatch(); };
|
||||
};
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
function MessageSelectorWatchers(msgSelector, scope, listener, objectEquality) {
|
||||
var self = this;
|
||||
this.scope = scope;
|
||||
this.msgSelector = msgSelector;
|
||||
this.listener = listener;
|
||||
this.objectEquality = objectEquality;
|
||||
this.lastMessage = void 0;
|
||||
this.messageFnWatcher = noop;
|
||||
var expressionFnListener = function(newValue, oldValue) { return self.expressionFnListener(newValue, oldValue); };
|
||||
this.expressionFnWatcher = scope['$watch'](msgSelector.expressionFn, expressionFnListener, objectEquality);
|
||||
}
|
||||
|
||||
MessageSelectorWatchers.prototype.expressionFnListener = function expressionFnListener(newValue, oldValue) {
|
||||
var self = this;
|
||||
this.messageFnWatcher();
|
||||
var messageFnListener = function(newMessage, oldMessage) { return self.messageFnListener(newMessage, oldMessage); };
|
||||
var messageFn = this.msgSelector.getMessageFn(newValue);
|
||||
this.messageFnWatcher = this.scope['$watch'](messageFn, messageFnListener, this.objectEquality);
|
||||
};
|
||||
|
||||
MessageSelectorWatchers.prototype.messageFnListener = function messageFnListener(newMessage, oldMessage) {
|
||||
if (isFunction(this.listener)) {
|
||||
this.listener.call(null, newMessage, newMessage === oldMessage ? newMessage : this.lastMessage, this.scope);
|
||||
}
|
||||
this.lastMessage = newMessage;
|
||||
};
|
||||
|
||||
MessageSelectorWatchers.prototype.cancelWatch = function cancelWatch() {
|
||||
this.expressionFnWatcher();
|
||||
this.messageFnWatcher();
|
||||
};
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @extends MessageSelectorBase
|
||||
* @private
|
||||
*/
|
||||
function SelectMessage(expressionFn, choices) {
|
||||
MessageSelectorBase.call(this, expressionFn, choices);
|
||||
}
|
||||
|
||||
function SelectMessageProto() {}
|
||||
SelectMessageProto.prototype = MessageSelectorBase.prototype;
|
||||
|
||||
SelectMessage.prototype = new SelectMessageProto();
|
||||
SelectMessage.prototype.categorizeValue = function categorizeSelectValue(value) {
|
||||
return (this.choices[value] !== void 0) ? value : "other";
|
||||
};
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @extends MessageSelectorBase
|
||||
* @private
|
||||
*/
|
||||
function PluralMessage(expressionFn, choices, offset, pluralCat) {
|
||||
MessageSelectorBase.call(this, expressionFn, choices);
|
||||
this.offset = offset;
|
||||
this.pluralCat = pluralCat;
|
||||
}
|
||||
|
||||
function PluralMessageProto() {}
|
||||
PluralMessageProto.prototype = MessageSelectorBase.prototype;
|
||||
|
||||
PluralMessage.prototype = new PluralMessageProto();
|
||||
PluralMessage.prototype.categorizeValue = function categorizePluralValue(value) {
|
||||
if (isNaN(value)) {
|
||||
return "other";
|
||||
} else if (this.choices[value] !== void 0) {
|
||||
return value;
|
||||
} else {
|
||||
var category = this.pluralCat(value - this.offset);
|
||||
return (this.choices[category] !== void 0) ? category : "other";
|
||||
}
|
||||
};
|
||||
|
||||
// NOTE: ADVANCED_OPTIMIZATIONS mode.
|
||||
//
|
||||
// This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
|
||||
// constructs incompatible with that mode.
|
||||
|
||||
/* global $interpolateMinErr: false */
|
||||
/* global isFunction: false */
|
||||
/* global parseTextLiteral: false */
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
function InterpolationParts(trustedContext, allOrNothing) {
|
||||
this.trustedContext = trustedContext;
|
||||
this.allOrNothing = allOrNothing;
|
||||
this.textParts = [];
|
||||
this.expressionFns = [];
|
||||
this.expressionIndices = [];
|
||||
this.partialText = '';
|
||||
this.concatParts = null;
|
||||
}
|
||||
|
||||
InterpolationParts.prototype.flushPartialText = function flushPartialText() {
|
||||
if (this.partialText) {
|
||||
if (this.concatParts == null) {
|
||||
this.textParts.push(this.partialText);
|
||||
} else {
|
||||
this.textParts.push(this.concatParts.join(''));
|
||||
this.concatParts = null;
|
||||
}
|
||||
this.partialText = '';
|
||||
}
|
||||
};
|
||||
|
||||
InterpolationParts.prototype.addText = function addText(text) {
|
||||
if (text.length) {
|
||||
if (!this.partialText) {
|
||||
this.partialText = text;
|
||||
} else if (this.concatParts) {
|
||||
this.concatParts.push(text);
|
||||
} else {
|
||||
this.concatParts = [this.partialText, text];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
InterpolationParts.prototype.addExpressionFn = function addExpressionFn(expressionFn) {
|
||||
this.flushPartialText();
|
||||
this.expressionIndices.push(this.textParts.length);
|
||||
this.expressionFns.push(expressionFn);
|
||||
this.textParts.push('');
|
||||
};
|
||||
|
||||
InterpolationParts.prototype.getExpressionValues = function getExpressionValues(context) {
|
||||
var expressionValues = new Array(this.expressionFns.length);
|
||||
for (var i = 0; i < this.expressionFns.length; i++) {
|
||||
expressionValues[i] = this.expressionFns[i](context);
|
||||
}
|
||||
return expressionValues;
|
||||
};
|
||||
|
||||
InterpolationParts.prototype.getResult = function getResult(expressionValues) {
|
||||
for (var i = 0; i < this.expressionIndices.length; i++) {
|
||||
var expressionValue = expressionValues[i];
|
||||
if (this.allOrNothing && expressionValue === void 0) return;
|
||||
this.textParts[this.expressionIndices[i]] = expressionValue;
|
||||
}
|
||||
return this.textParts.join('');
|
||||
};
|
||||
|
||||
|
||||
InterpolationParts.prototype.toParsedFn = function toParsedFn(mustHaveExpression, originalText) {
|
||||
var self = this;
|
||||
this.flushPartialText();
|
||||
if (mustHaveExpression && this.expressionFns.length === 0) {
|
||||
return void 0;
|
||||
}
|
||||
if (this.textParts.length === 0) {
|
||||
return parseTextLiteral('');
|
||||
}
|
||||
if (this.trustedContext && this.textParts.length > 1) {
|
||||
$interpolateMinErr['throwNoconcat'](originalText);
|
||||
}
|
||||
if (this.expressionFns.length === 0) {
|
||||
if (this.textParts.length != 1) { this.errorInParseLogic(); }
|
||||
return parseTextLiteral(this.textParts[0]);
|
||||
}
|
||||
var parsedFn = function(context) {
|
||||
return self.getResult(self.getExpressionValues(context));
|
||||
};
|
||||
parsedFn['$$watchDelegate'] = function $$watchDelegate(scope, listener, objectEquality) {
|
||||
return self.watchDelegate(scope, listener, objectEquality);
|
||||
};
|
||||
|
||||
parsedFn['exp'] = originalText; // Needed to pretend to be $interpolate for tests copied from interpolateSpec.js
|
||||
parsedFn['expressions'] = new Array(this.expressionFns.length); // Require this to call $compile.$$addBindingInfo() which allows Protractor to find elements by binding.
|
||||
for (var i = 0; i < this.expressionFns.length; i++) {
|
||||
parsedFn['expressions'][i] = this.expressionFns[i]['exp'];
|
||||
}
|
||||
|
||||
return parsedFn;
|
||||
};
|
||||
|
||||
InterpolationParts.prototype.watchDelegate = function watchDelegate(scope, listener, objectEquality) {
|
||||
var watcher = new InterpolationPartsWatcher(this, scope, listener, objectEquality);
|
||||
return function() { watcher.cancelWatch(); };
|
||||
};
|
||||
|
||||
function InterpolationPartsWatcher(interpolationParts, scope, listener, objectEquality) {
|
||||
this.interpolationParts = interpolationParts;
|
||||
this.scope = scope;
|
||||
this.previousResult = (void 0);
|
||||
this.listener = listener;
|
||||
var self = this;
|
||||
this.expressionFnsWatcher = scope['$watchGroup'](interpolationParts.expressionFns, function(newExpressionValues, oldExpressionValues) {
|
||||
self.watchListener(newExpressionValues, oldExpressionValues);
|
||||
});
|
||||
}
|
||||
|
||||
InterpolationPartsWatcher.prototype.watchListener = function watchListener(newExpressionValues, oldExpressionValues) {
|
||||
var result = this.interpolationParts.getResult(newExpressionValues);
|
||||
if (isFunction(this.listener)) {
|
||||
this.listener.call(null, result, newExpressionValues === oldExpressionValues ? result : this.previousResult, this.scope);
|
||||
}
|
||||
this.previousResult = result;
|
||||
};
|
||||
|
||||
InterpolationPartsWatcher.prototype.cancelWatch = function cancelWatch() {
|
||||
this.expressionFnsWatcher();
|
||||
};
|
||||
|
||||
// NOTE: ADVANCED_OPTIMIZATIONS mode.
|
||||
//
|
||||
// This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
|
||||
// constructs incompatible with that mode.
|
||||
|
||||
/* global $interpolateMinErr: false */
|
||||
/* global indexToLineAndColumn: false */
|
||||
/* global InterpolationParts: false */
|
||||
/* global PluralMessage: false */
|
||||
/* global SelectMessage: false */
|
||||
/* global subtractOffset: false */
|
||||
|
||||
// The params src and dst are exactly one of two types: NestedParserState or MessageFormatParser.
|
||||
// This function is fully optimized by V8. (inspect via IRHydra or --trace-deopt.)
|
||||
// The idea behind writing it this way is to avoid repeating oneself. This is the ONE place where
|
||||
// the parser state that is saved/restored when parsing nested mustaches is specified.
|
||||
function copyNestedParserState(src, dst) {
|
||||
dst.expressionFn = src.expressionFn;
|
||||
dst.expressionMinusOffsetFn = src.expressionMinusOffsetFn;
|
||||
dst.pluralOffset = src.pluralOffset;
|
||||
dst.choices = src.choices;
|
||||
dst.choiceKey = src.choiceKey;
|
||||
dst.interpolationParts = src.interpolationParts;
|
||||
dst.ruleChoiceKeyword = src.ruleChoiceKeyword;
|
||||
dst.msgStartIndex = src.msgStartIndex;
|
||||
dst.expressionStartIndex = src.expressionStartIndex;
|
||||
}
|
||||
|
||||
function NestedParserState(parser) {
|
||||
copyNestedParserState(parser, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
function MessageFormatParser(text, startIndex, $parse, pluralCat, stringifier,
|
||||
mustHaveExpression, trustedContext, allOrNothing) {
|
||||
this.text = text;
|
||||
this.index = startIndex || 0;
|
||||
this.$parse = $parse;
|
||||
this.pluralCat = pluralCat;
|
||||
this.stringifier = stringifier;
|
||||
this.mustHaveExpression = !!mustHaveExpression;
|
||||
this.trustedContext = trustedContext;
|
||||
this.allOrNothing = !!allOrNothing;
|
||||
this.expressionFn = null;
|
||||
this.expressionMinusOffsetFn = null;
|
||||
this.pluralOffset = null;
|
||||
this.choices = null;
|
||||
this.choiceKey = null;
|
||||
this.interpolationParts = null;
|
||||
this.msgStartIndex = null;
|
||||
this.nestedStateStack = [];
|
||||
this.parsedFn = null;
|
||||
this.rule = null;
|
||||
this.ruleStack = null;
|
||||
this.ruleChoiceKeyword = null;
|
||||
this.interpNestLevel = null;
|
||||
this.expressionStartIndex = null;
|
||||
this.stringStartIndex = null;
|
||||
this.stringQuote = null;
|
||||
this.stringInterestsRe = null;
|
||||
this.angularOperatorStack = null;
|
||||
this.textPart = null;
|
||||
}
|
||||
|
||||
// preserve v8 optimization.
|
||||
var EMPTY_STATE = new NestedParserState(new MessageFormatParser(
|
||||
/* text= */ '', /* startIndex= */ 0, /* $parse= */ null, /* pluralCat= */ null, /* stringifier= */ null,
|
||||
/* mustHaveExpression= */ false, /* trustedContext= */ null, /* allOrNothing */ false));
|
||||
|
||||
MessageFormatParser.prototype.pushState = function pushState() {
|
||||
this.nestedStateStack.push(new NestedParserState(this));
|
||||
copyNestedParserState(EMPTY_STATE, this);
|
||||
};
|
||||
|
||||
MessageFormatParser.prototype.popState = function popState() {
|
||||
if (this.nestedStateStack.length === 0) {
|
||||
this.errorInParseLogic();
|
||||
}
|
||||
var previousState = this.nestedStateStack.pop();
|
||||
copyNestedParserState(previousState, this);
|
||||
};
|
||||
|
||||
// Oh my JavaScript! Who knew you couldn't match a regex at a specific
|
||||
// location in a string but will always search forward?!
|
||||
// Apparently you'll be growing this ability via the sticky flag (y) in
|
||||
// ES6. I'll just to work around you for now.
|
||||
MessageFormatParser.prototype.matchRe = function matchRe(re, search) {
|
||||
re.lastIndex = this.index;
|
||||
var match = re.exec(this.text);
|
||||
if (match != null && (search === true || (match.index == this.index))) {
|
||||
this.index = re.lastIndex;
|
||||
return match;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
MessageFormatParser.prototype.searchRe = function searchRe(re) {
|
||||
return this.matchRe(re, true);
|
||||
};
|
||||
|
||||
|
||||
MessageFormatParser.prototype.consumeRe = function consumeRe(re) {
|
||||
// Without the sticky flag, we can't use the .test() method to consume a
|
||||
// match at the current index. Instead, we'll use the slower .exec() method
|
||||
// and verify match.index.
|
||||
return !!this.matchRe(re);
|
||||
};
|
||||
|
||||
// Run through our grammar avoiding deeply nested function call chains.
|
||||
MessageFormatParser.prototype.run = function run(initialRule) {
|
||||
this.ruleStack = [initialRule];
|
||||
do {
|
||||
this.rule = this.ruleStack.pop();
|
||||
while (this.rule) {
|
||||
this.rule();
|
||||
}
|
||||
this.assertRuleOrNull(this.rule);
|
||||
} while (this.ruleStack.length > 0);
|
||||
};
|
||||
|
||||
MessageFormatParser.prototype.errorInParseLogic = function errorInParseLogic() {
|
||||
throw $interpolateMinErr('logicbug',
|
||||
'The messageformat parser has encountered an internal error. Please file a github issue against the AngularJS project and provide this message text that triggers the bug. Text: “{0}”',
|
||||
this.text);
|
||||
};
|
||||
|
||||
MessageFormatParser.prototype.assertRuleOrNull = function assertRuleOrNull(rule) {
|
||||
if (rule === void 0) {
|
||||
this.errorInParseLogic();
|
||||
}
|
||||
};
|
||||
|
||||
var NEXT_WORD_RE = /\s*(\w+)\s*/g;
|
||||
MessageFormatParser.prototype.errorExpecting = function errorExpecting() {
|
||||
// What was wrong with the syntax? Unsupported type, missing comma, or something else?
|
||||
var match = this.matchRe(NEXT_WORD_RE), position;
|
||||
if (match == null) {
|
||||
position = indexToLineAndColumn(this.text, this.index);
|
||||
throw $interpolateMinErr('reqarg',
|
||||
'Expected one of “plural” or “select” at line {0}, column {1} of text “{2}”',
|
||||
position.line, position.column, this.text);
|
||||
}
|
||||
var word = match[1];
|
||||
if (word == "select" || word == "plural") {
|
||||
position = indexToLineAndColumn(this.text, this.index);
|
||||
throw $interpolateMinErr('reqcomma',
|
||||
'Expected a comma after the keyword “{0}” at line {1}, column {2} of text “{3}”',
|
||||
word, position.line, position.column, this.text);
|
||||
} else {
|
||||
position = indexToLineAndColumn(this.text, this.index);
|
||||
throw $interpolateMinErr('unknarg',
|
||||
'Unsupported keyword “{0}” at line {0}, column {1}. Only “plural” and “select” are currently supported. Text: “{3}”',
|
||||
word, position.line, position.column, this.text);
|
||||
}
|
||||
};
|
||||
|
||||
var STRING_START_RE = /['"]/g;
|
||||
MessageFormatParser.prototype.ruleString = function ruleString() {
|
||||
var match = this.matchRe(STRING_START_RE);
|
||||
if (match == null) {
|
||||
var position = indexToLineAndColumn(this.text, this.index);
|
||||
throw $interpolateMinErr('wantstring',
|
||||
'Expected the beginning of a string at line {0}, column {1} in text “{2}”',
|
||||
position.line, position.column, this.text);
|
||||
}
|
||||
this.startStringAtMatch(match);
|
||||
};
|
||||
|
||||
MessageFormatParser.prototype.startStringAtMatch = function startStringAtMatch(match) {
|
||||
this.stringStartIndex = match.index;
|
||||
this.stringQuote = match[0];
|
||||
this.stringInterestsRe = this.stringQuote == "'" ? SQUOTED_STRING_INTEREST_RE : DQUOTED_STRING_INTEREST_RE;
|
||||
this.rule = this.ruleInsideString;
|
||||
};
|
||||
|
||||
var SQUOTED_STRING_INTEREST_RE = /\\(?:\\|'|u[0-9A-Fa-f]{4}|x[0-9A-Fa-f]{2}|[0-7]{3}|\r\n|\n|[\s\S])|'/g;
|
||||
var DQUOTED_STRING_INTEREST_RE = /\\(?:\\|"|u[0-9A-Fa-f]{4}|x[0-9A-Fa-f]{2}|[0-7]{3}|\r\n|\n|[\s\S])|"/g;
|
||||
MessageFormatParser.prototype.ruleInsideString = function ruleInsideString() {
|
||||
var match = this.searchRe(this.stringInterestsRe);
|
||||
if (match == null) {
|
||||
var position = indexToLineAndColumn(this.text, this.stringStartIndex);
|
||||
throw $interpolateMinErr('untermstr',
|
||||
'The string beginning at line {0}, column {1} is unterminated in text “{2}”',
|
||||
position.line, position.column, this.text);
|
||||
}
|
||||
var chars = match[0];
|
||||
if (match == this.stringQuote) {
|
||||
this.rule = null;
|
||||
}
|
||||
};
|
||||
|
||||
var PLURAL_OR_SELECT_ARG_TYPE_RE = /\s*(plural|select)\s*,\s*/g;
|
||||
MessageFormatParser.prototype.rulePluralOrSelect = function rulePluralOrSelect() {
|
||||
var match = this.searchRe(PLURAL_OR_SELECT_ARG_TYPE_RE);
|
||||
if (match == null) {
|
||||
this.errorExpecting();
|
||||
}
|
||||
var argType = match[1];
|
||||
switch (argType) {
|
||||
case "plural": this.rule = this.rulePluralStyle; break;
|
||||
case "select": this.rule = this.ruleSelectStyle; break;
|
||||
default: this.errorInParseLogic();
|
||||
}
|
||||
};
|
||||
|
||||
MessageFormatParser.prototype.rulePluralStyle = function rulePluralStyle() {
|
||||
this.choices = Object.create(null);
|
||||
this.ruleChoiceKeyword = this.rulePluralValueOrKeyword;
|
||||
this.rule = this.rulePluralOffset;
|
||||
};
|
||||
|
||||
MessageFormatParser.prototype.ruleSelectStyle = function ruleSelectStyle() {
|
||||
this.choices = Object.create(null);
|
||||
this.ruleChoiceKeyword = this.ruleSelectKeyword;
|
||||
this.rule = this.ruleSelectKeyword;
|
||||
};
|
||||
|
||||
var NUMBER_RE = /[0]|(?:[1-9][0-9]*)/g;
|
||||
var PLURAL_OFFSET_RE = new RegExp("\\s*offset\\s*:\\s*(" + NUMBER_RE.source + ")", "g");
|
||||
|
||||
MessageFormatParser.prototype.rulePluralOffset = function rulePluralOffset() {
|
||||
var match = this.matchRe(PLURAL_OFFSET_RE);
|
||||
this.pluralOffset = (match == null) ? 0 : parseInt(match[1], 10);
|
||||
this.expressionMinusOffsetFn = subtractOffset(this.expressionFn, this.pluralOffset);
|
||||
this.rule = this.rulePluralValueOrKeyword;
|
||||
};
|
||||
|
||||
MessageFormatParser.prototype.assertChoiceKeyIsNew = function assertChoiceKeyIsNew(choiceKey, index) {
|
||||
if (this.choices[choiceKey] !== void 0) {
|
||||
var position = indexToLineAndColumn(this.text, index);
|
||||
throw $interpolateMinErr('dupvalue',
|
||||
'The choice “{0}” is specified more than once. Duplicate key is at line {1}, column {2} in text “{3}”',
|
||||
choiceKey, position.line, position.column, this.text);
|
||||
}
|
||||
};
|
||||
|
||||
var SELECT_KEYWORD = /\s*(\w+)/g;
|
||||
MessageFormatParser.prototype.ruleSelectKeyword = function ruleSelectKeyword() {
|
||||
var match = this.matchRe(SELECT_KEYWORD);
|
||||
if (match == null) {
|
||||
this.parsedFn = new SelectMessage(this.expressionFn, this.choices).parsedFn;
|
||||
this.rule = null;
|
||||
return;
|
||||
}
|
||||
this.choiceKey = match[1];
|
||||
this.assertChoiceKeyIsNew(this.choiceKey, match.index);
|
||||
this.rule = this.ruleMessageText;
|
||||
};
|
||||
|
||||
var EXPLICIT_VALUE_OR_KEYWORD_RE = new RegExp("\\s*(?:(?:=(" + NUMBER_RE.source + "))|(\\w+))", "g");
|
||||
MessageFormatParser.prototype.rulePluralValueOrKeyword = function rulePluralValueOrKeyword() {
|
||||
var match = this.matchRe(EXPLICIT_VALUE_OR_KEYWORD_RE);
|
||||
if (match == null) {
|
||||
this.parsedFn = new PluralMessage(this.expressionFn, this.choices, this.pluralOffset, this.pluralCat).parsedFn;
|
||||
this.rule = null;
|
||||
return;
|
||||
}
|
||||
if (match[1] != null) {
|
||||
this.choiceKey = parseInt(match[1], 10);
|
||||
} else {
|
||||
this.choiceKey = match[2];
|
||||
}
|
||||
this.assertChoiceKeyIsNew(this.choiceKey, match.index);
|
||||
this.rule = this.ruleMessageText;
|
||||
};
|
||||
|
||||
var BRACE_OPEN_RE = /\s*{/g;
|
||||
var BRACE_CLOSE_RE = /}/g;
|
||||
MessageFormatParser.prototype.ruleMessageText = function ruleMessageText() {
|
||||
if (!this.consumeRe(BRACE_OPEN_RE)) {
|
||||
var position = indexToLineAndColumn(this.text, this.index);
|
||||
throw $interpolateMinErr('reqopenbrace',
|
||||
'The plural choice “{0}” must be followed by a message in braces at line {1}, column {2} in text “{3}”',
|
||||
this.choiceKey, position.line, position.column, this.text);
|
||||
}
|
||||
this.msgStartIndex = this.index;
|
||||
this.interpolationParts = new InterpolationParts(this.trustedContext, this.allOrNothing);
|
||||
this.rule = this.ruleInInterpolationOrMessageText;
|
||||
};
|
||||
|
||||
// Note: Since "\" is used as an escape character, don't allow it to be part of the
|
||||
// startSymbol/endSymbol when I add the feature to allow them to be redefined.
|
||||
var INTERP_OR_END_MESSAGE_RE = /\\.|{{|}/g;
|
||||
var INTERP_OR_PLURALVALUE_OR_END_MESSAGE_RE = /\\.|{{|#|}/g;
|
||||
var ESCAPE_OR_MUSTACHE_BEGIN_RE = /\\.|{{/g;
|
||||
MessageFormatParser.prototype.advanceInInterpolationOrMessageText = function advanceInInterpolationOrMessageText() {
|
||||
var currentIndex = this.index, match, re;
|
||||
if (this.ruleChoiceKeyword == null) { // interpolation
|
||||
match = this.searchRe(ESCAPE_OR_MUSTACHE_BEGIN_RE);
|
||||
if (match == null) { // End of interpolation text. Nothing more to process.
|
||||
this.textPart = this.text.substring(currentIndex);
|
||||
this.index = this.text.length;
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
match = this.searchRe(this.ruleChoiceKeyword == this.rulePluralValueOrKeyword ?
|
||||
INTERP_OR_PLURALVALUE_OR_END_MESSAGE_RE : INTERP_OR_END_MESSAGE_RE);
|
||||
if (match == null) {
|
||||
var position = indexToLineAndColumn(this.text, this.msgStartIndex);
|
||||
throw $interpolateMinErr('reqendbrace',
|
||||
'The plural/select choice “{0}” message starting at line {1}, column {2} does not have an ending closing brace. Text “{3}”',
|
||||
this.choiceKey, position.line, position.column, this.text);
|
||||
}
|
||||
}
|
||||
// match is non-null.
|
||||
var token = match[0];
|
||||
this.textPart = this.text.substring(currentIndex, match.index);
|
||||
return token;
|
||||
};
|
||||
|
||||
MessageFormatParser.prototype.ruleInInterpolationOrMessageText = function ruleInInterpolationOrMessageText() {
|
||||
var currentIndex = this.index;
|
||||
var token = this.advanceInInterpolationOrMessageText();
|
||||
if (token == null) {
|
||||
// End of interpolation text. Nothing more to process.
|
||||
this.index = this.text.length;
|
||||
this.interpolationParts.addText(this.text.substring(currentIndex));
|
||||
this.rule = null;
|
||||
return;
|
||||
}
|
||||
if (token[0] == "\\") {
|
||||
// unescape next character and continue
|
||||
this.interpolationParts.addText(this.textPart + token[1]);
|
||||
return;
|
||||
}
|
||||
this.interpolationParts.addText(this.textPart);
|
||||
if (token == "{{") {
|
||||
this.pushState();
|
||||
this.ruleStack.push(this.ruleEndMustacheInInterpolationOrMessage);
|
||||
this.rule = this.ruleEnteredMustache;
|
||||
} else if (token == "}") {
|
||||
this.choices[this.choiceKey] = this.interpolationParts.toParsedFn(/*mustHaveExpression=*/false, this.text);
|
||||
this.rule = this.ruleChoiceKeyword;
|
||||
} else if (token == "#") {
|
||||
this.interpolationParts.addExpressionFn(this.expressionMinusOffsetFn);
|
||||
} else {
|
||||
this.errorInParseLogic();
|
||||
}
|
||||
};
|
||||
|
||||
MessageFormatParser.prototype.ruleInterpolate = function ruleInterpolate() {
|
||||
this.interpolationParts = new InterpolationParts(this.trustedContext, this.allOrNothing);
|
||||
this.rule = this.ruleInInterpolation;
|
||||
};
|
||||
|
||||
MessageFormatParser.prototype.ruleInInterpolation = function ruleInInterpolation() {
|
||||
var currentIndex = this.index;
|
||||
var match = this.searchRe(ESCAPE_OR_MUSTACHE_BEGIN_RE);
|
||||
if (match == null) {
|
||||
// End of interpolation text. Nothing more to process.
|
||||
this.index = this.text.length;
|
||||
this.interpolationParts.addText(this.text.substring(currentIndex));
|
||||
this.parsedFn = this.interpolationParts.toParsedFn(this.mustHaveExpression, this.text);
|
||||
this.rule = null;
|
||||
return;
|
||||
}
|
||||
var token = match[0];
|
||||
if (token[0] == "\\") {
|
||||
// unescape next character and continue
|
||||
this.interpolationParts.addText(this.text.substring(currentIndex, match.index) + token[1]);
|
||||
return;
|
||||
}
|
||||
this.interpolationParts.addText(this.text.substring(currentIndex, match.index));
|
||||
this.pushState();
|
||||
this.ruleStack.push(this.ruleInterpolationEndMustache);
|
||||
this.rule = this.ruleEnteredMustache;
|
||||
};
|
||||
|
||||
MessageFormatParser.prototype.ruleInterpolationEndMustache = function ruleInterpolationEndMustache() {
|
||||
var expressionFn = this.parsedFn;
|
||||
this.popState();
|
||||
this.interpolationParts.addExpressionFn(expressionFn);
|
||||
this.rule = this.ruleInInterpolation;
|
||||
};
|
||||
|
||||
MessageFormatParser.prototype.ruleEnteredMustache = function ruleEnteredMustache() {
|
||||
this.parsedFn = null;
|
||||
this.ruleStack.push(this.ruleEndMustache);
|
||||
this.rule = this.ruleAngularExpression;
|
||||
};
|
||||
|
||||
MessageFormatParser.prototype.ruleEndMustacheInInterpolationOrMessage = function ruleEndMustacheInInterpolationOrMessage() {
|
||||
var expressionFn = this.parsedFn;
|
||||
this.popState();
|
||||
this.interpolationParts.addExpressionFn(expressionFn);
|
||||
this.rule = this.ruleInInterpolationOrMessageText;
|
||||
};
|
||||
|
||||
|
||||
|
||||
var INTERP_END_RE = /\s*}}/g;
|
||||
MessageFormatParser.prototype.ruleEndMustache = function ruleEndMustache() {
|
||||
var match = this.matchRe(INTERP_END_RE);
|
||||
if (match == null) {
|
||||
var position = indexToLineAndColumn(this.text, this.index);
|
||||
throw $interpolateMinErr('reqendinterp',
|
||||
'Expecting end of interpolation symbol, “{0}”, at line {1}, column {2} in text “{3}”',
|
||||
'}}', position.line, position.column, this.text);
|
||||
}
|
||||
if (this.parsedFn == null) {
|
||||
// If we parsed a MessageFormat extension, (e.g. select/plural today, maybe more some other
|
||||
// day), then the result *has* to be a string and those rules would have already set
|
||||
// this.parsedFn. If there was no MessageFormat extension, then there is no requirement to
|
||||
// stringify the result and parsedFn isn't set. We set it here. While we could have set it
|
||||
// unconditionally when exiting the Angular expression, I intend for us to not just replace
|
||||
// $interpolate, but also to replace $parse in a future version (so ng-bind can work), and in
|
||||
// such a case we do not want to unnecessarily stringify something if it's not going to be used
|
||||
// in a string context.
|
||||
this.parsedFn = this.$parse(this.expressionFn, this.stringifier);
|
||||
this.parsedFn['exp'] = this.expressionFn['exp']; // Needed to pretend to be $interpolate for tests copied from interpolateSpec.js
|
||||
this.parsedFn['expressions'] = this.expressionFn['expressions']; // Require this to call $compile.$$addBindingInfo() which allows Protractor to find elements by binding.
|
||||
}
|
||||
this.rule = null;
|
||||
};
|
||||
|
||||
MessageFormatParser.prototype.ruleAngularExpression = function ruleAngularExpression() {
|
||||
this.angularOperatorStack = [];
|
||||
this.expressionStartIndex = this.index;
|
||||
this.rule = this.ruleInAngularExpression;
|
||||
};
|
||||
|
||||
function getEndOperator(opBegin) {
|
||||
switch (opBegin) {
|
||||
case "{": return "}";
|
||||
case "[": return "]";
|
||||
case "(": return ")";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getBeginOperator(opEnd) {
|
||||
switch (opEnd) {
|
||||
case "}": return "{";
|
||||
case "]": return "[";
|
||||
case ")": return "(";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(chirayu): The interpolation endSymbol must also be accounted for. It
|
||||
// just so happens that "}" is an operator so it's in the list below. But we
|
||||
// should support any other type of start/end interpolation symbol.
|
||||
var INTERESTING_OPERATORS_RE = /[[\]{}()'",]/g;
|
||||
MessageFormatParser.prototype.ruleInAngularExpression = function ruleInAngularExpression() {
|
||||
var startIndex = this.index;
|
||||
var match = this.searchRe(INTERESTING_OPERATORS_RE);
|
||||
var position;
|
||||
if (match == null) {
|
||||
if (this.angularOperatorStack.length === 0) {
|
||||
// This is the end of the Angular expression so this is actually a
|
||||
// success. Note that when inside an interpolation, this means we even
|
||||
// consumed the closing interpolation symbols if they were curlies. This
|
||||
// is NOT an error at this point but will become an error further up the
|
||||
// stack when the part that saw the opening curlies is unable to find the
|
||||
// closing ones.
|
||||
this.index = this.text.length;
|
||||
this.expressionFn = this.$parse(this.text.substring(this.expressionStartIndex, this.index));
|
||||
// Needed to pretend to be $interpolate for tests copied from interpolateSpec.js
|
||||
this.expressionFn['exp'] = this.text.substring(this.expressionStartIndex, this.index);
|
||||
this.expressionFn['expressions'] = this.expressionFn['expressions'];
|
||||
this.rule = null;
|
||||
return;
|
||||
}
|
||||
var innermostOperator = this.angularOperatorStack[0];
|
||||
throw $interpolateMinErr('badexpr',
|
||||
'Unexpected end of Angular expression. Expecting operator “{0}” at the end of the text “{1}”',
|
||||
this.getEndOperator(innermostOperator), this.text);
|
||||
}
|
||||
var operator = match[0];
|
||||
if (operator == "'" || operator == '"') {
|
||||
this.ruleStack.push(this.ruleInAngularExpression);
|
||||
this.startStringAtMatch(match);
|
||||
return;
|
||||
}
|
||||
if (operator == ",") {
|
||||
if (this.trustedContext) {
|
||||
position = indexToLineAndColumn(this.text, this.index);
|
||||
throw $interpolateMinErr('unsafe',
|
||||
'Use of select/plural MessageFormat syntax is currently disallowed in a secure context ({0}). At line {1}, column {2} of text “{3}”',
|
||||
this.trustedContext, position.line, position.column, this.text);
|
||||
}
|
||||
// only the top level comma has relevance.
|
||||
if (this.angularOperatorStack.length === 0) {
|
||||
// todo: does this need to be trimmed?
|
||||
this.expressionFn = this.$parse(this.text.substring(this.expressionStartIndex, match.index));
|
||||
// Needed to pretend to be $interpolate for tests copied from interpolateSpec.js
|
||||
this.expressionFn['exp'] = this.text.substring(this.expressionStartIndex, match.index);
|
||||
this.expressionFn['expressions'] = this.expressionFn['expressions'];
|
||||
this.rule = null;
|
||||
this.rule = this.rulePluralOrSelect;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (getEndOperator(operator) != null) {
|
||||
this.angularOperatorStack.unshift(operator);
|
||||
return;
|
||||
}
|
||||
var beginOperator = getBeginOperator(operator);
|
||||
if (beginOperator == null) {
|
||||
this.errorInParseLogic();
|
||||
}
|
||||
if (this.angularOperatorStack.length > 0) {
|
||||
if (beginOperator == this.angularOperatorStack[0]) {
|
||||
this.angularOperatorStack.shift();
|
||||
return;
|
||||
}
|
||||
position = indexToLineAndColumn(this.text, this.index);
|
||||
throw $interpolateMinErr('badexpr',
|
||||
'Unexpected operator “{0}” at line {1}, column {2} in text. Was expecting “{3}”. Text: “{4}”',
|
||||
operator, position.line, position.column, getEndOperator(this.angularOperatorStack[0]), this.text);
|
||||
}
|
||||
// We are trying to pop off the operator stack but there really isn't anything to pop off.
|
||||
this.index = match.index;
|
||||
this.expressionFn = this.$parse(this.text.substring(this.expressionStartIndex, this.index));
|
||||
// Needed to pretend to be $interpolate for tests copied from interpolateSpec.js
|
||||
this.expressionFn['exp'] = this.text.substring(this.expressionStartIndex, this.index);
|
||||
this.expressionFn['expressions'] = this.expressionFn['expressions'];
|
||||
this.rule = null;
|
||||
};
|
||||
|
||||
// NOTE: ADVANCED_OPTIMIZATIONS mode.
|
||||
//
|
||||
// This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
|
||||
// constructs incompatible with that mode.
|
||||
|
||||
/* global $interpolateMinErr: false */
|
||||
/* global MessageFormatParser: false */
|
||||
/* global stringify: false */
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $$messageFormat
|
||||
*
|
||||
* @description
|
||||
* Angular internal service to recognize MessageFormat extensions in interpolation expressions.
|
||||
* For more information, see:
|
||||
* https://docs.google.com/a/google.com/document/d/1pbtW2yvtmFBikfRrJd8VAsabiFkKezmYZ_PbgdjQOVU/edit
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* <example name="ngMessageFormat-example" module="msgFmtExample" deps="angular-message-format.min.js">
|
||||
* <file name="index.html">
|
||||
* <div ng-controller="AppController">
|
||||
* <button ng-click="decreaseRecipients()" id="decreaseRecipients">decreaseRecipients</button><br>
|
||||
* <span>{{recipients.length, plural, offset:1
|
||||
* =0 {{{sender.name}} gave no gifts (\#=#)}
|
||||
* =1 {{{sender.name}} gave one gift to {{recipients[0].name}} (\#=#)}
|
||||
* one {{{sender.name}} gave {{recipients[0].name}} and one other person a gift (\#=#)}
|
||||
* other {{{sender.name}} gave {{recipients[0].name}} and # other people a gift (\#=#)}
|
||||
* }}</span>
|
||||
* </div>
|
||||
* </file>
|
||||
*
|
||||
* <file name="script.js">
|
||||
* function Person(name, gender) {
|
||||
* this.name = name;
|
||||
* this.gender = gender;
|
||||
* }
|
||||
*
|
||||
* var alice = new Person("Alice", "female"),
|
||||
* bob = new Person("Bob", "male"),
|
||||
* charlie = new Person("Charlie", "male"),
|
||||
* harry = new Person("Harry Potter", "male");
|
||||
*
|
||||
* angular.module('msgFmtExample', ['ngMessageFormat'])
|
||||
* .controller('AppController', ['$scope', function($scope) {
|
||||
* $scope.recipients = [alice, bob, charlie];
|
||||
* $scope.sender = harry;
|
||||
* $scope.decreaseRecipients = function() {
|
||||
* --$scope.recipients.length;
|
||||
* };
|
||||
* }]);
|
||||
* </file>
|
||||
*
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* describe('MessageFormat plural', function() {
|
||||
* it('should pluralize initial values', function() {
|
||||
* var messageElem = element(by.binding('recipients.length')), decreaseRecipientsBtn = element(by.id('decreaseRecipients'));
|
||||
* expect(messageElem.getText()).toEqual('Harry Potter gave Alice and 2 other people a gift (#=2)');
|
||||
* decreaseRecipientsBtn.click();
|
||||
* expect(messageElem.getText()).toEqual('Harry Potter gave Alice and one other person a gift (#=1)');
|
||||
* decreaseRecipientsBtn.click();
|
||||
* expect(messageElem.getText()).toEqual('Harry Potter gave one gift to Alice (#=0)');
|
||||
* decreaseRecipientsBtn.click();
|
||||
* expect(messageElem.getText()).toEqual('Harry Potter gave no gifts (#=-1)');
|
||||
* });
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
var $$MessageFormatFactory = ['$parse', '$locale', '$sce', '$exceptionHandler', function $$messageFormat(
|
||||
$parse, $locale, $sce, $exceptionHandler) {
|
||||
|
||||
function getStringifier(trustedContext, allOrNothing, text) {
|
||||
return function stringifier(value) {
|
||||
try {
|
||||
value = trustedContext ? $sce['getTrusted'](trustedContext, value) : $sce['valueOf'](value);
|
||||
return allOrNothing && (value === void 0) ? value : stringify(value);
|
||||
} catch (err) {
|
||||
$exceptionHandler($interpolateMinErr['interr'](text, err));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
|
||||
var stringifier = getStringifier(trustedContext, allOrNothing, text);
|
||||
var parser = new MessageFormatParser(text, 0, $parse, $locale['pluralCat'], stringifier,
|
||||
mustHaveExpression, trustedContext, allOrNothing);
|
||||
parser.run(parser.ruleInterpolate);
|
||||
return parser.parsedFn;
|
||||
}
|
||||
|
||||
return {
|
||||
'interpolate': interpolate
|
||||
};
|
||||
}];
|
||||
|
||||
var $$interpolateDecorator = ['$$messageFormat', '$delegate', function $$interpolateDecorator($$messageFormat, $interpolate) {
|
||||
if ($interpolate['startSymbol']() != "{{" || $interpolate['endSymbol']() != "}}") {
|
||||
throw $interpolateMinErr('nochgmustache', 'angular-message-format.js currently does not allow you to use custom start and end symbols for interpolation.');
|
||||
}
|
||||
var interpolate = $$messageFormat['interpolate'];
|
||||
interpolate['startSymbol'] = $interpolate['startSymbol'];
|
||||
interpolate['endSymbol'] = $interpolate['endSymbol'];
|
||||
return interpolate;
|
||||
}];
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc module
|
||||
* @name ngMessageFormat
|
||||
* @packageName angular-message-format
|
||||
* @description
|
||||
*/
|
||||
var module = window['angular']['module']('ngMessageFormat', ['ng']);
|
||||
module['factory']('$$messageFormat', $$MessageFormatFactory);
|
||||
module['config'](['$provide', function($provide) {
|
||||
$provide['decorator']('$interpolate', $$interpolateDecorator);
|
||||
}]);
|
||||
|
||||
|
||||
})(window, window.angular);
|
|
@ -1,10 +1,18 @@
|
|||
/**
|
||||
* @license AngularJS v1.3.18
|
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* @license AngularJS v1.4.10
|
||||
* (c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, angular, undefined) {'use strict';
|
||||
|
||||
/* jshint ignore:start */
|
||||
// this code is in the core, but not in angular-messages.js
|
||||
var isArray = angular.isArray;
|
||||
var forEach = angular.forEach;
|
||||
var isString = angular.isString;
|
||||
var jqLite = angular.element;
|
||||
/* jshint ignore:end */
|
||||
|
||||
/**
|
||||
* @ngdoc module
|
||||
* @name ngMessages
|
||||
|
@ -17,47 +25,77 @@
|
|||
* `ngMessage` directives are designed to handle the complexity, inheritance and priority
|
||||
* sequencing based on the order of how the messages are defined in the template.
|
||||
*
|
||||
* Currently, the ngMessages module only contains the code for the `ngMessages`
|
||||
* and `ngMessage` directives.
|
||||
* Currently, the ngMessages module only contains the code for the `ngMessages`, `ngMessagesInclude`
|
||||
* `ngMessage` and `ngMessageExp` directives.
|
||||
*
|
||||
* # Usage
|
||||
* The `ngMessages` directive listens on a key/value collection which is set on the ngMessages attribute.
|
||||
* Since the {@link ngModel ngModel} directive exposes an `$error` object, this error object can be
|
||||
* used with `ngMessages` to display control error messages in an easier way than with just regular angular
|
||||
* template directives.
|
||||
* The `ngMessages` directive allows keys in a key/value collection to be associated with a child element
|
||||
* (or 'message') that will show or hide based on the truthiness of that key's value in the collection. A common use
|
||||
* case for `ngMessages` is to display error messages for inputs using the `$error` object exposed by the
|
||||
* {@link ngModel ngModel} directive.
|
||||
*
|
||||
* The child elements of the `ngMessages` directive are matched to the collection keys by a `ngMessage` or
|
||||
* `ngMessageExp` directive. The value of these attributes must match a key in the collection that is provided by
|
||||
* the `ngMessages` directive.
|
||||
*
|
||||
* Consider the following example, which illustrates a typical use case of `ngMessages`. Within the form `myForm` we
|
||||
* have a text input named `myField` which is bound to the scope variable `field` using the {@link ngModel ngModel}
|
||||
* directive.
|
||||
*
|
||||
* The `myField` field is a required input of type `email` with a maximum length of 15 characters.
|
||||
*
|
||||
* ```html
|
||||
* <form name="myForm">
|
||||
* <input type="text" ng-model="field" name="myField" required minlength="5" />
|
||||
* <div ng-messages="myForm.myField.$error">
|
||||
* <div ng-message="required">You did not enter a field</div>
|
||||
* <div ng-message="minlength">The value entered is too short</div>
|
||||
* <label>
|
||||
* Enter text:
|
||||
* <input type="email" ng-model="field" name="myField" required maxlength="15" />
|
||||
* </label>
|
||||
* <div ng-messages="myForm.myField.$error" role="alert">
|
||||
* <div ng-message="required">Please enter a value for this field.</div>
|
||||
* <div ng-message="email">This field must be a valid email address.</div>
|
||||
* <div ng-message="maxlength">This field can be at most 15 characters long.</div>
|
||||
* </div>
|
||||
* </form>
|
||||
* ```
|
||||
*
|
||||
* Now whatever key/value entries are present within the provided object (in this case `$error`) then
|
||||
* the ngMessages directive will render the inner first ngMessage directive (depending if the key values
|
||||
* match the attribute value present on each ngMessage directive). In other words, if your errors
|
||||
* object contains the following data:
|
||||
* In order to show error messages corresponding to `myField` we first create an element with an `ngMessages` attribute
|
||||
* set to the `$error` object owned by the `myField` input in our `myForm` form.
|
||||
*
|
||||
* Within this element we then create separate elements for each of the possible errors that `myField` could have.
|
||||
* The `ngMessage` attribute is used to declare which element(s) will appear for which error - for example,
|
||||
* setting `ng-message="required"` specifies that this particular element should be displayed when there
|
||||
* is no value present for the required field `myField` (because the key `required` will be `true` in the object
|
||||
* `myForm.myField.$error`).
|
||||
*
|
||||
* ### Message order
|
||||
*
|
||||
* By default, `ngMessages` will only display one message for a particular key/value collection at any time. If more
|
||||
* than one message (or error) key is currently true, then which message is shown is determined by the order of messages
|
||||
* in the HTML template code (messages declared first are prioritised). This mechanism means the developer does not have
|
||||
* to prioritise messages using custom JavaScript code.
|
||||
*
|
||||
* Given the following error object for our example (which informs us that the field `myField` currently has both the
|
||||
* `required` and `email` errors):
|
||||
*
|
||||
* ```javascript
|
||||
* <!-- keep in mind that ngModel automatically sets these error flags -->
|
||||
* myField.$error = { minlength : true, required : false };
|
||||
* myField.$error = { required : true, email: true, maxlength: false };
|
||||
* ```
|
||||
* The `required` message will be displayed to the user since it appears before the `email` message in the DOM.
|
||||
* Once the user types a single character, the `required` message will disappear (since the field now has a value)
|
||||
* but the `email` message will be visible because it is still applicable.
|
||||
*
|
||||
* Then the `required` message will be displayed first. When required is false then the `minlength` message
|
||||
* will be displayed right after (since these messages are ordered this way in the template HTML code).
|
||||
* The prioritization of each message is determined by what order they're present in the DOM.
|
||||
* Therefore, instead of having custom JavaScript code determine the priority of what errors are
|
||||
* present before others, the presentation of the errors are handled within the template.
|
||||
* ### Displaying multiple messages at the same time
|
||||
*
|
||||
* By default, ngMessages will only display one error at a time. However, if you wish to display all
|
||||
* messages then the `ng-messages-multiple` attribute flag can be used on the element containing the
|
||||
* ngMessages directive to make this happen.
|
||||
* While `ngMessages` will by default only display one error element at a time, the `ng-messages-multiple` attribute can
|
||||
* be applied to the `ngMessages` container element to cause it to display all applicable error messages at once:
|
||||
*
|
||||
* ```html
|
||||
* <!-- attribute-style usage -->
|
||||
* <div ng-messages="myForm.myField.$error" ng-messages-multiple>...</div>
|
||||
*
|
||||
* <!-- element-style usage -->
|
||||
* <ng-messages for="myForm.myField.$error" multiple>...</ng-messages>
|
||||
* ```
|
||||
*
|
||||
* ## Reusing and Overriding Messages
|
||||
|
@ -70,12 +108,15 @@
|
|||
* <div ng-message="required">This field is required</div>
|
||||
* <div ng-message="minlength">This field is too short</div>
|
||||
* </script>
|
||||
* <div ng-messages="myForm.myField.$error" ng-messages-include="error-messages"></div>
|
||||
*
|
||||
* <div ng-messages="myForm.myField.$error" role="alert">
|
||||
* <div ng-messages-include="error-messages"></div>
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* However, including generic messages may not be useful enough to match all input fields, therefore,
|
||||
* `ngMessages` provides the ability to override messages defined in the remote template by redefining
|
||||
* then within the directive container.
|
||||
* them within the directive container.
|
||||
*
|
||||
* ```html
|
||||
* <!-- a generic template of error messages known as "my-custom-messages" -->
|
||||
|
@ -85,19 +126,26 @@
|
|||
* </script>
|
||||
*
|
||||
* <form name="myForm">
|
||||
* <input type="email"
|
||||
* id="email"
|
||||
* name="myEmail"
|
||||
* ng-model="email"
|
||||
* minlength="5"
|
||||
* required />
|
||||
*
|
||||
* <div ng-messages="myForm.myEmail.$error" ng-messages-include="my-custom-messages">
|
||||
* <label>
|
||||
* Email address
|
||||
* <input type="email"
|
||||
* id="email"
|
||||
* name="myEmail"
|
||||
* ng-model="email"
|
||||
* minlength="5"
|
||||
* required />
|
||||
* </label>
|
||||
* <!-- any ng-message elements that appear BEFORE the ng-messages-include will
|
||||
* override the messages present in the ng-messages-include template -->
|
||||
* <div ng-messages="myForm.myEmail.$error" role="alert">
|
||||
* <!-- this required message has overridden the template message -->
|
||||
* <div ng-message="required">You did not enter your email address</div>
|
||||
*
|
||||
* <!-- this is a brand new message and will appear last in the prioritization -->
|
||||
* <div ng-message="email">Your email address is invalid</div>
|
||||
*
|
||||
* <!-- and here are the generic error messages -->
|
||||
* <div ng-messages-include="my-custom-messages"></div>
|
||||
* </div>
|
||||
* </form>
|
||||
* ```
|
||||
|
@ -107,20 +155,80 @@
|
|||
* email addresses, date fields, autocomplete inputs, etc...), specialized error messages can be applied
|
||||
* while more generic messages can be used to handle other, more general input errors.
|
||||
*
|
||||
* ## Dynamic Messaging
|
||||
* ngMessages also supports using expressions to dynamically change key values. Using arrays and
|
||||
* repeaters to list messages is also supported. This means that the code below will be able to
|
||||
* fully adapt itself and display the appropriate message when any of the expression data changes:
|
||||
*
|
||||
* ```html
|
||||
* <form name="myForm">
|
||||
* <label>
|
||||
* Email address
|
||||
* <input type="email"
|
||||
* name="myEmail"
|
||||
* ng-model="email"
|
||||
* minlength="5"
|
||||
* required />
|
||||
* </label>
|
||||
* <div ng-messages="myForm.myEmail.$error" role="alert">
|
||||
* <div ng-message="required">You did not enter your email address</div>
|
||||
* <div ng-repeat="errorMessage in errorMessages">
|
||||
* <!-- use ng-message-exp for a message whose key is given by an expression -->
|
||||
* <div ng-message-exp="errorMessage.type">{{ errorMessage.text }}</div>
|
||||
* </div>
|
||||
* </div>
|
||||
* </form>
|
||||
* ```
|
||||
*
|
||||
* The `errorMessage.type` expression can be a string value or it can be an array so
|
||||
* that multiple errors can be associated with a single error message:
|
||||
*
|
||||
* ```html
|
||||
* <label>
|
||||
* Email address
|
||||
* <input type="email"
|
||||
* ng-model="data.email"
|
||||
* name="myEmail"
|
||||
* ng-minlength="5"
|
||||
* ng-maxlength="100"
|
||||
* required />
|
||||
* </label>
|
||||
* <div ng-messages="myForm.myEmail.$error" role="alert">
|
||||
* <div ng-message-exp="'required'">You did not enter your email address</div>
|
||||
* <div ng-message-exp="['minlength', 'maxlength']">
|
||||
* Your email must be between 5 and 100 characters long
|
||||
* </div>
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* Feel free to use other structural directives such as ng-if and ng-switch to further control
|
||||
* what messages are active and when. Be careful, if you place ng-message on the same element
|
||||
* as these structural directives, Angular may not be able to determine if a message is active
|
||||
* or not. Therefore it is best to place the ng-message on a child element of the structural
|
||||
* directive.
|
||||
*
|
||||
* ```html
|
||||
* <div ng-messages="myForm.myEmail.$error" role="alert">
|
||||
* <div ng-if="showRequiredError">
|
||||
* <div ng-message="required">Please enter something</div>
|
||||
* </div>
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* ## Animations
|
||||
* If the `ngAnimate` module is active within the application then both the `ngMessages` and
|
||||
* `ngMessage` directives will trigger animations whenever any messages are added and removed
|
||||
* from the DOM by the `ngMessages` directive.
|
||||
* If the `ngAnimate` module is active within the application then the `ngMessages`, `ngMessage` and
|
||||
* `ngMessageExp` directives will trigger animations whenever any messages are added and removed from
|
||||
* the DOM by the `ngMessages` directive.
|
||||
*
|
||||
* Whenever the `ngMessages` directive contains one or more visible messages then the `.ng-active` CSS
|
||||
* class will be added to the element. The `.ng-inactive` CSS class will be applied when there are no
|
||||
* animations present. Therefore, CSS transitions and keyframes as well as JavaScript animations can
|
||||
* messages present. Therefore, CSS transitions and keyframes as well as JavaScript animations can
|
||||
* hook into the animations whenever these classes are added/removed.
|
||||
*
|
||||
* Let's say that our HTML code for our messages container looks like so:
|
||||
*
|
||||
* ```html
|
||||
* <div ng-messages="myMessages" class="my-messages">
|
||||
* <div ng-messages="myMessages" class="my-messages" role="alert">
|
||||
* <div ng-message="alert" class="some-message">...</div>
|
||||
* <div ng-message="fail" class="some-message">...</div>
|
||||
* </div>
|
||||
|
@ -169,14 +277,14 @@ angular.module('ngMessages', [])
|
|||
*
|
||||
* @description
|
||||
* `ngMessages` is a directive that is designed to show and hide messages based on the state
|
||||
* of a key/value object that it listens on. The directive itself compliments error message
|
||||
* of a key/value object that it listens on. The directive itself complements error message
|
||||
* reporting with the `ngModel` $error object (which stores a key/value state of validation errors).
|
||||
*
|
||||
* `ngMessages` manages the state of internal messages within its container element. The internal
|
||||
* messages use the `ngMessage` directive and will be inserted/removed from the page depending
|
||||
* on if they're present within the key/value object. By default, only one message will be displayed
|
||||
* at a time and this depends on the prioritization of the messages within the template. (This can
|
||||
* be changed by using the ng-messages-multiple on the directive container.)
|
||||
* be changed by using the `ng-messages-multiple` or `multiple` attribute on the directive container.)
|
||||
*
|
||||
* A remote template can also be used to promote message reusability and messages can also be
|
||||
* overridden.
|
||||
|
@ -186,24 +294,23 @@ angular.module('ngMessages', [])
|
|||
* @usage
|
||||
* ```html
|
||||
* <!-- using attribute directives -->
|
||||
* <ANY ng-messages="expression">
|
||||
* <ANY ng-message="keyValue1">...</ANY>
|
||||
* <ANY ng-message="keyValue2">...</ANY>
|
||||
* <ANY ng-message="keyValue3">...</ANY>
|
||||
* <ANY ng-messages="expression" role="alert">
|
||||
* <ANY ng-message="stringValue">...</ANY>
|
||||
* <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
|
||||
* <ANY ng-message-exp="expressionValue">...</ANY>
|
||||
* </ANY>
|
||||
*
|
||||
* <!-- or by using element directives -->
|
||||
* <ng-messages for="expression">
|
||||
* <ng-message when="keyValue1">...</ng-message>
|
||||
* <ng-message when="keyValue2">...</ng-message>
|
||||
* <ng-message when="keyValue3">...</ng-message>
|
||||
* <ng-messages for="expression" role="alert">
|
||||
* <ng-message when="stringValue">...</ng-message>
|
||||
* <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
|
||||
* <ng-message when-exp="expressionValue">...</ng-message>
|
||||
* </ng-messages>
|
||||
* ```
|
||||
*
|
||||
* @param {string} ngMessages an angular expression evaluating to a key/value object
|
||||
* (this is typically the $error object on an ngModel instance).
|
||||
* @param {string=} ngMessagesMultiple|multiple when set, all messages will be displayed with true
|
||||
* @param {string=} ngMessagesInclude|include when set, the specified template will be included into the ng-messages container
|
||||
*
|
||||
* @example
|
||||
* <example name="ngMessages-directive" module="ngMessagesExample"
|
||||
|
@ -211,17 +318,18 @@ angular.module('ngMessages', [])
|
|||
* animations="true" fixBase="true">
|
||||
* <file name="index.html">
|
||||
* <form name="myForm">
|
||||
* <label>Enter your name:</label>
|
||||
* <input type="text"
|
||||
* name="myName"
|
||||
* ng-model="name"
|
||||
* ng-minlength="5"
|
||||
* ng-maxlength="20"
|
||||
* required />
|
||||
*
|
||||
* <label>
|
||||
* Enter your name:
|
||||
* <input type="text"
|
||||
* name="myName"
|
||||
* ng-model="name"
|
||||
* ng-minlength="5"
|
||||
* ng-maxlength="20"
|
||||
* required />
|
||||
* </label>
|
||||
* <pre>myForm.myName.$error = {{ myForm.myName.$error | json }}</pre>
|
||||
*
|
||||
* <div ng-messages="myForm.myName.$error" style="color:maroon">
|
||||
* <div ng-messages="myForm.myName.$error" style="color:maroon" role="alert">
|
||||
* <div ng-message="required">You did not enter a field</div>
|
||||
* <div ng-message="minlength">Your field is too short</div>
|
||||
* <div ng-message="maxlength">Your field is too long</div>
|
||||
|
@ -233,91 +341,223 @@ angular.module('ngMessages', [])
|
|||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
.directive('ngMessages', ['$compile', '$animate', '$templateRequest',
|
||||
function($compile, $animate, $templateRequest) {
|
||||
var ACTIVE_CLASS = 'ng-active';
|
||||
var INACTIVE_CLASS = 'ng-inactive';
|
||||
.directive('ngMessages', ['$animate', function($animate) {
|
||||
var ACTIVE_CLASS = 'ng-active';
|
||||
var INACTIVE_CLASS = 'ng-inactive';
|
||||
|
||||
return {
|
||||
restrict: 'AE',
|
||||
controller: function() {
|
||||
this.$renderNgMessageClasses = angular.noop;
|
||||
return {
|
||||
require: 'ngMessages',
|
||||
restrict: 'AE',
|
||||
controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
|
||||
var ctrl = this;
|
||||
var latestKey = 0;
|
||||
var nextAttachId = 0;
|
||||
|
||||
var messages = [];
|
||||
this.registerMessage = function(index, message) {
|
||||
for (var i = 0; i < messages.length; i++) {
|
||||
if (messages[i].type == message.type) {
|
||||
if (index != i) {
|
||||
var temp = messages[index];
|
||||
messages[index] = messages[i];
|
||||
if (index < messages.length) {
|
||||
messages[i] = temp;
|
||||
} else {
|
||||
messages.splice(0, i); //remove the old one (and shift left)
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
messages.splice(index, 0, message); //add the new one (and shift right)
|
||||
};
|
||||
this.getAttachId = function getAttachId() { return nextAttachId++; };
|
||||
|
||||
this.renderMessages = function(values, multiple) {
|
||||
values = values || {};
|
||||
var messages = this.messages = {};
|
||||
var renderLater, cachedCollection;
|
||||
|
||||
var found;
|
||||
angular.forEach(messages, function(message) {
|
||||
if ((!found || multiple) && truthyVal(values[message.type])) {
|
||||
message.attach();
|
||||
found = true;
|
||||
} else {
|
||||
message.detach();
|
||||
}
|
||||
});
|
||||
this.render = function(collection) {
|
||||
collection = collection || {};
|
||||
|
||||
this.renderElementClasses(found);
|
||||
renderLater = false;
|
||||
cachedCollection = collection;
|
||||
|
||||
function truthyVal(value) {
|
||||
return value !== null && value !== false && value;
|
||||
}
|
||||
};
|
||||
},
|
||||
require: 'ngMessages',
|
||||
link: function($scope, element, $attrs, ctrl) {
|
||||
ctrl.renderElementClasses = function(bool) {
|
||||
bool ? $animate.setClass(element, ACTIVE_CLASS, INACTIVE_CLASS)
|
||||
: $animate.setClass(element, INACTIVE_CLASS, ACTIVE_CLASS);
|
||||
};
|
||||
// this is true if the attribute is empty or if the attribute value is truthy
|
||||
var multiple = isAttrTruthy($scope, $attrs.ngMessagesMultiple) ||
|
||||
isAttrTruthy($scope, $attrs.multiple);
|
||||
|
||||
//JavaScript treats empty strings as false, but ng-message-multiple by itself is an empty string
|
||||
var multiple = angular.isString($attrs.ngMessagesMultiple) ||
|
||||
angular.isString($attrs.multiple);
|
||||
var unmatchedMessages = [];
|
||||
var matchedKeys = {};
|
||||
var messageItem = ctrl.head;
|
||||
var messageFound = false;
|
||||
var totalMessages = 0;
|
||||
|
||||
var cachedValues, watchAttr = $attrs.ngMessages || $attrs['for']; //for is a reserved keyword
|
||||
$scope.$watchCollection(watchAttr, function(values) {
|
||||
cachedValues = values;
|
||||
ctrl.renderMessages(values, multiple);
|
||||
});
|
||||
// we use != instead of !== to allow for both undefined and null values
|
||||
while (messageItem != null) {
|
||||
totalMessages++;
|
||||
var messageCtrl = messageItem.message;
|
||||
|
||||
var tpl = $attrs.ngMessagesInclude || $attrs.include;
|
||||
if (tpl) {
|
||||
$templateRequest(tpl)
|
||||
.then(function processTemplate(html) {
|
||||
var after, container = angular.element('<div/>').html(html);
|
||||
angular.forEach(container.children(), function(elm) {
|
||||
elm = angular.element(elm);
|
||||
after ? after.after(elm)
|
||||
: element.prepend(elm); //start of the container
|
||||
after = elm;
|
||||
$compile(elm)($scope);
|
||||
});
|
||||
ctrl.renderMessages(cachedValues, multiple);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}])
|
||||
var messageUsed = false;
|
||||
if (!messageFound) {
|
||||
forEach(collection, function(value, key) {
|
||||
if (!messageUsed && truthy(value) && messageCtrl.test(key)) {
|
||||
// this is to prevent the same error name from showing up twice
|
||||
if (matchedKeys[key]) return;
|
||||
matchedKeys[key] = true;
|
||||
|
||||
messageUsed = true;
|
||||
messageCtrl.attach();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (messageUsed) {
|
||||
// unless we want to display multiple messages then we should
|
||||
// set a flag here to avoid displaying the next message in the list
|
||||
messageFound = !multiple;
|
||||
} else {
|
||||
unmatchedMessages.push(messageCtrl);
|
||||
}
|
||||
|
||||
messageItem = messageItem.next;
|
||||
}
|
||||
|
||||
forEach(unmatchedMessages, function(messageCtrl) {
|
||||
messageCtrl.detach();
|
||||
});
|
||||
|
||||
unmatchedMessages.length !== totalMessages
|
||||
? $animate.setClass($element, ACTIVE_CLASS, INACTIVE_CLASS)
|
||||
: $animate.setClass($element, INACTIVE_CLASS, ACTIVE_CLASS);
|
||||
};
|
||||
|
||||
$scope.$watchCollection($attrs.ngMessages || $attrs['for'], ctrl.render);
|
||||
|
||||
this.reRender = function() {
|
||||
if (!renderLater) {
|
||||
renderLater = true;
|
||||
$scope.$evalAsync(function() {
|
||||
if (renderLater) {
|
||||
cachedCollection && ctrl.render(cachedCollection);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.register = function(comment, messageCtrl) {
|
||||
var nextKey = latestKey.toString();
|
||||
messages[nextKey] = {
|
||||
message: messageCtrl
|
||||
};
|
||||
insertMessageNode($element[0], comment, nextKey);
|
||||
comment.$$ngMessageNode = nextKey;
|
||||
latestKey++;
|
||||
|
||||
ctrl.reRender();
|
||||
};
|
||||
|
||||
this.deregister = function(comment) {
|
||||
var key = comment.$$ngMessageNode;
|
||||
delete comment.$$ngMessageNode;
|
||||
removeMessageNode($element[0], comment, key);
|
||||
delete messages[key];
|
||||
ctrl.reRender();
|
||||
};
|
||||
|
||||
function findPreviousMessage(parent, comment) {
|
||||
var prevNode = comment;
|
||||
var parentLookup = [];
|
||||
while (prevNode && prevNode !== parent) {
|
||||
var prevKey = prevNode.$$ngMessageNode;
|
||||
if (prevKey && prevKey.length) {
|
||||
return messages[prevKey];
|
||||
}
|
||||
|
||||
// dive deeper into the DOM and examine its children for any ngMessage
|
||||
// comments that may be in an element that appears deeper in the list
|
||||
if (prevNode.childNodes.length && parentLookup.indexOf(prevNode) == -1) {
|
||||
parentLookup.push(prevNode);
|
||||
prevNode = prevNode.childNodes[prevNode.childNodes.length - 1];
|
||||
} else {
|
||||
prevNode = prevNode.previousSibling || prevNode.parentNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function insertMessageNode(parent, comment, key) {
|
||||
var messageNode = messages[key];
|
||||
if (!ctrl.head) {
|
||||
ctrl.head = messageNode;
|
||||
} else {
|
||||
var match = findPreviousMessage(parent, comment);
|
||||
if (match) {
|
||||
messageNode.next = match.next;
|
||||
match.next = messageNode;
|
||||
} else {
|
||||
messageNode.next = ctrl.head;
|
||||
ctrl.head = messageNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeMessageNode(parent, comment, key) {
|
||||
var messageNode = messages[key];
|
||||
|
||||
var match = findPreviousMessage(parent, comment);
|
||||
if (match) {
|
||||
match.next = messageNode.next;
|
||||
} else {
|
||||
ctrl.head = messageNode.next;
|
||||
}
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
function isAttrTruthy(scope, attr) {
|
||||
return (isString(attr) && attr.length === 0) || //empty attribute
|
||||
truthy(scope.$eval(attr));
|
||||
}
|
||||
|
||||
function truthy(val) {
|
||||
return isString(val) ? val.length : !!val;
|
||||
}
|
||||
}])
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngMessagesInclude
|
||||
* @restrict AE
|
||||
* @scope
|
||||
*
|
||||
* @description
|
||||
* `ngMessagesInclude` is a directive with the purpose to import existing ngMessage template
|
||||
* code from a remote template and place the downloaded template code into the exact spot
|
||||
* that the ngMessagesInclude directive is placed within the ngMessages container. This allows
|
||||
* for a series of pre-defined messages to be reused and also allows for the developer to
|
||||
* determine what messages are overridden due to the placement of the ngMessagesInclude directive.
|
||||
*
|
||||
* @usage
|
||||
* ```html
|
||||
* <!-- using attribute directives -->
|
||||
* <ANY ng-messages="expression" role="alert">
|
||||
* <ANY ng-messages-include="remoteTplString">...</ANY>
|
||||
* </ANY>
|
||||
*
|
||||
* <!-- or by using element directives -->
|
||||
* <ng-messages for="expression" role="alert">
|
||||
* <ng-messages-include src="expressionValue1">...</ng-messages-include>
|
||||
* </ng-messages>
|
||||
* ```
|
||||
*
|
||||
* {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
|
||||
*
|
||||
* @param {string} ngMessagesInclude|src a string value corresponding to the remote template.
|
||||
*/
|
||||
.directive('ngMessagesInclude',
|
||||
['$templateRequest', '$document', '$compile', function($templateRequest, $document, $compile) {
|
||||
|
||||
return {
|
||||
restrict: 'AE',
|
||||
require: '^^ngMessages', // we only require this for validation sake
|
||||
link: function($scope, element, attrs) {
|
||||
var src = attrs.ngMessagesInclude || attrs.src;
|
||||
$templateRequest(src).then(function(html) {
|
||||
$compile(html)($scope, function(contents) {
|
||||
element.after(contents);
|
||||
|
||||
// the anchor is placed for debugging purposes
|
||||
var anchor = jqLite($document[0].createComment(' ngMessagesInclude: ' + src + ' '));
|
||||
element.after(anchor);
|
||||
|
||||
// we don't want to pollute the DOM anymore by keeping an empty directive element
|
||||
element.remove();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}])
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
|
@ -337,65 +577,130 @@ angular.module('ngMessages', [])
|
|||
* @usage
|
||||
* ```html
|
||||
* <!-- using attribute directives -->
|
||||
* <ANY ng-messages="expression" role="alert">
|
||||
* <ANY ng-message="stringValue">...</ANY>
|
||||
* <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
|
||||
* </ANY>
|
||||
*
|
||||
* <!-- or by using element directives -->
|
||||
* <ng-messages for="expression" role="alert">
|
||||
* <ng-message when="stringValue">...</ng-message>
|
||||
* <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
|
||||
* </ng-messages>
|
||||
* ```
|
||||
*
|
||||
* @param {expression} ngMessage|when a string value corresponding to the message key.
|
||||
*/
|
||||
.directive('ngMessage', ngMessageDirectiveFactory('AE'))
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngMessageExp
|
||||
* @restrict AE
|
||||
* @scope
|
||||
*
|
||||
* @description
|
||||
* `ngMessageExp` is a directive with the purpose to show and hide a particular message.
|
||||
* For `ngMessageExp` to operate, a parent `ngMessages` directive on a parent DOM element
|
||||
* must be situated since it determines which messages are visible based on the state
|
||||
* of the provided key/value map that `ngMessages` listens on.
|
||||
*
|
||||
* @usage
|
||||
* ```html
|
||||
* <!-- using attribute directives -->
|
||||
* <ANY ng-messages="expression">
|
||||
* <ANY ng-message="keyValue1">...</ANY>
|
||||
* <ANY ng-message="keyValue2">...</ANY>
|
||||
* <ANY ng-message="keyValue3">...</ANY>
|
||||
* <ANY ng-message-exp="expressionValue">...</ANY>
|
||||
* </ANY>
|
||||
*
|
||||
* <!-- or by using element directives -->
|
||||
* <ng-messages for="expression">
|
||||
* <ng-message when="keyValue1">...</ng-message>
|
||||
* <ng-message when="keyValue2">...</ng-message>
|
||||
* <ng-message when="keyValue3">...</ng-message>
|
||||
* <ng-message when-exp="expressionValue">...</ng-message>
|
||||
* </ng-messages>
|
||||
* ```
|
||||
*
|
||||
* @param {string} ngMessage a string value corresponding to the message key.
|
||||
* {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
|
||||
*
|
||||
* @param {expression} ngMessageExp|whenExp an expression value corresponding to the message key.
|
||||
*/
|
||||
.directive('ngMessage', ['$animate', function($animate) {
|
||||
var COMMENT_NODE = 8;
|
||||
.directive('ngMessageExp', ngMessageDirectiveFactory('A'));
|
||||
|
||||
function ngMessageDirectiveFactory(restrict) {
|
||||
return ['$animate', function($animate) {
|
||||
return {
|
||||
require: '^ngMessages',
|
||||
restrict: 'AE',
|
||||
transclude: 'element',
|
||||
terminal: true,
|
||||
restrict: 'AE',
|
||||
link: function($scope, $element, $attrs, ngMessages, $transclude) {
|
||||
var index, element;
|
||||
require: '^^ngMessages',
|
||||
link: function(scope, element, attrs, ngMessagesCtrl, $transclude) {
|
||||
var commentNode = element[0];
|
||||
|
||||
var commentNode = $element[0];
|
||||
var parentNode = commentNode.parentNode;
|
||||
for (var i = 0, j = 0; i < parentNode.childNodes.length; i++) {
|
||||
var node = parentNode.childNodes[i];
|
||||
if (node.nodeType == COMMENT_NODE && node.nodeValue.indexOf('ngMessage') >= 0) {
|
||||
if (node === commentNode) {
|
||||
index = j;
|
||||
break;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
var records;
|
||||
var staticExp = attrs.ngMessage || attrs.when;
|
||||
var dynamicExp = attrs.ngMessageExp || attrs.whenExp;
|
||||
var assignRecords = function(items) {
|
||||
records = items
|
||||
? (isArray(items)
|
||||
? items
|
||||
: items.split(/[\s,]+/))
|
||||
: null;
|
||||
ngMessagesCtrl.reRender();
|
||||
};
|
||||
|
||||
if (dynamicExp) {
|
||||
assignRecords(scope.$eval(dynamicExp));
|
||||
scope.$watchCollection(dynamicExp, assignRecords);
|
||||
} else {
|
||||
assignRecords(staticExp);
|
||||
}
|
||||
|
||||
ngMessages.registerMessage(index, {
|
||||
type: $attrs.ngMessage || $attrs.when,
|
||||
var currentElement, messageCtrl;
|
||||
ngMessagesCtrl.register(commentNode, messageCtrl = {
|
||||
test: function(name) {
|
||||
return contains(records, name);
|
||||
},
|
||||
attach: function() {
|
||||
if (!element) {
|
||||
$transclude($scope, function(clone) {
|
||||
$animate.enter(clone, null, $element);
|
||||
element = clone;
|
||||
if (!currentElement) {
|
||||
$transclude(scope, function(elm) {
|
||||
$animate.enter(elm, null, element);
|
||||
currentElement = elm;
|
||||
|
||||
// Each time we attach this node to a message we get a new id that we can match
|
||||
// when we are destroying the node later.
|
||||
var $$attachId = currentElement.$$attachId = ngMessagesCtrl.getAttachId();
|
||||
|
||||
// in the event that the parent element is destroyed
|
||||
// by any other structural directive then it's time
|
||||
// to deregister the message from the controller
|
||||
currentElement.on('$destroy', function() {
|
||||
if (currentElement && currentElement.$$attachId === $$attachId) {
|
||||
ngMessagesCtrl.deregister(commentNode);
|
||||
messageCtrl.detach();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
detach: function(now) {
|
||||
if (element) {
|
||||
$animate.leave(element);
|
||||
element = null;
|
||||
detach: function() {
|
||||
if (currentElement) {
|
||||
var elm = currentElement;
|
||||
currentElement = null;
|
||||
$animate.leave(elm);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
}];
|
||||
|
||||
function contains(collection, key) {
|
||||
if (collection) {
|
||||
return isArray(collection)
|
||||
? collection.indexOf(key) >= 0
|
||||
: collection.hasOwnProperty(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
})(window, window.angular);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* @license AngularJS v1.3.18
|
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* @license AngularJS v1.4.10
|
||||
* (c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, angular, undefined) {
|
||||
|
@ -64,10 +64,9 @@ angular.mock.$Browser = function() {
|
|||
return listener;
|
||||
};
|
||||
|
||||
self.$$applicationDestroyed = angular.noop;
|
||||
self.$$checkUrlChange = angular.noop;
|
||||
|
||||
self.cookieHash = {};
|
||||
self.lastCookieHash = {};
|
||||
self.deferredFns = [];
|
||||
self.deferredNextId = 0;
|
||||
|
||||
|
@ -95,7 +94,7 @@ angular.mock.$Browser = function() {
|
|||
if (fn.id === deferId) fnIndex = index;
|
||||
});
|
||||
|
||||
if (fnIndex !== undefined) {
|
||||
if (angular.isDefined(fnIndex)) {
|
||||
self.deferredFns.splice(fnIndex, 1);
|
||||
return true;
|
||||
}
|
||||
|
@ -147,11 +146,6 @@ angular.mock.$Browser.prototype = {
|
|||
});
|
||||
},
|
||||
|
||||
addPollFn: function(pollFn) {
|
||||
this.pollFns.push(pollFn);
|
||||
return pollFn;
|
||||
},
|
||||
|
||||
url: function(url, replace, state) {
|
||||
if (angular.isUndefined(state)) {
|
||||
state = null;
|
||||
|
@ -170,25 +164,6 @@ angular.mock.$Browser.prototype = {
|
|||
return this.$$state;
|
||||
},
|
||||
|
||||
cookies: function(name, value) {
|
||||
if (name) {
|
||||
if (angular.isUndefined(value)) {
|
||||
delete this.cookieHash[name];
|
||||
} else {
|
||||
if (angular.isString(value) && //strings only
|
||||
value.length <= 4096) { //strict cookie storage limits
|
||||
this.cookieHash[name] = value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!angular.equals(this.cookieHash, this.lastCookieHash)) {
|
||||
this.lastCookieHash = angular.copy(this.cookieHash);
|
||||
this.cookieHash = angular.copy(this.cookieHash);
|
||||
}
|
||||
return this.cookieHash;
|
||||
}
|
||||
},
|
||||
|
||||
notifyWhenNoOutstandingRequests: function(fn) {
|
||||
fn();
|
||||
}
|
||||
|
@ -458,6 +433,7 @@ angular.mock.$LogProvider = function() {
|
|||
* indefinitely.
|
||||
* @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
|
||||
* will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
|
||||
* @param {...*=} Pass additional parameters to the executed function.
|
||||
* @returns {promise} A promise which will be notified on each iteration.
|
||||
*/
|
||||
angular.mock.$IntervalProvider = function() {
|
||||
|
@ -468,13 +444,17 @@ angular.mock.$IntervalProvider = function() {
|
|||
now = 0;
|
||||
|
||||
var $interval = function(fn, delay, count, invokeApply) {
|
||||
var iteration = 0,
|
||||
var hasParams = arguments.length > 4,
|
||||
args = hasParams ? Array.prototype.slice.call(arguments, 4) : [],
|
||||
iteration = 0,
|
||||
skipApply = (angular.isDefined(invokeApply) && !invokeApply),
|
||||
deferred = (skipApply ? $$q : $q).defer(),
|
||||
promise = deferred.promise;
|
||||
|
||||
count = (angular.isDefined(count)) ? count : 0;
|
||||
promise.then(null, null, fn);
|
||||
promise.then(null, null, (!hasParams) ? fn : function() {
|
||||
fn.apply(null, args);
|
||||
});
|
||||
|
||||
promise.$$intervalId = nextRepeatId;
|
||||
|
||||
|
@ -489,7 +469,7 @@ angular.mock.$IntervalProvider = function() {
|
|||
if (fn.id === promise.$$intervalId) fnIndex = index;
|
||||
});
|
||||
|
||||
if (fnIndex !== undefined) {
|
||||
if (angular.isDefined(fnIndex)) {
|
||||
repeatFns.splice(fnIndex, 1);
|
||||
}
|
||||
}
|
||||
|
@ -531,7 +511,7 @@ angular.mock.$IntervalProvider = function() {
|
|||
if (fn.id === promise.$$intervalId) fnIndex = index;
|
||||
});
|
||||
|
||||
if (fnIndex !== undefined) {
|
||||
if (angular.isDefined(fnIndex)) {
|
||||
repeatFns[fnIndex].deferred.reject('canceled');
|
||||
repeatFns.splice(fnIndex, 1);
|
||||
return true;
|
||||
|
@ -581,20 +561,20 @@ function jsonStringToDate(string) {
|
|||
tzHour = 0,
|
||||
tzMin = 0;
|
||||
if (match[9]) {
|
||||
tzHour = int(match[9] + match[10]);
|
||||
tzMin = int(match[9] + match[11]);
|
||||
tzHour = toInt(match[9] + match[10]);
|
||||
tzMin = toInt(match[9] + match[11]);
|
||||
}
|
||||
date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
|
||||
date.setUTCHours(int(match[4] || 0) - tzHour,
|
||||
int(match[5] || 0) - tzMin,
|
||||
int(match[6] || 0),
|
||||
int(match[7] || 0));
|
||||
date.setUTCFullYear(toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
|
||||
date.setUTCHours(toInt(match[4] || 0) - tzHour,
|
||||
toInt(match[5] || 0) - tzMin,
|
||||
toInt(match[6] || 0),
|
||||
toInt(match[7] || 0));
|
||||
return date;
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
function int(str) {
|
||||
function toInt(str) {
|
||||
return parseInt(str, 10);
|
||||
}
|
||||
|
||||
|
@ -606,8 +586,9 @@ function padNumber(num, digits, trim) {
|
|||
}
|
||||
num = '' + num;
|
||||
while (num.length < digits) num = '0' + num;
|
||||
if (trim)
|
||||
if (trim) {
|
||||
num = num.substr(num.length - digits);
|
||||
}
|
||||
return neg + num;
|
||||
}
|
||||
|
||||
|
@ -657,11 +638,12 @@ angular.mock.TzDate = function(offset, timestamp) {
|
|||
self.origDate = jsonStringToDate(timestamp);
|
||||
|
||||
timestamp = self.origDate.getTime();
|
||||
if (isNaN(timestamp))
|
||||
if (isNaN(timestamp)) {
|
||||
throw {
|
||||
name: "Illegal Argument",
|
||||
message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string"
|
||||
};
|
||||
}
|
||||
} else {
|
||||
self.origDate = new Date(timestamp);
|
||||
}
|
||||
|
@ -776,40 +758,152 @@ angular.mock.TzDate = function(offset, timestamp) {
|
|||
angular.mock.TzDate.prototype = Date.prototype;
|
||||
/* jshint +W101 */
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $animate
|
||||
*
|
||||
* @description
|
||||
* Mock implementation of the {@link ng.$animate `$animate`} service. Exposes two additional methods
|
||||
* for testing animations.
|
||||
*/
|
||||
angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
|
||||
|
||||
.config(['$provide', function($provide) {
|
||||
|
||||
var reflowQueue = [];
|
||||
$provide.value('$$animateReflow', function(fn) {
|
||||
var index = reflowQueue.length;
|
||||
reflowQueue.push(fn);
|
||||
return function cancel() {
|
||||
reflowQueue.splice(index, 1);
|
||||
};
|
||||
$provide.factory('$$forceReflow', function() {
|
||||
function reflowFn() {
|
||||
reflowFn.totalReflows++;
|
||||
}
|
||||
reflowFn.totalReflows = 0;
|
||||
return reflowFn;
|
||||
});
|
||||
|
||||
$provide.decorator('$animate', ['$delegate', '$$asyncCallback', '$timeout', '$browser',
|
||||
function($delegate, $$asyncCallback, $timeout, $browser) {
|
||||
$provide.factory('$$animateAsyncRun', function() {
|
||||
var queue = [];
|
||||
var queueFn = function() {
|
||||
return function(fn) {
|
||||
queue.push(fn);
|
||||
};
|
||||
};
|
||||
queueFn.flush = function() {
|
||||
if (queue.length === 0) return false;
|
||||
|
||||
for (var i = 0; i < queue.length; i++) {
|
||||
queue[i]();
|
||||
}
|
||||
queue = [];
|
||||
|
||||
return true;
|
||||
};
|
||||
return queueFn;
|
||||
});
|
||||
|
||||
$provide.decorator('$$animateJs', ['$delegate', function($delegate) {
|
||||
var runners = [];
|
||||
|
||||
var animateJsConstructor = function() {
|
||||
var animator = $delegate.apply($delegate, arguments);
|
||||
// If no javascript animation is found, animator is undefined
|
||||
if (animator) {
|
||||
runners.push(animator);
|
||||
}
|
||||
return animator;
|
||||
};
|
||||
|
||||
animateJsConstructor.$closeAndFlush = function() {
|
||||
runners.forEach(function(runner) {
|
||||
runner.end();
|
||||
});
|
||||
runners = [];
|
||||
};
|
||||
|
||||
return animateJsConstructor;
|
||||
}]);
|
||||
|
||||
$provide.decorator('$animateCss', ['$delegate', function($delegate) {
|
||||
var runners = [];
|
||||
|
||||
var animateCssConstructor = function(element, options) {
|
||||
var animator = $delegate(element, options);
|
||||
runners.push(animator);
|
||||
return animator;
|
||||
};
|
||||
|
||||
animateCssConstructor.$closeAndFlush = function() {
|
||||
runners.forEach(function(runner) {
|
||||
runner.end();
|
||||
});
|
||||
runners = [];
|
||||
};
|
||||
|
||||
return animateCssConstructor;
|
||||
}]);
|
||||
|
||||
$provide.decorator('$animate', ['$delegate', '$timeout', '$browser', '$$rAF', '$animateCss', '$$animateJs',
|
||||
'$$forceReflow', '$$animateAsyncRun', '$rootScope',
|
||||
function($delegate, $timeout, $browser, $$rAF, $animateCss, $$animateJs,
|
||||
$$forceReflow, $$animateAsyncRun, $rootScope) {
|
||||
var animate = {
|
||||
queue: [],
|
||||
cancel: $delegate.cancel,
|
||||
on: $delegate.on,
|
||||
off: $delegate.off,
|
||||
pin: $delegate.pin,
|
||||
get reflows() {
|
||||
return $$forceReflow.totalReflows;
|
||||
},
|
||||
enabled: $delegate.enabled,
|
||||
triggerCallbackEvents: function() {
|
||||
$$asyncCallback.flush();
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $animate#closeAndFlush
|
||||
* @description
|
||||
*
|
||||
* This method will close all pending animations (both {@link ngAnimate#javascript-based-animations Javascript}
|
||||
* and {@link ngAnimate.$animateCss CSS}) and it will also flush any remaining animation frames and/or callbacks.
|
||||
*/
|
||||
closeAndFlush: function() {
|
||||
// we allow the flush command to swallow the errors
|
||||
// because depending on whether CSS or JS animations are
|
||||
// used, there may not be a RAF flush. The primary flush
|
||||
// at the end of this function must throw an exception
|
||||
// because it will track if there were pending animations
|
||||
this.flush(true);
|
||||
$animateCss.$closeAndFlush();
|
||||
$$animateJs.$closeAndFlush();
|
||||
this.flush();
|
||||
},
|
||||
triggerCallbackPromise: function() {
|
||||
$timeout.flush(0);
|
||||
},
|
||||
triggerCallbacks: function() {
|
||||
this.triggerCallbackEvents();
|
||||
this.triggerCallbackPromise();
|
||||
},
|
||||
triggerReflow: function() {
|
||||
angular.forEach(reflowQueue, function(fn) {
|
||||
fn();
|
||||
});
|
||||
reflowQueue = [];
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $animate#flush
|
||||
* @description
|
||||
*
|
||||
* This method is used to flush the pending callbacks and animation frames to either start
|
||||
* an animation or conclude an animation. Note that this will not actually close an
|
||||
* actively running animation (see {@link ngMock.$animate#closeAndFlush `closeAndFlush()`} for that).
|
||||
*/
|
||||
flush: function(hideErrors) {
|
||||
$rootScope.$digest();
|
||||
|
||||
var doNextRun, somethingFlushed = false;
|
||||
do {
|
||||
doNextRun = false;
|
||||
|
||||
if ($$rAF.queue.length) {
|
||||
$$rAF.flush();
|
||||
doNextRun = somethingFlushed = true;
|
||||
}
|
||||
|
||||
if ($$animateAsyncRun.flush()) {
|
||||
doNextRun = somethingFlushed = true;
|
||||
}
|
||||
} while (doNextRun);
|
||||
|
||||
if (!somethingFlushed && !hideErrors) {
|
||||
throw new Error('No pending animations ready to be closed or flushed');
|
||||
}
|
||||
|
||||
$rootScope.$digest();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1026,7 +1120,7 @@ angular.mock.dump = function(object) {
|
|||
$http.post('/add-msg.py', message, { headers: headers } ).success(function(response) {
|
||||
$scope.status = '';
|
||||
}).error(function() {
|
||||
$scope.status = 'ERROR!';
|
||||
$scope.status = 'Failed...';
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -1107,7 +1201,7 @@ angular.mock.dump = function(object) {
|
|||
$httpBackend.flush();
|
||||
|
||||
$httpBackend.expectPOST('/add-msg.py', undefined, function(headers) {
|
||||
// check if the header was send, if it wasn't the expectation won't
|
||||
// check if the header was sent, if it wasn't the expectation won't
|
||||
// match the request and the test will fail
|
||||
return headers['Authorization'] == 'xxx';
|
||||
}).respond(201, '');
|
||||
|
@ -1154,7 +1248,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
|||
}
|
||||
|
||||
// TODO(vojta): change params to: method, url, data, headers, callback
|
||||
function $httpBackend(method, url, data, callback, headers, timeout, withCredentials) {
|
||||
function $httpBackend(method, url, data, callback, headers, timeout, withCredentials, responseType) {
|
||||
|
||||
var xhr = new MockXhr(),
|
||||
expectation = expectations[0],
|
||||
wasExpected = false;
|
||||
|
@ -1191,14 +1286,16 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
|||
}
|
||||
|
||||
if (expectation && expectation.match(method, url)) {
|
||||
if (!expectation.matchData(data))
|
||||
if (!expectation.matchData(data)) {
|
||||
throw new Error('Expected ' + expectation + ' with different data\n' +
|
||||
'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data);
|
||||
}
|
||||
|
||||
if (!expectation.matchHeaders(headers))
|
||||
if (!expectation.matchHeaders(headers)) {
|
||||
throw new Error('Expected ' + expectation + ' with different headers\n' +
|
||||
'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' +
|
||||
prettyPrint(headers));
|
||||
}
|
||||
|
||||
expectations.shift();
|
||||
|
||||
|
@ -1216,7 +1313,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
|||
// if $browser specified, we do auto flush all requests
|
||||
($browser ? $browser.defer : responsesPush)(wrapResponse(definition));
|
||||
} else if (definition.passThrough) {
|
||||
$delegate(method, url, data, callback, headers, timeout, withCredentials);
|
||||
$delegate(method, url, data, callback, headers, timeout, withCredentials, responseType);
|
||||
} else throw new Error('No response defined !');
|
||||
return;
|
||||
}
|
||||
|
@ -1234,8 +1331,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
|||
* Creates a new backend definition.
|
||||
*
|
||||
* @param {string} method HTTP method.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
|
@ -1280,8 +1377,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new backend definition for GET requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
|
@ -1294,8 +1391,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new backend definition for HEAD requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
|
@ -1308,8 +1405,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new backend definition for DELETE requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
|
@ -1322,8 +1419,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new backend definition for POST requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
|
@ -1338,8 +1435,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new backend definition for PUT requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
|
@ -1354,8 +1451,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new backend definition for JSONP requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
|
@ -1370,8 +1467,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
|||
* Creates a new request expectation.
|
||||
*
|
||||
* @param {string} method HTTP method.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
|
@ -1409,8 +1506,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new request expectation for GET requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
|
@ -1423,8 +1520,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new request expectation for HEAD requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
|
@ -1437,8 +1534,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new request expectation for DELETE requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
|
@ -1451,8 +1548,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new request expectation for POST requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
|
@ -1468,8 +1565,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new request expectation for PUT requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
|
@ -1485,8 +1582,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new request expectation for PATCH requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
|
@ -1502,8 +1599,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
|||
* @description
|
||||
* Creates a new request expectation for JSONP requests. For more info see `expect()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives an url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
|
@ -1759,47 +1856,33 @@ angular.mock.$TimeoutDecorator = ['$delegate', '$browser', function($delegate, $
|
|||
}];
|
||||
|
||||
angular.mock.$RAFDecorator = ['$delegate', function($delegate) {
|
||||
var queue = [];
|
||||
var rafFn = function(fn) {
|
||||
var index = queue.length;
|
||||
queue.push(fn);
|
||||
var index = rafFn.queue.length;
|
||||
rafFn.queue.push(fn);
|
||||
return function() {
|
||||
queue.splice(index, 1);
|
||||
rafFn.queue.splice(index, 1);
|
||||
};
|
||||
};
|
||||
|
||||
rafFn.queue = [];
|
||||
rafFn.supported = $delegate.supported;
|
||||
|
||||
rafFn.flush = function() {
|
||||
if (queue.length === 0) {
|
||||
if (rafFn.queue.length === 0) {
|
||||
throw new Error('No rAF callbacks present');
|
||||
}
|
||||
|
||||
var length = queue.length;
|
||||
var length = rafFn.queue.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
queue[i]();
|
||||
rafFn.queue[i]();
|
||||
}
|
||||
|
||||
queue = [];
|
||||
rafFn.queue = rafFn.queue.slice(i);
|
||||
};
|
||||
|
||||
return rafFn;
|
||||
}];
|
||||
|
||||
angular.mock.$AsyncCallbackDecorator = ['$delegate', function($delegate) {
|
||||
var callbacks = [];
|
||||
var addFn = function(fn) {
|
||||
callbacks.push(fn);
|
||||
};
|
||||
addFn.flush = function() {
|
||||
angular.forEach(callbacks, function(fn) {
|
||||
fn();
|
||||
});
|
||||
callbacks = [];
|
||||
};
|
||||
return addFn;
|
||||
}];
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -1833,19 +1916,19 @@ angular.mock.$RootElementProvider = function() {
|
|||
*
|
||||
* // Controller definition ...
|
||||
*
|
||||
* myMod.controller('MyDirectiveController', ['log', function($log) {
|
||||
* myMod.controller('MyDirectiveController', ['$log', function($log) {
|
||||
* $log.info(this.name);
|
||||
* })];
|
||||
* }]);
|
||||
*
|
||||
*
|
||||
* // In a test ...
|
||||
*
|
||||
* describe('myDirectiveController', function() {
|
||||
* it('should write the bound name to the log', inject(function($controller, $log) {
|
||||
* var ctrl = $controller('MyDirective', { /* no locals */ }, { name: 'Clark Kent' });
|
||||
* var ctrl = $controller('MyDirectiveController', { /* no locals */ }, { name: 'Clark Kent' });
|
||||
* expect(ctrl.name).toEqual('Clark Kent');
|
||||
* expect($log.info.logs).toEqual(['Clark Kent']);
|
||||
* });
|
||||
* }));
|
||||
* });
|
||||
*
|
||||
* ```
|
||||
|
@ -1906,7 +1989,6 @@ angular.module('ngMock', ['ng']).provider({
|
|||
}).config(['$provide', function($provide) {
|
||||
$provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
|
||||
$provide.decorator('$$rAF', angular.mock.$RAFDecorator);
|
||||
$provide.decorator('$$asyncCallback', angular.mock.$AsyncCallbackDecorator);
|
||||
$provide.decorator('$rootScope', angular.mock.$RootScopeDecorator);
|
||||
$provide.decorator('$controller', angular.mock.$ControllerDecorator);
|
||||
}]);
|
||||
|
@ -1985,8 +2067,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
|||
* Creates a new backend definition.
|
||||
*
|
||||
* @param {string} method HTTP method.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp)=} data HTTP request body.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current definition.
|
||||
|
@ -2013,8 +2095,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
|||
* @description
|
||||
* Creates a new backend definition for GET requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
||||
* control how a matched request is handled. You can save this object for later use and invoke
|
||||
|
@ -2028,8 +2110,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
|||
* @description
|
||||
* Creates a new backend definition for HEAD requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
||||
* control how a matched request is handled. You can save this object for later use and invoke
|
||||
|
@ -2043,8 +2125,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
|||
* @description
|
||||
* Creates a new backend definition for DELETE requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
||||
* control how a matched request is handled. You can save this object for later use and invoke
|
||||
|
@ -2058,8 +2140,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
|||
* @description
|
||||
* Creates a new backend definition for POST requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp)=} data HTTP request body.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
||||
|
@ -2074,8 +2156,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
|||
* @description
|
||||
* Creates a new backend definition for PUT requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp)=} data HTTP request body.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
||||
|
@ -2090,8 +2172,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
|||
* @description
|
||||
* Creates a new backend definition for PATCH requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @param {(string|RegExp)=} data HTTP request body.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
||||
|
@ -2106,8 +2188,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
|||
* @description
|
||||
* Creates a new backend definition for JSONP requests. For more info see `when()`.
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
|
||||
* and returns true if the url matches the current definition.
|
||||
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
|
||||
* control how a matched request is handled. You can save this object for later use and invoke
|
||||
* `respond` or `passThrough` again in order to change how a matched request is handled.
|
||||
|
@ -2244,8 +2326,6 @@ if (window.jasmine || window.mocha) {
|
|||
|
||||
if (injector) {
|
||||
injector.get('$rootElement').off();
|
||||
var $browser = injector.get('$browser');
|
||||
if ($browser.pollFns) $browser.pollFns.length = 0;
|
||||
}
|
||||
|
||||
// clean up jquery's fragment cache
|
||||
|
@ -2277,8 +2357,9 @@ if (window.jasmine || window.mocha) {
|
|||
* @param {...(string|Function|Object)} fns any number of modules which are represented as string
|
||||
* aliases or as anonymous module initialization functions. The modules are used to
|
||||
* configure the injector. The 'ng' and 'ngMock' modules are automatically loaded. If an
|
||||
* object literal is passed they will be registered as values in the module, the key being
|
||||
* the module name and the value being what is returned.
|
||||
* object literal is passed each key-value pair will be registered on the module via
|
||||
* {@link auto.$provide $provide}.value, the key being the string name (or token) to associate
|
||||
* with the value on the injector.
|
||||
*/
|
||||
window.module = angular.mock.module = function() {
|
||||
var moduleFns = Array.prototype.slice.call(arguments, 0);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* @license AngularJS v1.3.18
|
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* @license AngularJS v1.4.10
|
||||
* (c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, angular, undefined) {'use strict';
|
||||
|
@ -78,8 +78,8 @@ function $RouteProvider() {
|
|||
* - `controller` – `{(string|function()=}` – Controller fn that should be associated with
|
||||
* newly created scope or the name of a {@link angular.Module#controller registered
|
||||
* controller} if passed as a string.
|
||||
* - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be
|
||||
* published to scope under the `controllerAs` name.
|
||||
* - `controllerAs` – `{string=}` – An identifier name for a reference to the controller.
|
||||
* If present, the controller will be published to scope under the `controllerAs` name.
|
||||
* - `template` – `{string=|function()=}` – html template as a string or a function that
|
||||
* returns an html template as a string which should be used by {@link
|
||||
* ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives.
|
||||
|
@ -206,9 +206,9 @@ function $RouteProvider() {
|
|||
|
||||
path = path
|
||||
.replace(/([().])/g, '\\$1')
|
||||
.replace(/(\/)?:(\w+)([\?\*])?/g, function(_, slash, key, option) {
|
||||
var optional = option === '?' ? option : null;
|
||||
var star = option === '*' ? option : null;
|
||||
.replace(/(\/)?:(\w+)(\*\?|[\?\*])?/g, function(_, slash, key, option) {
|
||||
var optional = (option === '?' || option === '*?') ? '?' : null;
|
||||
var star = (option === '*' || option === '*?') ? '*' : null;
|
||||
keys.push({ name: key, optional: !!optional });
|
||||
slash = slash || '';
|
||||
return ''
|
||||
|
@ -468,10 +468,18 @@ function $RouteProvider() {
|
|||
*/
|
||||
reload: function() {
|
||||
forceReload = true;
|
||||
|
||||
var fakeLocationEvent = {
|
||||
defaultPrevented: false,
|
||||
preventDefault: function fakePreventDefault() {
|
||||
this.defaultPrevented = true;
|
||||
forceReload = false;
|
||||
}
|
||||
};
|
||||
|
||||
$rootScope.$evalAsync(function() {
|
||||
// Don't support cancellation of a reload for now...
|
||||
prepareRoute();
|
||||
commitRoute();
|
||||
prepareRoute(fakeLocationEvent);
|
||||
if (!fakeLocationEvent.defaultPrevented) commitRoute();
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -609,8 +617,8 @@ function $RouteProvider() {
|
|||
return $q.all(locals);
|
||||
}
|
||||
}).
|
||||
// after route change
|
||||
then(function(locals) {
|
||||
// after route change
|
||||
if (nextRoute == $route.current) {
|
||||
if (nextRoute) {
|
||||
nextRoute.locals = locals;
|
||||
|
@ -796,7 +804,6 @@ ngRouteModule.directive('ngView', ngViewFillContentFactory);
|
|||
}
|
||||
|
||||
.view-animate.ng-enter, .view-animate.ng-leave {
|
||||
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
|
||||
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
|
||||
|
||||
display:block;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* @license AngularJS v1.3.18
|
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* @license AngularJS v1.4.10
|
||||
* (c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, angular, undefined) {'use strict';
|
||||
|
@ -211,10 +211,11 @@ var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a
|
|||
|
||||
// SVG Elements
|
||||
// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements
|
||||
var svgElements = makeMap("animate,animateColor,animateMotion,animateTransform,circle,defs," +
|
||||
"desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,hkern,image,linearGradient," +
|
||||
"line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,radialGradient,rect,set," +
|
||||
"stop,svg,switch,text,title,tspan,use");
|
||||
// Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted.
|
||||
// They can potentially allow for arbitrary javascript to be executed. See #11290
|
||||
var svgElements = makeMap("circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph," +
|
||||
"hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline," +
|
||||
"radialGradient,rect,stop,svg,switch,text,title,tspan,use");
|
||||
|
||||
// Special Elements (can contain anything)
|
||||
var specialElements = makeMap("script,style");
|
||||
|
@ -232,36 +233,37 @@ var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap,xlink:href");
|
|||
var htmlAttrs = makeMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' +
|
||||
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' +
|
||||
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' +
|
||||
'scope,scrolling,shape,size,span,start,summary,target,title,type,' +
|
||||
'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' +
|
||||
'valign,value,vspace,width');
|
||||
|
||||
// SVG attributes (without "id" and "name" attributes)
|
||||
// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes
|
||||
var svgAttrs = makeMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' +
|
||||
'attributeName,attributeType,baseProfile,bbox,begin,by,calcMode,cap-height,class,color,' +
|
||||
'color-rendering,content,cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,' +
|
||||
'font-size,font-stretch,font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,' +
|
||||
'gradientUnits,hanging,height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,' +
|
||||
'keySplines,keyTimes,lang,marker-end,marker-mid,marker-start,markerHeight,markerUnits,' +
|
||||
'markerWidth,mathematical,max,min,offset,opacity,orient,origin,overline-position,' +
|
||||
'overline-thickness,panose-1,path,pathLength,points,preserveAspectRatio,r,refX,refY,' +
|
||||
'repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,' +
|
||||
'stemv,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,stroke,' +
|
||||
'stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,' +
|
||||
'stroke-opacity,stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,' +
|
||||
'underline-position,underline-thickness,unicode,unicode-range,units-per-em,values,version,' +
|
||||
'viewBox,visibility,width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,' +
|
||||
'xlink:show,xlink:title,xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,' +
|
||||
'zoomAndPan');
|
||||
'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' +
|
||||
'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' +
|
||||
'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' +
|
||||
'height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,' +
|
||||
'marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,' +
|
||||
'max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,' +
|
||||
'path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,' +
|
||||
'requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,' +
|
||||
'stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,' +
|
||||
'stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,' +
|
||||
'stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,' +
|
||||
'underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,' +
|
||||
'width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,' +
|
||||
'xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan', true);
|
||||
|
||||
var validAttrs = angular.extend({},
|
||||
uriAttrs,
|
||||
svgAttrs,
|
||||
htmlAttrs);
|
||||
|
||||
function makeMap(str) {
|
||||
function makeMap(str, lowercaseKeys) {
|
||||
var obj = {}, items = str.split(','), i;
|
||||
for (i = 0; i < items.length; i++) obj[items[i]] = true;
|
||||
for (i = 0; i < items.length; i++) {
|
||||
obj[lowercaseKeys ? angular.lowercase(items[i]) : items[i]] = true;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
@ -389,8 +391,9 @@ function htmlParser(html, handler) {
|
|||
|
||||
unary = voidElements[tagName] || !!unary;
|
||||
|
||||
if (!unary)
|
||||
if (!unary) {
|
||||
stack.push(tagName);
|
||||
}
|
||||
|
||||
var attrs = {};
|
||||
|
||||
|
@ -409,11 +412,12 @@ function htmlParser(html, handler) {
|
|||
function parseEndTag(tag, tagName) {
|
||||
var pos = 0, i;
|
||||
tagName = angular.lowercase(tagName);
|
||||
if (tagName)
|
||||
if (tagName) {
|
||||
// Find the closest opened tag of the same type
|
||||
for (pos = stack.length - 1; pos >= 0; pos--)
|
||||
if (stack[pos] == tagName)
|
||||
break;
|
||||
for (pos = stack.length - 1; pos >= 0; pos--) {
|
||||
if (stack[pos] == tagName) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pos >= 0) {
|
||||
// Close all the open elements, up the stack
|
||||
|
@ -627,7 +631,7 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
|
|||
*/
|
||||
angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
|
||||
var LINKY_URL_REGEXP =
|
||||
/((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"”’]/i,
|
||||
/((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i,
|
||||
MAILTO_REGEXP = /^mailto:/i;
|
||||
|
||||
return function(text, target) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* @license AngularJS v1.3.18
|
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* @license AngularJS v1.4.10
|
||||
* (c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, angular, undefined) {'use strict';
|
||||
|
@ -43,8 +43,7 @@ function nodeName_(element) {
|
|||
*
|
||||
* Requires the {@link ngTouch `ngTouch`} module to be installed.
|
||||
*
|
||||
* `$swipe` is used by the `ngSwipeLeft` and `ngSwipeRight` directives in `ngTouch`, and by
|
||||
* `ngCarousel` in a separate component.
|
||||
* `$swipe` is used by the `ngSwipeLeft` and `ngSwipeRight` directives in `ngTouch`.
|
||||
*
|
||||
* # Usage
|
||||
* The `$swipe` service is an object with a single method: `bind`. `bind` takes an element
|
||||
|
@ -105,7 +104,8 @@ ngTouch.factory('$swipe', [function() {
|
|||
* `$swipe` will listen for `mouse` and `touch` events.
|
||||
*
|
||||
* The four events are `start`, `move`, `end`, and `cancel`. `start`, `move`, and `end`
|
||||
* receive as a parameter a coordinates object of the form `{ x: 150, y: 310 }`.
|
||||
* receive as a parameter a coordinates object of the form `{ x: 150, y: 310 }` and the raw
|
||||
* `event`. `cancel` receives the raw `event` as its single parameter.
|
||||
*
|
||||
* `start` is called on either `mousedown` or `touchstart`. After this event, `$swipe` is
|
||||
* watching for `touchmove` or `mousemove` events. These events are ignored until the total
|
||||
|
@ -263,7 +263,7 @@ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement',
|
|||
// double-tapping, and then fire a click event.
|
||||
//
|
||||
// This delay sucks and makes mobile apps feel unresponsive.
|
||||
// So we detect touchstart, touchmove, touchcancel and touchend ourselves and determine when
|
||||
// So we detect touchstart, touchcancel and touchend ourselves and determine when
|
||||
// the user has tapped on something.
|
||||
//
|
||||
// What happens when the browser then generates a click event?
|
||||
|
@ -275,7 +275,7 @@ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement',
|
|||
// So the sequence for a tap is:
|
||||
// - global touchstart: Sets an "allowable region" at the point touched.
|
||||
// - element's touchstart: Starts a touch
|
||||
// (- touchmove or touchcancel ends the touch, no click follows)
|
||||
// (- touchcancel ends the touch, no click follows)
|
||||
// - element's touchend: Determines if the tap is valid (didn't move too far away, didn't hold
|
||||
// too long) and fires the user's tap handler. The touchend also calls preventGhostClick().
|
||||
// - preventGhostClick() removes the allowable region the global touchstart created.
|
||||
|
@ -426,10 +426,6 @@ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement',
|
|||
touchStartY = e.clientY;
|
||||
});
|
||||
|
||||
element.on('touchmove', function(event) {
|
||||
resetState();
|
||||
});
|
||||
|
||||
element.on('touchcancel', function(event) {
|
||||
resetState();
|
||||
});
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -1 +1 @@
|
|||
{"raw":"v1.3.18","major":1,"minor":3,"patch":18,"prerelease":[],"build":[],"version":"1.3.18","codeName":"collective-penmanship","full":"1.3.18","branch":"v1.3.x","cdn":{"raw":"v1.3.17","major":1,"minor":3,"patch":17,"prerelease":[],"build":[],"version":"1.3.17","docsUrl":"http://code.angularjs.org/1.3.17/docs"}}
|
||||
{"raw":"v1.4.10","major":1,"minor":4,"patch":10,"prerelease":[],"build":[],"version":"1.4.10","codeName":"benignant-oscillation","full":"1.4.10","branch":"v1.4.x","cdn":{"raw":"v1.4.9","major":1,"minor":4,"patch":9,"prerelease":[],"build":[],"version":"1.4.9","docsUrl":"http://code.angularjs.org/1.4.9/docs"}}
|
|
@ -1 +1 @@
|
|||
1.3.18
|
||||
1.4.10
|
Loading…
Reference in New Issue