} */
var modules = {};
@@ -1008,15 +1461,19 @@ function setupModuleLoader(window) {
* @name angular.module
* @description
*
- * The `angular.module` is a global place for creating and registering Angular modules. All
- * modules (angular core or 3rd party) that should be available to an application must be
+ * The `angular.module` is a global place for creating, registering and retrieving Angular
+ * modules.
+ * 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.
+ *
*
* # Module
*
- * A module is a collocation of services, directives, filters, and configure information. Module
- * is used to configure the {@link AUTO.$injector $injector}.
+ * A module is a collection of services, directives, filters, and configuration information.
+ * `angular.module` is used to configure the {@link AUTO.$injector $injector}.
*
*
* // Create a new module
@@ -1043,19 +1500,28 @@ function setupModuleLoader(window) {
* {@link angular.bootstrap} to simplify this process for you.
*
* @param {!string} name The name of the module to create or retrieve.
- * @param {Array.=} requires If specified then new module is being created. If unspecified then the
- * the module is being retrieved for further configuration.
- * @param {Function} configFn Option configuration function for the module. Same as
- * {@link angular.Module#config Module#config()}.
+ * @param {Array.=} requires If specified then new module is being created. If
+ * unspecified then the the module is being retrieved for further configuration.
+ * @param {Function} configFn Optional configuration function for the module. Same as
+ * {@link angular.Module#methods_config Module#config()}.
* @returns {module} new module with the {@link angular.Module} api.
*/
return function module(name, requires, configFn) {
+ var assertNotHasOwnProperty = function(name, context) {
+ if (name === 'hasOwnProperty') {
+ throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
+ }
+ };
+
+ assertNotHasOwnProperty(name, 'module');
if (requires && modules.hasOwnProperty(name)) {
modules[name] = null;
}
return ensure(modules, name, function() {
if (!requires) {
- throw Error('No module: ' + name);
+ throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
+ "the module name or forgot to load it. If registering a module ensure that you " +
+ "specify the dependencies as the second argument.", name);
}
/** @type {!Array.>} */
@@ -1078,7 +1544,8 @@ function setupModuleLoader(window) {
* @propertyOf angular.Module
* @returns {Array.} List of module names which must be loaded before this module.
* @description
- * Holds the list of modules which the injector will load before the current module is loaded.
+ * Holds the list of modules which the injector will load before the current module is
+ * loaded.
*/
requires: requires,
@@ -1097,7 +1564,8 @@ function setupModuleLoader(window) {
* @name angular.Module#provider
* @methodOf angular.Module
* @param {string} name service name
- * @param {Function} providerType Construction function for creating new instance of the service.
+ * @param {Function} providerType Construction function for creating new instance of the
+ * service.
* @description
* See {@link AUTO.$provide#provider $provide.provider()}.
*/
@@ -1148,6 +1616,40 @@ function setupModuleLoader(window) {
*/
constant: invokeLater('$provide', 'constant', 'unshift'),
+ /**
+ * @ngdoc method
+ * @name angular.Module#animation
+ * @methodOf angular.Module
+ * @param {string} name animation name
+ * @param {Function} animationFactory Factory function for creating new instance of an
+ * animation.
+ * @description
+ *
+ * **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
+ *
+ *
+ * Defines an animation hook that can be later used with
+ * {@link ngAnimate.$animate $animate} service and directives that use this service.
+ *
+ *
+ * module.animation('.animation-name', function($inject1, $inject2) {
+ * return {
+ * eventName : function(element, done) {
+ * //code to run the animation
+ * //once complete, then run done()
+ * return function cancellationFunction(element) {
+ * //code to cancel the animation
+ * }
+ * }
+ * }
+ * })
+ *
+ *
+ * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and
+ * {@link ngAnimate ngAnimate module} for more information.
+ */
+ animation: invokeLater('$animateProvider', 'register'),
+
/**
* @ngdoc method
* @name angular.Module#filter
@@ -1163,7 +1665,8 @@ function setupModuleLoader(window) {
* @ngdoc method
* @name angular.Module#controller
* @methodOf angular.Module
- * @param {string} name Controller name.
+ * @param {string|Object} name Controller name, or an object map of controllers where the
+ * keys are the names and the values are the constructors.
* @param {Function} constructor Controller constructor function.
* @description
* See {@link ng.$controllerProvider#register $controllerProvider.register()}.
@@ -1174,11 +1677,12 @@ function setupModuleLoader(window) {
* @ngdoc method
* @name angular.Module#directive
* @methodOf angular.Module
- * @param {string} name directive name
+ * @param {string|Object} name Directive name, or an object map of directives where the
+ * keys are the names and the values are the factories.
* @param {Function} directiveFactory Factory function for creating new instance of
* directives.
* @description
- * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
+ * See {@link ng.$compileProvider#methods_directive $compileProvider.directive()}.
*/
directive: invokeLater('$compileProvider', 'directive'),
@@ -1200,8 +1704,8 @@ function setupModuleLoader(window) {
* @param {Function} initializationFn Execute this function after injector creation.
* Useful for application initialization.
* @description
- * Use this method to register work which needs to be performed when the injector with
- * with the current module is finished loading.
+ * Use this method to register work which should be performed when the injector is done
+ * loading all modules.
*/
run: function(block) {
runBlocks.push(block);
@@ -1225,7 +1729,7 @@ function setupModuleLoader(window) {
return function() {
invokeQueue[insertMethod || 'push']([provider, method, arguments]);
return moduleInstance;
- }
+ };
}
});
};
@@ -1233,6 +1737,81 @@ function setupModuleLoader(window) {
}
+/* global
+ angularModule: true,
+ version: true,
+
+ $LocaleProvider,
+ $CompileProvider,
+
+ htmlAnchorDirective,
+ inputDirective,
+ inputDirective,
+ formDirective,
+ scriptDirective,
+ selectDirective,
+ styleDirective,
+ optionDirective,
+ ngBindDirective,
+ ngBindHtmlDirective,
+ ngBindTemplateDirective,
+ ngClassDirective,
+ ngClassEvenDirective,
+ ngClassOddDirective,
+ ngCspDirective,
+ ngCloakDirective,
+ ngControllerDirective,
+ ngFormDirective,
+ ngHideDirective,
+ ngIfDirective,
+ ngIncludeDirective,
+ ngInitDirective,
+ ngNonBindableDirective,
+ ngPluralizeDirective,
+ ngRepeatDirective,
+ ngShowDirective,
+ ngStyleDirective,
+ ngSwitchDirective,
+ ngSwitchWhenDirective,
+ ngSwitchDefaultDirective,
+ ngOptionsDirective,
+ ngTranscludeDirective,
+ ngModelDirective,
+ ngListDirective,
+ ngChangeDirective,
+ requiredDirective,
+ requiredDirective,
+ ngValueDirective,
+ ngAttributeAliasDirectives,
+ ngEventDirectives,
+
+ $AnchorScrollProvider,
+ $AnimateProvider,
+ $BrowserProvider,
+ $CacheFactoryProvider,
+ $ControllerProvider,
+ $DocumentProvider,
+ $ExceptionHandlerProvider,
+ $FilterProvider,
+ $InterpolateProvider,
+ $IntervalProvider,
+ $HttpProvider,
+ $HttpBackendProvider,
+ $LocationProvider,
+ $LogProvider,
+ $ParseProvider,
+ $RootScopeProvider,
+ $QProvider,
+ $$SanitizeUriProvider,
+ $SceProvider,
+ $SceDelegateProvider,
+ $SnifferProvider,
+ $TemplateCacheProvider,
+ $TimeoutProvider,
+ $WindowProvider
+*/
+
+
/**
* @ngdoc property
* @name angular.version
@@ -1247,11 +1826,11 @@ function setupModuleLoader(window) {
* - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
*/
var version = {
- full: '1.0.2', // all of these placeholder strings will be replaced by rake's
- major: 1, // compile task
- minor: 0,
- dot: 2,
- codeName: 'debilitating-awesomeness'
+ full: '1.2.3', // all of these placeholder strings will be replaced by grunt's
+ major: 1, // package task
+ minor: 2,
+ dot: 3,
+ codeName: 'unicorn-zapper'
};
@@ -1281,7 +1860,9 @@ function publishExternalAPI(angular){
'isDate': isDate,
'lowercase': lowercase,
'uppercase': uppercase,
- 'callbacks': {counter: 0}
+ 'callbacks': {counter: 0},
+ '$$minErr': minErr,
+ '$$csp': csp
});
angularModule = setupModuleLoader(window);
@@ -1293,6 +1874,10 @@ function publishExternalAPI(angular){
angularModule('ng', ['ngLocale'], ['$provide',
function ngModule($provide) {
+ // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
+ $provide.provider({
+ $$sanitizeUri: $$SanitizeUriProvider
+ });
$provide.provider('$compile', $CompileProvider).
directive({
a: htmlAnchorDirective,
@@ -1304,29 +1889,27 @@ function publishExternalAPI(angular){
style: styleDirective,
option: optionDirective,
ngBind: ngBindDirective,
- ngBindHtmlUnsafe: ngBindHtmlUnsafeDirective,
+ ngBindHtml: ngBindHtmlDirective,
ngBindTemplate: ngBindTemplateDirective,
ngClass: ngClassDirective,
ngClassEven: ngClassEvenDirective,
ngClassOdd: ngClassOddDirective,
- ngCsp: ngCspDirective,
ngCloak: ngCloakDirective,
ngController: ngControllerDirective,
ngForm: ngFormDirective,
ngHide: ngHideDirective,
+ ngIf: ngIfDirective,
ngInclude: ngIncludeDirective,
ngInit: ngInitDirective,
ngNonBindable: ngNonBindableDirective,
ngPluralize: ngPluralizeDirective,
ngRepeat: ngRepeatDirective,
ngShow: ngShowDirective,
- ngSubmit: ngSubmitDirective,
ngStyle: ngStyleDirective,
ngSwitch: ngSwitchDirective,
ngSwitchWhen: ngSwitchWhenDirective,
ngSwitchDefault: ngSwitchDefaultDirective,
ngOptions: ngOptionsDirective,
- ngView: ngViewDirective,
ngTransclude: ngTranscludeDirective,
ngModel: ngModelDirective,
ngList: ngListDirective,
@@ -1339,6 +1922,7 @@ function publishExternalAPI(angular){
directive(ngEventDirectives);
$provide.provider({
$anchorScroll: $AnchorScrollProvider,
+ $animate: $AnimateProvider,
$browser: $BrowserProvider,
$cacheFactory: $CacheFactoryProvider,
$controller: $ControllerProvider,
@@ -1346,15 +1930,16 @@ function publishExternalAPI(angular){
$exceptionHandler: $ExceptionHandlerProvider,
$filter: $FilterProvider,
$interpolate: $InterpolateProvider,
+ $interval: $IntervalProvider,
$http: $HttpProvider,
$httpBackend: $HttpBackendProvider,
$location: $LocationProvider,
$log: $LogProvider,
$parse: $ParseProvider,
- $route: $RouteProvider,
- $routeParams: $RouteParamsProvider,
$rootScope: $RootScopeProvider,
$q: $QProvider,
+ $sce: $SceProvider,
+ $sceDelegate: $SceDelegateProvider,
$sniffer: $SnifferProvider,
$templateCache: $TemplateCacheProvider,
$timeout: $TimeoutProvider,
@@ -1364,6 +1949,14 @@ function publishExternalAPI(angular){
]);
}
+/* global
+
+ -JQLitePrototype,
+ -addEventListenerFn,
+ -removeEventListenerFn,
+ -BOOLEAN_ATTR
+*/
+
//////////////////////////////////
//JQLite
//////////////////////////////////
@@ -1375,55 +1968,65 @@ function publishExternalAPI(angular){
*
* @description
* Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element.
- * `angular.element` can be either an alias for [jQuery](http://api.jquery.com/jQuery/) function, if
- * jQuery is available, or a function that wraps the element or string in Angular's jQuery lite
- * implementation (commonly referred to as jqLite).
- *
- * Real jQuery always takes precedence over jqLite, provided it was loaded before `DOMContentLoaded`
- * event fired.
- *
- * jqLite is a tiny, API-compatible subset of jQuery that allows
- * Angular to manipulate the DOM. jqLite implements only the most commonly needed functionality
- * within a very small footprint, so only a subset of the jQuery API - methods, arguments and
- * invocation styles - are supported.
- *
- * Note: All element references in Angular are always wrapped with jQuery or jqLite; they are never
- * raw DOM references.
- *
- * ## Angular's jQuery lite provides the following methods:
- *
- * - [addClass()](http://api.jquery.com/addClass/)
- * - [after()](http://api.jquery.com/after/)
- * - [append()](http://api.jquery.com/append/)
- * - [attr()](http://api.jquery.com/attr/)
- * - [bind()](http://api.jquery.com/bind/)
- * - [children()](http://api.jquery.com/children/)
- * - [clone()](http://api.jquery.com/clone/)
- * - [contents()](http://api.jquery.com/contents/)
- * - [css()](http://api.jquery.com/css/)
- * - [data()](http://api.jquery.com/data/)
- * - [eq()](http://api.jquery.com/eq/)
- * - [find()](http://api.jquery.com/find/) - Limited to lookups by tag name.
- * - [hasClass()](http://api.jquery.com/hasClass/)
- * - [html()](http://api.jquery.com/html/)
- * - [next()](http://api.jquery.com/next/)
- * - [parent()](http://api.jquery.com/parent/)
- * - [prepend()](http://api.jquery.com/prepend/)
- * - [prop()](http://api.jquery.com/prop/)
- * - [ready()](http://api.jquery.com/ready/)
- * - [remove()](http://api.jquery.com/remove/)
- * - [removeAttr()](http://api.jquery.com/removeAttr/)
- * - [removeClass()](http://api.jquery.com/removeClass/)
- * - [removeData()](http://api.jquery.com/removeData/)
- * - [replaceWith()](http://api.jquery.com/replaceWith/)
- * - [text()](http://api.jquery.com/text/)
- * - [toggleClass()](http://api.jquery.com/toggleClass/)
- * - [unbind()](http://api.jquery.com/unbind/)
- * - [val()](http://api.jquery.com/val/)
- * - [wrap()](http://api.jquery.com/wrap/)
- *
- * ## In addtion to the above, Angular privides an additional method to both jQuery and jQuery lite:
*
+ * If jQuery is available, `angular.element` is an alias for the
+ * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element`
+ * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite."
+ *
+ * jqLite is a tiny, API-compatible subset of jQuery that allows
+ * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most
+ * commonly needed functionality with the goal of having a very small footprint.
+ *
+ * To use jQuery, simply load it before `DOMContentLoaded` event fired.
+ *
+ * **Note:** all element references in Angular are always wrapped with jQuery or
+ * jqLite; they are never raw DOM references.
+ *
+ * ## Angular's jqLite
+ * jqLite provides only the following jQuery methods:
+ *
+ * - [`addClass()`](http://api.jquery.com/addClass/)
+ * - [`after()`](http://api.jquery.com/after/)
+ * - [`append()`](http://api.jquery.com/append/)
+ * - [`attr()`](http://api.jquery.com/attr/)
+ * - [`bind()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
+ * - [`children()`](http://api.jquery.com/children/) - Does not support selectors
+ * - [`clone()`](http://api.jquery.com/clone/)
+ * - [`contents()`](http://api.jquery.com/contents/)
+ * - [`css()`](http://api.jquery.com/css/)
+ * - [`data()`](http://api.jquery.com/data/)
+ * - [`eq()`](http://api.jquery.com/eq/)
+ * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name
+ * - [`hasClass()`](http://api.jquery.com/hasClass/)
+ * - [`html()`](http://api.jquery.com/html/)
+ * - [`next()`](http://api.jquery.com/next/) - Does not support selectors
+ * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
+ * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors
+ * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors
+ * - [`prepend()`](http://api.jquery.com/prepend/)
+ * - [`prop()`](http://api.jquery.com/prop/)
+ * - [`ready()`](http://api.jquery.com/ready/)
+ * - [`remove()`](http://api.jquery.com/remove/)
+ * - [`removeAttr()`](http://api.jquery.com/removeAttr/)
+ * - [`removeClass()`](http://api.jquery.com/removeClass/)
+ * - [`removeData()`](http://api.jquery.com/removeData/)
+ * - [`replaceWith()`](http://api.jquery.com/replaceWith/)
+ * - [`text()`](http://api.jquery.com/text/)
+ * - [`toggleClass()`](http://api.jquery.com/toggleClass/)
+ * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers.
+ * - [`unbind()`](http://api.jquery.com/off/) - Does not support namespaces
+ * - [`val()`](http://api.jquery.com/val/)
+ * - [`wrap()`](http://api.jquery.com/wrap/)
+ *
+ * ## jQuery/jqLite Extras
+ * Angular also provides the following additional methods and events to both jQuery and jqLite:
+ *
+ * ### Events
+ * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event
+ * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM
+ * element before it is removed.
+ *
+ * ### Methods
* - `controller(name)` - retrieves the controller of the current element or its parent. By default
* retrieves controller associated with the `ngController` directive. If `name` is provided as
* camelCase directive name, then the controller for this directive will be retrieved (e.g.
@@ -1431,6 +2034,9 @@ function publishExternalAPI(angular){
* - `injector()` - retrieves the injector of the current element or its parent.
* - `scope()` - retrieves the {@link api/ng.$rootScope.Scope scope} of the current
* element or its parent.
+ * - `isolateScope()` - retrieves an isolate {@link api/ng.$rootScope.Scope scope} if one is attached directly to the
+ * current element. This getter should be used only on elements that contain a directive which starts a new isolate
+ * scope. Calling `scope()` on this element always returns the original non-isolate scope.
* - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
* parent element is reached.
*
@@ -1453,6 +2059,7 @@ function jqNextId() { return ++jqId; }
var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
var MOZ_HACK_REGEXP = /^moz([A-Z])/;
+var jqLiteMinErr = minErr('jqLite');
/**
* Converts snake_case to camelCase.
@@ -1470,42 +2077,39 @@ function camelCase(name) {
/////////////////////////////////////////////
// jQuery mutation patch
//
-// In conjunction with bindJQuery intercepts all jQuery's DOM destruction apis and fires a
+// In conjunction with bindJQuery intercepts all jQuery's DOM destruction apis and fires a
// $destroy event on all DOM nodes being removed.
//
/////////////////////////////////////////////
-function JQLitePatchJQueryRemove(name, dispatchThis) {
+function jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) {
var originalJqFn = jQuery.fn[name];
originalJqFn = originalJqFn.$original || originalJqFn;
removePatch.$original = originalJqFn;
jQuery.fn[name] = removePatch;
- function removePatch() {
- var list = [this],
+ function removePatch(param) {
+ // jshint -W040
+ var list = filterElems && param ? [this.filter(param)] : [this],
fireEvent = dispatchThis,
set, setIndex, setLength,
- element, childIndex, childLength, children,
- fns, events;
-
- while(list.length) {
- set = list.shift();
- for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) {
- element = jqLite(set[setIndex]);
- if (fireEvent) {
- events = element.data('events');
- if ( (fns = events && events.$destroy) ) {
- forEach(fns, function(fn){
- fn.handler();
- });
+ element, childIndex, childLength, children;
+
+ if (!getterIfNoArguments || param != null) {
+ while(list.length) {
+ set = list.shift();
+ for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) {
+ element = jqLite(set[setIndex]);
+ if (fireEvent) {
+ element.triggerHandler('$destroy');
+ } else {
+ fireEvent = !fireEvent;
+ }
+ for(childIndex = 0, childLength = (children = element.children()).length;
+ childIndex < childLength;
+ childIndex++) {
+ list.push(jQuery(children[childIndex]));
}
- } else {
- fireEvent = !fireEvent;
- }
- for(childIndex = 0, childLength = (children = element.children()).length;
- childIndex < childLength;
- childIndex++) {
- list.push(jQuery(children[childIndex]));
}
}
}
@@ -1520,7 +2124,7 @@ function JQLite(element) {
}
if (!(this instanceof JQLite)) {
if (isString(element) && element.charAt(0) != '<') {
- throw Error('selectors not implemented');
+ throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
}
return new JQLite(element);
}
@@ -1531,27 +2135,30 @@ function JQLite(element) {
// http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx
div.innerHTML = '
' + element; // IE insanity to make NoScope elements work!
div.removeChild(div.firstChild); // remove the superfluous div
- JQLiteAddNodes(this, div.childNodes);
- this.remove(); // detach the elements from the temporary DOM div.
+ jqLiteAddNodes(this, div.childNodes);
+ var fragment = jqLite(document.createDocumentFragment());
+ fragment.append(this); // detach the elements from the temporary DOM div.
} else {
- JQLiteAddNodes(this, element);
+ jqLiteAddNodes(this, element);
}
}
-function JQLiteClone(element) {
+function jqLiteClone(element) {
return element.cloneNode(true);
}
-function JQLiteDealoc(element){
- JQLiteRemoveData(element);
+function jqLiteDealoc(element){
+ jqLiteRemoveData(element);
for ( var i = 0, children = element.childNodes || []; i < children.length; i++) {
- JQLiteDealoc(children[i]);
+ jqLiteDealoc(children[i]);
}
}
-function JQLiteUnbind(element, type, fn) {
- var events = JQLiteExpandoStore(element, 'events'),
- handle = JQLiteExpandoStore(element, 'handle');
+function jqLiteOff(element, type, fn, unsupported) {
+ if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument');
+
+ var events = jqLiteExpandoStore(element, 'events'),
+ handle = jqLiteExpandoStore(element, 'handle');
if (!handle) return; //no listeners registered
@@ -1561,30 +2168,37 @@ function JQLiteUnbind(element, type, fn) {
delete events[type];
});
} else {
- if (isUndefined(fn)) {
- removeEventListenerFn(element, type, events[type]);
- delete events[type];
- } else {
- arrayRemove(events[type], fn);
- }
+ forEach(type.split(' '), function(type) {
+ if (isUndefined(fn)) {
+ removeEventListenerFn(element, type, events[type]);
+ delete events[type];
+ } else {
+ arrayRemove(events[type] || [], fn);
+ }
+ });
}
}
-function JQLiteRemoveData(element) {
+function jqLiteRemoveData(element, name) {
var expandoId = element[jqName],
expandoStore = jqCache[expandoId];
if (expandoStore) {
+ if (name) {
+ delete jqCache[expandoId].data[name];
+ return;
+ }
+
if (expandoStore.handle) {
expandoStore.events.$destroy && expandoStore.handle({}, '$destroy');
- JQLiteUnbind(element);
+ jqLiteOff(element);
}
delete jqCache[expandoId];
element[jqName] = undefined; // ie does not allow deletion of attributes on elements.
}
}
-function JQLiteExpandoStore(element, key, value) {
+function jqLiteExpandoStore(element, key, value) {
var expandoId = element[jqName],
expandoStore = jqCache[expandoId || -1];
@@ -1599,14 +2213,14 @@ function JQLiteExpandoStore(element, key, value) {
}
}
-function JQLiteData(element, key, value) {
- var data = JQLiteExpandoStore(element, 'data'),
+function jqLiteData(element, key, value) {
+ var data = jqLiteExpandoStore(element, 'data'),
isSetter = isDefined(value),
keyDefined = !isSetter && isDefined(key),
isSimpleGetter = keyDefined && !isObject(key);
if (!data && !isSimpleGetter) {
- JQLiteExpandoStore(element, 'data', data = {});
+ jqLiteExpandoStore(element, 'data', data = {});
}
if (isSetter) {
@@ -1625,34 +2239,41 @@ function JQLiteData(element, key, value) {
}
}
-function JQLiteHasClass(element, selector) {
- return ((" " + element.className + " ").replace(/[\n\t]/g, " ").
+function jqLiteHasClass(element, selector) {
+ if (!element.getAttribute) return false;
+ return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " ").
indexOf( " " + selector + " " ) > -1);
}
-function JQLiteRemoveClass(element, selector) {
- if (selector) {
- forEach(selector.split(' '), function(cssClass) {
- element.className = trim(
- (" " + element.className + " ")
+function jqLiteRemoveClass(element, cssClasses) {
+ if (cssClasses && element.setAttribute) {
+ forEach(cssClasses.split(' '), function(cssClass) {
+ element.setAttribute('class', trim(
+ (" " + (element.getAttribute('class') || '') + " ")
.replace(/[\n\t]/g, " ")
- .replace(" " + trim(cssClass) + " ", " ")
+ .replace(" " + trim(cssClass) + " ", " "))
);
});
}
}
-function JQLiteAddClass(element, selector) {
- if (selector) {
- forEach(selector.split(' '), function(cssClass) {
- if (!JQLiteHasClass(element, cssClass)) {
- element.className = trim(element.className + ' ' + trim(cssClass));
+function jqLiteAddClass(element, cssClasses) {
+ if (cssClasses && element.setAttribute) {
+ var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ')
+ .replace(/[\n\t]/g, " ");
+
+ forEach(cssClasses.split(' '), function(cssClass) {
+ cssClass = trim(cssClass);
+ if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
+ existingClasses += cssClass + ' ';
}
});
+
+ element.setAttribute('class', trim(existingClasses));
}
}
-function JQLiteAddNodes(root, elements) {
+function jqLiteAddNodes(root, elements) {
if (elements) {
elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements))
? elements
@@ -1663,11 +2284,11 @@ function JQLiteAddNodes(root, elements) {
}
}
-function JQLiteController(element, name) {
- return JQLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller');
+function jqLiteController(element, name) {
+ return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller');
}
-function JQLiteInheritedData(element, name, value) {
+function jqLiteInheritedData(element, name, value) {
element = jqLite(element);
// if element is the document object work with the html element instead
@@ -1675,9 +2296,13 @@ function JQLiteInheritedData(element, name, value) {
if(element[0].nodeType == 9) {
element = element.find('html');
}
+ var names = isArray(name) ? name : [name];
while (element.length) {
- if (value = element.data(name)) return value;
+
+ for (var i = 0, ii = names.length; i < ii; i++) {
+ if ((value = element.data(names[i])) !== undefined) return value;
+ }
element = element.parent();
}
}
@@ -1695,9 +2320,16 @@ var JQLitePrototype = JQLite.prototype = {
fn();
}
- this.bind('DOMContentLoaded', trigger); // works for modern browsers and IE9
- // we can not use jqLite since we are not done loading and jQuery could be loaded later.
- JQLite(window).bind('load', trigger); // fallback to window.onload for others
+ // check if document already is loaded
+ if (document.readyState === 'complete'){
+ setTimeout(trigger);
+ } else {
+ this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9
+ // we can not use jqLite since we are not done loading and jQuery could be loaded later.
+ // jshint -W064
+ JQLite(window).on('load', trigger); // fallback to window.onload for others
+ // jshint +W064
+ }
},
toString: function() {
var value = [];
@@ -1721,11 +2353,11 @@ var JQLitePrototype = JQLite.prototype = {
// value on get.
//////////////////////////////////////////
var BOOLEAN_ATTR = {};
-forEach('multiple,selected,checked,disabled,readOnly,required'.split(','), function(value) {
+forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) {
BOOLEAN_ATTR[lowercase(value)] = value;
});
var BOOLEAN_ELEMENTS = {};
-forEach('input,select,option,textarea,button,form'.split(','), function(value) {
+forEach('input,select,option,textarea,button,form,details'.split(','), function(value) {
BOOLEAN_ELEMENTS[uppercase(value)] = true;
});
@@ -1738,24 +2370,30 @@ function getBooleanAttrName(element, name) {
}
forEach({
- data: JQLiteData,
- inheritedData: JQLiteInheritedData,
+ data: jqLiteData,
+ inheritedData: jqLiteInheritedData,
scope: function(element) {
- return JQLiteInheritedData(element, '$scope');
+ // Can't use jqLiteData here directly so we stay compatible with jQuery!
+ return jqLite(element).data('$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']);
},
- controller: JQLiteController ,
+ isolateScope: function(element) {
+ // Can't use jqLiteData here directly so we stay compatible with jQuery!
+ return jqLite(element).data('$isolateScope') || jqLite(element).data('$isolateScopeNoTemplate');
+ },
+
+ controller: jqLiteController ,
injector: function(element) {
- return JQLiteInheritedData(element, '$injector');
+ return jqLiteInheritedData(element, '$injector');
},
removeAttr: function(element,name) {
element.removeAttribute(name);
},
- hasClass: JQLiteHasClass,
+ hasClass: jqLiteHasClass,
css: function(element, name, value) {
name = camelCase(name);
@@ -1818,27 +2456,38 @@ forEach({
}
},
- text: extend((msie < 9)
- ? function(element, value) {
- if (element.nodeType == 1 /** Element */) {
- if (isUndefined(value))
- return element.innerText;
- element.innerText = value;
- } else {
- if (isUndefined(value))
- return element.nodeValue;
- element.nodeValue = value;
- }
+ text: (function() {
+ var NODE_TYPE_TEXT_PROPERTY = [];
+ if (msie < 9) {
+ NODE_TYPE_TEXT_PROPERTY[1] = 'innerText'; /** Element **/
+ NODE_TYPE_TEXT_PROPERTY[3] = 'nodeValue'; /** Text **/
+ } else {
+ NODE_TYPE_TEXT_PROPERTY[1] = /** Element **/
+ NODE_TYPE_TEXT_PROPERTY[3] = 'textContent'; /** Text **/
+ }
+ getText.$dv = '';
+ return getText;
+
+ function getText(element, value) {
+ var textProp = NODE_TYPE_TEXT_PROPERTY[element.nodeType];
+ if (isUndefined(value)) {
+ return textProp ? element[textProp] : '';
}
- : function(element, value) {
- if (isUndefined(value)) {
- return element.textContent;
- }
- element.textContent = value;
- }, {$dv:''}),
+ element[textProp] = value;
+ }
+ })(),
val: function(element, value) {
if (isUndefined(value)) {
+ if (nodeName_(element) === 'SELECT' && element.multiple) {
+ var result = [];
+ forEach(element.options, function (option) {
+ if (option.selected) {
+ result.push(option.value || option.text);
+ }
+ });
+ return result.length === 0 ? null : result;
+ }
return element.value;
}
element.value = value;
@@ -1849,7 +2498,7 @@ forEach({
return element.innerHTML;
}
for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) {
- JQLiteDealoc(childNodes[i]);
+ jqLiteDealoc(childNodes[i]);
}
element.innerHTML = value;
}
@@ -1860,14 +2509,14 @@ forEach({
JQLite.prototype[name] = function(arg1, arg2) {
var i, key;
- // JQLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
+ // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
// in a way that survives minification.
- if (((fn.length == 2 && (fn !== JQLiteHasClass && fn !== JQLiteController)) ? arg1 : arg2) === undefined) {
+ if (((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined) {
if (isObject(arg1)) {
// we are a write, but the object properties are the key/values
for(i=0; i < this.length; i++) {
- if (fn === JQLiteData) {
+ if (fn === jqLiteData) {
// data() takes the whole object in jQuery
fn(this[i], arg1);
} else {
@@ -1880,8 +2529,14 @@ forEach({
return this;
} else {
// we are a read, so read the first child.
- if (this.length)
- return fn(this[0], arg1, arg2);
+ var value = fn.$dv;
+ // Only if we have $dv do we iterate over all, otherwise it is just the first element.
+ var jj = (value === undefined) ? Math.min(this.length, 1) : this.length;
+ for (var j = 0; j < jj; j++) {
+ var nodeValue = fn(this[j], arg1, arg2);
+ value = value ? value + nodeValue : nodeValue;
+ }
+ return value;
}
} else {
// we are a write, so apply to all children
@@ -1891,7 +2546,6 @@ forEach({
// return self for chaining
return this;
}
- return fn.$dv;
};
});
@@ -1923,7 +2577,7 @@ function createEventHandler(element, events) {
}
event.isDefaultPrevented = function() {
- return event.defaultPrevented;
+ return event.defaultPrevented || event.returnValue === false;
};
forEach(events[type || event.type], function(fn) {
@@ -1954,54 +2608,77 @@ function createEventHandler(element, events) {
// selector.
//////////////////////////////////////////
forEach({
- removeData: JQLiteRemoveData,
+ removeData: jqLiteRemoveData,
- dealoc: JQLiteDealoc,
+ dealoc: jqLiteDealoc,
- bind: function bindFn(element, type, fn){
- var events = JQLiteExpandoStore(element, 'events'),
- handle = JQLiteExpandoStore(element, 'handle');
+ on: function onFn(element, type, fn, unsupported){
+ if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters');
- if (!events) JQLiteExpandoStore(element, 'events', events = {});
- if (!handle) JQLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events));
+ var events = jqLiteExpandoStore(element, 'events'),
+ handle = jqLiteExpandoStore(element, 'handle');
+
+ if (!events) jqLiteExpandoStore(element, 'events', events = {});
+ if (!handle) jqLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events));
forEach(type.split(' '), function(type){
var eventFns = events[type];
if (!eventFns) {
if (type == 'mouseenter' || type == 'mouseleave') {
- var counter = 0;
+ var contains = document.body.contains || document.body.compareDocumentPosition ?
+ function( a, b ) {
+ // jshint bitwise: false
+ var adown = a.nodeType === 9 ? a.documentElement : a,
+ bup = b && b.parentNode;
+ return a === bup || !!( bup && bup.nodeType === 1 && (
+ adown.contains ?
+ adown.contains( bup ) :
+ a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
+ ));
+ } :
+ function( a, b ) {
+ if ( b ) {
+ while ( (b = b.parentNode) ) {
+ if ( b === a ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
- events.mouseenter = [];
- events.mouseleave = [];
+ events[type] = [];
- bindFn(element, 'mouseover', function(event) {
- counter++;
- if (counter == 1) {
- handle(event, 'mouseenter');
- }
- });
- bindFn(element, 'mouseout', function(event) {
- counter --;
- if (counter == 0) {
- handle(event, 'mouseleave');
+ // Refer to jQuery's implementation of mouseenter & mouseleave
+ // Read about mouseenter and mouseleave:
+ // http://www.quirksmode.org/js/events_mouse.html#link8
+ var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"};
+
+ onFn(element, eventmap[type], function(event) {
+ var target = this, related = event.relatedTarget;
+ // For mousenter/leave call the handler if related is outside the target.
+ // NB: No relatedTarget if the mouse left/entered the browser window
+ if ( !related || (related !== target && !contains(target, related)) ){
+ handle(event, type);
}
});
+
} else {
addEventListenerFn(element, type, handle);
events[type] = [];
}
- eventFns = events[type]
+ eventFns = events[type];
}
eventFns.push(fn);
});
},
- unbind: JQLiteUnbind,
+ off: jqLiteOff,
replaceWith: function(element, replaceNode) {
var index, parent = element.parentNode;
- JQLiteDealoc(element);
+ jqLiteDealoc(element);
forEach(new JQLite(replaceNode), function(node){
if (index) {
parent.insertBefore(node, index.nextSibling);
@@ -2015,20 +2692,21 @@ forEach({
children: function(element) {
var children = [];
forEach(element.childNodes, function(element){
- if (element.nodeName != '#text')
+ if (element.nodeType === 1)
children.push(element);
});
return children;
},
contents: function(element) {
- return element.childNodes;
+ return element.childNodes || [];
},
append: function(element, node) {
forEach(new JQLite(node), function(child){
- if (element.nodeType === 1)
+ if (element.nodeType === 1 || element.nodeType === 11) {
element.appendChild(child);
+ }
});
},
@@ -2036,12 +2714,7 @@ forEach({
if (element.nodeType === 1) {
var index = element.firstChild;
forEach(new JQLite(node), function(child){
- if (index) {
- element.insertBefore(child, index);
- } else {
- element.appendChild(child);
- index = child;
- }
+ element.insertBefore(child, index);
});
}
},
@@ -2056,7 +2729,7 @@ forEach({
},
remove: function(element) {
- JQLiteDealoc(element);
+ jqLiteDealoc(element);
var parent = element.parentNode;
if (parent) parent.removeChild(element);
},
@@ -2069,14 +2742,14 @@ forEach({
});
},
- addClass: JQLiteAddClass,
- removeClass: JQLiteRemoveClass,
+ addClass: jqLiteAddClass,
+ removeClass: jqLiteRemoveClass,
toggleClass: function(element, selector, condition) {
if (isUndefined(condition)) {
- condition = !JQLiteHasClass(element, selector);
+ condition = !jqLiteHasClass(element, selector);
}
- (condition ? JQLiteAddClass : JQLiteRemoveClass)(element, selector);
+ (condition ? jqLiteAddClass : jqLiteRemoveClass)(element, selector);
},
parent: function(element) {
@@ -2085,33 +2758,61 @@ forEach({
},
next: function(element) {
- return element.nextSibling;
+ if (element.nextElementSibling) {
+ return element.nextElementSibling;
+ }
+
+ // IE8 doesn't have nextElementSibling
+ var elm = element.nextSibling;
+ while (elm != null && elm.nodeType !== 1) {
+ elm = elm.nextSibling;
+ }
+ return elm;
},
find: function(element, selector) {
return element.getElementsByTagName(selector);
},
- clone: JQLiteClone
+ clone: jqLiteClone,
+
+ triggerHandler: function(element, eventName, eventData) {
+ var eventFns = (jqLiteExpandoStore(element, 'events') || {})[eventName];
+
+ eventData = eventData || [];
+
+ var event = [{
+ preventDefault: noop,
+ stopPropagation: noop
+ }];
+
+ forEach(eventFns, function(fn) {
+ fn.apply(element, event.concat(eventData));
+ });
+ }
}, function(fn, name){
/**
* chaining functions
*/
- JQLite.prototype[name] = function(arg1, arg2) {
+ JQLite.prototype[name] = function(arg1, arg2, arg3) {
var value;
for(var i=0; i < this.length; i++) {
- if (value == undefined) {
- value = fn(this[i], arg1, arg2);
- if (value !== undefined) {
+ if (isUndefined(value)) {
+ value = fn(this[i], arg1, arg2, arg3);
+ if (isDefined(value)) {
// any function which returns a value needs to be wrapped
value = jqLite(value);
}
} else {
- JQLiteAddNodes(value, fn(this[i], arg1, arg2));
+ jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3));
}
}
- return value == undefined ? this : value;
+ return isDefined(value) ? value : this;
};
+
+ // bind legacy bind/unbind to on/off
+ JQLite.prototype.bind = JQLite.prototype.on;
+ JQLite.prototype.unbind = JQLite.prototype.off;
});
/**
@@ -2179,40 +2880,6 @@ HashMap.prototype = {
}
};
-/**
- * A map where multiple values can be added to the same key such that they form a queue.
- * @returns {HashQueueMap}
- */
-function HashQueueMap() {}
-HashQueueMap.prototype = {
- /**
- * Same as array push, but using an array as the value for the hash
- */
- push: function(key, value) {
- var array = this[key = hashKey(key)];
- if (!array) {
- this[key] = [value];
- } else {
- array.push(value);
- }
- },
-
- /**
- * Same as array shift, but using an array as the value for the hash
- */
- shift: function(key) {
- var array = this[key = hashKey(key)];
- if (array) {
- if (array.length == 1) {
- delete this[key];
- return array[0];
- } else {
- return array.shift();
- }
- }
- }
-};
-
/**
* @ngdoc function
* @name angular.injector
@@ -2233,7 +2900,7 @@ HashQueueMap.prototype = {
* // create an injector
* var $injector = angular.injector(['ng']);
*
- * // use the injector to kick of your application
+ * // use the injector to kick off your application
* // use the type inference to auto inject arguments, or use implicit injection
* $injector.invoke(function($rootScope, $compile, $document){
* $compile($document)($rootScope);
@@ -2253,8 +2920,9 @@ HashQueueMap.prototype = {
var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
-var FN_ARG = /^\s*(_?)(.+?)\1\s*$/;
+var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
+var $injectorMinErr = minErr('$injector');
function annotate(fn) {
var $inject,
fnText,
@@ -2264,18 +2932,20 @@ function annotate(fn) {
if (typeof fn == 'function') {
if (!($inject = fn.$inject)) {
$inject = [];
- fnText = fn.toString().replace(STRIP_COMMENTS, '');
- argDecl = fnText.match(FN_ARGS);
- forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
- arg.replace(FN_ARG, function(all, underscore, name){
- $inject.push(name);
+ if (fn.length) {
+ fnText = fn.toString().replace(STRIP_COMMENTS, '');
+ argDecl = fnText.match(FN_ARGS);
+ forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
+ arg.replace(FN_ARG, function(all, underscore, name){
+ $inject.push(name);
+ });
});
- });
+ }
fn.$inject = $inject;
}
} else if (isArray(fn)) {
last = fn.length - 1;
- assertArgFn(fn[last], 'fn')
+ assertArgFn(fn[last], 'fn');
$inject = fn.slice(0, last);
} else {
assertArgFn(fn, 'fn', true);
@@ -2309,26 +2979,26 @@ function annotate(fn) {
* # Injection Function Annotation
*
* JavaScript does not have annotations, and annotations are needed for dependency injection. The
- * following ways are all valid way of annotating function with injection arguments and are equivalent.
+ * following are all valid ways of annotating function with injection arguments and are equivalent.
*
*
* // inferred (only works if code not minified/obfuscated)
- * $inject.invoke(function(serviceA){});
+ * $injector.invoke(function(serviceA){});
*
* // annotated
* function explicit(serviceA) {};
* explicit.$inject = ['serviceA'];
- * $inject.invoke(explicit);
+ * $injector.invoke(explicit);
*
* // inline
- * $inject.invoke(['serviceA', function(serviceA){}]);
+ * $injector.invoke(['serviceA', function(serviceA){}]);
*
*
* ## Inference
*
- * In JavaScript calling `toString()` on a function returns the function definition. The definition can then be
- * parsed and the function arguments can be extracted. *NOTE:* This does not work with minification, and obfuscation
- * tools since these tools change the argument names.
+ * In JavaScript calling `toString()` on a function returns the function definition. The definition
+ * can then be parsed and the function arguments can be extracted. *NOTE:* This does not work with
+ * minification, and obfuscation tools since these tools change the argument names.
*
* ## `$inject` Annotation
* By adding a `$inject` property onto a function the injection parameters can be specified.
@@ -2357,24 +3027,38 @@ function annotate(fn) {
* @description
* Invoke the method and supply the method arguments from the `$injector`.
*
- * @param {!function} fn The function to invoke. The function arguments come form the function annotation.
+ * @param {!function} fn The function to invoke. Function parameters are injected according to the
+ * {@link guide/di $inject Annotation} rules.
* @param {Object=} self The `this` for the invoked method.
- * @param {Object=} locals Optional object. If preset then any argument names are read from this object first, before
- * the `$injector` is consulted.
+ * @param {Object=} locals Optional object. If preset then any argument names are read from this
+ * object first, before the `$injector` is consulted.
* @returns {*} the value returned by the invoked `fn` function.
*/
+/**
+ * @ngdoc method
+ * @name AUTO.$injector#has
+ * @methodOf AUTO.$injector
+ *
+ * @description
+ * Allows the user to query if the particular service exist.
+ *
+ * @param {string} Name of the service to query.
+ * @returns {boolean} returns true if injector has given service.
+ */
+
/**
* @ngdoc method
* @name AUTO.$injector#instantiate
* @methodOf AUTO.$injector
* @description
- * Create a new instance of JS type. The method takes a constructor function invokes the new operator and supplies
- * all of the arguments to the constructor function as specified by the constructor annotation.
+ * Create a new instance of JS type. The method takes a constructor function invokes the new
+ * operator and supplies all of the arguments to the constructor function as specified by the
+ * constructor annotation.
*
* @param {function} Type Annotated constructor function.
- * @param {Object=} locals Optional object. If preset then any argument names are read from this object first, before
- * the `$injector` is consulted.
+ * @param {Object=} locals Optional object. If preset then any argument names are read from this
+ * object first, before the `$injector` is consulted.
* @returns {Object} new instance of `Type`.
*/
@@ -2384,14 +3068,16 @@ function annotate(fn) {
* @methodOf AUTO.$injector
*
* @description
- * Returns an array of service names which the function is requesting for injection. This API is used by the injector
- * to determine which services need to be injected into the function when the function is invoked. There are three
- * ways in which the function can be annotated with the needed dependencies.
+ * Returns an array of service names which the function is requesting for injection. This API is
+ * used by the injector to determine which services need to be injected into the function when the
+ * function is invoked. There are three ways in which the function can be annotated with the needed
+ * dependencies.
*
* # Argument names
*
- * The simplest form is to extract the dependencies from the arguments of the function. This is done by converting
- * the function into a string using `toString()` method and extracting the argument names.
+ * The simplest form is to extract the dependencies from the arguments of the function. This is done
+ * by converting the function into a string using `toString()` method and extracting the argument
+ * names.
*
* // Given
* function MyController($scope, $route) {
@@ -2402,13 +3088,13 @@ function annotate(fn) {
* expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
*
*
- * This method does not work with code minfication / obfuscation. For this reason the following annotation strategies
- * are supported.
+ * This method does not work with code minification / obfuscation. For this reason the following
+ * annotation strategies are supported.
*
- * # The `$injector` property
+ * # The `$inject` property
*
- * If a function has an `$inject` property and its value is an array of strings, then the strings represent names of
- * services to be injected into the function.
+ * If a function has an `$inject` property and its value is an array of strings, then the strings
+ * represent names of services to be injected into the function.
*
* // Given
* var MyController = function(obfuscatedScope, obfuscatedRoute) {
@@ -2423,9 +3109,9 @@ function annotate(fn) {
*
* # The array notation
*
- * It is often desirable to inline Injected functions and that's when setting the `$inject` property is very
- * inconvenient. In these situations using the array notation to specify the dependencies in a way that survives
- * minification is a better choice:
+ * It is often desirable to inline Injected functions and that's when setting the `$inject` property
+ * is very inconvenient. In these situations using the array notation to specify the dependencies in
+ * a way that survives minification is a better choice:
*
*
* // We wish to write this (not minification / obfuscation safe)
@@ -2438,7 +3124,7 @@ function annotate(fn) {
* // ...
* };
* tmpFn.$inject = ['$compile', '$rootScope'];
- * injector.invoke(tempFn);
+ * injector.invoke(tmpFn);
*
* // To better support inline function the inline annotation is supported
* injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
@@ -2451,8 +3137,8 @@ function annotate(fn) {
* ).toEqual(['$compile', '$rootScope']);
*
*
- * @param {function|Array.} fn Function for which dependent service names need to be retrieved as described
- * above.
+ * @param {function|Array.} fn Function for which dependent service names need to
+ * be retrieved as described above.
*
* @returns {Array.} The names of the services which the function requires.
*/
@@ -2466,48 +3152,38 @@ function annotate(fn) {
*
* @description
*
- * Use `$provide` to register new providers with the `$injector`. The providers are the factories for the instance.
- * The providers share the same name as the instance they create with the `Provider` suffixed to them.
- *
- * A provider is an object with a `$get()` method. The injector calls the `$get` method to create a new instance of
- * a service. The Provider can have additional methods which would allow for configuration of the provider.
- *
- *
- * function GreetProvider() {
- * var salutation = 'Hello';
- *
- * this.salutation = function(text) {
- * salutation = text;
- * };
- *
- * this.$get = function() {
- * return function (name) {
- * return salutation + ' ' + name + '!';
- * };
- * };
- * }
+ * The {@link AUTO.$provide $provide} service has a number of methods for registering components
+ * with the {@link AUTO.$injector $injector}. Many of these functions are also exposed on
+ * {@link angular.Module}.
*
- * describe('Greeter', function(){
+ * An Angular **service** is a singleton object created by a **service factory**. These **service
+ * factories** are functions which, in turn, are created by a **service provider**.
+ * The **service providers** are constructor functions. When instantiated they must contain a
+ * property called `$get`, which holds the **service factory** function.
*
- * beforeEach(module(function($provide) {
- * $provide.provider('greet', GreetProvider);
- * });
+ * When you request a service, the {@link AUTO.$injector $injector} is responsible for finding the
+ * correct **service provider**, instantiating it and then calling its `$get` **service factory**
+ * function to get the instance of the **service**.
*
- * it('should greet', inject(function(greet) {
- * expect(greet('angular')).toEqual('Hello angular!');
- * }));
+ * Often services have no configuration options and there is no need to add methods to the service
+ * provider. The provider will be no more than a constructor function with a `$get` property. For
+ * these cases the {@link AUTO.$provide $provide} service has additional helper methods to register
+ * services without specifying a provider.
*
- * it('should allow configuration of salutation', function() {
- * module(function(greetProvider) {
- * greetProvider.salutation('Ahoj');
- * });
- * inject(function(greet) {
- * expect(greet('angular')).toEqual('Ahoj angular!');
- * });
- * )};
+ * * {@link AUTO.$provide#methods_provider provider(provider)} - registers a **service provider** with the
+ * {@link AUTO.$injector $injector}
+ * * {@link AUTO.$provide#methods_constant constant(obj)} - registers a value/object that can be accessed by
+ * providers and services.
+ * * {@link AUTO.$provide#methods_value value(obj)} - registers a value/object that can only be accessed by
+ * services, not providers.
+ * * {@link AUTO.$provide#methods_factory factory(fn)} - registers a service **factory function**, `fn`,
+ * that will be wrapped in a **service provider** object, whose `$get` property will contain the
+ * given factory function.
+ * * {@link AUTO.$provide#methods_service service(class)} - registers a **constructor function**, `class` that
+ * that will be wrapped in a **service provider** object, whose `$get` property will instantiate
+ * a new object using the given constructor function.
*
- * });
- *
+ * See the individual methods for more information and examples.
*/
/**
@@ -2516,17 +3192,97 @@ function annotate(fn) {
* @methodOf AUTO.$provide
* @description
*
- * Register a provider for a service. The providers can be retrieved and can have additional configuration methods.
+ * Register a **provider function** with the {@link AUTO.$injector $injector}. Provider functions
+ * are constructor functions, whose instances are responsible for "providing" a factory for a
+ * service.
+ *
+ * Service provider names start with the name of the service they provide followed by `Provider`.
+ * For example, the {@link ng.$log $log} service has a provider called
+ * {@link ng.$logProvider $logProvider}.
*
- * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 'Provider'` key.
+ * Service provider objects can have additional methods which allow configuration of the provider
+ * and its service. Importantly, you can configure what kind of service is created by the `$get`
+ * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a
+ * method {@link ng.$logProvider#debugEnabled debugEnabled}
+ * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the
+ * console or not.
+ *
+ * @param {string} name The name of the instance. NOTE: the provider will be available under `name +
+ 'Provider'` key.
* @param {(Object|function())} provider If the provider is:
*
* - `Object`: then it should have a `$get` method. The `$get` method will be invoked using
- * {@link AUTO.$injector#invoke $injector.invoke()} when an instance needs to be created.
+ * {@link AUTO.$injector#invoke $injector.invoke()} when an instance needs to be
+ * created.
* - `Constructor`: a new instance of the provider will be created using
- * {@link AUTO.$injector#instantiate $injector.instantiate()}, then treated as `object`.
+ * {@link AUTO.$injector#instantiate $injector.instantiate()}, then treated as
+ * `object`.
*
* @returns {Object} registered provider instance
+
+ * @example
+ *
+ * The following example shows how to create a simple event tracking service and register it using
+ * {@link AUTO.$provide#methods_provider $provide.provider()}.
+ *
+ *
+ * // Define the eventTracker provider
+ * function EventTrackerProvider() {
+ * var trackingUrl = '/track';
+ *
+ * // A provider method for configuring where the tracked events should been saved
+ * this.setTrackingUrl = function(url) {
+ * trackingUrl = url;
+ * };
+ *
+ * // The service factory function
+ * this.$get = ['$http', function($http) {
+ * var trackedEvents = {};
+ * return {
+ * // Call this to track an event
+ * event: function(event) {
+ * var count = trackedEvents[event] || 0;
+ * count += 1;
+ * trackedEvents[event] = count;
+ * return count;
+ * },
+ * // Call this to save the tracked events to the trackingUrl
+ * save: function() {
+ * $http.post(trackingUrl, trackedEvents);
+ * }
+ * };
+ * }];
+ * }
+ *
+ * describe('eventTracker', function() {
+ * var postSpy;
+ *
+ * beforeEach(module(function($provide) {
+ * // Register the eventTracker provider
+ * $provide.provider('eventTracker', EventTrackerProvider);
+ * }));
+ *
+ * beforeEach(module(function(eventTrackerProvider) {
+ * // Configure eventTracker provider
+ * eventTrackerProvider.setTrackingUrl('/custom-track');
+ * }));
+ *
+ * it('tracks events', inject(function(eventTracker) {
+ * expect(eventTracker.event('login')).toEqual(1);
+ * expect(eventTracker.event('login')).toEqual(2);
+ * }));
+ *
+ * it('saves to the tracking url', inject(function(eventTracker, $http) {
+ * postSpy = spyOn($http, 'post');
+ * eventTracker.event('login');
+ * eventTracker.save();
+ * expect(postSpy).toHaveBeenCalled();
+ * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track');
+ * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track');
+ * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 });
+ * }));
+ * });
+ *
*/
/**
@@ -2535,12 +3291,32 @@ function annotate(fn) {
* @methodOf AUTO.$provide
* @description
*
- * A short hand for configuring services if only `$get` method is required.
+ * Register a **service factory**, which will be called to return the service instance.
+ * This is short for registering a service where its provider consists of only a `$get` property,
+ * which is the given service factory function.
+ * You should use {@link AUTO.$provide#factory $provide.factory(getFn)} if you do not need to
+ * configure your service in a provider.
*
* @param {string} name The name of the instance.
- * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand for
- * `$provide.provider(name, {$get: $getFn})`.
+ * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand
+ * for `$provide.provider(name, {$get: $getFn})`.
* @returns {Object} registered provider instance
+ *
+ * @example
+ * Here is an example of registering a service
+ *
+ * $provide.factory('ping', ['$http', function($http) {
+ * return function ping() {
+ * return $http.send('/ping');
+ * };
+ * }]);
+ *
+ * You would then inject and use this service like this:
+ *
+ * someModule.controller('Ctrl', ['ping', function(ping) {
+ * ping();
+ * }]);
+ *
*/
@@ -2550,11 +3326,35 @@ function annotate(fn) {
* @methodOf AUTO.$provide
* @description
*
- * A short hand for registering service of given class.
+ * Register a **service constructor**, which will be invoked with `new` to create the service
+ * instance.
+ * This is short for registering a service where its provider's `$get` property is the service
+ * constructor function that will be used to instantiate the service instance.
+ *
+ * You should use {@link AUTO.$provide#methods_service $provide.service(class)} if you define your service
+ * as a type/class. This is common when using {@link http://coffeescript.org CoffeeScript}.
*
* @param {string} name The name of the instance.
* @param {Function} constructor A class (constructor function) that will be instantiated.
* @returns {Object} registered provider instance
+ *
+ * @example
+ * Here is an example of registering a service using
+ * {@link AUTO.$provide#methods_service $provide.service(class)} that is defined as a CoffeeScript class.
+ *
+ * class Ping
+ * constructor: (@$http)->
+ * send: ()=>
+ * @$http.get('/ping')
+ *
+ * $provide.service('ping', ['$http', Ping])
+ *
+ * You would then inject and use this service like this:
+ *
+ * someModule.controller 'Ctrl', ['ping', (ping)->
+ * ping.send()
+ * ]
+ *
*/
@@ -2564,11 +3364,31 @@ function annotate(fn) {
* @methodOf AUTO.$provide
* @description
*
- * A short hand for configuring services if the `$get` method is a constant.
+ * Register a **value service** with the {@link AUTO.$injector $injector}, such as a string, a
+ * number, an array, an object or a function. This is short for registering a service where its
+ * provider's `$get` property is a factory function that takes no arguments and returns the **value
+ * service**.
+ *
+ * Value services are similar to constant services, except that they cannot be injected into a
+ * module configuration function (see {@link angular.Module#config}) but they can be overridden by
+ * an Angular
+ * {@link AUTO.$provide#decorator decorator}.
*
* @param {string} name The name of the instance.
* @param {*} value The value.
* @returns {Object} registered provider instance
+ *
+ * @example
+ * Here are some examples of creating value services.
+ *
+ * $provide.value('ADMIN_USER', 'admin');
+ *
+ * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 });
+ *
+ * $provide.value('halfOf', function(value) {
+ * return value / 2;
+ * });
+ *
*/
@@ -2578,13 +3398,26 @@ function annotate(fn) {
* @methodOf AUTO.$provide
* @description
*
- * A constant value, but unlike {@link AUTO.$provide#value value} it can be injected
- * into configuration function (other modules) and it is not interceptable by
- * {@link AUTO.$provide#decorator decorator}.
+ * Register a **constant service**, such as a string, a number, an array, an object or a function,
+ * with the {@link AUTO.$injector $injector}. Unlike {@link AUTO.$provide#value value} it can be
+ * injected into a module configuration function (see {@link angular.Module#config}) and it cannot
+ * be overridden by an Angular {@link AUTO.$provide#decorator decorator}.
*
* @param {string} name The name of the constant.
* @param {*} value The constant value.
* @returns {Object} registered instance
+ *
+ * @example
+ * Here a some examples of creating constants:
+ *
+ * $provide.constant('SHARD_HEIGHT', 306);
+ *
+ * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']);
+ *
+ * $provide.constant('double', function(value) {
+ * return value * 2;
+ * });
+ *
*/
@@ -2594,17 +3427,29 @@ function annotate(fn) {
* @methodOf AUTO.$provide
* @description
*
- * Decoration of service, allows the decorator to intercept the service instance creation. The
- * returned instance may be the original instance, or a new instance which delegates to the
- * original instance.
+ * Register a **service decorator** with the {@link AUTO.$injector $injector}. A service decorator
+ * intercepts the creation of a service, allowing it to override or modify the behaviour of the
+ * service. The object returned by the decorator may be the original service, or a new service
+ * object which replaces or wraps and delegates to the original service.
*
* @param {string} name The name of the service to decorate.
* @param {function()} decorator This function will be invoked when the service needs to be
- * instanciated. The function is called using the {@link AUTO.$injector#invoke
- * injector.invoke} method and is therefore fully injectable. Local injection arguments:
+ * instantiated and should return the decorated service instance. The function is called using
+ * the {@link AUTO.$injector#invoke injector.invoke} method and is therefore fully injectable.
+ * Local injection arguments:
*
* * `$delegate` - The original service instance, which can be monkey patched, configured,
* decorated or delegated to.
+ *
+ * @example
+ * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting
+ * calls to {@link ng.$log#error $log.warn()}.
+ *
+ * $provider.decorator('$log', ['$delegate', function($delegate) {
+ * $delegate.warn = $delegate.error;
+ * return $delegate;
+ * }]);
+ *
*/
@@ -2623,9 +3468,10 @@ function createInjector(modulesToLoad) {
decorator: decorator
}
},
- providerInjector = createInternalInjector(providerCache, function() {
- throw Error("Unknown provider: " + path.join(' <- '));
- }),
+ providerInjector = (providerCache.$injector =
+ createInternalInjector(providerCache, function() {
+ throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
+ })),
instanceCache = {},
instanceInjector = (instanceCache.$injector =
createInternalInjector(instanceCache, function(servicename) {
@@ -2649,15 +3495,16 @@ function createInjector(modulesToLoad) {
} else {
return delegate(key, value);
}
- }
+ };
}
function provider(name, provider_) {
- if (isFunction(provider_)) {
+ assertNotHasOwnProperty(name, 'service');
+ if (isFunction(provider_) || isArray(provider_)) {
provider_ = providerInjector.instantiate(provider_);
}
if (!provider_.$get) {
- throw Error('Provider ' + name + ' must define $get factory method.');
+ throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name);
}
return providerCache[name + providerSuffix] = provider_;
}
@@ -2670,9 +3517,10 @@ function createInjector(modulesToLoad) {
}]);
}
- function value(name, value) { return factory(name, valueFn(value)); }
+ function value(name, val) { return factory(name, valueFn(val)); }
function constant(name, value) {
+ assertNotHasOwnProperty(name, 'constant');
providerCache[name] = value;
instanceCache[name] = value;
}
@@ -2691,43 +3539,43 @@ function createInjector(modulesToLoad) {
// Module Loading
////////////////////////////////////
function loadModules(modulesToLoad){
- var runBlocks = [];
+ var runBlocks = [], moduleFn, invokeQueue, i, ii;
forEach(modulesToLoad, function(module) {
if (loadedModules.get(module)) return;
loadedModules.put(module, true);
- if (isString(module)) {
- var moduleFn = angularModule(module);
- runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
- try {
- for(var invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) {
+ try {
+ if (isString(module)) {
+ moduleFn = angularModule(module);
+ runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
+
+ for(invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) {
var invokeArgs = invokeQueue[i],
- provider = invokeArgs[0] == '$injector'
- ? providerInjector
- : providerInjector.get(invokeArgs[0]);
+ provider = providerInjector.get(invokeArgs[0]);
provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
}
- } catch (e) {
- if (e.message) e.message += ' from ' + module;
- throw e;
+ } else if (isFunction(module)) {
+ runBlocks.push(providerInjector.invoke(module));
+ } else if (isArray(module)) {
+ runBlocks.push(providerInjector.invoke(module));
+ } else {
+ assertArgFn(module, 'module');
}
- } else if (isFunction(module)) {
- try {
- runBlocks.push(providerInjector.invoke(module));
- } catch (e) {
- if (e.message) e.message += ' from ' + module;
- throw e;
+ } catch (e) {
+ if (isArray(module)) {
+ module = module[module.length - 1];
}
- } else if (isArray(module)) {
- try {
- runBlocks.push(providerInjector.invoke(module));
- } catch (e) {
- if (e.message) e.message += ' from ' + String(module[module.length - 1]);
- throw e;
+ if (e.message && e.stack && e.stack.indexOf(e.message) == -1) {
+ // Safari & FF's stack traces don't contain error.message content
+ // unlike those of Chrome and IE
+ // So if stack doesn't contain message, we create a new string that contains both.
+ // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here.
+ /* jshint -W022 */
+ e = e.message + '\n' + e.stack;
}
- } else {
- assertArgFn(module, 'module');
+ throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}",
+ module, e.stack || e.message || e);
}
});
return runBlocks;
@@ -2740,12 +3588,9 @@ function createInjector(modulesToLoad) {
function createInternalInjector(cache, factory) {
function getService(serviceName) {
- if (typeof serviceName !== 'string') {
- throw Error('Service name expected');
- }
if (cache.hasOwnProperty(serviceName)) {
if (cache[serviceName] === INSTANTIATING) {
- throw Error('Circular dependency: ' + path.join(' <- '));
+ throw $injectorMinErr('cdep', 'Circular dependency found: {0}', path.join(' <- '));
}
return cache[serviceName];
} else {
@@ -2767,10 +3612,14 @@ function createInjector(modulesToLoad) {
for(i = 0, length = $inject.length; i < length; i++) {
key = $inject[i];
+ if (typeof key !== 'string') {
+ throw $injectorMinErr('itkn',
+ 'Incorrect injection token! Expected service name as string, got {0}', key);
+ }
args.push(
locals && locals.hasOwnProperty(key)
? locals[key]
- : getService(key, path)
+ : getService(key)
);
}
if (!fn.$inject) {
@@ -2790,8 +3639,10 @@ function createInjector(modulesToLoad) {
case 6: return fn(args[0], args[1], args[2], args[3], args[4], args[5]);
case 7: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
case 8: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
- case 9: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]);
- case 10: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]);
+ case 9: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7],
+ args[8]);
+ case 10: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7],
+ args[8], args[9]);
default: return fn.apply(self, args);
}
}
@@ -2800,21 +3651,27 @@ function createInjector(modulesToLoad) {
var Constructor = function() {},
instance, returnedValue;
+ // Check if Type is annotated and use just the given function at n-1 as parameter
+ // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype;
instance = new Constructor();
returnedValue = invoke(Type, instance, locals);
- return isObject(returnedValue) ? returnedValue : instance;
+ return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
}
return {
invoke: invoke,
instantiate: instantiate,
get: getService,
- annotate: annotate
+ annotate: annotate,
+ has: function(name) {
+ return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
+ }
};
}
}
+
/**
* @ngdoc function
* @name ng.$anchorScroll
@@ -2827,8 +3684,41 @@ function createInjector(modulesToLoad) {
* according to rules specified in
* {@link http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document Html5 spec}.
*
- * It also watches the `$location.hash()` and scroll whenever it changes to match any anchor.
+ * It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor.
* This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`.
+ *
+ * @example
+
+
+
+
+
+ function ScrollCtrl($scope, $location, $anchorScroll) {
+ $scope.gotoBottom = function (){
+ // set the location.hash to the id of
+ // the element you wish to scroll to.
+ $location.hash('bottom');
+
+ // call $anchorScroll()
+ $anchorScroll();
+ }
+ }
+
+
+ #scrollArea {
+ height: 350px;
+ overflow: auto;
+ }
+
+ #bottom {
+ display: block;
+ margin-top: 2000px;
+ }
+
+
*/
function $AnchorScrollProvider() {
@@ -2870,17 +3760,223 @@ function $AnchorScrollProvider() {
}
// does not scroll when user clicks on anchor link that is currently on
- // (no url change, no $locaiton.hash() change), browser native does scroll
+ // (no url change, no $location.hash() change), browser native does scroll
if (autoScrollingEnabled) {
- $rootScope.$watch(function() {return $location.hash();}, function() {
- $rootScope.$evalAsync(scroll);
- });
+ $rootScope.$watch(function autoScrollWatch() {return $location.hash();},
+ function autoScrollWatchAction() {
+ $rootScope.$evalAsync(scroll);
+ });
}
return scroll;
}];
}
+var $animateMinErr = minErr('$animate');
+
+/**
+ * @ngdoc object
+ * @name ng.$animateProvider
+ *
+ * @description
+ * Default implementation of $animate that doesn't perform any animations, instead just
+ * synchronously performs DOM
+ * updates and calls done() callbacks.
+ *
+ * In order to enable animations the ngAnimate module has to be loaded.
+ *
+ * To see the functional implementation check out src/ngAnimate/animate.js
+ */
+var $AnimateProvider = ['$provide', function($provide) {
+
+
+ this.$$selectors = {};
+
+
+ /**
+ * @ngdoc function
+ * @name ng.$animateProvider#register
+ * @methodOf ng.$animateProvider
+ *
+ * @description
+ * Registers a new injectable animation factory function. The factory function produces the
+ * animation object which contains callback functions for each event that is expected to be
+ * animated.
+ *
+ * * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction`
+ * must be called once the element animation is complete. If a function is returned then the
+ * animation service will use this function to cancel the animation whenever a cancel event is
+ * triggered.
+ *
+ *
+ *
+ * return {
+ * eventFn : function(element, done) {
+ * //code to run the animation
+ * //once complete, then run done()
+ * return function cancellationFunction() {
+ * //code to cancel the animation
+ * }
+ * }
+ * }
+ *
+ *
+ * @param {string} name The name of the animation.
+ * @param {function} factory The factory function that will be executed to return the animation
+ * object.
+ */
+ this.register = function(name, factory) {
+ var key = name + '-animation';
+ if (name && name.charAt(0) != '.') throw $animateMinErr('notcsel',
+ "Expecting class selector starting with '.' got '{0}'.", name);
+ this.$$selectors[name.substr(1)] = key;
+ $provide.factory(key, factory);
+ };
+
+ this.$get = ['$timeout', function($timeout) {
+
+ /**
+ *
+ * @ngdoc object
+ * @name ng.$animate
+ * @description The $animate service provides rudimentary DOM manipulation functions to
+ * insert, remove and move elements within the DOM, as well as adding and removing classes.
+ * This service is the core service used by the ngAnimate $animator service which provides
+ * high-level animation hooks for CSS and JavaScript.
+ *
+ * $animate is available in the AngularJS core, however, the ngAnimate module must be included
+ * to enable full out animation support. Otherwise, $animate will only perform simple DOM
+ * manipulation operations.
+ *
+ * To learn more about enabling animation support, click here to visit the {@link ngAnimate
+ * ngAnimate module page} as well as the {@link ngAnimate.$animate ngAnimate $animate service
+ * page}.
+ */
+ return {
+
+ /**
+ *
+ * @ngdoc function
+ * @name ng.$animate#enter
+ * @methodOf ng.$animate
+ * @function
+ * @description Inserts the element into the DOM either after the `after` element or within
+ * the `parent` element. Once complete, the done() callback will be fired (if provided).
+ * @param {jQuery/jqLite element} element the element which will be inserted into the DOM
+ * @param {jQuery/jqLite element} parent the parent element which will append the element as
+ * a child (if the after element is not present)
+ * @param {jQuery/jqLite element} after the sibling element which will append the element
+ * after itself
+ * @param {function=} done callback function that will be called after the element has been
+ * inserted into the DOM
+ */
+ enter : function(element, parent, after, done) {
+ if (after) {
+ after.after(element);
+ } else {
+ if (!parent || !parent[0]) {
+ parent = after.parent();
+ }
+ parent.append(element);
+ }
+ done && $timeout(done, 0, false);
+ },
+
+ /**
+ *
+ * @ngdoc function
+ * @name ng.$animate#leave
+ * @methodOf ng.$animate
+ * @function
+ * @description Removes the element from the DOM. Once complete, the done() callback will be
+ * fired (if provided).
+ * @param {jQuery/jqLite element} element the element which will be removed from the DOM
+ * @param {function=} done callback function that will be called after the element has been
+ * removed from the DOM
+ */
+ leave : function(element, done) {
+ element.remove();
+ done && $timeout(done, 0, false);
+ },
+
+ /**
+ *
+ * @ngdoc function
+ * @name ng.$animate#move
+ * @methodOf ng.$animate
+ * @function
+ * @description Moves the position of the provided element within the DOM to be placed
+ * either after the `after` element or inside of the `parent` element. Once complete, the
+ * done() callback will be fired (if provided).
+ *
+ * @param {jQuery/jqLite element} element the element which will be moved around within the
+ * DOM
+ * @param {jQuery/jqLite element} parent the parent element where the element will be
+ * inserted into (if the after element is not present)
+ * @param {jQuery/jqLite element} after the sibling element where the element will be
+ * positioned next to
+ * @param {function=} done the callback function (if provided) that will be fired after the
+ * element has been moved to its new position
+ */
+ move : function(element, parent, after, done) {
+ // Do not remove element before insert. Removing will cause data associated with the
+ // element to be dropped. Insert will implicitly do the remove.
+ this.enter(element, parent, after, done);
+ },
+
+ /**
+ *
+ * @ngdoc function
+ * @name ng.$animate#addClass
+ * @methodOf ng.$animate
+ * @function
+ * @description Adds the provided className CSS class value to the provided element. Once
+ * complete, the done() callback will be fired (if provided).
+ * @param {jQuery/jqLite element} element the element which will have the className value
+ * added to it
+ * @param {string} className the CSS class which will be added to the element
+ * @param {function=} done the callback function (if provided) that will be fired after the
+ * className value has been added to the element
+ */
+ addClass : function(element, className, done) {
+ className = isString(className) ?
+ className :
+ isArray(className) ? className.join(' ') : '';
+ forEach(element, function (element) {
+ jqLiteAddClass(element, className);
+ });
+ done && $timeout(done, 0, false);
+ },
+
+ /**
+ *
+ * @ngdoc function
+ * @name ng.$animate#removeClass
+ * @methodOf ng.$animate
+ * @function
+ * @description Removes the provided className CSS class value from the provided element.
+ * Once complete, the done() callback will be fired (if provided).
+ * @param {jQuery/jqLite element} element the element which will have the className value
+ * removed from it
+ * @param {string} className the CSS class which will be removed from the element
+ * @param {function=} done the callback function (if provided) that will be fired after the
+ * className value has been removed from the element
+ */
+ removeClass : function(element, className, done) {
+ className = isString(className) ?
+ className :
+ isArray(className) ? className.join(' ') : '';
+ forEach(element, function (element) {
+ jqLiteRemoveClass(element, className);
+ });
+ done && $timeout(done, 0, false);
+ },
+
+ enabled : noop
+ };
+ }];
+}];
+
/**
* ! This is a private undocumented service !
*
@@ -3005,7 +4101,8 @@ function Browser(window, document, $log, $sniffer) {
//////////////////////////////////////////////////////////////
var lastBrowserUrl = location.href,
- baseElement = document.find('base');
+ baseElement = document.find('base'),
+ newLocation = null;
/**
* @name ng.$browser#url
@@ -3028,6 +4125,9 @@ function Browser(window, document, $log, $sniffer) {
* @param {boolean=} replace Should new url replace current history record ?
*/
self.url = function(url, replace) {
+ // Android Browser BFCache causes location reference to become stale.
+ if (location !== window.location) location = window.location;
+
// setter
if (url) {
if (lastBrowserUrl == url) return;
@@ -3040,14 +4140,20 @@ function Browser(window, document, $log, $sniffer) {
baseElement.attr('href', baseElement.attr('href'));
}
} else {
- if (replace) location.replace(url);
- else location.href = url;
+ newLocation = url;
+ if (replace) {
+ location.replace(url);
+ } else {
+ location.href = url;
+ }
}
return self;
// getter
} else {
- // the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
- return location.href.replace(/%27/g,"'");
+ // - newLocation is a workaround for an IE7-9 issue with location.replace and location.href
+ // methods not updating location.href synchronously.
+ // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
+ return newLocation || location.href.replace(/%27/g,"'");
}
};
@@ -3055,6 +4161,7 @@ function Browser(window, document, $log, $sniffer) {
urlChangeInit = false;
function fireUrlChange() {
+ newLocation = null;
if (lastBrowserUrl == self.url()) return;
lastBrowserUrl = self.url();
@@ -3093,9 +4200,9 @@ function Browser(window, document, $log, $sniffer) {
// changed by push/replaceState
// html5 history api - popstate event
- if ($sniffer.history) jqLite(window).bind('popstate', fireUrlChange);
+ if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange);
// hashchange event
- if ($sniffer.hashchange) jqLite(window).bind('hashchange', fireUrlChange);
+ if ($sniffer.hashchange) jqLite(window).on('hashchange', fireUrlChange);
// polling
else self.addPollFn(fireUrlChange);
@@ -3111,14 +4218,18 @@ function Browser(window, document, $log, $sniffer) {
//////////////////////////////////////////////////////////////
/**
+ * @name ng.$browser#baseHref
+ * @methodOf ng.$browser
+ *
+ * @description
* Returns current
* (always relative - without domain)
*
- * @returns {string=}
+ * @returns {string=} current
*/
self.baseHref = function() {
var href = baseElement.attr('href');
- return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : href;
+ return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : '';
};
//////////////////////////////////////////////////////////////
@@ -3133,38 +4244,44 @@ function Browser(window, document, $log, $sniffer) {
* @methodOf ng.$browser
*
* @param {string=} name Cookie name
- * @param {string=} value Cokkie value
+ * @param {string=} value Cookie value
*
* @description
* The cookies method provides a 'private' low level access to browser cookies.
* It is not meant to be used directly, use the $cookie service instead.
*
* The return values vary depending on the arguments that the method was called with as follows:
- *
- * cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify it
- * cookies(name, value) -> set name to value, if value is undefined delete the cookie
- * cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that way)
- *
- *
+ *
+ * - cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify
+ * it
+ * - cookies(name, value) -> set name to value, if value is undefined delete the cookie
+ * - cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that
+ * way)
+ *
* @returns {Object} Hash of all cookies (if called without any parameter)
*/
self.cookies = function(name, value) {
+ /* global escape: false, unescape: false */
var cookieLength, cookieArray, cookie, i, index;
if (name) {
if (value === undefined) {
- rawDocument.cookie = escape(name) + "=;path=" + cookiePath + ";expires=Thu, 01 Jan 1970 00:00:00 GMT";
+ rawDocument.cookie = escape(name) + "=;path=" + cookiePath +
+ ";expires=Thu, 01 Jan 1970 00:00:00 GMT";
} else {
if (isString(value)) {
- cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + ';path=' + cookiePath).length + 1;
+ cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) +
+ ';path=' + cookiePath).length + 1;
+
+ // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum:
+ // - 300 cookies
+ // - 20 cookies per unique domain
+ // - 4096 bytes per cookie
if (cookieLength > 4096) {
- $log.warn("Cookie '"+ name +"' possibly not set or overflowed because it was too large ("+
+ $log.warn("Cookie '"+ name +
+ "' possibly not set or overflowed because it was too large ("+
cookieLength + " > 4096 bytes)!");
}
- if (lastCookies.length > 20) {
- $log.warn("Cookie '"+ name +"' possibly not set or overflowed because too many cookies " +
- "were already set (" + lastCookies.length + " > 20 )");
- }
}
}
} else {
@@ -3177,7 +4294,13 @@ function Browser(window, document, $log, $sniffer) {
cookie = cookieArray[i];
index = cookie.indexOf('=');
if (index > 0) { //ignore nameless cookies
- lastCookies[unescape(cookie.substring(0, index))] = unescape(cookie.substring(index + 1));
+ name = unescape(cookie.substring(0, index));
+ // the first value that is seen for a cookie is the most
+ // specific one. values for the same cookie name that
+ // follow are for less specific paths.
+ if (lastCookies[name] === undefined) {
+ lastCookies[name] = unescape(cookie.substring(index + 1));
+ }
}
}
}
@@ -3189,12 +4312,12 @@ function Browser(window, document, $log, $sniffer) {
/**
* @name ng.$browser#defer
* @methodOf ng.$browser
- * @param {function()} fn A function, who's execution should be defered.
+ * @param {function()} fn A function, who's execution should be deferred.
* @param {number=} [delay=0] of milliseconds to defer the function execution.
* @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
*
* @description
- * Executes a fn asynchroniously via `setTimeout(fn, delay)`.
+ * Executes a fn asynchronously via `setTimeout(fn, delay)`.
*
* Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using
* `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed
@@ -3218,10 +4341,11 @@ function Browser(window, document, $log, $sniffer) {
* @methodOf ng.$browser.defer
*
* @description
- * Cancels a defered task identified with `deferId`.
+ * Cancels a deferred task identified with `deferId`.
*
* @param {*} deferId Token returned by the `$browser.defer` function.
- * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfuly canceled.
+ * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
+ * canceled.
*/
self.defer.cancel = function(deferId) {
if (pendingDeferIds[deferId]) {
@@ -3241,12 +4365,27 @@ function $BrowserProvider(){
return new Browser($window, $document, $log, $sniffer);
}];
}
+
/**
* @ngdoc object
* @name ng.$cacheFactory
*
* @description
- * Factory that constructs cache objects.
+ * Factory that constructs cache objects and gives access to them.
+ *
+ *
+ *
+ * var cache = $cacheFactory('cacheId');
+ * expect($cacheFactory.get('cacheId')).toBe(cache);
+ * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined();
+ *
+ * cache.put("key", "value");
+ * cache.put("another key", "another value");
+ *
+ * // We've specified no options on creation
+ * expect(cache.info()).toEqual({id: 'cacheId', size: 2});
+ *
+ *
*
*
* @param {string} cacheId Name or id of the newly created cache.
@@ -3257,11 +4396,12 @@ function $BrowserProvider(){
* @returns {object} Newly created cache object with the following set of methods:
*
* - `{object}` `info()` — Returns id, size, and options of cache.
- * - `{void}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache.
- * - `{{*}} `get({string} key) — Returns cached value for `key` or undefined for cache miss.
- * - `{void}` `remove({string} key) — Removes a key-value pair from the cache.
- * - `{void}` `removeAll() — Removes all cached values.
- * - `{void}` `destroy() — Removes references to this cache from $cacheFactory.
+ * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns
+ * it.
+ * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss.
+ * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache.
+ * - `{void}` `removeAll()` — Removes all cached values.
+ * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory.
*
*/
function $CacheFactoryProvider() {
@@ -3271,7 +4411,7 @@ function $CacheFactoryProvider() {
function cacheFactory(cacheId, options) {
if (cacheId in caches) {
- throw Error('cacheId ' + cacheId + ' taken');
+ throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId);
}
var size = 0,
@@ -3296,6 +4436,8 @@ function $CacheFactoryProvider() {
if (size > capacity) {
this.remove(staleEnd.key);
}
+
+ return value;
},
@@ -3313,6 +4455,8 @@ function $CacheFactoryProvider() {
remove: function(key) {
var lruEntry = lruHash[key];
+ if (!lruEntry) return;
+
if (lruEntry == freshEnd) freshEnd = lruEntry.p;
if (lruEntry == staleEnd) staleEnd = lruEntry.n;
link(lruEntry.n,lruEntry.p);
@@ -3376,6 +4520,16 @@ function $CacheFactoryProvider() {
}
+ /**
+ * @ngdoc method
+ * @name ng.$cacheFactory#info
+ * @methodOf ng.$cacheFactory
+ *
+ * @description
+ * Get information about all the of the caches that have been created
+ *
+ * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info`
+ */
cacheFactory.info = function() {
var info = {};
forEach(caches, function(cache, cacheId) {
@@ -3385,6 +4539,17 @@ function $CacheFactoryProvider() {
};
+ /**
+ * @ngdoc method
+ * @name ng.$cacheFactory#get
+ * @methodOf ng.$cacheFactory
+ *
+ * @description
+ * Get access to a cache object by the `cacheId` used when it was created.
+ *
+ * @param {string} cacheId Name or id of a cache to access.
+ * @returns {object} Cache object identified by the cacheId or undefined if no such cache.
+ */
cacheFactory.get = function(cacheId) {
return caches[cacheId];
};
@@ -3399,8 +4564,44 @@ function $CacheFactoryProvider() {
* @name ng.$templateCache
*
* @description
- * Cache used for storing html templates.
- *
+ * The first time a template is used, it is loaded in the template cache for quick retrieval. You
+ * can load templates directly into the cache in a `script` tag, or by consuming the
+ * `$templateCache` service directly.
+ *
+ * Adding via the `script` tag:
+ *
+ *
+ *
+ *
+ *
+ * ...
+ *
+ *
+ *
+ * **Note:** the `script` tag containing the template does not need to be included in the `head` of
+ * the document, but it must be below the `ng-app` definition.
+ *
+ * Adding via the $templateCache service:
+ *
+ *
+ * var myApp = angular.module('myApp', []);
+ * myApp.run(function($templateCache) {
+ * $templateCache.put('templateId.html', 'This is the content of the template');
+ * });
+ *
+ *
+ * To retrieve the template later, simply use it in your HTML:
+ *
+ *
+ *
+ *
+ * or get it via Javascript:
+ *
+ * $templateCache.get('templateId.html')
+ *
+ *
* See {@link ng.$cacheFactory $cacheFactory}.
*
*/
@@ -3428,9 +4629,6 @@ function $TemplateCacheProvider() {
*/
-var NON_ASSIGNABLE_MODEL_EXPRESSION = 'Non-assignable model expression: ';
-
-
/**
* @ngdoc function
* @name ng.$compile
@@ -3438,56 +4636,402 @@ var NON_ASSIGNABLE_MODEL_EXPRESSION = 'Non-assignable model expression: ';
*
* @description
* Compiles a piece of HTML string or DOM into a template and produces a template function, which
- * can then be used to link {@link ng.$rootScope.Scope scope} and the template together.
+ * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together.
*
- * The compilation is a process of walking the DOM tree and trying to match DOM elements to
- * {@link ng.$compileProvider#directive directives}. For each match it
- * executes corresponding template function and collects the
- * instance functions into a single template function which is then returned.
+ * The compilation is a process of walking the DOM tree and matching DOM elements to
+ * {@link ng.$compileProvider#methods_directive directives}.
*
- * The template function can then be used once to produce the view or as it is the case with
- * {@link ng.directive:ngRepeat repeater} many-times, in which
- * case each call results in a view that is a DOM clone of the original template.
+ *
+ * **Note:** This document is an in-depth reference of all directive options.
+ * For a gentle introduction to directives with examples of common use cases,
+ * see the {@link guide/directive directive guide}.
+ *
*
-
-
-
-
-
-
+ * ## Comprehensive Directive API
+ *
+ * There are many different options for a directive.
+ *
+ * The difference resides in the return value of the factory function.
+ * You can either return a "Directive Definition Object" (see below) that defines the directive properties,
+ * or just the `postLink` function (all other properties will have the default values).
+ *
+ *
+ * **Best Practice:** It's recommended to use the "directive definition object" form.
+ *
+ *
+ * Here's an example directive declared with a Directive Definition Object:
+ *
+ *
+ * var myModule = angular.module(...);
+ *
+ * myModule.directive('directiveName', function factory(injectables) {
+ * var directiveDefinitionObject = {
+ * priority: 0,
+ * template: '
', // or // function(tElement, tAttrs) { ... },
+ * // or
+ * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... },
+ * replace: false,
+ * transclude: false,
+ * restrict: 'A',
+ * scope: false,
+ * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
+ * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
+ * compile: function compile(tElement, tAttrs, transclude) {
+ * return {
+ * pre: function preLink(scope, iElement, iAttrs, controller) { ... },
+ * post: function postLink(scope, iElement, iAttrs, controller) { ... }
+ * }
+ * // or
+ * // return function postLink( ... ) { ... }
+ * },
+ * // or
+ * // link: {
+ * // pre: function preLink(scope, iElement, iAttrs, controller) { ... },
+ * // post: function postLink(scope, iElement, iAttrs, controller) { ... }
+ * // }
+ * // or
+ * // link: function postLink( ... ) { ... }
+ * };
+ * return directiveDefinitionObject;
+ * });
+ *
+ *
+ *
+ * **Note:** Any unspecified options will use the default value. You can see the default values below.
+ *
+ *
+ * Therefore the above can be simplified as:
+ *
+ *
+ * var myModule = angular.module(...);
+ *
+ * myModule.directive('directiveName', function factory(injectables) {
+ * var directiveDefinitionObject = {
+ * link: function postLink(scope, iElement, iAttrs) { ... }
+ * };
+ * return directiveDefinitionObject;
+ * // or
+ * // return function postLink(scope, iElement, iAttrs) { ... }
+ * });
+ *
+ *
+ *
+ *
+ * ### Directive Definition Object
+ *
+ * The directive definition object provides instructions to the {@link api/ng.$compile
+ * compiler}. The attributes are:
+ *
+ * #### `priority`
+ * When there are multiple directives defined on a single DOM element, sometimes it
+ * is necessary to specify the order in which the directives are applied. The `priority` is used
+ * to sort the directives before their `compile` functions get called. Priority is defined as a
+ * number. Directives with greater numerical `priority` are compiled first. Pre-link functions
+ * are also run in priority order, but post-link functions are run in reverse order. The order
+ * of directives with the same priority is undefined. The default priority is `0`.
+ *
+ * #### `terminal`
+ * If set to true then the current `priority` will be the last set of directives
+ * which will execute (any directives at the current priority will still execute
+ * as the order of execution on same `priority` is undefined).
+ *
+ * #### `scope`
+ * **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the
+ * same element request a new scope, only one new scope is created. The new scope rule does not
+ * apply for the root of the template since the root of the template always gets a new scope.
+ *
+ * **If set to `{}` (object hash),** then a new "isolate" scope is created. The 'isolate' scope differs from
+ * normal scope in that it does not prototypically inherit from the parent scope. This is useful
+ * when creating reusable components, which should not accidentally read or modify data in the
+ * parent scope.
+ *
+ * The 'isolate' scope takes an object hash which defines a set of local scope properties
+ * derived from the parent scope. These local properties are useful for aliasing values for
+ * templates. Locals definition is a hash of local scope property to its source:
+ *
+ * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
+ * always a string since DOM attributes are strings. If no `attr` name is specified then the
+ * attribute name is assumed to be the same as the local name.
+ * Given `
` and widget definition
+ * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect
+ * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the
+ * `localName` property on the widget scope. The `name` is read from the parent scope (not
+ * component scope).
+ *
+ * * `=` or `=attr` - set up bi-directional binding between a local scope property and the
+ * parent scope property of name defined via the value of the `attr` attribute. If no `attr`
+ * name is specified then the attribute name is assumed to be the same as the local name.
+ * Given `` and widget definition of
+ * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the
+ * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected
+ * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent
+ * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You
+ * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional.
+ *
+ * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope.
+ * If no `attr` name is specified then the attribute name is assumed to be the same as the
+ * local name. Given `` and widget definition of
+ * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to
+ * a function wrapper for the `count = count + value` expression. Often it's desirable to
+ * pass data from the isolated scope via an expression and to the parent scope, this can be
+ * done by passing a map of local variable names and values into the expression wrapper fn.
+ * For example, if the expression is `increment(amount)` then we can specify the amount value
+ * by calling the `localFn` as `localFn({amount: 22})`.
+ *
+ *
+ *
+ * #### `controller`
+ * Controller constructor function. The controller is instantiated before the
+ * pre-linking phase and it is shared with other directives (see
+ * `require` attribute). This allows the directives to communicate with each other and augment
+ * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals:
+ *
+ * * `$scope` - Current scope associated with the element
+ * * `$element` - Current element
+ * * `$attrs` - Current attributes object for the element
+ * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope.
+ * The scope can be overridden by an optional first argument.
+ * `function([scope], cloneLinkingFn)`.
+ *
+ *
+ * #### `require`
+ * Require another directive and inject its controller as the fourth argument to the linking function. The
+ * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the
+ * injected argument will be an array in corresponding order. If no such directive can be
+ * found, or if the directive does not have a controller, then an error is raised. The name can be prefixed with:
+ *
+ * * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
+ * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
+ * * `^` - Locate the required controller by searching the element's parents. Throw an error if not found.
+ * * `?^` - Attempt to locate the required controller by searching the element's parentsor pass `null` to the
+ * `link` fn if not found.
+ *
+ *
+ * #### `controllerAs`
+ * Controller alias at the directive scope. An alias for the controller so it
+ * can be referenced at the directive template. The directive needs to define a scope for this
+ * configuration to be used. Useful in the case when directive is used as component.
+ *
+ *
+ * #### `restrict`
+ * String of subset of `EACM` which restricts the directive to a specific directive
+ * declaration style. If omitted, the default (attributes only) is used.
+ *
+ * * `E` - Element name: ` `
+ * * `A` - Attribute (default): `
`
+ * * `C` - Class: `
`
+ * * `M` - Comment: ``
+ *
+ *
+ * #### `template`
+ * replace the current element with the contents of the HTML. The replacement process
+ * migrates all of the attributes / classes from the old element to the new one. See the
+ * {@link guide/directive#creating-custom-directives_creating-directives_template-expanding-directive
+ * Directives Guide} for an example.
+ *
+ * You can specify `template` as a string representing the template or as a function which takes
+ * two arguments `tElement` and `tAttrs` (described in the `compile` function api below) and
+ * returns a string value representing the template.
+ *
+ *
+ * #### `templateUrl`
+ * Same as `template` but the template is loaded from the specified URL. Because
+ * the template loading is asynchronous the compilation/linking is suspended until the template
+ * is loaded.
+ *
+ * You can specify `templateUrl` as a string representing the URL or as a function which takes two
+ * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns
+ * a string value representing the url. In either case, the template URL is passed through {@link
+ * api/ng.$sce#methods_getTrustedResourceUrl $sce.getTrustedResourceUrl}.
+ *
+ *
+ * #### `replace`
+ * specify where the template should be inserted. Defaults to `false`.
+ *
+ * * `true` - the template will replace the current element.
+ * * `false` - the template will replace the contents of the current element.
+ *
+ *
+ * #### `transclude`
+ * compile the content of the element and make it available to the directive.
+ * Typically used with {@link api/ng.directive:ngTransclude
+ * ngTransclude}. The advantage of transclusion is that the linking function receives a
+ * transclusion function which is pre-bound to the correct scope. In a typical setup the widget
+ * creates an `isolate` scope, but the transclusion is not a child, but a sibling of the `isolate`
+ * scope. This makes it possible for the widget to have private state, and the transclusion to
+ * be bound to the parent (pre-`isolate`) scope.
+ *
+ * * `true` - transclude the content of the directive.
+ * * `'element'` - transclude the whole element including any directives defined at lower priority.
+ *
+ *
+ * #### `compile`
+ *
+ *
+ * function compile(tElement, tAttrs, transclude) { ... }
+ *
+ *
+ * The compile function deals with transforming the template DOM. Since most directives do not do
+ * template transformation, it is not used often. Examples that require compile functions are
+ * directives that transform template DOM, such as {@link
+ * api/ng.directive:ngRepeat ngRepeat}, or load the contents
+ * asynchronously, such as {@link api/ngRoute.directive:ngView ngView}. The
+ * compile function takes the following arguments.
+ *
+ * * `tElement` - template element - The element where the directive has been declared. It is
+ * safe to do template transformation on the element and child elements only.
+ *
+ * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared
+ * between all directive compile functions.
+ *
+ * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)`
+ *
+ *
+ * **Note:** The template instance and the link instance may be different objects if the template has
+ * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that
+ * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
+ * should be done in a linking function rather than in a compile function.
+ *
+ *
+ *
+ * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it
+ * e.g. does not know about the right outer scope. Please use the transclude function that is passed
+ * to the link function instead.
+ *
+
+ * A compile function can have a return value which can be either a function or an object.
+ *
+ * * returning a (post-link) function - is equivalent to registering the linking function via the
+ * `link` property of the config object when the compile function is empty.
+ *
+ * * returning an object with function(s) registered via `pre` and `post` properties - allows you to
+ * control when a linking function should be called during the linking phase. See info about
+ * pre-linking and post-linking functions below.
+ *
+ *
+ * #### `link`
+ * This property is used only if the `compile` property is not defined.
+ *
+ *
+ * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }
+ *
+ *
+ * The link function is responsible for registering DOM listeners as well as updating the DOM. It is
+ * executed after the template has been cloned. This is where most of the directive logic will be
+ * put.
+ *
+ * * `scope` - {@link api/ng.$rootScope.Scope Scope} - The scope to be used by the
+ * directive for registering {@link api/ng.$rootScope.Scope#methods_$watch watches}.
+ *
+ * * `iElement` - instance element - The element where the directive is to be used. It is safe to
+ * manipulate the children of the element only in `postLink` function since the children have
+ * already been linked.
+ *
+ * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared
+ * between all directive linking functions.
+ *
+ * * `controller` - a controller instance - A controller instance if at least one directive on the
+ * element defines a controller. The controller is shared among all the directives, which allows
+ * the directives to use the controllers as a communication channel.
+ *
+ * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
+ * The scope can be overridden by an optional first argument. This is the same as the `$transclude`
+ * parameter of directive controllers.
+ * `function([scope], cloneLinkingFn)`.
+ *
+ *
+ * #### Pre-linking function
+ *
+ * Executed before the child elements are linked. Not safe to do DOM transformation since the
+ * compiler linking function will fail to locate the correct elements for linking.
+ *
+ * #### Post-linking function
+ *
+ * Executed after the child elements are linked. It is safe to do DOM transformation in the post-linking function.
+ *
+ *
+ * ### Attributes
+ *
+ * The {@link api/ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the
+ * `link()` or `compile()` functions. It has a variety of uses.
+ *
+ * accessing *Normalized attribute names:*
+ * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'.
+ * the attributes object allows for normalized access to
+ * the attributes.
+ *
+ * * *Directive inter-communication:* All directives share the same instance of the attributes
+ * object which allows the directives to use the attributes object as inter directive
+ * communication.
+ *
+ * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object
+ * allowing other directives to read the interpolated value.
+ *
+ * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes
+ * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also
+ * the only way to easily get the actual value because during the linking phase the interpolation
+ * hasn't been evaluated yet and so the value is at this time set to `undefined`.
+ *
+ *
+ * function linkingFn(scope, elm, attrs, ctrl) {
+ * // get the attribute value
+ * console.log(attrs.ngModel);
+ *
+ * // change the attribute
+ * attrs.$set('ngModel', 'new value');
+ *
+ * // observe changes to interpolated attribute
+ * attrs.$observe('ngModel', function(value) {
+ * console.log('ngModel has changed value to ' + value);
+ * });
+ * }
+ *
+ *
+ * Below is an example using `$compileProvider`.
+ *
+ *
+ * **Note**: Typically directives are registered with `module.directive`. The example below is
+ * to illustrate how `$compile` works.
+ *
+ *
+
+
+
+
@@ -3511,15 +5055,15 @@ var NON_ASSIGNABLE_MODEL_EXPRESSION = 'Non-assignable model expression: ';
*
* * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
* * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
- * `template` and call the `cloneAttachFn` function allowing the caller to attach the
- * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
- * called as: `cloneAttachFn(clonedElement, scope)` where:
+ * `template` and call the `cloneAttachFn` function allowing the caller to attach the
+ * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
+ * called as: `cloneAttachFn(clonedElement, scope)` where:
*
* * `clonedElement` - is a clone of the original `element` passed into the compiler.
* * `scope` - is the current scope with which the linking function is working with.
*
- * Calling the linking function returns the element of the template. It is either the original element
- * passed in, or the clone of the element if the `cloneAttachFn` is provided.
+ * Calling the linking function returns the element of the template. It is either the original
+ * element passed in, or the clone of the element if the `cloneAttachFn` is provided.
*
* After linking the view is not updated until after a call to $digest which typically is done by
* Angular automatically.
@@ -3551,6 +5095,7 @@ var NON_ASSIGNABLE_MODEL_EXPRESSION = 'Non-assignable model expression: ';
* {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
*/
+var $compileMinErr = minErr('$compile');
/**
* @ngdoc service
@@ -3559,14 +5104,17 @@ var NON_ASSIGNABLE_MODEL_EXPRESSION = 'Non-assignable model expression: ';
*
* @description
*/
-$CompileProvider.$inject = ['$provide'];
-function $CompileProvider($provide) {
+$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider'];
+function $CompileProvider($provide, $$sanitizeUriProvider) {
var hasDirectives = {},
Suffix = 'Directive',
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
- CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
- MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: ';
+ CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/;
+ // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
+ // The assumption is that future DOM event attribute names will begin with
+ // 'on' and be composed of only English letters.
+ var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
/**
* @ngdoc function
@@ -3575,23 +5123,25 @@ function $CompileProvider($provide) {
* @function
*
* @description
- * Register a new directives with the compiler.
+ * Register a new directive with the compiler.
*
- * @param {string} name Name of the directive in camel-case. (ie ngBind which will match as
- * ng-bind).
- * @param {function} directiveFactory An injectable directive factroy function. See {@link guide/directive} for more
- * info.
+ * @param {string|Object} name Name of the directive in camel-case (i.e. ngBind which
+ * will match as ng-bind), or an object map of directives where the keys are the
+ * names and the values are the factories.
+ * @param {function|Array} directiveFactory An injectable directive factory function. See
+ * {@link guide/directive} for more info.
* @returns {ng.$compileProvider} Self for chaining.
*/
this.directive = function registerDirective(name, directiveFactory) {
+ assertNotHasOwnProperty(name, 'directive');
if (isString(name)) {
- assertArg(directiveFactory, 'directive');
+ assertArg(directiveFactory, 'directiveFactory');
if (!hasDirectives.hasOwnProperty(name)) {
hasDirectives[name] = [];
$provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
function($injector, $exceptionHandler) {
var directives = [];
- forEach(hasDirectives[name], function(directiveFactory) {
+ forEach(hasDirectives[name], function(directiveFactory, index) {
try {
var directive = $injector.invoke(directiveFactory);
if (isFunction(directive)) {
@@ -3600,6 +5150,7 @@ function $CompileProvider($provide) {
directive.compile = valueFn(directive.link);
}
directive.priority = directive.priority || 0;
+ directive.index = index;
directive.name = directive.name || name;
directive.require = directive.require || (directive.controller && directive.name);
directive.restrict = directive.restrict || 'A';
@@ -3619,11 +5170,72 @@ function $CompileProvider($provide) {
};
+ /**
+ * @ngdoc function
+ * @name ng.$compileProvider#aHrefSanitizationWhitelist
+ * @methodOf ng.$compileProvider
+ * @function
+ *
+ * @description
+ * Retrieves or overrides the default regular expression that is used for whitelisting of safe
+ * urls during a[href] sanitization.
+ *
+ * The sanitization is a security measure aimed at prevent XSS attacks via html links.
+ *
+ * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
+ * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
+ * regular expression. If a match is found, the original url is written into the dom. Otherwise,
+ * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
+ *
+ * @param {RegExp=} regexp New regexp to whitelist urls with.
+ * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
+ * chaining otherwise.
+ */
+ this.aHrefSanitizationWhitelist = function(regexp) {
+ if (isDefined(regexp)) {
+ $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp);
+ return this;
+ } else {
+ return $$sanitizeUriProvider.aHrefSanitizationWhitelist();
+ }
+ };
+
+
+ /**
+ * @ngdoc function
+ * @name ng.$compileProvider#imgSrcSanitizationWhitelist
+ * @methodOf ng.$compileProvider
+ * @function
+ *
+ * @description
+ * Retrieves or overrides the default regular expression that is used for whitelisting of safe
+ * urls during img[src] sanitization.
+ *
+ * The sanitization is a security measure aimed at prevent XSS attacks via html links.
+ *
+ * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
+ * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
+ * regular expression. If a match is found, the original url is written into the dom. Otherwise,
+ * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
+ *
+ * @param {RegExp=} regexp New regexp to whitelist urls with.
+ * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
+ * chaining otherwise.
+ */
+ this.imgSrcSanitizationWhitelist = function(regexp) {
+ if (isDefined(regexp)) {
+ $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp);
+ return this;
+ } else {
+ return $$sanitizeUriProvider.imgSrcSanitizationWhitelist();
+ }
+ };
+
this.$get = [
'$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
- '$controller', '$rootScope',
+ '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri',
function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
- $controller, $rootScope) {
+ $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) {
var Attributes = function(element, attr) {
this.$$element = element;
@@ -3634,6 +5246,60 @@ function $CompileProvider($provide) {
$normalize: directiveNormalize,
+ /**
+ * @ngdoc function
+ * @name ng.$compile.directive.Attributes#$addClass
+ * @methodOf ng.$compile.directive.Attributes
+ * @function
+ *
+ * @description
+ * Adds the CSS class value specified by the classVal parameter to the element. If animations
+ * are enabled then an animation will be triggered for the class addition.
+ *
+ * @param {string} classVal The className value that will be added to the element
+ */
+ $addClass : function(classVal) {
+ if(classVal && classVal.length > 0) {
+ $animate.addClass(this.$$element, classVal);
+ }
+ },
+
+ /**
+ * @ngdoc function
+ * @name ng.$compile.directive.Attributes#$removeClass
+ * @methodOf ng.$compile.directive.Attributes
+ * @function
+ *
+ * @description
+ * Removes the CSS class value specified by the classVal parameter from the element. If
+ * animations are enabled then an animation will be triggered for the class removal.
+ *
+ * @param {string} classVal The className value that will be removed from the element
+ */
+ $removeClass : function(classVal) {
+ if(classVal && classVal.length > 0) {
+ $animate.removeClass(this.$$element, classVal);
+ }
+ },
+
+ /**
+ * @ngdoc function
+ * @name ng.$compile.directive.Attributes#$updateClass
+ * @methodOf ng.$compile.directive.Attributes
+ * @function
+ *
+ * @description
+ * Adds and removes the appropriate CSS class values to the element based on the difference
+ * between the new and old CSS class values (specified as newClasses and oldClasses).
+ *
+ * @param {string} newClasses The current CSS className value
+ * @param {string} oldClasses The former CSS className value
+ */
+ $updateClass : function(newClasses, oldClasses) {
+ this.$removeClass(tokenDifference(oldClasses, newClasses));
+ this.$addClass(tokenDifference(newClasses, oldClasses));
+ },
+
/**
* Set a normalized attribute on the element in a way such that all directives
* can share the attribute. This function properly handles boolean attributes.
@@ -3644,8 +5310,13 @@ function $CompileProvider($provide) {
* @param {string=} attrName Optional none normalized name. Defaults to key.
*/
$set: function(key, value, writeAttr, attrName) {
+ // TODO: decide whether or not to throw an error if "class"
+ //is set through this function since it may cause $updateClass to
+ //become unstable.
+
var booleanKey = getBooleanAttrName(this.$$element[0], key),
- $$observers = this.$$observers;
+ normalizedVal,
+ nodeName;
if (booleanKey) {
this.$$element.prop(key, value);
@@ -3664,6 +5335,14 @@ function $CompileProvider($provide) {
}
}
+ nodeName = nodeName_(this.$$element);
+
+ // sanitize a[href] and img[src] values
+ if ((nodeName === 'A' && key === 'href') ||
+ (nodeName === 'IMG' && key === 'src')) {
+ this[key] = value = $$sanitizeUri(value, key === 'src');
+ }
+
if (writeAttr !== false) {
if (value === null || value === undefined) {
this.$$element.removeAttr(attrName);
@@ -3673,6 +5352,7 @@ function $CompileProvider($provide) {
}
// fire observers
+ var $$observers = this.$$observers;
$$observers && forEach($$observers[key], function(fn) {
try {
fn(value);
@@ -3684,12 +5364,23 @@ function $CompileProvider($provide) {
/**
- * Observe an interpolated attribute.
- * The observer will never be called, if given attribute is not interpolated.
+ * @ngdoc function
+ * @name ng.$compile.directive.Attributes#$observe
+ * @methodOf ng.$compile.directive.Attributes
+ * @function
+ *
+ * @description
+ * Observes an interpolated attribute.
+ *
+ * The observer function will be invoked once during the next `$digest` following
+ * compilation. The observer is then invoked whenever the interpolated value
+ * changes.
*
* @param {string} key Normalized key. (ie ngAttribute) .
- * @param {function(*)} fn Function that will be called whenever the attribute value changes.
- * @returns {function(*)} the `fn` Function passed in.
+ * @param {function(interpolatedValue)} fn Function that will be called whenever
+ the interpolated value of the attribute changes.
+ * See the {@link guide/directive#Attributes Directives} guide for more info.
+ * @returns {function()} the `fn` parameter.
*/
$observe: function(key, fn) {
var attrs = this,
@@ -3713,34 +5404,50 @@ function $CompileProvider($provide) {
? identity
: function denormalizeTemplate(template) {
return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
- };
+ },
+ NG_ATTR_BINDING = /^ngAttr[A-Z]/;
return compile;
//================================
- function compile($compileNode, transcludeFn, maxPriority) {
- if (!($compileNode instanceof jqLite)) {
- // jquery always rewraps, where as we need to preserve the original selector so that we can modify it.
- $compileNode = jqLite($compileNode);
+ function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective,
+ previousCompileContext) {
+ if (!($compileNodes instanceof jqLite)) {
+ // jquery always rewraps, whereas we need to preserve the original selector so that we can
+ // modify it.
+ $compileNodes = jqLite($compileNodes);
}
// We can not compile top level text elements since text nodes can be merged and we will
// not be able to attach scope data to them, so we will wrap them in
- forEach($compileNode, function(node, index){
- if (node.nodeType == 3 /* text node */) {
- $compileNode[index] = jqLite(node).wrap(' ').parent()[0];
+ forEach($compileNodes, function(node, index){
+ if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) {
+ $compileNodes[index] = node = jqLite(node).wrap(' ').parent()[0];
}
});
- var compositeLinkFn = compileNodes($compileNode, transcludeFn, $compileNode, maxPriority);
- return function(scope, cloneConnectFn){
+ var compositeLinkFn =
+ compileNodes($compileNodes, transcludeFn, $compileNodes,
+ maxPriority, ignoreDirective, previousCompileContext);
+ return function publicLinkFn(scope, cloneConnectFn, transcludeControllers){
assertArg(scope, 'scope');
// important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
// and sometimes changes the structure of the DOM.
var $linkNode = cloneConnectFn
- ? JQLitePrototype.clone.call($compileNode) // IMPORTANT!!!
- : $compileNode;
- $linkNode.data('$scope', scope);
+ ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!!
+ : $compileNodes;
+
+ forEach(transcludeControllers, function(instance, name) {
+ $linkNode.data('$' + name + 'Controller', instance);
+ });
+
+ // Attach scope only to non-text nodes.
+ for(var i = 0, ii = $linkNode.length; i
addDirective(directives,
- directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority);
+ directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective);
// iterate over the attributes
- for (var attr, name, nName, value, nAttrs = node.attributes,
+ for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes,
j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
+ var attrStartName = false;
+ var attrEndName = false;
+
attr = nAttrs[j];
- if (attr.specified) {
+ if (!msie || msie >= 8 || attr.specified) {
name = attr.name;
+ // support ngAttr attribute binding
+ ngAttrName = directiveNormalize(name);
+ if (NG_ATTR_BINDING.test(ngAttrName)) {
+ name = snake_case(ngAttrName.substr(6), '-');
+ }
+
+ var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
+ if (ngAttrName === directiveNName + 'Start') {
+ attrStartName = name;
+ attrEndName = name.substr(0, name.length - 5) + 'end';
+ name = name.substr(0, name.length - 6);
+ }
+
nName = directiveNormalize(name.toLowerCase());
attrsMap[nName] = name;
attrs[nName] = value = trim((msie && name == 'href')
@@ -3877,16 +5622,17 @@ function $CompileProvider($provide) {
attrs[nName] = true; // presence means true
}
addAttrInterpolateDirective(node, directives, value, nName);
- addDirective(directives, nName, 'A', maxPriority);
+ addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
+ attrEndName);
}
}
// use class as directive
className = node.className;
- if (isString(className)) {
+ if (isString(className) && className !== '') {
while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
nName = directiveNormalize(match[2]);
- if (addDirective(directives, nName, 'C', maxPriority)) {
+ if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) {
attrs[nName] = trim(match[3]);
}
className = className.substr(match.index + match[0].length);
@@ -3901,12 +5647,13 @@ function $CompileProvider($provide) {
match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
if (match) {
nName = directiveNormalize(match[1]);
- if (addDirective(directives, nName, 'M', maxPriority)) {
+ if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) {
attrs[nName] = trim(match[2]);
}
}
} catch (e) {
- // turns out that under some circumstances IE9 throws errors when one attempts to read comment's node value.
+ // turns out that under some circumstances IE9 throws errors when one attempts to read
+ // comment's node value.
// Just ignore it and continue. (Can't seem to reproduce in test case.)
}
break;
@@ -3916,61 +5663,133 @@ function $CompileProvider($provide) {
return directives;
}
+ /**
+ * Given a node with an directive-start it collects all of the siblings until it finds
+ * directive-end.
+ * @param node
+ * @param attrStart
+ * @param attrEnd
+ * @returns {*}
+ */
+ function groupScan(node, attrStart, attrEnd) {
+ var nodes = [];
+ var depth = 0;
+ if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) {
+ var startNode = node;
+ do {
+ if (!node) {
+ throw $compileMinErr('uterdir',
+ "Unterminated attribute, found '{0}' but no matching '{1}' found.",
+ attrStart, attrEnd);
+ }
+ if (node.nodeType == 1 /** Element **/) {
+ if (node.hasAttribute(attrStart)) depth++;
+ if (node.hasAttribute(attrEnd)) depth--;
+ }
+ nodes.push(node);
+ node = node.nextSibling;
+ } while (depth > 0);
+ } else {
+ nodes.push(node);
+ }
+
+ return jqLite(nodes);
+ }
+
+ /**
+ * Wrapper for linking function which converts normal linking function into a grouped
+ * linking function.
+ * @param linkFn
+ * @param attrStart
+ * @param attrEnd
+ * @returns {Function}
+ */
+ function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) {
+ return function(scope, element, attrs, controllers, transcludeFn) {
+ element = groupScan(element[0], attrStart, attrEnd);
+ return linkFn(scope, element, attrs, controllers, transcludeFn);
+ };
+ }
/**
- * Once the directives have been collected their compile functions is executed. This method
+ * Once the directives have been collected, their compile functions are executed. This method
* is responsible for inlining directive templates as well as terminating the application
- * of the directives if the terminal directive has been reached..
+ * of the directives if the terminal directive has been reached.
*
* @param {Array} directives Array of collected directives to execute their compile function.
* this needs to be pre-sorted by priority order.
* @param {Node} compileNode The raw DOM node to apply the compile functions to
* @param {Object} templateAttrs The shared attribute function
* @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the
- * scope argument is auto-generated to the new child of the transcluded parent scope.
- * @param {DOMElement} $rootElement If we are working on the root of the compile tree then this
- * argument has the root jqLite array so that we can replace widgets on it.
+ * scope argument is auto-generated to the new
+ * child of the transcluded parent scope.
+ * @param {JQLite} jqCollection If we are working on the root of the compile tree then this
+ * argument has the root jqLite array so that we can replace nodes
+ * on it.
+ * @param {Object=} originalReplaceDirective An optional directive that will be ignored when
+ * compiling the transclusion.
+ * @param {Array.} preLinkFns
+ * @param {Array.} postLinkFns
+ * @param {Object} previousCompileContext Context used for previous compilation of the current
+ * node
* @returns linkFn
*/
- function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, $rootElement) {
+ function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn,
+ jqCollection, originalReplaceDirective, preLinkFns, postLinkFns,
+ previousCompileContext) {
+ previousCompileContext = previousCompileContext || {};
+
var terminalPriority = -Number.MAX_VALUE,
- preLinkFns = [],
- postLinkFns = [],
- newScopeDirective = null,
- newIsolatedScopeDirective = null,
- templateDirective = null,
+ newScopeDirective,
+ controllerDirectives = previousCompileContext.controllerDirectives,
+ newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective,
+ templateDirective = previousCompileContext.templateDirective,
+ nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
+ hasTranscludeDirective = false,
+ hasElementTranscludeDirective = false,
$compileNode = templateAttrs.$$element = jqLite(compileNode),
directive,
directiveName,
$template,
- transcludeDirective,
+ replaceDirective = originalReplaceDirective,
childTranscludeFn = transcludeFn,
- controllerDirectives,
linkFn,
directiveValue;
// executes all directives on the current element
for(var i = 0, ii = directives.length; i < ii; i++) {
directive = directives[i];
- $template = undefined;
+ var attrStart = directive.$$start;
+ var attrEnd = directive.$$end;
+
+ // collect multiblock sections
+ if (attrStart) {
+ $compileNode = groupScan(compileNode, attrStart, attrEnd);
+ }
+ $template = undefined;
if (terminalPriority > directive.priority) {
break; // prevent further processing of directives
}
if (directiveValue = directive.scope) {
- assertNoDuplicate('isolated scope', newIsolatedScopeDirective, directive, $compileNode);
- if (isObject(directiveValue)) {
- safeAddClass($compileNode, 'ng-isolate-scope');
- newIsolatedScopeDirective = directive;
- }
- safeAddClass($compileNode, 'ng-scope');
newScopeDirective = newScopeDirective || directive;
+
+ // skip the check for directives with async templates, we'll check the derived sync
+ // directive when the template arrives
+ if (!directive.templateUrl) {
+ assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive,
+ $compileNode);
+ if (isObject(directiveValue)) {
+ newIsolateScopeDirective = directive;
+ }
+ }
}
directiveName = directive.name;
- if (directiveValue = directive.controller) {
+ if (!directive.templateUrl && directive.controller) {
+ directiveValue = directive.controller;
controllerDirectives = controllerDirectives || {};
assertNoDuplicate("'" + directiveName + "' controller",
controllerDirectives[directiveName], directive, $compileNode);
@@ -3978,54 +5797,83 @@ function $CompileProvider($provide) {
}
if (directiveValue = directive.transclude) {
- assertNoDuplicate('transclusion', transcludeDirective, directive, $compileNode);
- transcludeDirective = directive;
- terminalPriority = directive.priority;
+ hasTranscludeDirective = true;
+
+ // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion.
+ // This option should only be used by directives that know how to how to safely handle element transclusion,
+ // where the transcluded nodes are added or replaced after linking.
+ if (!directive.$$tlb) {
+ assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode);
+ nonTlbTranscludeDirective = directive;
+ }
+
if (directiveValue == 'element') {
- $template = jqLite(compileNode);
+ hasElementTranscludeDirective = true;
+ terminalPriority = directive.priority;
+ $template = groupScan(compileNode, attrStart, attrEnd);
$compileNode = templateAttrs.$$element =
- jqLite('');
+ jqLite(document.createComment(' ' + directiveName + ': ' +
+ templateAttrs[directiveName] + ' '));
compileNode = $compileNode[0];
- replaceWith($rootElement, jqLite($template[0]), compileNode);
- childTranscludeFn = compile($template, transcludeFn, terminalPriority);
+ replaceWith(jqCollection, jqLite(sliceArgs($template)), compileNode);
+
+ childTranscludeFn = compile($template, transcludeFn, terminalPriority,
+ replaceDirective && replaceDirective.name, {
+ // Don't pass in:
+ // - controllerDirectives - otherwise we'll create duplicates controllers
+ // - newIsolateScopeDirective or templateDirective - combining templates with
+ // element transclusion doesn't make sense.
+ //
+ // We need only nonTlbTranscludeDirective so that we prevent putting transclusion
+ // on the same element more than once.
+ nonTlbTranscludeDirective: nonTlbTranscludeDirective
+ });
} else {
- $template = jqLite(JQLiteClone(compileNode)).contents();
+ $template = jqLite(jqLiteClone(compileNode)).contents();
$compileNode.html(''); // clear contents
childTranscludeFn = compile($template, transcludeFn);
}
}
- if ((directiveValue = directive.template)) {
+ if (directive.template) {
assertNoDuplicate('template', templateDirective, directive, $compileNode);
templateDirective = directive;
+
+ directiveValue = (isFunction(directive.template))
+ ? directive.template($compileNode, templateAttrs)
+ : directive.template;
+
directiveValue = denormalizeTemplate(directiveValue);
if (directive.replace) {
+ replaceDirective = directive;
$template = jqLite('' +
trim(directiveValue) +
'
').contents();
compileNode = $template[0];
if ($template.length != 1 || compileNode.nodeType !== 1) {
- throw new Error(MULTI_ROOT_TEMPLATE_ERROR + directiveValue);
+ throw $compileMinErr('tplrt',
+ "Template for directive '{0}' must have exactly one root element. {1}",
+ directiveName, '');
}
- replaceWith($rootElement, $compileNode, compileNode);
+ replaceWith(jqCollection, $compileNode, compileNode);
var newTemplateAttrs = {$attr: {}};
// combine directives from the original node and from the template:
// - take the array of directives for this element
- // - split it into two parts, those that were already applied and those that weren't
- // - collect directives from the template, add them to the second group and sort them
- // - append the second group with new directives to the first group
- directives = directives.concat(
- collectDirectives(
- compileNode,
- directives.splice(i + 1, directives.length - (i + 1)),
- newTemplateAttrs
- )
- );
+ // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed)
+ // - collect directives from the template and sort them by priority
+ // - combine directives as: processed + template + unprocessed
+ var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs);
+ var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1));
+
+ if (newIsolateScopeDirective) {
+ markDirectivesAsIsolate(templateDirectives);
+ }
+ directives = directives.concat(templateDirectives).concat(unprocessedDirectives);
mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
ii = directives.length;
@@ -4037,17 +5885,26 @@ function $CompileProvider($provide) {
if (directive.templateUrl) {
assertNoDuplicate('template', templateDirective, directive, $compileNode);
templateDirective = directive;
- nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i),
- nodeLinkFn, $compileNode, templateAttrs, $rootElement, directive.replace,
- childTranscludeFn);
+
+ if (directive.replace) {
+ replaceDirective = directive;
+ }
+
+ nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
+ templateAttrs, jqCollection, childTranscludeFn, preLinkFns, postLinkFns, {
+ controllerDirectives: controllerDirectives,
+ newIsolateScopeDirective: newIsolateScopeDirective,
+ templateDirective: templateDirective,
+ nonTlbTranscludeDirective: nonTlbTranscludeDirective
+ });
ii = directives.length;
} else if (directive.compile) {
try {
linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
if (isFunction(linkFn)) {
- addLinkFns(null, linkFn);
+ addLinkFns(null, linkFn, attrStart, attrEnd);
} else if (linkFn) {
- addLinkFns(linkFn.pre, linkFn.post);
+ addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd);
}
} catch (e) {
$exceptionHandler(e, startingTag($compileNode));
@@ -4061,27 +5918,35 @@ function $CompileProvider($provide) {
}
- nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope;
- nodeLinkFn.transclude = transcludeDirective && childTranscludeFn;
+ nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
+ nodeLinkFn.transclude = hasTranscludeDirective && childTranscludeFn;
// might be normal or delayed nodeLinkFn depending on if templateUrl is present
return nodeLinkFn;
////////////////////
- function addLinkFns(pre, post) {
+ function addLinkFns(pre, post, attrStart, attrEnd) {
if (pre) {
+ if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd);
pre.require = directive.require;
+ if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
+ pre = cloneAndAnnotateFn(pre, {isolateScope: true});
+ }
preLinkFns.push(pre);
}
if (post) {
+ if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd);
post.require = directive.require;
+ if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
+ post = cloneAndAnnotateFn(post, {isolateScope: true});
+ }
postLinkFns.push(post);
}
}
- function getControllers(require, $element) {
+ function getControllers(require, $element, elementControllers) {
var value, retrievalMethod = 'data', optional = false;
if (isString(require)) {
while((value = require.charAt(0)) == '^' || value == '?') {
@@ -4091,15 +5956,23 @@ function $CompileProvider($provide) {
}
optional = optional || value == '?';
}
- value = $element[retrievalMethod]('$' + require + 'Controller');
+ value = null;
+
+ if (elementControllers && retrievalMethod === 'data') {
+ value = elementControllers[require];
+ }
+ value = value || $element[retrievalMethod]('$' + require + 'Controller');
+
if (!value && !optional) {
- throw Error("No controller: " + require);
+ throw $compileMinErr('ctreq',
+ "Controller '{0}', required by directive '{1}', can't be found!",
+ require, directiveName);
}
return value;
} else if (isArray(require)) {
value = [];
forEach(require, function(require) {
- value.push(getControllers(require, $element));
+ value.push(getControllers(require, $element, elementControllers));
});
}
return value;
@@ -4107,7 +5980,7 @@ function $CompileProvider($provide) {
function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
- var attrs, $element, i, ii, linkFn, controller;
+ var attrs, $element, i, ii, linkFn, controller, isolateScope, elementControllers = {}, transcludeFn;
if (compileNode === linkNode) {
attrs = templateAttrs;
@@ -4116,88 +5989,120 @@ function $CompileProvider($provide) {
}
$element = attrs.$$element;
- if (newScopeDirective && isObject(newScopeDirective.scope)) {
- var LOCAL_REGEXP = /^\s*([@=&])\s*(\w*)\s*$/;
+ if (newIsolateScopeDirective) {
+ var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/;
+ var $linkNode = jqLite(linkNode);
- var parentScope = scope.$parent || scope;
+ isolateScope = scope.$new(true);
- forEach(newScopeDirective.scope, function(definiton, scopeName) {
- var match = definiton.match(LOCAL_REGEXP) || [],
- attrName = match[2]|| scopeName,
+ if (templateDirective && (templateDirective === newIsolateScopeDirective.$$originalDirective)) {
+ $linkNode.data('$isolateScope', isolateScope) ;
+ } else {
+ $linkNode.data('$isolateScopeNoTemplate', isolateScope);
+ }
+
+
+
+ safeAddClass($linkNode, 'ng-isolate-scope');
+
+ forEach(newIsolateScopeDirective.scope, function(definition, scopeName) {
+ var match = definition.match(LOCAL_REGEXP) || [],
+ attrName = match[3] || scopeName,
+ optional = (match[2] == '?'),
mode = match[1], // @, =, or &
lastValue,
parentGet, parentSet;
+ isolateScope.$$isolateBindings[scopeName] = mode + attrName;
+
switch (mode) {
- case '@': {
+ case '@':
attrs.$observe(attrName, function(value) {
- scope[scopeName] = value;
+ isolateScope[scopeName] = value;
});
- attrs.$$observers[attrName].$$scope = parentScope;
+ attrs.$$observers[attrName].$$scope = scope;
+ if( attrs[attrName] ) {
+ // If the attribute has been provided then we trigger an interpolation to ensure
+ // the value is there for use in the link fn
+ isolateScope[scopeName] = $interpolate(attrs[attrName])(scope);
+ }
break;
- }
- case '=': {
+ case '=':
+ if (optional && !attrs[attrName]) {
+ return;
+ }
parentGet = $parse(attrs[attrName]);
parentSet = parentGet.assign || function() {
// reset the change, or we will throw this exception on every $digest
- lastValue = scope[scopeName] = parentGet(parentScope);
- throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + attrs[attrName] +
- ' (directive: ' + newScopeDirective.name + ')');
+ lastValue = isolateScope[scopeName] = parentGet(scope);
+ throw $compileMinErr('nonassign',
+ "Expression '{0}' used with directive '{1}' is non-assignable!",
+ attrs[attrName], newIsolateScopeDirective.name);
};
- lastValue = scope[scopeName] = parentGet(parentScope);
- scope.$watch(function() {
- var parentValue = parentGet(parentScope);
+ lastValue = isolateScope[scopeName] = parentGet(scope);
+ isolateScope.$watch(function parentValueWatch() {
+ var parentValue = parentGet(scope);
- if (parentValue !== scope[scopeName]) {
+ if (parentValue !== isolateScope[scopeName]) {
// we are out of sync and need to copy
if (parentValue !== lastValue) {
// parent changed and it has precedence
- lastValue = scope[scopeName] = parentValue;
+ lastValue = isolateScope[scopeName] = parentValue;
} else {
// if the parent can be assigned then do so
- parentSet(parentScope, lastValue = scope[scopeName]);
+ parentSet(scope, parentValue = lastValue = isolateScope[scopeName]);
}
}
return parentValue;
});
break;
- }
- case '&': {
+ case '&':
parentGet = $parse(attrs[attrName]);
- scope[scopeName] = function(locals) {
- return parentGet(parentScope, locals);
- }
+ isolateScope[scopeName] = function(locals) {
+ return parentGet(scope, locals);
+ };
break;
- }
- default: {
- throw Error('Invalid isolate scope definition for directive ' +
- newScopeDirective.name + ': ' + definiton);
- }
+ default:
+ throw $compileMinErr('iscp',
+ "Invalid isolate scope definition for directive '{0}'." +
+ " Definition: {... {1}: '{2}' ...}",
+ newIsolateScopeDirective.name, scopeName, definition);
}
});
}
-
+ transcludeFn = boundTranscludeFn && controllersBoundTransclude;
if (controllerDirectives) {
forEach(controllerDirectives, function(directive) {
var locals = {
- $scope: scope,
+ $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
$element: $element,
$attrs: attrs,
- $transclude: boundTranscludeFn
- };
+ $transclude: transcludeFn
+ }, controllerInstance;
controller = directive.controller;
if (controller == '@') {
controller = attrs[directive.name];
}
- $element.data(
- '$' + directive.name + 'Controller',
- $controller(controller, locals));
+ controllerInstance = $controller(controller, locals);
+ // For directives with element transclusion the element is a comment,
+ // but jQuery .data doesn't support attaching data to comment nodes as it's hard to
+ // clean up (http://bugs.jquery.com/ticket/8335).
+ // Instead, we save the controllers for the element in a local hash and attach to .data
+ // later, once we have the actual element.
+ elementControllers[directive.name] = controllerInstance;
+ if (!hasElementTranscludeDirective) {
+ $element.data('$' + directive.name + 'Controller', controllerInstance);
+ }
+
+ if (directive.controllerAs) {
+ locals.$scope[directive.controllerAs] = controllerInstance;
+ }
});
}
@@ -4205,29 +6110,58 @@ function $CompileProvider($provide) {
for(i = 0, ii = preLinkFns.length; i < ii; i++) {
try {
linkFn = preLinkFns[i];
- linkFn(scope, $element, attrs,
- linkFn.require && getControllers(linkFn.require, $element));
+ linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs,
+ linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn);
} catch (e) {
$exceptionHandler(e, startingTag($element));
}
}
// RECURSION
- childLinkFn && childLinkFn(scope, linkNode.childNodes, undefined, boundTranscludeFn);
+ // We only pass the isolate scope, if the isolate directive has a template,
+ // otherwise the child elements do not belong to the isolate directive.
+ var scopeToChild = scope;
+ if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) {
+ scopeToChild = isolateScope;
+ }
+ childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);
// POSTLINKING
- for(i = 0, ii = postLinkFns.length; i < ii; i++) {
+ for(i = postLinkFns.length - 1; i >= 0; i--) {
try {
linkFn = postLinkFns[i];
- linkFn(scope, $element, attrs,
- linkFn.require && getControllers(linkFn.require, $element));
+ linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs,
+ linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn);
} catch (e) {
$exceptionHandler(e, startingTag($element));
}
}
+
+ // This is the function that is injected as `$transclude`.
+ function controllersBoundTransclude(scope, cloneAttachFn) {
+ var transcludeControllers;
+
+ // no scope passed
+ if (arguments.length < 2) {
+ cloneAttachFn = scope;
+ scope = undefined;
+ }
+
+ if (hasElementTranscludeDirective) {
+ transcludeControllers = elementControllers;
+ }
+
+ return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers);
+ }
}
}
+ function markDirectivesAsIsolate(directives) {
+ // mark all directives as needing isolate scope.
+ for (var j = 0, jj = directives.length; j < jj; j++) {
+ directives[j] = inherit(directives[j], {$$isolateScope: true});
+ }
+ }
/**
* looks up the directive and decorates it with exception handling and proper parameters. We
@@ -4243,8 +6177,10 @@ function $CompileProvider($provide) {
* * `M`: comment
* @returns true if directive was added.
*/
- function addDirective(tDirectives, name, location, maxPriority) {
- var match = false;
+ function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName,
+ endAttrName) {
+ if (name === ignoreDirective) return null;
+ var match = null;
if (hasDirectives.hasOwnProperty(name)) {
for(var directive, directives = $injector.get(name + Suffix),
i = 0, ii = directives.length; i directive.priority) &&
directive.restrict.indexOf(location) != -1) {
+ if (startAttrName) {
+ directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
+ }
tDirectives.push(directive);
- match = true;
+ match = directive;
}
} catch(e) { $exceptionHandler(e); }
}
@@ -4292,6 +6231,10 @@ function $CompileProvider($provide) {
dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value;
} else if (key == 'style') {
$element.attr('style', $element.attr('style') + ';' + value);
+ dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value;
+ // `dst` will never contain hasOwnProperty as DOM parser won't let it.
+ // You will get an "InvalidCharacterError: DOM Exception 5" error if you
+ // have an attribute like "has-own-property" or "data-has-own-property", etc.
} else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) {
dst[key] = value;
dstAttr[key] = srcAttr[key];
@@ -4300,8 +6243,8 @@ function $CompileProvider($provide) {
}
- function compileTemplateUrl(directives, beforeTemplateNodeLinkFn, $compileNode, tAttrs,
- $rootElement, replace, childTranscludeFn) {
+ function compileTemplateUrl(directives, $compileNode, tAttrs,
+ $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) {
var linkQueue = [],
afterTemplateNodeLinkFn,
afterTemplateChildLinkFn,
@@ -4309,28 +6252,38 @@ function $CompileProvider($provide) {
origAsyncDirective = directives.shift(),
// The fact that we have to copy and patch the directive seems wrong!
derivedSyncDirective = extend({}, origAsyncDirective, {
- controller: null, templateUrl: null, transclude: null
- });
+ templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective
+ }),
+ templateUrl = (isFunction(origAsyncDirective.templateUrl))
+ ? origAsyncDirective.templateUrl($compileNode, tAttrs)
+ : origAsyncDirective.templateUrl;
$compileNode.html('');
- $http.get(origAsyncDirective.templateUrl, {cache: $templateCache}).
+ $http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}).
success(function(content) {
- var compileNode, tempTemplateAttrs, $template;
+ var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn;
content = denormalizeTemplate(content);
- if (replace) {
+ if (origAsyncDirective.replace) {
$template = jqLite('' + trim(content) + '
').contents();
compileNode = $template[0];
if ($template.length != 1 || compileNode.nodeType !== 1) {
- throw new Error(MULTI_ROOT_TEMPLATE_ERROR + content);
+ throw $compileMinErr('tplrt',
+ "Template for directive '{0}' must have exactly one root element. {1}",
+ origAsyncDirective.name, templateUrl);
}
tempTemplateAttrs = {$attr: {}};
replaceWith($rootElement, $compileNode, compileNode);
- collectDirectives(compileNode, directives, tempTemplateAttrs);
+ var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs);
+
+ if (isObject(origAsyncDirective.scope)) {
+ markDirectivesAsIsolate(templateDirectives);
+ }
+ directives = templateDirectives.concat(directives);
mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
} else {
compileNode = beforeTemplateCompileNode;
@@ -4338,43 +6291,52 @@ function $CompileProvider($provide) {
}
directives.unshift(derivedSyncDirective);
- afterTemplateNodeLinkFn = applyDirectivesToNode(directives, $compileNode, tAttrs, childTranscludeFn);
- afterTemplateChildLinkFn = compileNodes($compileNode.contents(), childTranscludeFn);
+
+ afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs,
+ childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns,
+ previousCompileContext);
+ forEach($rootElement, function(node, i) {
+ if (node == compileNode) {
+ $rootElement[i] = $compileNode[0];
+ }
+ });
+ afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
while(linkQueue.length) {
- var controller = linkQueue.pop(),
- linkRootElement = linkQueue.pop(),
- beforeTemplateLinkNode = linkQueue.pop(),
- scope = linkQueue.pop(),
- linkNode = compileNode;
+ var scope = linkQueue.shift(),
+ beforeTemplateLinkNode = linkQueue.shift(),
+ linkRootElement = linkQueue.shift(),
+ boundTranscludeFn = linkQueue.shift(),
+ linkNode = $compileNode[0];
if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
// it was cloned therefore we have to clone as well.
- linkNode = JQLiteClone(compileNode);
+ linkNode = jqLiteClone(compileNode);
replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
}
-
- afterTemplateNodeLinkFn(function() {
- beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, controller);
- }, scope, linkNode, $rootElement, controller);
+ if (afterTemplateNodeLinkFn.transclude) {
+ childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude);
+ } else {
+ childBoundTranscludeFn = boundTranscludeFn;
+ }
+ afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
+ childBoundTranscludeFn);
}
linkQueue = null;
}).
error(function(response, code, headers, config) {
- throw Error('Failed to load template: ' + config.url);
+ throw $compileMinErr('tpload', 'Failed to load template: {0}', config.url);
});
- return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, controller) {
+ return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
if (linkQueue) {
linkQueue.push(scope);
linkQueue.push(node);
linkQueue.push(rootElement);
- linkQueue.push(controller);
+ linkQueue.push(boundTranscludeFn);
} else {
- afterTemplateNodeLinkFn(function() {
- beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, controller);
- }, scope, node, rootElement, controller);
+ afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, boundTranscludeFn);
}
};
}
@@ -4384,14 +6346,17 @@ function $CompileProvider($provide) {
* Sorting function for bound directives.
*/
function byPriority(a, b) {
- return b.priority - a.priority;
+ var diff = b.priority - a.priority;
+ if (diff !== 0) return diff;
+ if (a.name !== b.name) return (a.name < b.name) ? -1 : 1;
+ return a.index - b.index;
}
function assertNoDuplicate(what, previousDirective, directive, element) {
if (previousDirective) {
- throw Error('Multiple directives [' + previousDirective.name + ', ' +
- directive.name + '] asking for ' + what + ' on: ' + startingTag(element));
+ throw $compileMinErr('multidir', 'Multiple directives [{0}, {1}] asking for {2} on: {3}',
+ previousDirective.name, directive.name, what, startingTag(element));
}
}
@@ -4401,12 +6366,12 @@ function $CompileProvider($provide) {
if (interpolateFn) {
directives.push({
priority: 0,
- compile: valueFn(function(scope, node) {
+ compile: valueFn(function textInterpolateLinkFn(scope, node) {
var parent = node.parent(),
bindings = parent.data('$binding') || [];
bindings.push(interpolateFn);
safeAddClass(parent.data('$binding', bindings), 'ng-binding');
- scope.$watch(interpolateFn, function(value) {
+ scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
node[0].nodeValue = value;
});
})
@@ -4415,31 +6380,76 @@ function $CompileProvider($provide) {
}
+ function getTrustedContext(node, attrNormalizedName) {
+ if (attrNormalizedName == "srcdoc") {
+ return $sce.HTML;
+ }
+ var tag = nodeName_(node);
+ // maction[xlink:href] can source SVG. It's not limited to .
+ if (attrNormalizedName == "xlinkHref" ||
+ (tag == "FORM" && attrNormalizedName == "action") ||
+ (tag != "IMG" && (attrNormalizedName == "src" ||
+ attrNormalizedName == "ngSrc"))) {
+ return $sce.RESOURCE_URL;
+ }
+ }
+
+
function addAttrInterpolateDirective(node, directives, value, name) {
var interpolateFn = $interpolate(value, true);
-
// no interpolation found -> ignore
if (!interpolateFn) return;
+
+ if (name === "multiple" && nodeName_(node) === "SELECT") {
+ throw $compileMinErr("selmulti",
+ "Binding to the 'multiple' attribute is not supported. Element: {0}",
+ startingTag(node));
+ }
+
directives.push({
priority: 100,
- compile: valueFn(function(scope, element, attr) {
- var $$observers = (attr.$$observers || (attr.$$observers = {}));
+ compile: function() {
+ return {
+ pre: function attrInterpolatePreLinkFn(scope, element, attr) {
+ var $$observers = (attr.$$observers || (attr.$$observers = {}));
+
+ if (EVENT_HANDLER_ATTR_REGEXP.test(name)) {
+ throw $compileMinErr('nodomevents',
+ "Interpolations for HTML DOM event attributes are disallowed. Please use the " +
+ "ng- versions (such as ng-click instead of onclick) instead.");
+ }
- if (name === 'class') {
- // we need to interpolate classes again, in the case the element was replaced
- // and therefore the two class attrs got merged - we want to interpolate the result
- interpolateFn = $interpolate(attr[name], true);
+ // we need to interpolate again, in case the attribute value has been updated
+ // (e.g. by another directive's compile function)
+ interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name));
+
+ // if attribute was updated so that there is no interpolation going on we don't want to
+ // register any observers
+ if (!interpolateFn) return;
+
+ // TODO(i): this should likely be attr.$set(name, iterpolateFn(scope) so that we reset the
+ // actual attr value
+ attr[name] = interpolateFn(scope);
+ ($$observers[name] || ($$observers[name] = [])).$$inter = true;
+ (attr.$$observers && attr.$$observers[name].$$scope || scope).
+ $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) {
+ //special case for class attribute addition + removal
+ //so that class changes can tap into the animation
+ //hooks provided by the $animate service. Be sure to
+ //skip animations when the first digest occurs (when
+ //both the new and the old values are the same) since
+ //the CSS classes are the non-interpolated values
+ if(name === 'class' && newValue != oldValue) {
+ attr.$updateClass(newValue, oldValue);
+ } else {
+ attr.$set(name, newValue);
+ }
+ });
+ }
+ };
}
-
- attr[name] = undefined;
- ($$observers[name] || ($$observers[name] = [])).$$inter = true;
- (attr.$$observers && attr.$$observers[name].$$scope || scope).
- $watch(interpolateFn, function(value) {
- attr.$set(name, value);
- });
- })
});
}
@@ -4449,31 +6459,56 @@ function $CompileProvider($provide) {
* have no parents, provided that the containing jqLite collection is provided.
*
* @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
- * in the root of the tree.
- * @param {JqLite} $element The jqLite element which we are going to replace. We keep the shell,
- * but replace its DOM node reference.
+ * in the root of the tree.
+ * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep
+ * the shell, but replace its DOM node reference.
* @param {Node} newNode The new DOM node.
*/
- function replaceWith($rootElement, $element, newNode) {
- var oldNode = $element[0],
- parent = oldNode.parentNode,
+ function replaceWith($rootElement, elementsToRemove, newNode) {
+ var firstElementToRemove = elementsToRemove[0],
+ removeCount = elementsToRemove.length,
+ parent = firstElementToRemove.parentNode,
i, ii;
if ($rootElement) {
for(i = 0, ii = $rootElement.length; i < ii; i++) {
- if ($rootElement[i] == oldNode) {
- $rootElement[i] = newNode;
+ if ($rootElement[i] == firstElementToRemove) {
+ $rootElement[i++] = newNode;
+ for (var j = i, j2 = j + removeCount - 1,
+ jj = $rootElement.length;
+ j < jj; j++, j2++) {
+ if (j2 < jj) {
+ $rootElement[j] = $rootElement[j2];
+ } else {
+ delete $rootElement[j];
+ }
+ }
+ $rootElement.length -= removeCount - 1;
break;
}
}
}
if (parent) {
- parent.replaceChild(newNode, oldNode);
+ parent.replaceChild(newNode, firstElementToRemove);
}
+ var fragment = document.createDocumentFragment();
+ fragment.appendChild(firstElementToRemove);
+ newNode[jqLite.expando] = firstElementToRemove[jqLite.expando];
+ for (var k = 1, kk = elementsToRemove.length; k < kk; k++) {
+ var element = elementsToRemove[k];
+ jqLite(element).remove(); // must do this way to clean up expando
+ fragment.appendChild(element);
+ delete elementsToRemove[k];
+ }
+
+ elementsToRemove[0] = newNode;
+ elementsToRemove.length = 1;
+ }
+
- newNode[jqLite.expando] = oldNode[jqLite.expando];
- $element[0] = newNode;
+ function cloneAndAnnotateFn(fn, annotation) {
+ return extend(function() { return fn.apply(null, arguments); }, fn, annotation);
}
}];
}
@@ -4482,7 +6517,7 @@ var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i;
/**
* Converts all accepted directives format into proper directive name.
* All of these will become 'myDirective':
- * my:DiRective
+ * my:Directive
* my-directive
* x-my-directive
* data-my:directive
@@ -4497,13 +6532,13 @@ function directiveNormalize(name) {
/**
* @ngdoc object
* @name ng.$compile.directive.Attributes
- * @description
*
- * A shared object between directive compile / linking functions which contains normalized DOM element
- * attributes. The the values reflect current binding state `{{ }}`. The normalization is needed
- * since all of these are treated as equivalent in Angular:
+ * @description
+ * A shared object between directive compile / linking functions which contains normalized DOM
+ * element attributes. The values reflect current binding state `{{ }}`. The normalization is
+ * needed since all of these are treated as equivalent in Angular:
*
- *
+ *
*/
/**
@@ -4511,7 +6546,7 @@ function directiveNormalize(name) {
* @name ng.$compile.directive.Attributes#$attr
* @propertyOf ng.$compile.directive.Attributes
* @returns {object} A map of DOM element attribute names to the normalized name. This is
- * needed to do reverse lookup from normalized name back to actual name.
+ * needed to do reverse lookup from normalized name back to actual name.
*/
@@ -4528,7 +6563,7 @@ function directiveNormalize(name) {
* @param {string} name Normalized element attribute name of the property to modify. The name is
* revers translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
* property to the original name.
- * @param {string} value Value to set the attribute to.
+ * @param {string} value Value to set the attribute to. The value can be an interpolated string.
*/
@@ -4552,6 +6587,22 @@ function directiveLinkingFn(
/* function(Function) */ boundTranscludeFn
){}
+function tokenDifference(str1, str2) {
+ var values = '',
+ tokens1 = str1.split(/\s+/),
+ tokens2 = str2.split(/\s+/);
+
+ outer:
+ for(var i = 0; i < tokens1.length; i++) {
+ var token = tokens1[i];
+ for(var j = 0; j < tokens2.length; j++) {
+ if(token == tokens2[j]) continue outer;
+ }
+ values += (values.length > 0 ? ' ' : '') + token;
+ }
+ return values;
+}
+
/**
* @ngdoc object
* @name ng.$controllerProvider
@@ -4560,23 +6611,26 @@ function directiveLinkingFn(
* controllers.
*
* This provider allows controller registration via the
- * {@link ng.$controllerProvider#register register} method.
+ * {@link ng.$controllerProvider#methods_register register} method.
*/
function $ControllerProvider() {
- var controllers = {};
+ var controllers = {},
+ CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/;
/**
* @ngdoc function
* @name ng.$controllerProvider#register
* @methodOf ng.$controllerProvider
- * @param {string} name Controller name
+ * @param {string|Object} name Controller name, or an object map of controllers where the keys are
+ * the names and the values are the constructors.
* @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI
* annotations in the array notation).
*/
this.register = function(name, constructor) {
+ assertNotHasOwnProperty(name, 'controller');
if (isObject(name)) {
- extend(controllers, name)
+ extend(controllers, name);
} else {
controllers[name] = constructor;
}
@@ -4604,21 +6658,37 @@ function $ControllerProvider() {
* @description
* `$controller` service is responsible for instantiating controllers.
*
- * It's just simple call to {@link AUTO.$injector $injector}, but extracted into
+ * It's just a simple call to {@link AUTO.$injector $injector}, but extracted into
* a service, so that one can override this service with {@link https://gist.github.com/1649788
* BC version}.
*/
- return function(constructor, locals) {
- if(isString(constructor)) {
- var name = constructor;
- constructor = controllers.hasOwnProperty(name)
- ? controllers[name]
- : getter(locals.$scope, name, true) || getter($window, name, true);
-
- assertArgFn(constructor, name, true);
+ return function(expression, locals) {
+ var instance, match, constructor, identifier;
+
+ if(isString(expression)) {
+ match = expression.match(CNTRL_REG),
+ constructor = match[1],
+ identifier = match[3];
+ expression = controllers.hasOwnProperty(constructor)
+ ? controllers[constructor]
+ : getter(locals.$scope, constructor, true) || getter($window, constructor, true);
+
+ assertArgFn(expression, constructor, true);
+ }
+
+ instance = $injector.instantiate(expression, locals);
+
+ if (identifier) {
+ if (!(locals && typeof locals.$scope == 'object')) {
+ throw minErr('$controller')('noscp',
+ "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.",
+ constructor || expression.name, identifier);
+ }
+
+ locals.$scope[identifier] = instance;
}
- return $injector.instantiate(constructor, locals);
+ return instance;
};
}];
}
@@ -4647,16 +6717,31 @@ function $DocumentProvider(){
* Any uncaught exception in angular expressions is delegated to this service.
* The default implementation simply delegates to `$log.error` which logs it into
* the browser console.
- *
+ *
* In unit tests, if `angular-mocks.js` is loaded, this service is overridden by
- * {@link ngMock.$exceptionHandler mock $exceptionHandler}
+ * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing.
+ *
+ * ## Example:
+ *
+ *
+ * angular.module('exceptionOverride', []).factory('$exceptionHandler', function () {
+ * return function (exception, cause) {
+ * exception.message += ' (caused by "' + cause + '")';
+ * throw exception;
+ * };
+ * });
+ *
+ *
+ * This example will override the normal action of `$exceptionHandler`, to make angular
+ * exceptions fail hard when they happen, instead of just logging to the console.
*
* @param {Error} exception Exception associated with the error.
* @param {string=} cause optional information about the context in which
* the error was thrown.
+ *
*/
function $ExceptionHandlerProvider() {
- this.$get = ['$log', function($log){
+ this.$get = ['$log', function($log) {
return function(exception, cause) {
$log.error.apply($log, arguments);
};
@@ -4664,9738 +6749,2310 @@ function $ExceptionHandlerProvider() {
}
/**
- * @ngdoc object
- * @name ng.$interpolateProvider
- * @function
+ * Parse headers into key value object
*
- * @description
+ * @param {string} headers Raw headers as a string
+ * @returns {Object} Parsed headers as key value object
+ */
+function parseHeaders(headers) {
+ var parsed = {}, key, val, i;
+
+ if (!headers) return parsed;
+
+ forEach(headers.split('\n'), function(line) {
+ i = line.indexOf(':');
+ key = lowercase(trim(line.substr(0, i)));
+ val = trim(line.substr(i + 1));
+
+ if (key) {
+ if (parsed[key]) {
+ parsed[key] += ', ' + val;
+ } else {
+ parsed[key] = val;
+ }
+ }
+ });
+
+ return parsed;
+}
+
+
+/**
+ * Returns a function that provides access to parsed headers.
*
- * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
+ * Headers are lazy parsed when first requested.
+ * @see parseHeaders
+ *
+ * @param {(string|Object)} headers Headers to provide access to.
+ * @returns {function(string=)} Returns a getter function which if called with:
+ *
+ * - if called with single an argument returns a single header value or null
+ * - if called with no arguments returns an object containing all headers.
*/
-function $InterpolateProvider() {
- var startSymbol = '{{';
- var endSymbol = '}}';
+function headersGetter(headers) {
+ var headersObj = isObject(headers) ? headers : undefined;
- /**
- * @ngdoc method
- * @name ng.$interpolateProvider#startSymbol
- * @methodOf ng.$interpolateProvider
- * @description
- * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
- *
- * @param {string=} value new value to set the starting symbol to.
- * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
- */
- this.startSymbol = function(value){
- if (value) {
- startSymbol = value;
- return this;
- } else {
- return startSymbol;
+ return function(name) {
+ if (!headersObj) headersObj = parseHeaders(headers);
+
+ if (name) {
+ return headersObj[lowercase(name)] || null;
}
+
+ return headersObj;
+ };
+}
+
+
+/**
+ * Chain all given functions
+ *
+ * This function is used for both request and response transforming
+ *
+ * @param {*} data Data to transform.
+ * @param {function(string=)} headers Http headers getter fn.
+ * @param {(function|Array.)} fns Function or an array of functions.
+ * @returns {*} Transformed data.
+ */
+function transformData(data, headers, fns) {
+ if (isFunction(fns))
+ return fns(data, headers);
+
+ forEach(fns, function(fn) {
+ data = fn(data, headers);
+ });
+
+ return data;
+}
+
+
+function isSuccess(status) {
+ return 200 <= status && status < 300;
+}
+
+
+function $HttpProvider() {
+ var JSON_START = /^\s*(\[|\{[^\{])/,
+ JSON_END = /[\}\]]\s*$/,
+ PROTECTION_PREFIX = /^\)\]\}',?\n/,
+ CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'};
+
+ var defaults = this.defaults = {
+ // transform incoming response data
+ transformResponse: [function(data) {
+ if (isString(data)) {
+ // strip json vulnerability protection prefix
+ data = data.replace(PROTECTION_PREFIX, '');
+ if (JSON_START.test(data) && JSON_END.test(data))
+ data = fromJson(data);
+ }
+ return data;
+ }],
+
+ // transform outgoing request data
+ transformRequest: [function(d) {
+ return isObject(d) && !isFile(d) ? toJson(d) : d;
+ }],
+
+ // default headers
+ headers: {
+ common: {
+ 'Accept': 'application/json, text/plain, */*'
+ },
+ post: CONTENT_TYPE_APPLICATION_JSON,
+ put: CONTENT_TYPE_APPLICATION_JSON,
+ patch: CONTENT_TYPE_APPLICATION_JSON
+ },
+
+ xsrfCookieName: 'XSRF-TOKEN',
+ xsrfHeaderName: 'X-XSRF-TOKEN'
};
/**
- * @ngdoc method
- * @name ng.$interpolateProvider#endSymbol
- * @methodOf ng.$interpolateProvider
- * @description
- * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
- *
- * @param {string=} value new value to set the ending symbol to.
- * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
+ * Are ordered by request, i.e. they are applied in the same order as the
+ * array, on request, but reverse order, on response.
*/
- this.endSymbol = function(value){
- if (value) {
- endSymbol = value;
- return this;
- } else {
- return endSymbol;
- }
- };
+ var interceptorFactories = this.interceptors = [];
+ /**
+ * For historical reasons, response interceptors are ordered by the order in which
+ * they are applied to the response. (This is the opposite of interceptorFactories)
+ */
+ var responseInterceptorFactories = this.responseInterceptors = [];
+
+ this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector',
+ function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) {
+
+ var defaultCache = $cacheFactory('$http');
+
+ /**
+ * Interceptors stored in reverse order. Inner interceptors before outer interceptors.
+ * The reversal is needed so that we can build up the interception chain around the
+ * server request.
+ */
+ var reversedInterceptors = [];
+
+ forEach(interceptorFactories, function(interceptorFactory) {
+ reversedInterceptors.unshift(isString(interceptorFactory)
+ ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory));
+ });
+
+ forEach(responseInterceptorFactories, function(interceptorFactory, index) {
+ var responseFn = isString(interceptorFactory)
+ ? $injector.get(interceptorFactory)
+ : $injector.invoke(interceptorFactory);
+
+ /**
+ * Response interceptors go before "around" interceptors (no real reason, just
+ * had to pick one.) But they are already reversed, so we can't use unshift, hence
+ * the splice.
+ */
+ reversedInterceptors.splice(index, 0, {
+ response: function(response) {
+ return responseFn($q.when(response));
+ },
+ responseError: function(response) {
+ return responseFn($q.reject(response));
+ }
+ });
+ });
- this.$get = ['$parse', function($parse) {
- var startSymbolLength = startSymbol.length,
- endSymbolLength = endSymbol.length;
/**
* @ngdoc function
- * @name ng.$interpolate
- * @function
- *
- * @requires $parse
+ * @name ng.$http
+ * @requires $httpBackend
+ * @requires $browser
+ * @requires $cacheFactory
+ * @requires $rootScope
+ * @requires $q
+ * @requires $injector
*
* @description
+ * The `$http` service is a core Angular service that facilitates communication with the remote
+ * HTTP servers via the browser's {@link https://developer.mozilla.org/en/xmlhttprequest
+ * XMLHttpRequest} object or via {@link http://en.wikipedia.org/wiki/JSONP JSONP}.
*
- * Compiles a string with markup into an interpolation function. This service is used by the
- * HTML {@link ng.$compile $compile} service for data binding. See
- * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
- * interpolation markup.
+ * For unit testing applications that use `$http` service, see
+ * {@link ngMock.$httpBackend $httpBackend mock}.
*
+ * For a higher level of abstraction, please check out the {@link ngResource.$resource
+ * $resource} service.
*
-
- var $interpolate = ...; // injected
- var exp = $interpolate('Hello {{name}}!');
- expect(exp({name:'Angular'}).toEqual('Hello Angular!');
-
+ * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by
+ * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage
+ * it is important to familiarize yourself with these APIs and the guarantees they provide.
*
*
- * @param {string} text The text with markup to interpolate.
- * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
- * embedded expression in order to return an interpolation function. Strings with no
- * embedded expression will return null for the interpolation function.
- * @returns {function(context)} an interpolation function which is used to compute the interpolated
- * string. The function has these parameters:
+ * # General usage
+ * The `$http` service is a function which takes a single argument — a configuration object —
+ * that is used to generate an HTTP request and returns a {@link ng.$q promise}
+ * with two $http specific methods: `success` and `error`.
*
- * * `context`: an object against which any expressions embedded in the strings are evaluated
- * against.
+ *
+ * $http({method: 'GET', url: '/someUrl'}).
+ * success(function(data, status, headers, config) {
+ * // this callback will be called asynchronously
+ * // when the response is available
+ * }).
+ * error(function(data, status, headers, config) {
+ * // called asynchronously if an error occurs
+ * // or server returns response with an error status.
+ * });
+ *
*
- */
- function $interpolate(text, mustHaveExpression) {
- var startIndex,
- endIndex,
- index = 0,
- parts = [],
- length = text.length,
- hasInterpolation = false,
- fn,
- exp,
- concat = [];
-
- while(index < length) {
- if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) &&
- ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) {
- (index != startIndex) && parts.push(text.substring(index, startIndex));
- parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex)));
- fn.exp = exp;
- index = endIndex + endSymbolLength;
- hasInterpolation = true;
- } else {
- // we did not find anything, so we have to add the remainder to the parts array
- (index != length) && parts.push(text.substring(index));
- index = length;
- }
- }
-
- if (!(length = parts.length)) {
- // we added, nothing, must have been an empty string.
- parts.push('');
- length = 1;
- }
-
- if (!mustHaveExpression || hasInterpolation) {
- concat.length = length;
- fn = function(context) {
- for(var i = 0, ii = length, part; i html5 url
- } else {
- return composeProtocolHostPort(match.protocol, match.host, match.port) +
- pathPrefixFromBase(basePath) + match.hash.substr(hashPrefix.length);
- }
-}
-
-
-function convertToHashbangUrl(url, basePath, hashPrefix) {
- var match = matchUrl(url);
-
- // already hashbang url
- if (decodeURIComponent(match.path) == basePath) {
- return url;
- // convert html5 url -> hashbang url
- } else {
- var search = match.search && '?' + match.search || '',
- hash = match.hash && '#' + match.hash || '',
- pathPrefix = pathPrefixFromBase(basePath),
- path = match.path.substr(pathPrefix.length);
-
- if (match.path.indexOf(pathPrefix) !== 0) {
- throw Error('Invalid url "' + url + '", missing path prefix "' + pathPrefix + '" !');
- }
-
- return composeProtocolHostPort(match.protocol, match.host, match.port) + basePath +
- '#' + hashPrefix + path + search + hash;
- }
-}
-
-
-/**
- * LocationUrl represents an url
- * This object is exposed as $location service when HTML5 mode is enabled and supported
- *
- * @constructor
- * @param {string} url HTML5 url
- * @param {string} pathPrefix
- */
-function LocationUrl(url, pathPrefix, appBaseUrl) {
- pathPrefix = pathPrefix || '';
-
- /**
- * Parse given html5 (regular) url string into properties
- * @param {string} newAbsoluteUrl HTML5 url
- * @private
- */
- this.$$parse = function(newAbsoluteUrl) {
- var match = matchUrl(newAbsoluteUrl, this);
-
- if (match.path.indexOf(pathPrefix) !== 0) {
- throw Error('Invalid url "' + newAbsoluteUrl + '", missing path prefix "' + pathPrefix + '" !');
- }
-
- this.$$path = decodeURIComponent(match.path.substr(pathPrefix.length));
- this.$$search = parseKeyValue(match.search);
- this.$$hash = match.hash && decodeURIComponent(match.hash) || '';
-
- this.$$compose();
- };
-
- /**
- * Compose url and update `absUrl` property
- * @private
- */
- this.$$compose = function() {
- var search = toKeyValue(this.$$search),
- hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
-
- this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
- this.$$absUrl = composeProtocolHostPort(this.$$protocol, this.$$host, this.$$port) +
- pathPrefix + this.$$url;
- };
-
-
- this.$$rewriteAppUrl = function(absoluteLinkUrl) {
- if(absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
- return absoluteLinkUrl;
- }
- }
-
-
- this.$$parse(url);
-}
-
-
-/**
- * LocationHashbangUrl represents url
- * This object is exposed as $location service when html5 history api is disabled or not supported
- *
- * @constructor
- * @param {string} url Legacy url
- * @param {string} hashPrefix Prefix for hash part (containing path and search)
- */
-function LocationHashbangUrl(url, hashPrefix, appBaseUrl) {
- var basePath;
-
- /**
- * Parse given hashbang url into properties
- * @param {string} url Hashbang url
- * @private
- */
- this.$$parse = function(url) {
- var match = matchUrl(url, this);
-
-
- if (match.hash && match.hash.indexOf(hashPrefix) !== 0) {
- throw Error('Invalid url "' + url + '", missing hash prefix "' + hashPrefix + '" !');
- }
-
- basePath = match.path + (match.search ? '?' + match.search : '');
- match = HASH_MATCH.exec((match.hash || '').substr(hashPrefix.length));
- if (match[1]) {
- this.$$path = (match[1].charAt(0) == '/' ? '' : '/') + decodeURIComponent(match[1]);
- } else {
- this.$$path = '';
- }
-
- this.$$search = parseKeyValue(match[3]);
- this.$$hash = match[5] && decodeURIComponent(match[5]) || '';
-
- this.$$compose();
- };
-
- /**
- * Compose hashbang url and update `absUrl` property
- * @private
- */
- this.$$compose = function() {
- var search = toKeyValue(this.$$search),
- hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
-
- this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
- this.$$absUrl = composeProtocolHostPort(this.$$protocol, this.$$host, this.$$port) +
- basePath + (this.$$url ? '#' + hashPrefix + this.$$url : '');
- };
-
- this.$$rewriteAppUrl = function(absoluteLinkUrl) {
- if(absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
- return absoluteLinkUrl;
- }
- }
-
-
- this.$$parse(url);
-}
-
-
-LocationUrl.prototype = {
-
- /**
- * Has any change been replacing ?
- * @private
- */
- $$replace: false,
-
- /**
- * @ngdoc method
- * @name ng.$location#absUrl
- * @methodOf ng.$location
- *
- * @description
- * This method is getter only.
- *
- * Return full url representation with all segments encoded according to rules specified in
- * {@link http://www.ietf.org/rfc/rfc3986.txt RFC 3986}.
- *
- * @return {string} full url
- */
- absUrl: locationGetter('$$absUrl'),
-
- /**
- * @ngdoc method
- * @name ng.$location#url
- * @methodOf ng.$location
- *
- * @description
- * This method is getter / setter.
- *
- * Return url (e.g. `/path?a=b#hash`) when called without any parameter.
- *
- * Change path, search and hash, when called with parameter and return `$location`.
- *
- * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
- * @return {string} url
- */
- url: function(url, replace) {
- if (isUndefined(url))
- return this.$$url;
-
- var match = PATH_MATCH.exec(url);
- if (match[1]) this.path(decodeURIComponent(match[1]));
- if (match[2] || match[1]) this.search(match[3] || '');
- this.hash(match[5] || '', replace);
-
- return this;
- },
-
- /**
- * @ngdoc method
- * @name ng.$location#protocol
- * @methodOf ng.$location
- *
- * @description
- * This method is getter only.
- *
- * Return protocol of current url.
- *
- * @return {string} protocol of current url
- */
- protocol: locationGetter('$$protocol'),
-
- /**
- * @ngdoc method
- * @name ng.$location#host
- * @methodOf ng.$location
- *
- * @description
- * This method is getter only.
- *
- * Return host of current url.
- *
- * @return {string} host of current url.
- */
- host: locationGetter('$$host'),
-
- /**
- * @ngdoc method
- * @name ng.$location#port
- * @methodOf ng.$location
- *
- * @description
- * This method is getter only.
- *
- * Return port of current url.
- *
- * @return {Number} port
- */
- port: locationGetter('$$port'),
-
- /**
- * @ngdoc method
- * @name ng.$location#path
- * @methodOf ng.$location
- *
- * @description
- * This method is getter / setter.
- *
- * Return path of current url when called without any parameter.
- *
- * Change path when called with parameter and return `$location`.
- *
- * Note: Path should always begin with forward slash (/), this method will add the forward slash
- * if it is missing.
- *
- * @param {string=} path New path
- * @return {string} path
- */
- path: locationGetterSetter('$$path', function(path) {
- return path.charAt(0) == '/' ? path : '/' + path;
- }),
-
- /**
- * @ngdoc method
- * @name ng.$location#search
- * @methodOf ng.$location
- *
- * @description
- * This method is getter / setter.
- *
- * Return search part (as object) of current url when called without any parameter.
- *
- * Change search part when called with parameter and return `$location`.
- *
- * @param {string|object=} search New search params - string or hash object
- * @param {string=} paramValue If `search` is a string, then `paramValue` will override only a
- * single search parameter. If the value is `null`, the parameter will be deleted.
- *
- * @return {string} search
- */
- search: function(search, paramValue) {
- if (isUndefined(search))
- return this.$$search;
-
- if (isDefined(paramValue)) {
- if (paramValue === null) {
- delete this.$$search[search];
- } else {
- this.$$search[search] = paramValue;
- }
- } else {
- this.$$search = isString(search) ? parseKeyValue(search) : search;
- }
-
- this.$$compose();
- return this;
- },
-
- /**
- * @ngdoc method
- * @name ng.$location#hash
- * @methodOf ng.$location
- *
- * @description
- * This method is getter / setter.
- *
- * Return hash fragment when called without any parameter.
- *
- * Change hash fragment when called with parameter and return `$location`.
- *
- * @param {string=} hash New hash fragment
- * @return {string} hash
- */
- hash: locationGetterSetter('$$hash', identity),
-
- /**
- * @ngdoc method
- * @name ng.$location#replace
- * @methodOf ng.$location
- *
- * @description
- * If called, all changes to $location during current `$digest` will be replacing current history
- * record, instead of adding new one.
- */
- replace: function() {
- this.$$replace = true;
- return this;
- }
-};
-
-LocationHashbangUrl.prototype = inherit(LocationUrl.prototype);
-
-function LocationHashbangInHtml5Url(url, hashPrefix, appBaseUrl, baseExtra) {
- LocationHashbangUrl.apply(this, arguments);
-
-
- this.$$rewriteAppUrl = function(absoluteLinkUrl) {
- if (absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
- return appBaseUrl + baseExtra + '#' + hashPrefix + absoluteLinkUrl.substr(appBaseUrl.length);
- }
- }
-}
-
-LocationHashbangInHtml5Url.prototype = inherit(LocationHashbangUrl.prototype);
-
-function locationGetter(property) {
- return function() {
- return this[property];
- };
-}
-
-
-function locationGetterSetter(property, preprocess) {
- return function(value) {
- if (isUndefined(value))
- return this[property];
-
- this[property] = preprocess(value);
- this.$$compose();
-
- return this;
- };
-}
-
-
-/**
- * @ngdoc object
- * @name ng.$location
- *
- * @requires $browser
- * @requires $sniffer
- * @requires $rootElement
- *
- * @description
- * The $location service parses the URL in the browser address bar (based on the
- * {@link https://developer.mozilla.org/en/window.location window.location}) and makes the URL
- * available to your application. Changes to the URL in the address bar are reflected into
- * $location service and changes to $location are reflected into the browser address bar.
- *
- * **The $location service:**
- *
- * - Exposes the current URL in the browser address bar, so you can
- * - Watch and observe the URL.
- * - Change the URL.
- * - Synchronizes the URL with the browser when the user
- * - Changes the address bar.
- * - Clicks the back or forward button (or clicks a History link).
- * - Clicks on a link.
- * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
- *
- * For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular
- * Services: Using $location}
- */
-
-/**
- * @ngdoc object
- * @name ng.$locationProvider
- * @description
- * Use the `$locationProvider` to configure how the application deep linking paths are stored.
- */
-function $LocationProvider(){
- var hashPrefix = '',
- html5Mode = false;
-
- /**
- * @ngdoc property
- * @name ng.$locationProvider#hashPrefix
- * @methodOf ng.$locationProvider
- * @description
- * @param {string=} prefix Prefix for hash part (containing path and search)
- * @returns {*} current value if used as getter or itself (chaining) if used as setter
- */
- this.hashPrefix = function(prefix) {
- if (isDefined(prefix)) {
- hashPrefix = prefix;
- return this;
- } else {
- return hashPrefix;
- }
- };
-
- /**
- * @ngdoc property
- * @name ng.$locationProvider#html5Mode
- * @methodOf ng.$locationProvider
- * @description
- * @param {string=} mode Use HTML5 strategy if available.
- * @returns {*} current value if used as getter or itself (chaining) if used as setter
- */
- this.html5Mode = function(mode) {
- if (isDefined(mode)) {
- html5Mode = mode;
- return this;
- } else {
- return html5Mode;
- }
- };
-
- this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement',
- function( $rootScope, $browser, $sniffer, $rootElement) {
- var $location,
- basePath,
- pathPrefix,
- initUrl = $browser.url(),
- initUrlParts = matchUrl(initUrl),
- appBaseUrl;
-
- if (html5Mode) {
- basePath = $browser.baseHref() || '/';
- pathPrefix = pathPrefixFromBase(basePath);
- appBaseUrl =
- composeProtocolHostPort(initUrlParts.protocol, initUrlParts.host, initUrlParts.port) +
- pathPrefix + '/';
-
- if ($sniffer.history) {
- $location = new LocationUrl(
- convertToHtml5Url(initUrl, basePath, hashPrefix),
- pathPrefix, appBaseUrl);
- } else {
- $location = new LocationHashbangInHtml5Url(
- convertToHashbangUrl(initUrl, basePath, hashPrefix),
- hashPrefix, appBaseUrl, basePath.substr(pathPrefix.length + 1));
- }
- } else {
- appBaseUrl =
- composeProtocolHostPort(initUrlParts.protocol, initUrlParts.host, initUrlParts.port) +
- (initUrlParts.path || '') +
- (initUrlParts.search ? ('?' + initUrlParts.search) : '') +
- '#' + hashPrefix + '/';
-
- $location = new LocationHashbangUrl(initUrl, hashPrefix, appBaseUrl);
- }
-
- $rootElement.bind('click', function(event) {
- // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
- // currently we open nice url link and redirect then
-
- if (event.ctrlKey || event.metaKey || event.which == 2) return;
-
- var elm = jqLite(event.target);
-
- // traverse the DOM up to find first A tag
- while (lowercase(elm[0].nodeName) !== 'a') {
- // ignore rewriting if no A tag (reached root element, or no parent - removed from document)
- if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return;
- }
-
- var absHref = elm.prop('href'),
- rewrittenUrl = $location.$$rewriteAppUrl(absHref);
-
- if (absHref && !elm.attr('target') && rewrittenUrl) {
- // update location manually
- $location.$$parse(rewrittenUrl);
- $rootScope.$apply();
- event.preventDefault();
- // hack to work around FF6 bug 684208 when scenario runner clicks on links
- window.angular['ff-684208-preventDefault'] = true;
- }
- });
-
-
- // rewrite hashbang url <> html5 url
- if ($location.absUrl() != initUrl) {
- $browser.url($location.absUrl(), true);
- }
-
- // update $location when $browser url changes
- $browser.onUrlChange(function(newUrl) {
- if ($location.absUrl() != newUrl) {
- $rootScope.$evalAsync(function() {
- var oldUrl = $location.absUrl();
-
- $location.$$parse(newUrl);
- afterLocationChange(oldUrl);
- });
- if (!$rootScope.$$phase) $rootScope.$digest();
- }
- });
-
- // update browser
- var changeCounter = 0;
- $rootScope.$watch(function $locationWatch() {
- var oldUrl = $browser.url();
-
- if (!changeCounter || oldUrl != $location.absUrl()) {
- changeCounter++;
- $rootScope.$evalAsync(function() {
- if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl).
- defaultPrevented) {
- $location.$$parse(oldUrl);
- } else {
- $browser.url($location.absUrl(), $location.$$replace);
- $location.$$replace = false;
- afterLocationChange(oldUrl);
- }
- });
- }
-
- return changeCounter;
- });
-
- return $location;
-
- function afterLocationChange(oldUrl) {
- $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl);
- }
-}];
-}
-
-/**
- * @ngdoc object
- * @name ng.$log
- * @requires $window
- *
- * @description
- * Simple service for logging. Default implementation writes the message
- * into the browser's console (if present).
- *
- * The main purpose of this service is to simplify debugging and troubleshooting.
- *
- * @example
-
-
- function LogCtrl($scope, $log) {
- $scope.$log = $log;
- $scope.message = 'Hello World!';
- }
-
-
-
-
Reload this page with open console, enter text and hit the log button...
- Message:
-
-
log
-
warn
-
info
-
error
-
-
-
- */
-
-function $LogProvider(){
- this.$get = ['$window', function($window){
- return {
- /**
- * @ngdoc method
- * @name ng.$log#log
- * @methodOf ng.$log
- *
- * @description
- * Write a log message
- */
- log: consoleLog('log'),
-
- /**
- * @ngdoc method
- * @name ng.$log#warn
- * @methodOf ng.$log
- *
- * @description
- * Write a warning message
- */
- warn: consoleLog('warn'),
-
- /**
- * @ngdoc method
- * @name ng.$log#info
- * @methodOf ng.$log
- *
- * @description
- * Write an information message
- */
- info: consoleLog('info'),
-
- /**
- * @ngdoc method
- * @name ng.$log#error
- * @methodOf ng.$log
- *
- * @description
- * Write an error message
- */
- error: consoleLog('error')
- };
-
- function formatError(arg) {
- if (arg instanceof Error) {
- if (arg.stack) {
- arg = (arg.message && arg.stack.indexOf(arg.message) === -1)
- ? 'Error: ' + arg.message + '\n' + arg.stack
- : arg.stack;
- } else if (arg.sourceURL) {
- arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
- }
- }
- return arg;
- }
-
- function consoleLog(type) {
- var console = $window.console || {},
- logFn = console[type] || console.log || noop;
-
- if (logFn.apply) {
- return function() {
- var args = [];
- forEach(arguments, function(arg) {
- args.push(formatError(arg));
- });
- return logFn.apply(console, args);
- };
- }
-
- // we are IE which either doesn't have window.console => this is noop and we do nothing,
- // or we are IE where console.log doesn't have apply so we log at least first 2 args
- return function(arg1, arg2) {
- logFn(arg1, arg2);
- }
- }
- }];
-}
-
-var OPERATORS = {
- 'null':function(){return null;},
- 'true':function(){return true;},
- 'false':function(){return false;},
- undefined:noop,
- '+':function(self, locals, a,b){a=a(self, locals); b=b(self, locals); return (isDefined(a)?a:0)+(isDefined(b)?b:0);},
- '-':function(self, locals, a,b){a=a(self, locals); b=b(self, locals); return (isDefined(a)?a:0)-(isDefined(b)?b:0);},
- '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);},
- '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);},
- '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);},
- '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);},
- '=':noop,
- '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);},
- '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);},
- '<':function(self, locals, a,b){return a(self, locals)':function(self, locals, a,b){return a(self, locals)>b(self, locals);},
- '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);},
- '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);},
- '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);},
- '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);},
- '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);},
-// '|':function(self, locals, a,b){return a|b;},
- '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));},
- '!':function(self, locals, a){return !a(self, locals);}
-};
-var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
-
-function lex(text, csp){
- var tokens = [],
- token,
- index = 0,
- json = [],
- ch,
- lastCh = ':'; // can start regexp
-
- while (index < text.length) {
- ch = text.charAt(index);
- if (is('"\'')) {
- readString(ch);
- } else if (isNumber(ch) || is('.') && isNumber(peek())) {
- readNumber();
- } else if (isIdent(ch)) {
- readIdent();
- // identifiers can only be if the preceding char was a { or ,
- if (was('{,') && json[0]=='{' &&
- (token=tokens[tokens.length-1])) {
- token.json = token.text.indexOf('.') == -1;
- }
- } else if (is('(){}[].,;:')) {
- tokens.push({
- index:index,
- text:ch,
- json:(was(':[,') && is('{[')) || is('}]:,')
- });
- if (is('{[')) json.unshift(ch);
- if (is('}]')) json.shift();
- index++;
- } else if (isWhitespace(ch)) {
- index++;
- continue;
- } else {
- var ch2 = ch + peek(),
- fn = OPERATORS[ch],
- fn2 = OPERATORS[ch2];
- if (fn2) {
- tokens.push({index:index, text:ch2, fn:fn2});
- index += 2;
- } else if (fn) {
- tokens.push({index:index, text:ch, fn:fn, json: was('[,:') && is('+-')});
- index += 1;
- } else {
- throwError("Unexpected next character ", index, index+1);
- }
- }
- lastCh = ch;
- }
- return tokens;
-
- function is(chars) {
- return chars.indexOf(ch) != -1;
- }
-
- function was(chars) {
- return chars.indexOf(lastCh) != -1;
- }
-
- function peek() {
- return index + 1 < text.length ? text.charAt(index + 1) : false;
- }
- function isNumber(ch) {
- return '0' <= ch && ch <= '9';
- }
- function isWhitespace(ch) {
- return ch == ' ' || ch == '\r' || ch == '\t' ||
- ch == '\n' || ch == '\v' || ch == '\u00A0'; // IE treats non-breaking space as \u00A0
- }
- function isIdent(ch) {
- return 'a' <= ch && ch <= 'z' ||
- 'A' <= ch && ch <= 'Z' ||
- '_' == ch || ch == '$';
- }
- function isExpOperator(ch) {
- return ch == '-' || ch == '+' || isNumber(ch);
- }
-
- function throwError(error, start, end) {
- end = end || index;
- throw Error("Lexer Error: " + error + " at column" +
- (isDefined(start)
- ? "s " + start + "-" + index + " [" + text.substring(start, end) + "]"
- : " " + end) +
- " in expression [" + text + "].");
- }
-
- function readNumber() {
- var number = "";
- var start = index;
- while (index < text.length) {
- var ch = lowercase(text.charAt(index));
- if (ch == '.' || isNumber(ch)) {
- number += ch;
- } else {
- var peekCh = peek();
- if (ch == 'e' && isExpOperator(peekCh)) {
- number += ch;
- } else if (isExpOperator(ch) &&
- peekCh && isNumber(peekCh) &&
- number.charAt(number.length - 1) == 'e') {
- number += ch;
- } else if (isExpOperator(ch) &&
- (!peekCh || !isNumber(peekCh)) &&
- number.charAt(number.length - 1) == 'e') {
- throwError('Invalid exponent');
- } else {
- break;
- }
- }
- index++;
- }
- number = 1 * number;
- tokens.push({index:start, text:number, json:true,
- fn:function() {return number;}});
- }
- function readIdent() {
- var ident = "",
- start = index,
- lastDot, peekIndex, methodName;
-
- while (index < text.length) {
- var ch = text.charAt(index);
- if (ch == '.' || isIdent(ch) || isNumber(ch)) {
- if (ch == '.') lastDot = index;
- ident += ch;
- } else {
- break;
- }
- index++;
- }
-
- //check if this is not a method invocation and if it is back out to last dot
- if (lastDot) {
- peekIndex = index;
- while(peekIndex < text.length) {
- var ch = text.charAt(peekIndex);
- if (ch == '(') {
- methodName = ident.substr(lastDot - start + 1);
- ident = ident.substr(0, lastDot - start);
- index = peekIndex;
- break;
- }
- if(isWhitespace(ch)) {
- peekIndex++;
- } else {
- break;
- }
- }
- }
-
-
- var token = {
- index:start,
- text:ident
- };
-
- if (OPERATORS.hasOwnProperty(ident)) {
- token.fn = token.json = OPERATORS[ident];
- } else {
- var getter = getterFn(ident, csp);
- token.fn = extend(function(self, locals) {
- return (getter(self, locals));
- }, {
- assign: function(self, value) {
- return setter(self, ident, value);
- }
- });
- }
-
- tokens.push(token);
-
- if (methodName) {
- tokens.push({
- index:lastDot,
- text: '.',
- json: false
- });
- tokens.push({
- index: lastDot + 1,
- text: methodName,
- json: false
- });
- }
- }
-
- function readString(quote) {
- var start = index;
- index++;
- var string = "";
- var rawString = quote;
- var escape = false;
- while (index < text.length) {
- var ch = text.charAt(index);
- rawString += ch;
- if (escape) {
- if (ch == 'u') {
- var hex = text.substring(index + 1, index + 5);
- if (!hex.match(/[\da-f]{4}/i))
- throwError( "Invalid unicode escape [\\u" + hex + "]");
- index += 4;
- string += String.fromCharCode(parseInt(hex, 16));
- } else {
- var rep = ESCAPE[ch];
- if (rep) {
- string += rep;
- } else {
- string += ch;
- }
- }
- escape = false;
- } else if (ch == '\\') {
- escape = true;
- } else if (ch == quote) {
- index++;
- tokens.push({
- index:start,
- text:rawString,
- string:string,
- json:true,
- fn:function() { return string; }
- });
- return;
- } else {
- string += ch;
- }
- index++;
- }
- throwError("Unterminated quote", start);
- }
-}
-
-/////////////////////////////////////////
-
-function parser(text, json, $filter, csp){
- var ZERO = valueFn(0),
- value,
- tokens = lex(text, csp),
- assignment = _assignment,
- functionCall = _functionCall,
- fieldAccess = _fieldAccess,
- objectIndex = _objectIndex,
- filterChain = _filterChain;
-
- if(json){
- // The extra level of aliasing is here, just in case the lexer misses something, so that
- // we prevent any accidental execution in JSON.
- assignment = logicalOR;
- functionCall =
- fieldAccess =
- objectIndex =
- filterChain =
- function() { throwError("is not valid json", {text:text, index:0}); };
- value = primary();
- } else {
- value = statements();
- }
- if (tokens.length !== 0) {
- throwError("is an unexpected token", tokens[0]);
- }
- return value;
-
- ///////////////////////////////////
- function throwError(msg, token) {
- throw Error("Syntax Error: Token '" + token.text +
- "' " + msg + " at column " +
- (token.index + 1) + " of the expression [" +
- text + "] starting at [" + text.substring(token.index) + "].");
- }
-
- function peekToken() {
- if (tokens.length === 0)
- throw Error("Unexpected end of expression: " + text);
- return tokens[0];
- }
-
- function peek(e1, e2, e3, e4) {
- if (tokens.length > 0) {
- var token = tokens[0];
- var t = token.text;
- if (t==e1 || t==e2 || t==e3 || t==e4 ||
- (!e1 && !e2 && !e3 && !e4)) {
- return token;
- }
- }
- return false;
- }
-
- function expect(e1, e2, e3, e4){
- var token = peek(e1, e2, e3, e4);
- if (token) {
- if (json && !token.json) {
- throwError("is not valid json", token);
- }
- tokens.shift();
- return token;
- }
- return false;
- }
-
- function consume(e1){
- if (!expect(e1)) {
- throwError("is unexpected, expecting [" + e1 + "]", peek());
- }
- }
-
- function unaryFn(fn, right) {
- return function(self, locals) {
- return fn(self, locals, right);
- };
- }
-
- function binaryFn(left, fn, right) {
- return function(self, locals) {
- return fn(self, locals, left, right);
- };
- }
-
- function statements() {
- var statements = [];
- while(true) {
- if (tokens.length > 0 && !peek('}', ')', ';', ']'))
- statements.push(filterChain());
- if (!expect(';')) {
- // optimize for the common case where there is only one statement.
- // TODO(size): maybe we should not support multiple statements?
- return statements.length == 1
- ? statements[0]
- : function(self, locals){
- var value;
- for ( var i = 0; i < statements.length; i++) {
- var statement = statements[i];
- if (statement)
- value = statement(self, locals);
- }
- return value;
- };
- }
- }
- }
-
- function _filterChain() {
- var left = expression();
- var token;
- while(true) {
- if ((token = expect('|'))) {
- left = binaryFn(left, token.fn, filter());
- } else {
- return left;
- }
- }
- }
-
- function filter() {
- var token = expect();
- var fn = $filter(token.text);
- var argsFn = [];
- while(true) {
- if ((token = expect(':'))) {
- argsFn.push(expression());
- } else {
- var fnInvoke = function(self, locals, input){
- var args = [input];
- for ( var i = 0; i < argsFn.length; i++) {
- args.push(argsFn[i](self, locals));
- }
- return fn.apply(self, args);
- };
- return function() {
- return fnInvoke;
- };
- }
- }
- }
-
- function expression() {
- return assignment();
- }
-
- function _assignment() {
- var left = logicalOR();
- var right;
- var token;
- if ((token = expect('='))) {
- if (!left.assign) {
- throwError("implies assignment but [" +
- text.substring(0, token.index) + "] can not be assigned to", token);
- }
- right = logicalOR();
- return function(self, locals){
- return left.assign(self, right(self, locals), locals);
- };
- } else {
- return left;
- }
- }
-
- function logicalOR() {
- var left = logicalAND();
- var token;
- while(true) {
- if ((token = expect('||'))) {
- left = binaryFn(left, token.fn, logicalAND());
- } else {
- return left;
- }
- }
- }
-
- function logicalAND() {
- var left = equality();
- var token;
- if ((token = expect('&&'))) {
- left = binaryFn(left, token.fn, logicalAND());
- }
- return left;
- }
-
- function equality() {
- var left = relational();
- var token;
- if ((token = expect('==','!='))) {
- left = binaryFn(left, token.fn, equality());
- }
- return left;
- }
-
- function relational() {
- var left = additive();
- var token;
- if ((token = expect('<', '>', '<=', '>='))) {
- left = binaryFn(left, token.fn, relational());
- }
- return left;
- }
-
- function additive() {
- var left = multiplicative();
- var token;
- while ((token = expect('+','-'))) {
- left = binaryFn(left, token.fn, multiplicative());
- }
- return left;
- }
-
- function multiplicative() {
- var left = unary();
- var token;
- while ((token = expect('*','/','%'))) {
- left = binaryFn(left, token.fn, unary());
- }
- return left;
- }
-
- function unary() {
- var token;
- if (expect('+')) {
- return primary();
- } else if ((token = expect('-'))) {
- return binaryFn(ZERO, token.fn, unary());
- } else if ((token = expect('!'))) {
- return unaryFn(token.fn, unary());
- } else {
- return primary();
- }
- }
-
-
- function primary() {
- var primary;
- if (expect('(')) {
- primary = filterChain();
- consume(')');
- } else if (expect('[')) {
- primary = arrayDeclaration();
- } else if (expect('{')) {
- primary = object();
- } else {
- var token = expect();
- primary = token.fn;
- if (!primary) {
- throwError("not a primary expression", token);
- }
- }
-
- var next, context;
- while ((next = expect('(', '[', '.'))) {
- if (next.text === '(') {
- primary = functionCall(primary, context);
- context = null;
- } else if (next.text === '[') {
- context = primary;
- primary = objectIndex(primary);
- } else if (next.text === '.') {
- context = primary;
- primary = fieldAccess(primary);
- } else {
- throwError("IMPOSSIBLE");
- }
- }
- return primary;
- }
-
- function _fieldAccess(object) {
- var field = expect().text;
- var getter = getterFn(field, csp);
- return extend(
- function(self, locals) {
- return getter(object(self, locals), locals);
- },
- {
- assign:function(self, value, locals) {
- return setter(object(self, locals), field, value);
- }
- }
- );
- }
-
- function _objectIndex(obj) {
- var indexFn = expression();
- consume(']');
- return extend(
- function(self, locals){
- var o = obj(self, locals),
- i = indexFn(self, locals),
- v, p;
-
- if (!o) return undefined;
- v = o[i];
- if (v && v.then) {
- p = v;
- if (!('$$v' in v)) {
- p.$$v = undefined;
- p.then(function(val) { p.$$v = val; });
- }
- v = v.$$v;
- }
- return v;
- }, {
- assign:function(self, value, locals){
- return obj(self, locals)[indexFn(self, locals)] = value;
- }
- });
- }
-
- function _functionCall(fn, contextGetter) {
- var argsFn = [];
- if (peekToken().text != ')') {
- do {
- argsFn.push(expression());
- } while (expect(','));
- }
- consume(')');
- return function(self, locals){
- var args = [],
- context = contextGetter ? contextGetter(self, locals) : self;
-
- for ( var i = 0; i < argsFn.length; i++) {
- args.push(argsFn[i](self, locals));
- }
- var fnPtr = fn(self, locals) || noop;
- // IE stupidity!
- return fnPtr.apply
- ? fnPtr.apply(context, args)
- : fnPtr(args[0], args[1], args[2], args[3], args[4]);
- };
- }
-
- // This is used with json array declaration
- function arrayDeclaration () {
- var elementFns = [];
- if (peekToken().text != ']') {
- do {
- elementFns.push(expression());
- } while (expect(','));
- }
- consume(']');
- return function(self, locals){
- var array = [];
- for ( var i = 0; i < elementFns.length; i++) {
- array.push(elementFns[i](self, locals));
- }
- return array;
- };
- }
-
- function object () {
- var keyValues = [];
- if (peekToken().text != '}') {
- do {
- var token = expect(),
- key = token.string || token.text;
- consume(":");
- var value = expression();
- keyValues.push({key:key, value:value});
- } while (expect(','));
- }
- consume('}');
- return function(self, locals){
- var object = {};
- for ( var i = 0; i < keyValues.length; i++) {
- var keyValue = keyValues[i];
- var value = keyValue.value(self, locals);
- object[keyValue.key] = value;
- }
- return object;
- };
- }
-}
-
-//////////////////////////////////////////////////
-// Parser helper functions
-//////////////////////////////////////////////////
-
-function setter(obj, path, setValue) {
- var element = path.split('.');
- for (var i = 0; element.length > 1; i++) {
- var key = element.shift();
- var propertyObj = obj[key];
- if (!propertyObj) {
- propertyObj = {};
- obj[key] = propertyObj;
- }
- obj = propertyObj;
- }
- obj[element.shift()] = setValue;
- return setValue;
-}
-
-/**
- * Return the value accesible from the object by path. Any undefined traversals are ignored
- * @param {Object} obj starting object
- * @param {string} path path to traverse
- * @param {boolean=true} bindFnToScope
- * @returns value as accesbile by path
- */
-//TODO(misko): this function needs to be removed
-function getter(obj, path, bindFnToScope) {
- if (!path) return obj;
- var keys = path.split('.');
- var key;
- var lastInstance = obj;
- var len = keys.length;
-
- for (var i = 0; i < len; i++) {
- key = keys[i];
- if (obj) {
- obj = (lastInstance = obj)[key];
- }
- }
- if (!bindFnToScope && isFunction(obj)) {
- return bind(lastInstance, obj);
- }
- return obj;
-}
-
-var getterFnCache = {};
-
-/**
- * Implementation of the "Black Hole" variant from:
- * - http://jsperf.com/angularjs-parse-getter/4
- * - http://jsperf.com/path-evaluation-simplified/7
- */
-function cspSafeGetterFn(key0, key1, key2, key3, key4) {
- return function(scope, locals) {
- var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope,
- promise;
-
- if (pathVal === null || pathVal === undefined) return pathVal;
-
- pathVal = pathVal[key0];
- if (pathVal && pathVal.then) {
- if (!("$$v" in pathVal)) {
- promise = pathVal;
- promise.$$v = undefined;
- promise.then(function(val) { promise.$$v = val; });
- }
- pathVal = pathVal.$$v;
- }
- if (!key1 || pathVal === null || pathVal === undefined) return pathVal;
-
- pathVal = pathVal[key1];
- if (pathVal && pathVal.then) {
- if (!("$$v" in pathVal)) {
- promise = pathVal;
- promise.$$v = undefined;
- promise.then(function(val) { promise.$$v = val; });
- }
- pathVal = pathVal.$$v;
- }
- if (!key2 || pathVal === null || pathVal === undefined) return pathVal;
-
- pathVal = pathVal[key2];
- if (pathVal && pathVal.then) {
- if (!("$$v" in pathVal)) {
- promise = pathVal;
- promise.$$v = undefined;
- promise.then(function(val) { promise.$$v = val; });
- }
- pathVal = pathVal.$$v;
- }
- if (!key3 || pathVal === null || pathVal === undefined) return pathVal;
-
- pathVal = pathVal[key3];
- if (pathVal && pathVal.then) {
- if (!("$$v" in pathVal)) {
- promise = pathVal;
- promise.$$v = undefined;
- promise.then(function(val) { promise.$$v = val; });
- }
- pathVal = pathVal.$$v;
- }
- if (!key4 || pathVal === null || pathVal === undefined) return pathVal;
-
- pathVal = pathVal[key4];
- if (pathVal && pathVal.then) {
- if (!("$$v" in pathVal)) {
- promise = pathVal;
- promise.$$v = undefined;
- promise.then(function(val) { promise.$$v = val; });
- }
- pathVal = pathVal.$$v;
- }
- return pathVal;
- };
-};
-
-function getterFn(path, csp) {
- if (getterFnCache.hasOwnProperty(path)) {
- return getterFnCache[path];
- }
-
- var pathKeys = path.split('.'),
- pathKeysLength = pathKeys.length,
- fn;
-
- if (csp) {
- fn = (pathKeysLength < 6)
- ? cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4])
- : function(scope, locals) {
- var i = 0, val
- do {
- val = cspSafeGetterFn(
- pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++]
- )(scope, locals);
-
- locals = undefined; // clear after first iteration
- scope = val;
- } while (i < pathKeysLength);
- return val;
- }
- } else {
- var code = 'var l, fn, p;\n';
- forEach(pathKeys, function(key, index) {
- code += 'if(s === null || s === undefined) return s;\n' +
- 'l=s;\n' +
- 's='+ (index
- // we simply dereference 's' on any .dot notation
- ? 's'
- // but if we are first then we check locals first, and if so read it first
- : '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' +
- 'if (s && s.then) {\n' +
- ' if (!("$$v" in s)) {\n' +
- ' p=s;\n' +
- ' p.$$v = undefined;\n' +
- ' p.then(function(v) {p.$$v=v;});\n' +
- '}\n' +
- ' s=s.$$v\n' +
- '}\n';
- });
- code += 'return s;';
- fn = Function('s', 'k', code); // s=scope, k=locals
- fn.toString = function() { return code; };
- }
-
- return getterFnCache[path] = fn;
-}
-
-///////////////////////////////////
-
-/**
- * @ngdoc function
- * @name ng.$parse
- * @function
- *
- * @description
- *
- * Converts Angular {@link guide/expression expression} into a function.
- *
- *
- * var getter = $parse('user.name');
- * var setter = getter.assign;
- * var context = {user:{name:'angular'}};
- * var locals = {user:{name:'local'}};
- *
- * expect(getter(context)).toEqual('angular');
- * setter(context, 'newValue');
- * expect(context.user.name).toEqual('newValue');
- * expect(getter(context, locals)).toEqual('local');
- *
- *
- *
- * @param {string} expression String expression to compile.
- * @returns {function(context, locals)} a function which represents the compiled expression:
- *
- * * `context`: an object against which any expressions embedded in the strings are evaluated
- * against (Topically a scope object).
- * * `locals`: local variables context object, useful for overriding values in `context`.
- *
- * The return function also has an `assign` property, if the expression is assignable, which
- * allows one to set values to expressions.
- *
- */
-function $ParseProvider() {
- var cache = {};
- this.$get = ['$filter', '$sniffer', function($filter, $sniffer) {
- return function(exp) {
- switch(typeof exp) {
- case 'string':
- return cache.hasOwnProperty(exp)
- ? cache[exp]
- : cache[exp] = parser(exp, false, $filter, $sniffer.csp);
- case 'function':
- return exp;
- default:
- return noop;
- }
- };
- }];
-}
-
-/**
- * @ngdoc service
- * @name ng.$q
- * @requires $rootScope
- *
- * @description
- * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q).
- *
- * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
- * interface for interacting with an object that represents the result of an action that is
- * performed asynchronously, and may or may not be finished at any given point in time.
- *
- * From the perspective of dealing with error handling, deferred and promise apis are to
- * asynchronous programing what `try`, `catch` and `throw` keywords are to synchronous programing.
- *
- *
- * // for the purpose of this example let's assume that variables `$q` and `scope` are
- * // available in the current lexical scope (they could have been injected or passed in).
- *
- * function asyncGreet(name) {
- * var deferred = $q.defer();
- *
- * setTimeout(function() {
- * // since this fn executes async in a future turn of the event loop, we need to wrap
- * // our code into an $apply call so that the model changes are properly observed.
- * scope.$apply(function() {
- * if (okToGreet(name)) {
- * deferred.resolve('Hello, ' + name + '!');
- * } else {
- * deferred.reject('Greeting ' + name + ' is not allowed.');
- * }
- * });
- * }, 1000);
- *
- * return deferred.promise;
- * }
- *
- * var promise = asyncGreet('Robin Hood');
- * promise.then(function(greeting) {
- * alert('Success: ' + greeting);
- * }, function(reason) {
- * alert('Failed: ' + reason);
- * );
- *
- *
- * At first it might not be obvious why this extra complexity is worth the trouble. The payoff
- * comes in the way of
- * [guarantees that promise and deferred apis make](https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md).
- *
- * Additionally the promise api allows for composition that is very hard to do with the
- * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
- * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the
- * section on serial or parallel joining of promises.
- *
- *
- * # The Deferred API
- *
- * A new instance of deferred is constructed by calling `$q.defer()`.
- *
- * The purpose of the deferred object is to expose the associated Promise instance as well as apis
- * that can be used for signaling the successful or unsuccessful completion of the task.
- *
- * **Methods**
- *
- * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection
- * constructed via `$q.reject`, the promise will be rejected instead.
- * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to
- * resolving it with a rejection constructed via `$q.reject`.
- *
- * **Properties**
- *
- * - promise – `{Promise}` – promise object associated with this deferred.
- *
- *
- * # The Promise API
- *
- * A new promise instance is created when a deferred instance is created and can be retrieved by
- * calling `deferred.promise`.
- *
- * The purpose of the promise object is to allow for interested parties to get access to the result
- * of the deferred task when it completes.
- *
- * **Methods**
- *
- * - `then(successCallback, errorCallback)` – regardless of when the promise was or will be resolved
- * or rejected calls one of the success or error callbacks asynchronously as soon as the result
- * is available. The callbacks are called with a single argument the result or rejection reason.
- *
- * This method *returns a new promise* which is resolved or rejected via the return value of the
- * `successCallback` or `errorCallback`.
- *
- *
- * # Chaining promises
- *
- * Because calling `then` api of a promise returns a new derived promise, it is easily possible
- * to create a chain of promises:
- *
- *
- * promiseB = promiseA.then(function(result) {
- * return result + 1;
- * });
- *
- * // promiseB will be resolved immediately after promiseA is resolved and it's value will be
- * // the result of promiseA incremented by 1
- *
- *
- * It is possible to create chains of any length and since a promise can be resolved with another
- * promise (which will defer its resolution further), it is possible to pause/defer resolution of
- * the promises at any point in the chain. This makes it possible to implement powerful apis like
- * $http's response interceptors.
- *
- *
- * # Differences between Kris Kowal's Q and $q
- *
- * There are three main differences:
- *
- * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation
- * mechanism in angular, which means faster propagation of resolution or rejection into your
- * models and avoiding unnecessary browser repaints, which would result in flickering UI.
- * - $q promises are recognized by the templating engine in angular, which means that in templates
- * you can treat promises attached to a scope as if they were the resulting values.
- * - Q has many more features that $q, but that comes at a cost of bytes. $q is tiny, but contains
- * all the important functionality needed for common async tasks.
- */
-function $QProvider() {
-
- this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
- return qFactory(function(callback) {
- $rootScope.$evalAsync(callback);
- }, $exceptionHandler);
- }];
-}
-
-
-/**
- * Constructs a promise manager.
- *
- * @param {function(function)} nextTick Function for executing functions in the next turn.
- * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for
- * debugging purposes.
- * @returns {object} Promise manager.
- */
-function qFactory(nextTick, exceptionHandler) {
-
- /**
- * @ngdoc
- * @name ng.$q#defer
- * @methodOf ng.$q
- * @description
- * Creates a `Deferred` object which represents a task which will finish in the future.
- *
- * @returns {Deferred} Returns a new instance of deferred.
- */
- var defer = function() {
- var pending = [],
- value, deferred;
-
- deferred = {
-
- resolve: function(val) {
- if (pending) {
- var callbacks = pending;
- pending = undefined;
- value = ref(val);
-
- if (callbacks.length) {
- nextTick(function() {
- var callback;
- for (var i = 0, ii = callbacks.length; i < ii; i++) {
- callback = callbacks[i];
- value.then(callback[0], callback[1]);
- }
- });
- }
- }
- },
-
-
- reject: function(reason) {
- deferred.resolve(reject(reason));
- },
-
-
- promise: {
- then: function(callback, errback) {
- var result = defer();
-
- var wrappedCallback = function(value) {
- try {
- result.resolve((callback || defaultCallback)(value));
- } catch(e) {
- exceptionHandler(e);
- result.reject(e);
- }
- };
-
- var wrappedErrback = function(reason) {
- try {
- result.resolve((errback || defaultErrback)(reason));
- } catch(e) {
- exceptionHandler(e);
- result.reject(e);
- }
- };
-
- if (pending) {
- pending.push([wrappedCallback, wrappedErrback]);
- } else {
- value.then(wrappedCallback, wrappedErrback);
- }
-
- return result.promise;
- }
- }
- };
-
- return deferred;
- };
-
-
- var ref = function(value) {
- if (value && value.then) return value;
- return {
- then: function(callback) {
- var result = defer();
- nextTick(function() {
- result.resolve(callback(value));
- });
- return result.promise;
- }
- };
- };
-
-
- /**
- * @ngdoc
- * @name ng.$q#reject
- * @methodOf ng.$q
- * @description
- * Creates a promise that is resolved as rejected with the specified `reason`. This api should be
- * used to forward rejection in a chain of promises. If you are dealing with the last promise in
- * a promise chain, you don't need to worry about it.
- *
- * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of
- * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via
- * a promise error callback and you want to forward the error to the promise derived from the
- * current promise, you have to "rethrow" the error by returning a rejection constructed via
- * `reject`.
- *
- *
- * promiseB = promiseA.then(function(result) {
- * // success: do something and resolve promiseB
- * // with the old or a new result
- * return result;
- * }, function(reason) {
- * // error: handle the error if possible and
- * // resolve promiseB with newPromiseOrValue,
- * // otherwise forward the rejection to promiseB
- * if (canHandle(reason)) {
- * // handle the error and recover
- * return newPromiseOrValue;
- * }
- * return $q.reject(reason);
- * });
- *
- *
- * @param {*} reason Constant, message, exception or an object representing the rejection reason.
- * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
- */
- var reject = function(reason) {
- return {
- then: function(callback, errback) {
- var result = defer();
- nextTick(function() {
- result.resolve((errback || defaultErrback)(reason));
- });
- return result.promise;
- }
- };
- };
-
-
- /**
- * @ngdoc
- * @name ng.$q#when
- * @methodOf ng.$q
- * @description
- * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
- * This is useful when you are dealing with on object that might or might not be a promise, or if
- * the promise comes from a source that can't be trusted.
- *
- * @param {*} value Value or a promise
- * @returns {Promise} Returns a single promise that will be resolved with an array of values,
- * each value coresponding to the promise at the same index in the `promises` array. If any of
- * the promises is resolved with a rejection, this resulting promise will be resolved with the
- * same rejection.
- */
- var when = function(value, callback, errback) {
- var result = defer(),
- done;
-
- var wrappedCallback = function(value) {
- try {
- return (callback || defaultCallback)(value);
- } catch (e) {
- exceptionHandler(e);
- return reject(e);
- }
- };
-
- var wrappedErrback = function(reason) {
- try {
- return (errback || defaultErrback)(reason);
- } catch (e) {
- exceptionHandler(e);
- return reject(e);
- }
- };
-
- nextTick(function() {
- ref(value).then(function(value) {
- if (done) return;
- done = true;
- result.resolve(ref(value).then(wrappedCallback, wrappedErrback));
- }, function(reason) {
- if (done) return;
- done = true;
- result.resolve(wrappedErrback(reason));
- });
- });
-
- return result.promise;
- };
-
-
- function defaultCallback(value) {
- return value;
- }
-
-
- function defaultErrback(reason) {
- return reject(reason);
- }
-
-
- /**
- * @ngdoc
- * @name ng.$q#all
- * @methodOf ng.$q
- * @description
- * Combines multiple promises into a single promise that is resolved when all of the input
- * promises are resolved.
- *
- * @param {Array.} promises An array of promises.
- * @returns {Promise} Returns a single promise that will be resolved with an array of values,
- * each value coresponding to the promise at the same index in the `promises` array. If any of
- * the promises is resolved with a rejection, this resulting promise will be resolved with the
- * same rejection.
- */
- function all(promises) {
- var deferred = defer(),
- counter = promises.length,
- results = [];
-
- if (counter) {
- forEach(promises, function(promise, index) {
- ref(promise).then(function(value) {
- if (index in results) return;
- results[index] = value;
- if (!(--counter)) deferred.resolve(results);
- }, function(reason) {
- if (index in results) return;
- deferred.reject(reason);
- });
- });
- } else {
- deferred.resolve(results);
- }
-
- return deferred.promise;
- }
-
- return {
- defer: defer,
- reject: reject,
- when: when,
- all: all
- };
-}
-
-/**
- * @ngdoc object
- * @name ng.$routeProvider
- * @function
- *
- * @description
- *
- * Used for configuring routes. See {@link ng.$route $route} for an example.
- */
-function $RouteProvider(){
- var routes = {};
-
- /**
- * @ngdoc method
- * @name ng.$routeProvider#when
- * @methodOf ng.$routeProvider
- *
- * @param {string} path Route path (matched against `$location.path`). If `$location.path`
- * contains redundant trailing slash or is missing one, the route will still match and the
- * `$location.path` will be updated to add or drop the trailing slash to exacly match the
- * route definition.
- * @param {Object} route Mapping information to be assigned to `$route.current` on route
- * match.
- *
- * Object properties:
- *
- * - `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.
- * - `template` – `{string=}` – html template as a string that should be used by
- * {@link ng.directive:ngView ngView} or
- * {@link ng.directive:ngInclude ngInclude} directives.
- * this property takes precedence over `templateUrl`.
- * - `templateUrl` – `{string=}` – path to an html template that should be used by
- * {@link ng.directive:ngView ngView}.
- * - `resolve` - `{Object.=}` - An optional map of dependencies which should
- * be injected into the controller. If any of these dependencies are promises, they will be
- * resolved and converted to a value before the controller is instantiated and the
- * `$afterRouteChange` event is fired. The map object is:
- *
- * - `key` – `{string}`: a name of a dependency to be injected into the controller.
- * - `factory` - `{string|function}`: If `string` then it is an alias for a service.
- * Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected}
- * and the return value is treated as the dependency. If the result is a promise, it is resolved
- * before its value is injected into the controller.
- *
- * - `redirectTo` – {(string|function())=} – value to update
- * {@link ng.$location $location} path with and trigger route redirection.
- *
- * If `redirectTo` is a function, it will be called with the following parameters:
- *
- * - `{Object.}` - route parameters extracted from the current
- * `$location.path()` by applying the current route templateUrl.
- * - `{string}` - current `$location.path()`
- * - `{Object}` - current `$location.search()`
- *
- * The custom `redirectTo` function is expected to return a string which will be used
- * to update `$location.path()` and `$location.search()`.
- *
- * - `[reloadOnSearch=true]` - {boolean=} - reload route when only $location.search()
- * changes.
- *
- * If the option is set to `false` and url in the browser changes, then
- * `$routeUpdate` event is broadcasted on the root scope.
- *
- * @returns {Object} self
- *
- * @description
- * Adds a new route definition to the `$route` service.
- */
- this.when = function(path, route) {
- routes[path] = extend({reloadOnSearch: true}, route);
-
- // create redirection for trailing slashes
- if (path) {
- var redirectPath = (path[path.length-1] == '/')
- ? path.substr(0, path.length-1)
- : path +'/';
-
- routes[redirectPath] = {redirectTo: path};
- }
-
- return this;
- };
-
- /**
- * @ngdoc method
- * @name ng.$routeProvider#otherwise
- * @methodOf ng.$routeProvider
- *
- * @description
- * Sets route definition that will be used on route change when no other route definition
- * is matched.
- *
- * @param {Object} params Mapping information to be assigned to `$route.current`.
- * @returns {Object} self
- */
- this.otherwise = function(params) {
- this.when(null, params);
- return this;
- };
-
-
- this.$get = ['$rootScope', '$location', '$routeParams', '$q', '$injector', '$http', '$templateCache',
- function( $rootScope, $location, $routeParams, $q, $injector, $http, $templateCache) {
-
- /**
- * @ngdoc object
- * @name ng.$route
- * @requires $location
- * @requires $routeParams
- *
- * @property {Object} current Reference to the current route definition.
- * The route definition contains:
- *
- * - `controller`: The controller constructor as define in route definition.
- * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for
- * controller instantiation. The `locals` contain
- * the resolved values of the `resolve` map. Additionally the `locals` also contain:
- *
- * - `$scope` - The current route scope.
- * - `$template` - The current route template HTML.
- *
- * @property {Array.} routes Array of all configured routes.
- *
- * @description
- * Is used for deep-linking URLs to controllers and views (HTML partials).
- * It watches `$location.url()` and tries to map the path to an existing route definition.
- *
- * You can define routes through {@link ng.$routeProvider $routeProvider}'s API.
- *
- * The `$route` service is typically used in conjunction with {@link ng.directive:ngView ngView}
- * directive and the {@link ng.$routeParams $routeParams} service.
- *
- * @example
- This example shows how changing the URL hash causes the `$route` to match a route against the
- URL, and the `ngView` pulls in the partial.
-
- Note that this example is using {@link ng.directive:script inlined templates}
- to get it working on jsfiddle as well.
-
-
-
-
- Choose:
-
Moby |
-
Moby: Ch1 |
-
Gatsby |
-
Gatsby: Ch4 |
-
Scarlet Letter
-
-
-
-
-
$location.path() = {{$location.path()}}
-
$route.current.templateUrl = {{$route.current.templateUrl}}
-
$route.current.params = {{$route.current.params}}
-
$route.current.scope.name = {{$route.current.scope.name}}
-
$routeParams = {{$routeParams}}
-
-
-
-
- controller: {{name}}
- Book Id: {{params.bookId}}
-
-
-
- controller: {{name}}
- Book Id: {{params.bookId}}
- Chapter Id: {{params.chapterId}}
-
-
-
- angular.module('ngView', [], function($routeProvider, $locationProvider) {
- $routeProvider.when('/Book/:bookId', {
- templateUrl: 'book.html',
- controller: BookCntl,
- resolve: {
- // I will cause a 1 second delay
- delay: function($q, $timeout) {
- var delay = $q.defer();
- $timeout(delay.resolve, 1000);
- return delay.promise;
- }
- }
- });
- $routeProvider.when('/Book/:bookId/ch/:chapterId', {
- templateUrl: 'chapter.html',
- controller: ChapterCntl
- });
-
- // configure html5 to get links working on jsfiddle
- $locationProvider.html5Mode(true);
- });
-
- function MainCntl($scope, $route, $routeParams, $location) {
- $scope.$route = $route;
- $scope.$location = $location;
- $scope.$routeParams = $routeParams;
- }
-
- function BookCntl($scope, $routeParams) {
- $scope.name = "BookCntl";
- $scope.params = $routeParams;
- }
-
- function ChapterCntl($scope, $routeParams) {
- $scope.name = "ChapterCntl";
- $scope.params = $routeParams;
- }
-
-
-
- it('should load and compile correct template', function() {
- element('a:contains("Moby: Ch1")').click();
- var content = element('.doc-example-live [ng-view]').text();
- expect(content).toMatch(/controller\: ChapterCntl/);
- expect(content).toMatch(/Book Id\: Moby/);
- expect(content).toMatch(/Chapter Id\: 1/);
-
- element('a:contains("Scarlet")').click();
- sleep(2); // promises are not part of scenario waiting
- content = element('.doc-example-live [ng-view]').text();
- expect(content).toMatch(/controller\: BookCntl/);
- expect(content).toMatch(/Book Id\: Scarlet/);
- });
-
-
- */
-
- /**
- * @ngdoc event
- * @name ng.$route#$routeChangeStart
- * @eventOf ng.$route
- * @eventType broadcast on root scope
- * @description
- * Broadcasted before a route change. At this point the route services starts
- * resolving all of the dependencies needed for the route change to occurs.
- * Typically this involves fetching the view template as well as any dependencies
- * defined in `resolve` route property. Once all of the dependencies are resolved
- * `$routeChangeSuccess` is fired.
- *
- * @param {Route} next Future route information.
- * @param {Route} current Current route information.
- */
-
- /**
- * @ngdoc event
- * @name ng.$route#$routeChangeSuccess
- * @eventOf ng.$route
- * @eventType broadcast on root scope
- * @description
- * Broadcasted after a route dependencies are resolved.
- * {@link ng.directive:ngView ngView} listens for the directive
- * to instantiate the controller and render the view.
- *
- * @param {Route} current Current route information.
- * @param {Route} previous Previous route information.
- */
-
- /**
- * @ngdoc event
- * @name ng.$route#$routeChangeError
- * @eventOf ng.$route
- * @eventType broadcast on root scope
- * @description
- * Broadcasted if any of the resolve promises are rejected.
- *
- * @param {Route} current Current route information.
- * @param {Route} previous Previous route information.
- * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise.
- */
-
- /**
- * @ngdoc event
- * @name ng.$route#$routeUpdate
- * @eventOf ng.$route
- * @eventType broadcast on root scope
- * @description
- *
- * The `reloadOnSearch` property has been set to false, and we are reusing the same
- * instance of the Controller.
- */
-
- var matcher = switchRouteMatcher,
- forceReload = false,
- $route = {
- routes: routes,
-
- /**
- * @ngdoc method
- * @name ng.$route#reload
- * @methodOf ng.$route
- *
- * @description
- * Causes `$route` service to reload the current route even if
- * {@link ng.$location $location} hasn't changed.
- *
- * As a result of that, {@link ng.directive:ngView ngView}
- * creates new scope, reinstantiates the controller.
- */
- reload: function() {
- forceReload = true;
- $rootScope.$evalAsync(updateRoute);
- }
- };
-
- $rootScope.$on('$locationChangeSuccess', updateRoute);
-
- return $route;
-
- /////////////////////////////////////////////////////
-
- function switchRouteMatcher(on, when) {
- // TODO(i): this code is convoluted and inefficient, we should construct the route matching
- // regex only once and then reuse it
- var regex = '^' + when.replace(/([\.\\\(\)\^\$])/g, "\\$1") + '$',
- params = [],
- dst = {};
- forEach(when.split(/\W/), function(param) {
- if (param) {
- var paramRegExp = new RegExp(":" + param + "([\\W])");
- if (regex.match(paramRegExp)) {
- regex = regex.replace(paramRegExp, "([^\\/]*)$1");
- params.push(param);
- }
- }
- });
- var match = on.match(new RegExp(regex));
- if (match) {
- forEach(params, function(name, index) {
- dst[name] = match[index + 1];
- });
- }
- return match ? dst : null;
- }
-
- function updateRoute() {
- var next = parseRoute(),
- last = $route.current;
-
- if (next && last && next.$route === last.$route
- && equals(next.pathParams, last.pathParams) && !next.reloadOnSearch && !forceReload) {
- last.params = next.params;
- copy(last.params, $routeParams);
- $rootScope.$broadcast('$routeUpdate', last);
- } else if (next || last) {
- forceReload = false;
- $rootScope.$broadcast('$routeChangeStart', next, last);
- $route.current = next;
- if (next) {
- if (next.redirectTo) {
- if (isString(next.redirectTo)) {
- $location.path(interpolate(next.redirectTo, next.params)).search(next.params)
- .replace();
- } else {
- $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search()))
- .replace();
- }
- }
- }
-
- $q.when(next).
- then(function() {
- if (next) {
- var keys = [],
- values = [],
- template;
-
- forEach(next.resolve || {}, function(value, key) {
- keys.push(key);
- values.push(isFunction(value) ? $injector.invoke(value) : $injector.get(value));
- });
- if (isDefined(template = next.template)) {
- } else if (isDefined(template = next.templateUrl)) {
- template = $http.get(template, {cache: $templateCache}).
- then(function(response) { return response.data; });
- }
- if (isDefined(template)) {
- keys.push('$template');
- values.push(template);
- }
- return $q.all(values).then(function(values) {
- var locals = {};
- forEach(values, function(value, index) {
- locals[keys[index]] = value;
- });
- return locals;
- });
- }
- }).
- // after route change
- then(function(locals) {
- if (next == $route.current) {
- if (next) {
- next.locals = locals;
- copy(next.params, $routeParams);
- }
- $rootScope.$broadcast('$routeChangeSuccess', next, last);
- }
- }, function(error) {
- if (next == $route.current) {
- $rootScope.$broadcast('$routeChangeError', next, last, error);
- }
- });
- }
- }
-
-
- /**
- * @returns the current active route, by matching it against the URL
- */
- function parseRoute() {
- // Match a route
- var params, match;
- forEach(routes, function(route, path) {
- if (!match && (params = matcher($location.path(), path))) {
- match = inherit(route, {
- params: extend({}, $location.search(), params),
- pathParams: params});
- match.$route = route;
- }
- });
- // No route matched; fallback to "otherwise" route
- return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}});
- }
-
- /**
- * @returns interpolation of the redirect path with the parametrs
- */
- function interpolate(string, params) {
- var result = [];
- forEach((string||'').split(':'), function(segment, i) {
- if (i == 0) {
- result.push(segment);
- } else {
- var segmentMatch = segment.match(/(\w+)(.*)/);
- var key = segmentMatch[1];
- result.push(params[key]);
- result.push(segmentMatch[2] || '');
- delete params[key];
- }
- });
- return result.join('');
- }
- }];
-}
-
-/**
- * @ngdoc object
- * @name ng.$routeParams
- * @requires $route
- *
- * @description
- * Current set of route parameters. The route parameters are a combination of the
- * {@link ng.$location $location} `search()`, and `path()`. The `path` parameters
- * are extracted when the {@link ng.$route $route} path is matched.
- *
- * In case of parameter name collision, `path` params take precedence over `search` params.
- *
- * The service guarantees that the identity of the `$routeParams` object will remain unchanged
- * (but its properties will likely change) even when a route change occurs.
- *
- * @example
- *
- * // Given:
- * // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
- * // Route: /Chapter/:chapterId/Section/:sectionId
- * //
- * // Then
- * $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
- *
- */
-function $RouteParamsProvider() {
- this.$get = valueFn({});
-}
-
-/**
- * DESIGN NOTES
- *
- * The design decisions behind the scope ware heavily favored for speed and memory consumption.
- *
- * The typical use of scope is to watch the expressions, which most of the time return the same
- * value as last time so we optimize the operation.
- *
- * Closures construction is expensive from speed as well as memory:
- * - no closures, instead ups prototypical inheritance for API
- * - Internal state needs to be stored on scope directly, which means that private state is
- * exposed as $$____ properties
- *
- * Loop operations are optimized by using while(count--) { ... }
- * - this means that in order to keep the same order of execution as addition we have to add
- * items to the array at the begging (shift) instead of at the end (push)
- *
- * Child scopes are created and removed often
- * - Using array would be slow since inserts in meddle are expensive so we use linked list
- *
- * There are few watches then a lot of observers. This is why you don't want the observer to be
- * implemented in the same way as watch. Watch requires return of initialization function which
- * are expensive to construct.
- */
-
-
-/**
- * @ngdoc object
- * @name ng.$rootScopeProvider
- * @description
- *
- * Provider for the $rootScope service.
- */
-
-/**
- * @ngdoc function
- * @name ng.$rootScopeProvider#digestTtl
- * @methodOf ng.$rootScopeProvider
- * @description
- *
- * Sets the number of digest iteration the scope should attempt to execute before giving up and
- * assuming that the model is unstable.
- *
- * The current default is 10 iterations.
- *
- * @param {number} limit The number of digest iterations.
- */
-
-
-/**
- * @ngdoc object
- * @name ng.$rootScope
- * @description
- *
- * Every application has a single root {@link ng.$rootScope.Scope scope}.
- * All other scopes are child scopes of the root scope. Scopes provide mechanism for watching the model and provide
- * event processing life-cycle. See {@link guide/scope developer guide on scopes}.
- */
-function $RootScopeProvider(){
- var TTL = 10;
-
- this.digestTtl = function(value) {
- if (arguments.length) {
- TTL = value;
- }
- return TTL;
- };
-
- this.$get = ['$injector', '$exceptionHandler', '$parse',
- function( $injector, $exceptionHandler, $parse) {
-
- /**
- * @ngdoc function
- * @name ng.$rootScope.Scope
- *
- * @description
- * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the
- * {@link AUTO.$injector $injector}. Child scopes are created using the
- * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when
- * compiled HTML template is executed.)
- *
- * Here is a simple scope snippet to show how you can interact with the scope.
- *
- angular.injector(['ng']).invoke(function($rootScope) {
- var scope = $rootScope.$new();
- scope.salutation = 'Hello';
- scope.name = 'World';
-
- expect(scope.greeting).toEqual(undefined);
-
- scope.$watch('name', function() {
- this.greeting = this.salutation + ' ' + this.name + '!';
- }); // initialize the watch
-
- expect(scope.greeting).toEqual(undefined);
- scope.name = 'Misko';
- // still old value, since watches have not been called yet
- expect(scope.greeting).toEqual(undefined);
-
- scope.$digest(); // fire all the watches
- expect(scope.greeting).toEqual('Hello Misko!');
- });
- *
- *
- * # Inheritance
- * A scope can inherit from a parent scope, as in this example:
- *
- var parent = $rootScope;
- var child = parent.$new();
-
- parent.salutation = "Hello";
- child.name = "World";
- expect(child.salutation).toEqual('Hello');
-
- child.salutation = "Welcome";
- expect(child.salutation).toEqual('Welcome');
- expect(parent.salutation).toEqual('Hello');
- *
- *
- *
- * @param {Object.=} providers Map of service factory which need to be provided
- * for the current scope. Defaults to {@link ng}.
- * @param {Object.=} instanceCache Provides pre-instantiated services which should
- * append/override services provided by `providers`. This is handy when unit-testing and having
- * the need to override a default service.
- * @returns {Object} Newly created scope.
- *
- */
- function Scope() {
- this.$id = nextUid();
- this.$$phase = this.$parent = this.$$watchers =
- this.$$nextSibling = this.$$prevSibling =
- this.$$childHead = this.$$childTail = null;
- this['this'] = this.$root = this;
- this.$$asyncQueue = [];
- this.$$listeners = {};
- }
-
- /**
- * @ngdoc property
- * @name ng.$rootScope.Scope#$id
- * @propertyOf ng.$rootScope.Scope
- * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for
- * debugging.
- */
-
-
- Scope.prototype = {
- /**
- * @ngdoc function
- * @name ng.$rootScope.Scope#$new
- * @methodOf ng.$rootScope.Scope
- * @function
- *
- * @description
- * Creates a new child {@link ng.$rootScope.Scope scope}.
- *
- * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} and
- * {@link ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the scope
- * hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}.
- *
- * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is desired for
- * the scope and its child scopes to be permanently detached from the parent and thus stop
- * participating in model change detection and listener notification by invoking.
- *
- * @param {boolean} isolate if true then the scoped does not prototypically inherit from the
- * parent scope. The scope is isolated, as it can not se parent scope properties.
- * When creating widgets it is useful for the widget to not accidently read parent
- * state.
- *
- * @returns {Object} The newly created child scope.
- *
- */
- $new: function(isolate) {
- var Child,
- child;
-
- if (isFunction(isolate)) {
- // TODO: remove at some point
- throw Error('API-CHANGE: Use $controller to instantiate controllers.');
- }
- if (isolate) {
- child = new Scope();
- child.$root = this.$root;
- } else {
- Child = function() {}; // should be anonymous; This is so that when the minifier munges
- // the name it does not become random set of chars. These will then show up as class
- // name in the debugger.
- Child.prototype = this;
- child = new Child();
- child.$id = nextUid();
- }
- child['this'] = child;
- child.$$listeners = {};
- child.$parent = this;
- child.$$asyncQueue = [];
- child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null;
- child.$$prevSibling = this.$$childTail;
- if (this.$$childHead) {
- this.$$childTail.$$nextSibling = child;
- this.$$childTail = child;
- } else {
- this.$$childHead = this.$$childTail = child;
- }
- return child;
- },
-
- /**
- * @ngdoc function
- * @name ng.$rootScope.Scope#$watch
- * @methodOf ng.$rootScope.Scope
- * @function
- *
- * @description
- * Registers a `listener` callback to be executed whenever the `watchExpression` changes.
- *
- * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest $digest()} and
- * should return the value which will be watched. (Since {@link ng.$rootScope.Scope#$digest $digest()}
- * reruns when it detects changes the `watchExpression` can execute multiple times per
- * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.)
- * - The `listener` is called only when the value from the current `watchExpression` and the
- * previous call to `watchExpression' are not equal (with the exception of the initial run
- * see below). The inequality is determined according to
- * {@link angular.equals} function. To save the value of the object for later comparison
- * {@link angular.copy} function is used. It also means that watching complex options will
- * have adverse memory and performance implications.
- * - The watch `listener` may change the model, which may trigger other `listener`s to fire. This
- * is achieved by rerunning the watchers until no changes are detected. The rerun iteration
- * limit is 100 to prevent infinity loop deadlock.
- *
- *
- * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
- * you can register an `watchExpression` function with no `listener`. (Since `watchExpression`,
- * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a change is
- * detected, be prepared for multiple calls to your listener.)
- *
- * After a watcher is registered with the scope, the `listener` fn is called asynchronously
- * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the
- * watcher. In rare cases, this is undesirable because the listener is called when the result
- * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you
- * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the
- * listener was called due to initialization.
- *
- *
- * # Example
- *
- // let's assume that scope was dependency injected as the $rootScope
- var scope = $rootScope;
- scope.name = 'misko';
- scope.counter = 0;
-
- expect(scope.counter).toEqual(0);
- scope.$watch('name', function(newValue, oldValue) { counter = counter + 1; });
- expect(scope.counter).toEqual(0);
-
- scope.$digest();
- // no variable change
- expect(scope.counter).toEqual(0);
-
- scope.name = 'adam';
- scope.$digest();
- expect(scope.counter).toEqual(1);
- *
- *
- *
- *
- * @param {(function()|string)} watchExpression Expression that is evaluated on each
- * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers a
- * call to the `listener`.
- *
- * - `string`: Evaluated as {@link guide/expression expression}
- * - `function(scope)`: called with current `scope` as a parameter.
- * @param {(function()|string)=} listener Callback called whenever the return value of
- * the `watchExpression` changes.
- *
- * - `string`: Evaluated as {@link guide/expression expression}
- * - `function(newValue, oldValue, scope)`: called with current and previous values as parameters.
- *
- * @param {boolean=} objectEquality Compare object for equality rather then for refference.
- * @returns {function()} Returns a deregistration function for this listener.
- */
- $watch: function(watchExp, listener, objectEquality) {
- var scope = this,
- get = compileToFn(watchExp, 'watch'),
- array = scope.$$watchers,
- watcher = {
- fn: listener,
- last: initWatchVal,
- get: get,
- exp: watchExp,
- eq: !!objectEquality
- };
-
- // in the case user pass string, we need to compile it, do we really need this ?
- if (!isFunction(listener)) {
- var listenFn = compileToFn(listener || noop, 'listener');
- watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);};
- }
-
- if (!array) {
- array = scope.$$watchers = [];
- }
- // we use unshift since we use a while loop in $digest for speed.
- // the while loop reads in reverse order.
- array.unshift(watcher);
-
- return function() {
- arrayRemove(array, watcher);
- };
- },
-
- /**
- * @ngdoc function
- * @name ng.$rootScope.Scope#$digest
- * @methodOf ng.$rootScope.Scope
- * @function
- *
- * @description
- * Process all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and its children.
- * Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change the model, the
- * `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} until no more listeners are
- * firing. This means that it is possible to get into an infinite loop. This function will throw
- * `'Maximum iteration limit exceeded.'` if the number of iterations exceeds 10.
- *
- * Usually you don't call `$digest()` directly in
- * {@link ng.directive:ngController controllers} or in
- * {@link ng.$compileProvider#directive directives}.
- * Instead a call to {@link ng.$rootScope.Scope#$apply $apply()} (typically from within a
- * {@link ng.$compileProvider#directive directives}) will force a `$digest()`.
- *
- * If you want to be notified whenever `$digest()` is called,
- * you can register a `watchExpression` function with {@link ng.$rootScope.Scope#$watch $watch()}
- * with no `listener`.
- *
- * You may have a need to call `$digest()` from within unit-tests, to simulate the scope
- * life-cycle.
- *
- * # Example
- *
- var scope = ...;
- scope.name = 'misko';
- scope.counter = 0;
-
- expect(scope.counter).toEqual(0);
- scope.$watch('name', function(newValue, oldValue) {
- counter = counter + 1;
- });
- expect(scope.counter).toEqual(0);
-
- scope.$digest();
- // no variable change
- expect(scope.counter).toEqual(0);
-
- scope.name = 'adam';
- scope.$digest();
- expect(scope.counter).toEqual(1);
- *
- *
- */
- $digest: function() {
- var watch, value, last,
- watchers,
- asyncQueue,
- length,
- dirty, ttl = TTL,
- next, current, target = this,
- watchLog = [],
- logIdx, logMsg;
-
- beginPhase('$digest');
-
- do {
- dirty = false;
- current = target;
- do {
- asyncQueue = current.$$asyncQueue;
- while(asyncQueue.length) {
- try {
- current.$eval(asyncQueue.shift());
- } catch (e) {
- $exceptionHandler(e);
- }
- }
- if ((watchers = current.$$watchers)) {
- // process our watches
- length = watchers.length;
- while (length--) {
- try {
- watch = watchers[length];
- // Most common watches are on primitives, in which case we can short
- // circuit it with === operator, only when === fails do we use .equals
- if ((value = watch.get(current)) !== (last = watch.last) &&
- !(watch.eq
- ? equals(value, last)
- : (typeof value == 'number' && typeof last == 'number'
- && isNaN(value) && isNaN(last)))) {
- dirty = true;
- watch.last = watch.eq ? copy(value) : value;
- watch.fn(value, ((last === initWatchVal) ? value : last), current);
- if (ttl < 5) {
- logIdx = 4 - ttl;
- if (!watchLog[logIdx]) watchLog[logIdx] = [];
- logMsg = (isFunction(watch.exp))
- ? 'fn: ' + (watch.exp.name || watch.exp.toString())
- : watch.exp;
- logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
- watchLog[logIdx].push(logMsg);
- }
- }
- } catch (e) {
- $exceptionHandler(e);
- }
- }
- }
-
- // Insanity Warning: scope depth-first traversal
- // yes, this code is a bit crazy, but it works and we have tests to prove it!
- // this piece should be kept in sync with the traversal in $broadcast
- if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) {
- while(current !== target && !(next = current.$$nextSibling)) {
- current = current.$parent;
- }
- }
- } while ((current = next));
-
- if(dirty && !(ttl--)) {
- clearPhase();
- throw Error(TTL + ' $digest() iterations reached. Aborting!\n' +
- 'Watchers fired in the last 5 iterations: ' + toJson(watchLog));
- }
- } while (dirty || asyncQueue.length);
-
- clearPhase();
- },
-
-
- /**
- * @ngdoc event
- * @name ng.$rootScope.Scope#$destroy
- * @eventOf ng.$rootScope.Scope
- * @eventType broadcast on scope being destroyed
- *
- * @description
- * Broadcasted when a scope and its children are being destroyed.
- */
-
- /**
- * @ngdoc function
- * @name ng.$rootScope.Scope#$destroy
- * @methodOf ng.$rootScope.Scope
- * @function
- *
- * @description
- * Remove the current scope (and all of its children) from the parent scope. Removal implies
- * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer
- * propagate to the current scope and its children. Removal also implies that the current
- * scope is eligible for garbage collection.
- *
- * The `$destroy()` is usually used by directives such as
- * {@link ng.directive:ngRepeat ngRepeat} for managing the
- * unrolling of the loop.
- *
- * Just before a scope is destroyed a `$destroy` event is broadcasted on this scope.
- * Application code can register a `$destroy` event handler that will give it chance to
- * perform any necessary cleanup.
- */
- $destroy: function() {
- if ($rootScope == this) return; // we can't remove the root node;
- var parent = this.$parent;
-
- this.$broadcast('$destroy');
-
- if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
- if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
- if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
- if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
- },
-
- /**
- * @ngdoc function
- * @name ng.$rootScope.Scope#$eval
- * @methodOf ng.$rootScope.Scope
- * @function
- *
- * @description
- * Executes the `expression` on the current scope returning the result. Any exceptions in the
- * expression are propagated (uncaught). This is useful when evaluating engular expressions.
- *
- * # Example
- *
- var scope = ng.$rootScope.Scope();
- scope.a = 1;
- scope.b = 2;
-
- expect(scope.$eval('a+b')).toEqual(3);
- expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
- *
- *
- * @param {(string|function())=} expression An angular expression to be executed.
- *
- * - `string`: execute using the rules as defined in {@link guide/expression expression}.
- * - `function(scope)`: execute the function with the current `scope` parameter.
- *
- * @returns {*} The result of evaluating the expression.
- */
- $eval: function(expr, locals) {
- return $parse(expr)(this, locals);
- },
-
- /**
- * @ngdoc function
- * @name ng.$rootScope.Scope#$evalAsync
- * @methodOf ng.$rootScope.Scope
- * @function
- *
- * @description
- * Executes the expression on the current scope at a later point in time.
- *
- * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only that:
- *
- * - it will execute in the current script execution context (before any DOM rendering).
- * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after
- * `expression` execution.
- *
- * Any exceptions from the execution of the expression are forwarded to the
- * {@link ng.$exceptionHandler $exceptionHandler} service.
- *
- * @param {(string|function())=} expression An angular expression to be executed.
- *
- * - `string`: execute using the rules as defined in {@link guide/expression expression}.
- * - `function(scope)`: execute the function with the current `scope` parameter.
- *
- */
- $evalAsync: function(expr) {
- this.$$asyncQueue.push(expr);
- },
-
- /**
- * @ngdoc function
- * @name ng.$rootScope.Scope#$apply
- * @methodOf ng.$rootScope.Scope
- * @function
- *
- * @description
- * `$apply()` is used to execute an expression in angular from outside of the angular framework.
- * (For example from browser DOM events, setTimeout, XHR or third party libraries).
- * Because we are calling into the angular framework we need to perform proper scope life-cycle
- * of {@link ng.$exceptionHandler exception handling},
- * {@link ng.$rootScope.Scope#$digest executing watches}.
- *
- * ## Life cycle
- *
- * # Pseudo-Code of `$apply()`
- *
- function $apply(expr) {
- try {
- return $eval(expr);
- } catch (e) {
- $exceptionHandler(e);
- } finally {
- $root.$digest();
- }
- }
- *
- *
- *
- * Scope's `$apply()` method transitions through the following stages:
- *
- * 1. The {@link guide/expression expression} is executed using the
- * {@link ng.$rootScope.Scope#$eval $eval()} method.
- * 2. Any exceptions from the execution of the expression are forwarded to the
- * {@link ng.$exceptionHandler $exceptionHandler} service.
- * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the expression
- * was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method.
- *
- *
- * @param {(string|function())=} exp An angular expression to be executed.
- *
- * - `string`: execute using the rules as defined in {@link guide/expression expression}.
- * - `function(scope)`: execute the function with current `scope` parameter.
- *
- * @returns {*} The result of evaluating the expression.
- */
- $apply: function(expr) {
- try {
- beginPhase('$apply');
- return this.$eval(expr);
- } catch (e) {
- $exceptionHandler(e);
- } finally {
- clearPhase();
- try {
- $rootScope.$digest();
- } catch (e) {
- $exceptionHandler(e);
- throw e;
- }
- }
- },
-
- /**
- * @ngdoc function
- * @name ng.$rootScope.Scope#$on
- * @methodOf ng.$rootScope.Scope
- * @function
- *
- * @description
- * Listen on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for discussion of
- * event life cycle.
- *
- * @param {string} name Event name to listen on.
- * @param {function(event)} listener Function to call when the event is emitted.
- * @returns {function()} Returns a deregistration function for this listener.
- *
- * The event listener function format is: `function(event, args...)`. The `event` object
- * passed into the listener has the following attributes:
- *
- * - `targetScope` - {Scope}: the scope on which the event was `$emit`-ed or `$broadcast`-ed.
- * - `currentScope` - {Scope}: the current scope which is handling the event.
- * - `name` - {string}: Name of the event.
- * - `stopPropagation` - {function=}: calling `stopPropagation` function will cancel further event propagation
- * (available only for events that were `$emit`-ed).
- * - `preventDefault` - {function}: calling `preventDefault` sets `defaultPrevented` flag to true.
- * - `defaultPrevented` - {boolean}: true if `preventDefault` was called.
- */
- $on: function(name, listener) {
- var namedListeners = this.$$listeners[name];
- if (!namedListeners) {
- this.$$listeners[name] = namedListeners = [];
- }
- namedListeners.push(listener);
-
- return function() {
- arrayRemove(namedListeners, listener);
- };
- },
-
-
- /**
- * @ngdoc function
- * @name ng.$rootScope.Scope#$emit
- * @methodOf ng.$rootScope.Scope
- * @function
- *
- * @description
- * Dispatches an event `name` upwards through the scope hierarchy notifying the
- * registered {@link ng.$rootScope.Scope#$on} listeners.
- *
- * The event life cycle starts at the scope on which `$emit` was called. All
- * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get notified.
- * Afterwards, the event traverses upwards toward the root scope and calls all registered
- * listeners along the way. The event will stop propagating if one of the listeners cancels it.
- *
- * Any exception emmited from the {@link ng.$rootScope.Scope#$on listeners} will be passed
- * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
- *
- * @param {string} name Event name to emit.
- * @param {...*} args Optional set of arguments which will be passed onto the event listeners.
- * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
- */
- $emit: function(name, args) {
- var empty = [],
- namedListeners,
- scope = this,
- stopPropagation = false,
- event = {
- name: name,
- targetScope: scope,
- stopPropagation: function() {stopPropagation = true;},
- preventDefault: function() {
- event.defaultPrevented = true;
- },
- defaultPrevented: false
- },
- listenerArgs = concat([event], arguments, 1),
- i, length;
-
- do {
- namedListeners = scope.$$listeners[name] || empty;
- event.currentScope = scope;
- for (i=0, length=namedListeners.length; i 7),
- hasEvent: function(event) {
- // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
- // it. In particular the event is not fired when backspace or delete key are pressed or
- // when cut operation is performed.
- if (event == 'input' && msie == 9) return false;
-
- if (isUndefined(eventSupport[event])) {
- var divElm = $window.document.createElement('div');
- eventSupport[event] = 'on' + event in divElm;
- }
-
- return eventSupport[event];
- },
- // TODO(i): currently there is no way to feature detect CSP without triggering alerts
- csp: false
- };
- }];
-}
-
-/**
- * @ngdoc object
- * @name ng.$window
- *
- * @description
- * A reference to the browser's `window` object. While `window`
- * is globally available in JavaScript, it causes testability problems, because
- * it is a global variable. In angular we always refer to it through the
- * `$window` service, so it may be overriden, removed or mocked for testing.
- *
- * All expressions are evaluated with respect to current scope so they don't
- * suffer from window globality.
- *
- * @example
-
-
-
- ALERT
-
-
-
-
- */
-function $WindowProvider(){
- this.$get = valueFn(window);
-}
-
-/**
- * Parse headers into key value object
- *
- * @param {string} headers Raw headers as a string
- * @returns {Object} Parsed headers as key value object
- */
-function parseHeaders(headers) {
- var parsed = {}, key, val, i;
-
- if (!headers) return parsed;
-
- forEach(headers.split('\n'), function(line) {
- i = line.indexOf(':');
- key = lowercase(trim(line.substr(0, i)));
- val = trim(line.substr(i + 1));
-
- if (key) {
- if (parsed[key]) {
- parsed[key] += ', ' + val;
- } else {
- parsed[key] = val;
- }
- }
- });
-
- return parsed;
-}
-
-
-/**
- * Returns a function that provides access to parsed headers.
- *
- * Headers are lazy parsed when first requested.
- * @see parseHeaders
- *
- * @param {(string|Object)} headers Headers to provide access to.
- * @returns {function(string=)} Returns a getter function which if called with:
- *
- * - if called with single an argument returns a single header value or null
- * - if called with no arguments returns an object containing all headers.
- */
-function headersGetter(headers) {
- var headersObj = isObject(headers) ? headers : undefined;
-
- return function(name) {
- if (!headersObj) headersObj = parseHeaders(headers);
-
- if (name) {
- return headersObj[lowercase(name)] || null;
- }
-
- return headersObj;
- };
-}
-
-
-/**
- * Chain all given functions
- *
- * This function is used for both request and response transforming
- *
- * @param {*} data Data to transform.
- * @param {function(string=)} headers Http headers getter fn.
- * @param {(function|Array.)} fns Function or an array of functions.
- * @returns {*} Transformed data.
- */
-function transformData(data, headers, fns) {
- if (isFunction(fns))
- return fns(data, headers);
-
- forEach(fns, function(fn) {
- data = fn(data, headers);
- });
-
- return data;
-}
-
-
-function isSuccess(status) {
- return 200 <= status && status < 300;
-}
-
-
-function $HttpProvider() {
- var JSON_START = /^\s*(\[|\{[^\{])/,
- JSON_END = /[\}\]]\s*$/,
- PROTECTION_PREFIX = /^\)\]\}',?\n/;
-
- var $config = this.defaults = {
- // transform incoming response data
- transformResponse: [function(data) {
- if (isString(data)) {
- // strip json vulnerability protection prefix
- data = data.replace(PROTECTION_PREFIX, '');
- if (JSON_START.test(data) && JSON_END.test(data))
- data = fromJson(data, true);
- }
- return data;
- }],
-
- // transform outgoing request data
- transformRequest: [function(d) {
- return isObject(d) && !isFile(d) ? toJson(d) : d;
- }],
-
- // default headers
- headers: {
- common: {
- 'Accept': 'application/json, text/plain, */*',
- 'X-Requested-With': 'XMLHttpRequest'
- },
- post: {'Content-Type': 'application/json;charset=utf-8'},
- put: {'Content-Type': 'application/json;charset=utf-8'}
- }
- };
-
- var providerResponseInterceptors = this.responseInterceptors = [];
-
- this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector',
- function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) {
-
- var defaultCache = $cacheFactory('$http'),
- responseInterceptors = [];
-
- forEach(providerResponseInterceptors, function(interceptor) {
- responseInterceptors.push(
- isString(interceptor)
- ? $injector.get(interceptor)
- : $injector.invoke(interceptor)
- );
- });
-
-
- /**
- * @ngdoc function
- * @name ng.$http
- * @requires $httpBacked
- * @requires $browser
- * @requires $cacheFactory
- * @requires $rootScope
- * @requires $q
- * @requires $injector
- *
- * @description
- * The `$http` service is a core Angular service that facilitates communication with the remote
- * HTTP servers via browser's {@link https://developer.mozilla.org/en/xmlhttprequest
- * XMLHttpRequest} object or via {@link http://en.wikipedia.org/wiki/JSONP JSONP}.
- *
- * For unit testing applications that use `$http` service, see
- * {@link ngMock.$httpBackend $httpBackend mock}.
- *
- * For a higher level of abstraction, please check out the {@link ngResource.$resource
- * $resource} service.
- *
- * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by
- * the $q service. While for simple usage patters this doesn't matter much, for advanced usage,
- * it is important to familiarize yourself with these apis and guarantees they provide.
- *
- *
- * # General usage
- * The `$http` service is a function which takes a single argument — a configuration object —
- * that is used to generate an http request and returns a {@link ng.$q promise}
- * with two $http specific methods: `success` and `error`.
- *
- *
- * $http({method: 'GET', url: '/someUrl'}).
- * success(function(data, status, headers, config) {
- * // this callback will be called asynchronously
- * // when the response is available
- * }).
- * error(function(data, status, headers, config) {
- * // called asynchronously if an error occurs
- * // or server returns response with status
- * // code outside of the <200, 400) range
- * });
- *
- *
- * Since the returned value of calling the $http function is a Promise object, you can also use
- * the `then` method to register callbacks, and these callbacks will receive a single argument –
- * an object representing the response. See the api signature and type info below for more
- * details.
- *
- *
- * # Shortcut methods
- *
- * Since all invocation of the $http service require definition of the http method and url and
- * POST and PUT requests require response body/data to be provided as well, shortcut methods
- * were created to simplify using the api:
- *
- *
- * $http.get('/someUrl').success(successCallback);
- * $http.post('/someUrl', data).success(successCallback);
- *
- *
- * Complete list of shortcut methods:
- *
- * - {@link ng.$http#get $http.get}
- * - {@link ng.$http#head $http.head}
- * - {@link ng.$http#post $http.post}
- * - {@link ng.$http#put $http.put}
- * - {@link ng.$http#delete $http.delete}
- * - {@link ng.$http#jsonp $http.jsonp}
- *
- *
- * # Setting HTTP Headers
- *
- * The $http service will automatically add certain http headers to all requests. These defaults
- * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration
- * object, which currently contains this default configuration:
- *
- * - `$httpProvider.defaults.headers.common` (headers that are common for all requests):
- * - `Accept: application/json, text/plain, * / *`
- * - `X-Requested-With: XMLHttpRequest`
- * - `$httpProvider.defaults.headers.post`: (header defaults for HTTP POST requests)
- * - `Content-Type: application/json`
- * - `$httpProvider.defaults.headers.put` (header defaults for HTTP PUT requests)
- * - `Content-Type: application/json`
- *
- * To add or overwrite these defaults, simply add or remove a property from this configuration
- * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
- * with name equal to the lower-cased http method name, e.g.
- * `$httpProvider.defaults.headers.get['My-Header']='value'`.
- *
- * Additionally, the defaults can be set at runtime via the `$http.defaults` object in a similar
- * fassion as described above.
- *
- *
- * # Transforming Requests and Responses
- *
- * Both requests and responses can be transformed using transform functions. By default, Angular
- * applies these transformations:
- *
- * Request transformations:
- *
- * - if the `data` property of the request config object contains an object, serialize it into
- * JSON format.
- *
- * Response transformations:
- *
- * - if XSRF prefix is detected, strip it (see Security Considerations section below)
- * - if json response is detected, deserialize it using a JSON parser
- *
- * To override these transformation locally, specify transform functions as `transformRequest`
- * and/or `transformResponse` properties of the config object. To globally override the default
- * transforms, override the `$httpProvider.defaults.transformRequest` and
- * `$httpProvider.defaults.transformResponse` properties of the `$httpProvider`.
- *
- *
- * # Caching
- *
- * To enable caching set the configuration property `cache` to `true`. When the cache is
- * enabled, `$http` stores the response from the server in local cache. Next time the
- * response is served from the cache without sending a request to the server.
- *
- * Note that even if the response is served from cache, delivery of the data is asynchronous in
- * the same way that real requests are.
- *
- * If there are multiple GET requests for the same url that should be cached using the same
- * cache, but the cache is not populated yet, only one request to the server will be made and
- * the remaining requests will be fulfilled using the response for the first request.
- *
- *
- * # Response interceptors
- *
- * Before you start creating interceptors, be sure to understand the
- * {@link ng.$q $q and deferred/promise APIs}.
- *
- * For purposes of global error handling, authentication or any kind of synchronous or
- * asynchronous preprocessing of received responses, it is desirable to be able to intercept
- * responses for http requests before they are handed over to the application code that
- * initiated these requests. The response interceptors leverage the {@link ng.$q
- * promise apis} to fulfil this need for both synchronous and asynchronous preprocessing.
- *
- * The interceptors are service factories that are registered with the $httpProvider by
- * adding them to the `$httpProvider.responseInterceptors` array. The factory is called and
- * injected with dependencies (if specified) and returns the interceptor — a function that
- * takes a {@link ng.$q promise} and returns the original or a new promise.
- *
- *
- * // register the interceptor as a service
- * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
- * return function(promise) {
- * return promise.then(function(response) {
- * // do something on success
- * }, function(response) {
- * // do something on error
- * if (canRecover(response)) {
- * return responseOrNewPromise
- * }
- * return $q.reject(response);
- * });
- * }
- * });
- *
- * $httpProvider.responseInterceptors.push('myHttpInterceptor');
- *
- *
- * // register the interceptor via an anonymous factory
- * $httpProvider.responseInterceptors.push(function($q, dependency1, dependency2) {
- * return function(promise) {
- * // same as above
- * }
- * });
- *
- *
- *
- * # Security Considerations
- *
- * When designing web applications, consider security threats from:
- *
- * - {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx
- * JSON Vulnerability}
- * - {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF}
- *
- * Both server and the client must cooperate in order to eliminate these threats. Angular comes
- * pre-configured with strategies that address these issues, but for this to work backend server
- * cooperation is required.
- *
- * ## JSON Vulnerability Protection
- *
- * A {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx
- * JSON Vulnerability} allows third party web-site to turn your JSON resource URL into
- * {@link http://en.wikipedia.org/wiki/JSON#JSONP JSONP} request under some conditions. To
- * counter this your server can prefix all JSON requests with following string `")]}',\n"`.
- * Angular will automatically strip the prefix before processing it as JSON.
- *
- * For example if your server needs to return:
- *
- * ['one','two']
- *
- *
- * which is vulnerable to attack, your server can return:
- *
- * )]}',
- * ['one','two']
- *
- *
- * Angular will strip the prefix, before processing the JSON.
- *
- *
- * ## Cross Site Request Forgery (XSRF) Protection
- *
- * {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} is a technique by which
- * an unauthorized site can gain your user's private data. Angular provides following mechanism
- * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie
- * called `XSRF-TOKEN` and sets it as the HTTP header `X-XSRF-TOKEN`. Since only JavaScript that
- * runs on your domain could read the cookie, your server can be assured that the XHR came from
- * JavaScript running on your domain.
- *
- * To take advantage of this, your server needs to set a token in a JavaScript readable session
- * cookie called `XSRF-TOKEN` on first HTTP GET request. On subsequent non-GET requests the
- * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure
- * that only JavaScript running on your domain could have read the token. The token must be
- * unique for each user and must be verifiable by the server (to prevent the JavaScript making
- * up its own tokens). We recommend that the token is a digest of your site's authentication
- * cookie with {@link http://en.wikipedia.org/wiki/Rainbow_table salt for added security}.
- *
- *
- * @param {object} config Object describing the request to be made and how it should be
- * processed. The object has following properties:
- *
- * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
- * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested.
- * - **params** – `{Object.}` – Map of strings or objects which will be turned to
- * `?key1=value1&key2=value2` after the url. If the value is not a string, it will be JSONified.
- * - **data** – `{string|Object}` – Data to be sent as the request message data.
- * - **headers** – `{Object}` – Map of strings representing HTTP headers to send to the server.
- * - **transformRequest** – `{function(data, headersGetter)|Array.}` –
- * transform function or an array of such functions. The transform function takes the http
- * request body and headers and returns its transformed (typically serialized) version.
- * - **transformResponse** – `{function(data, headersGetter)|Array.}` –
- * transform function or an array of such functions. The transform function takes the http
- * response body and headers and returns its transformed (typically deserialized) version.
- * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
- * GET request, otherwise if a cache instance built with
- * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
- * caching.
- * - **timeout** – `{number}` – timeout in milliseconds.
- * - **withCredentials** - `{boolean}` - whether to to set the `withCredentials` flag on the
- * XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5
- * requests with credentials} for more information.
- *
- * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the
- * standard `then` method and two http specific methods: `success` and `error`. The `then`
- * method takes two arguments a success and an error callback which will be called with a
- * response object. The `success` and `error` methods take a single argument - a function that
- * will be called when the request succeeds or fails respectively. The arguments passed into
- * these functions are destructured representation of the response object passed into the
- * `then` method. The response object has these properties:
- *
- * - **data** – `{string|Object}` – The response body transformed with the transform functions.
- * - **status** – `{number}` – HTTP status code of the response.
- * - **headers** – `{function([headerName])}` – Header getter function.
- * - **config** – `{Object}` – The configuration object that was used to generate the request.
- *
- * @property {Array.} pendingRequests Array of config objects for currently pending
- * requests. This is primarily meant to be used for debugging purposes.
- *
- *
- * @example
-
-
-
-
- GET
- JSONP
-
-
-
fetch
-
Sample GET
-
Sample JSONP
-
Invalid JSONP
-
http status code: {{status}}
-
http response data: {{data}}
-
-
-
- function FetchCtrl($scope, $http, $templateCache) {
- $scope.method = 'GET';
- $scope.url = 'http-hello.html';
-
- $scope.fetch = function() {
- $scope.code = null;
- $scope.response = null;
-
- $http({method: $scope.method, url: $scope.url, cache: $templateCache}).
- success(function(data, status) {
- $scope.status = status;
- $scope.data = data;
- }).
- error(function(data, status) {
- $scope.data = data || "Request failed";
- $scope.status = status;
- });
- };
-
- $scope.updateModel = function(method, url) {
- $scope.method = method;
- $scope.url = url;
- };
- }
-
-
- Hello, $http!
-
-
- it('should make an xhr GET request', function() {
- element(':button:contains("Sample GET")').click();
- element(':button:contains("fetch")').click();
- expect(binding('status')).toBe('200');
- expect(binding('data')).toMatch(/Hello, \$http!/);
- });
-
- it('should make a JSONP request to angularjs.org', function() {
- element(':button:contains("Sample JSONP")').click();
- element(':button:contains("fetch")').click();
- expect(binding('status')).toBe('200');
- expect(binding('data')).toMatch(/Super Hero!/);
- });
-
- it('should make JSONP request to invalid URL and invoke the error handler',
- function() {
- element(':button:contains("Invalid JSONP")').click();
- element(':button:contains("fetch")').click();
- expect(binding('status')).toBe('0');
- expect(binding('data')).toBe('Request failed');
- });
-
-
- */
- function $http(config) {
- config.method = uppercase(config.method);
-
- var reqTransformFn = config.transformRequest || $config.transformRequest,
- respTransformFn = config.transformResponse || $config.transformResponse,
- defHeaders = $config.headers,
- reqHeaders = extend({'X-XSRF-TOKEN': $browser.cookies()['XSRF-TOKEN']},
- defHeaders.common, defHeaders[lowercase(config.method)], config.headers),
- reqData = transformData(config.data, headersGetter(reqHeaders), reqTransformFn),
- promise;
-
- // strip content-type if data is undefined
- if (isUndefined(config.data)) {
- delete reqHeaders['Content-Type'];
- }
-
- // send request
- promise = sendReq(config, reqData, reqHeaders);
-
-
- // transform future response
- promise = promise.then(transformResponse, transformResponse);
-
- // apply interceptors
- forEach(responseInterceptors, function(interceptor) {
- promise = interceptor(promise);
- });
-
- promise.success = function(fn) {
- promise.then(function(response) {
- fn(response.data, response.status, response.headers, config);
- });
- return promise;
- };
-
- promise.error = function(fn) {
- promise.then(null, function(response) {
- fn(response.data, response.status, response.headers, config);
- });
- return promise;
- };
-
- return promise;
-
- function transformResponse(response) {
- // make a copy since the response must be cacheable
- var resp = extend({}, response, {
- data: transformData(response.data, response.headers, respTransformFn)
- });
- return (isSuccess(response.status))
- ? resp
- : $q.reject(resp);
- }
- }
-
- $http.pendingRequests = [];
-
- /**
- * @ngdoc method
- * @name ng.$http#get
- * @methodOf ng.$http
- *
- * @description
- * Shortcut method to perform `GET` request
- *
- * @param {string} url Relative or absolute URL specifying the destination of the request
- * @param {Object=} config Optional configuration object
- * @returns {HttpPromise} Future object
- */
-
- /**
- * @ngdoc method
- * @name ng.$http#delete
- * @methodOf ng.$http
- *
- * @description
- * Shortcut method to perform `DELETE` request
- *
- * @param {string} url Relative or absolute URL specifying the destination of the request
- * @param {Object=} config Optional configuration object
- * @returns {HttpPromise} Future object
- */
-
- /**
- * @ngdoc method
- * @name ng.$http#head
- * @methodOf ng.$http
- *
- * @description
- * Shortcut method to perform `HEAD` request
- *
- * @param {string} url Relative or absolute URL specifying the destination of the request
- * @param {Object=} config Optional configuration object
- * @returns {HttpPromise} Future object
- */
-
- /**
- * @ngdoc method
- * @name ng.$http#jsonp
- * @methodOf ng.$http
- *
- * @description
- * Shortcut method to perform `JSONP` request
- *
- * @param {string} url Relative or absolute URL specifying the destination of the request.
- * Should contain `JSON_CALLBACK` string.
- * @param {Object=} config Optional configuration object
- * @returns {HttpPromise} Future object
- */
- createShortMethods('get', 'delete', 'head', 'jsonp');
-
- /**
- * @ngdoc method
- * @name ng.$http#post
- * @methodOf ng.$http
- *
- * @description
- * Shortcut method to perform `POST` request
- *
- * @param {string} url Relative or absolute URL specifying the destination of the request
- * @param {*} data Request content
- * @param {Object=} config Optional configuration object
- * @returns {HttpPromise} Future object
- */
-
- /**
- * @ngdoc method
- * @name ng.$http#put
- * @methodOf ng.$http
- *
- * @description
- * Shortcut method to perform `PUT` request
- *
- * @param {string} url Relative or absolute URL specifying the destination of the request
- * @param {*} data Request content
- * @param {Object=} config Optional configuration object
- * @returns {HttpPromise} Future object
- */
- createShortMethodsWithData('post', 'put');
-
- /**
- * @ngdoc property
- * @name ng.$http#defaults
- * @propertyOf ng.$http
- *
- * @description
- * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
- * default headers as well as request and response transformations.
- *
- * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
- */
- $http.defaults = $config;
-
-
- return $http;
-
-
- function createShortMethods(names) {
- forEach(arguments, function(name) {
- $http[name] = function(url, config) {
- return $http(extend(config || {}, {
- method: name,
- url: url
- }));
- };
- });
- }
-
-
- function createShortMethodsWithData(name) {
- forEach(arguments, function(name) {
- $http[name] = function(url, data, config) {
- return $http(extend(config || {}, {
- method: name,
- url: url,
- data: data
- }));
- };
- });
- }
-
-
- /**
- * Makes the request
- *
- * !!! ACCESSES CLOSURE VARS:
- * $httpBackend, $config, $log, $rootScope, defaultCache, $http.pendingRequests
- */
- function sendReq(config, reqData, reqHeaders) {
- var deferred = $q.defer(),
- promise = deferred.promise,
- cache,
- cachedResp,
- url = buildUrl(config.url, config.params);
-
- $http.pendingRequests.push(config);
- promise.then(removePendingReq, removePendingReq);
-
-
- if (config.cache && config.method == 'GET') {
- cache = isObject(config.cache) ? config.cache : defaultCache;
- }
-
- if (cache) {
- cachedResp = cache.get(url);
- if (cachedResp) {
- if (cachedResp.then) {
- // cached request has already been sent, but there is no response yet
- cachedResp.then(removePendingReq, removePendingReq);
- return cachedResp;
- } else {
- // serving from cache
- if (isArray(cachedResp)) {
- resolvePromise(cachedResp[1], cachedResp[0], copy(cachedResp[2]));
- } else {
- resolvePromise(cachedResp, 200, {});
- }
- }
- } else {
- // put the promise for the non-transformed response into cache as a placeholder
- cache.put(url, promise);
- }
- }
-
- // if we won't have the response in cache, send the request to the backend
- if (!cachedResp) {
- $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
- config.withCredentials);
- }
-
- return promise;
-
-
- /**
- * Callback registered to $httpBackend():
- * - caches the response if desired
- * - resolves the raw $http promise
- * - calls $apply
- */
- function done(status, response, headersString) {
- if (cache) {
- if (isSuccess(status)) {
- cache.put(url, [status, response, parseHeaders(headersString)]);
- } else {
- // remove promise from the cache
- cache.remove(url);
- }
- }
-
- resolvePromise(response, status, headersString);
- $rootScope.$apply();
- }
-
-
- /**
- * Resolves the raw $http promise.
- */
- function resolvePromise(response, status, headers) {
- // normalize internal statuses to 0
- status = Math.max(status, 0);
-
- (isSuccess(status) ? deferred.resolve : deferred.reject)({
- data: response,
- status: status,
- headers: headersGetter(headers),
- config: config
- });
- }
-
-
- function removePendingReq() {
- var idx = indexOf($http.pendingRequests, config);
- if (idx !== -1) $http.pendingRequests.splice(idx, 1);
- }
- }
-
-
- function buildUrl(url, params) {
- if (!params) return url;
- var parts = [];
- forEachSorted(params, function(value, key) {
- if (value == null || value == undefined) return;
- if (isObject(value)) {
- value = toJson(value);
- }
- parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
- });
- return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&');
- }
-
-
- }];
-}
-var XHR = window.XMLHttpRequest || function() {
- try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {}
- try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {}
- try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {}
- throw new Error("This browser does not support XMLHttpRequest.");
-};
-
-
-/**
- * @ngdoc object
- * @name ng.$httpBackend
- * @requires $browser
- * @requires $window
- * @requires $document
- *
- * @description
- * HTTP backend used by the {@link ng.$http service} that delegates to
- * XMLHttpRequest object or JSONP and deals with browser incompatibilities.
- *
- * You should never need to use this service directly, instead use the higher-level abstractions:
- * {@link ng.$http $http} or {@link ngResource.$resource $resource}.
- *
- * During testing this implementation is swapped with {@link ngMock.$httpBackend mock
- * $httpBackend} which can be trained with responses.
- */
-function $HttpBackendProvider() {
- this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) {
- return createHttpBackend($browser, XHR, $browser.defer, $window.angular.callbacks,
- $document[0], $window.location.protocol.replace(':', ''));
- }];
-}
-
-function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument, locationProtocol) {
- // TODO(vojta): fix the signature
- return function(method, url, post, callback, headers, timeout, withCredentials) {
- $browser.$$incOutstandingRequestCount();
- url = url || $browser.url();
-
- if (lowercase(method) == 'jsonp') {
- var callbackId = '_' + (callbacks.counter++).toString(36);
- callbacks[callbackId] = function(data) {
- callbacks[callbackId].data = data;
- };
-
- jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId),
- function() {
- if (callbacks[callbackId].data) {
- completeRequest(callback, 200, callbacks[callbackId].data);
- } else {
- completeRequest(callback, -2);
- }
- delete callbacks[callbackId];
- });
- } else {
- var xhr = new XHR();
- xhr.open(method, url, true);
- forEach(headers, function(value, key) {
- if (value) xhr.setRequestHeader(key, value);
- });
-
- var status;
-
- // In IE6 and 7, this might be called synchronously when xhr.send below is called and the
- // response is in the cache. the promise api will ensure that to the app code the api is
- // always async
- xhr.onreadystatechange = function() {
- if (xhr.readyState == 4) {
- completeRequest(
- callback, status || xhr.status, xhr.responseText, xhr.getAllResponseHeaders());
- }
- };
-
- if (withCredentials) {
- xhr.withCredentials = true;
- }
-
- xhr.send(post || '');
-
- if (timeout > 0) {
- $browserDefer(function() {
- status = -1;
- xhr.abort();
- }, timeout);
- }
- }
-
-
- function completeRequest(callback, status, response, headersString) {
- // URL_MATCH is defined in src/service/location.js
- var protocol = (url.match(URL_MATCH) || ['', locationProtocol])[1];
-
- // fix status code for file protocol (it's always 0)
- status = (protocol == 'file') ? (response ? 200 : 404) : status;
-
- // normalize IE bug (http://bugs.jquery.com/ticket/1450)
- status = status == 1223 ? 204 : status;
-
- callback(status, response, headersString);
- $browser.$$completeOutstandingRequest(noop);
- }
- };
-
- function jsonpReq(url, done) {
- // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.:
- // - fetches local scripts via XHR and evals them
- // - adds and immediately removes script elements from the document
- var script = rawDocument.createElement('script'),
- doneWrapper = function() {
- rawDocument.body.removeChild(script);
- if (done) done();
- };
-
- script.type = 'text/javascript';
- script.src = url;
-
- if (msie) {
- script.onreadystatechange = function() {
- if (/loaded|complete/.test(script.readyState)) doneWrapper();
- };
- } else {
- script.onload = script.onerror = doneWrapper;
- }
-
- rawDocument.body.appendChild(script);
- }
-}
-
-/**
- * @ngdoc object
- * @name ng.$locale
- *
- * @description
- * $locale service provides localization rules for various Angular components. As of right now the
- * only public api is:
- *
- * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`)
- */
-function $LocaleProvider(){
- this.$get = function() {
- return {
- id: 'en-us',
-
- NUMBER_FORMATS: {
- DECIMAL_SEP: '.',
- GROUP_SEP: ',',
- PATTERNS: [
- { // Decimal Pattern
- minInt: 1,
- minFrac: 0,
- maxFrac: 3,
- posPre: '',
- posSuf: '',
- negPre: '-',
- negSuf: '',
- gSize: 3,
- lgSize: 3
- },{ //Currency Pattern
- minInt: 1,
- minFrac: 2,
- maxFrac: 2,
- posPre: '\u00A4',
- posSuf: '',
- negPre: '(\u00A4',
- negSuf: ')',
- gSize: 3,
- lgSize: 3
- }
- ],
- CURRENCY_SYM: '$'
- },
-
- DATETIME_FORMATS: {
- MONTH: 'January,February,March,April,May,June,July,August,September,October,November,December'
- .split(','),
- SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','),
- DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','),
- SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','),
- AMPMS: ['AM','PM'],
- medium: 'MMM d, y h:mm:ss a',
- short: 'M/d/yy h:mm a',
- fullDate: 'EEEE, MMMM d, y',
- longDate: 'MMMM d, y',
- mediumDate: 'MMM d, y',
- shortDate: 'M/d/yy',
- mediumTime: 'h:mm:ss a',
- shortTime: 'h:mm a'
- },
-
- pluralCat: function(num) {
- if (num === 1) {
- return 'one';
- }
- return 'other';
- }
- };
- };
-}
-
-function $TimeoutProvider() {
- this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler',
- function($rootScope, $browser, $q, $exceptionHandler) {
- var deferreds = {};
-
-
- /**
- * @ngdoc function
- * @name ng.$timeout
- * @requires $browser
- *
- * @description
- * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch
- * block and delegates any exceptions to
- * {@link ng.$exceptionHandler $exceptionHandler} service.
- *
- * The return value of registering a timeout function is a promise which will be resolved when
- * the timeout is reached and the timeout function is executed.
- *
- * To cancel a the timeout request, call `$timeout.cancel(promise)`.
- *
- * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to
- * synchronously flush the queue of deferred functions.
- *
- * @param {function()} fn A function, who's execution should be delayed.
- * @param {number=} [delay=0] Delay in milliseconds.
- * @param {boolean=} [invokeApply=true] If set to false skips model dirty checking, otherwise
- * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
- * @returns {*} Promise that will be resolved when the timeout is reached. The value this
- * promise will be resolved with is the return value of the `fn` function.
- */
- function timeout(fn, delay, invokeApply) {
- var deferred = $q.defer(),
- promise = deferred.promise,
- skipApply = (isDefined(invokeApply) && !invokeApply),
- timeoutId, cleanup;
-
- timeoutId = $browser.defer(function() {
- try {
- deferred.resolve(fn());
- } catch(e) {
- deferred.reject(e);
- $exceptionHandler(e);
- }
-
- if (!skipApply) $rootScope.$apply();
- }, delay);
-
- cleanup = function() {
- delete deferreds[promise.$$timeoutId];
- };
-
- promise.$$timeoutId = timeoutId;
- deferreds[timeoutId] = deferred;
- promise.then(cleanup, cleanup);
-
- return promise;
- }
-
-
- /**
- * @ngdoc function
- * @name ng.$timeout#cancel
- * @methodOf ng.$timeout
- *
- * @description
- * Cancels a task associated with the `promise`. As a result of this the promise will be
- * resolved with a rejection.
- *
- * @param {Promise=} promise Promise returned by the `$timeout` function.
- * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
- * canceled.
- */
- timeout.cancel = function(promise) {
- if (promise && promise.$$timeoutId in deferreds) {
- deferreds[promise.$$timeoutId].reject('canceled');
- return $browser.defer.cancel(promise.$$timeoutId);
- }
- return false;
- };
-
- return timeout;
- }];
-}
-
-/**
- * @ngdoc object
- * @name ng.$filterProvider
- * @description
- *
- * Filters are just functions which transform input to an output. However filters need to be Dependency Injected. To
- * achieve this a filter definition consists of a factory function which is annotated with dependencies and is
- * responsible for creating a the filter function.
- *
- *
- * // Filter registration
- * function MyModule($provide, $filterProvider) {
- * // create a service to demonstrate injection (not always needed)
- * $provide.value('greet', function(name){
- * return 'Hello ' + name + '!';
- * });
- *
- * // register a filter factory which uses the
- * // greet service to demonstrate DI.
- * $filterProvider.register('greet', function(greet){
- * // return the filter function which uses the greet service
- * // to generate salutation
- * return function(text) {
- * // filters need to be forgiving so check input validity
- * return text && greet(text) || text;
- * };
- * });
- * }
- *
- *
- * The filter function is registered with the `$injector` under the filter name suffixe with `Filter`.
- *
- * it('should be the same instance', inject(
- * function($filterProvider) {
- * $filterProvider.register('reverse', function(){
- * return ...;
- * });
- * },
- * function($filter, reverseFilter) {
- * expect($filter('reverse')).toBe(reverseFilter);
- * });
- *
- *
- *
- * For more information about how angular filters work, and how to create your own filters, see
- * {@link guide/dev_guide.templates.filters Understanding Angular Filters} in the angular Developer
- * Guide.
- */
-/**
- * @ngdoc method
- * @name ng.$filterProvider#register
- * @methodOf ng.$filterProvider
- * @description
- * Register filter factory function.
- *
- * @param {String} name Name of the filter.
- * @param {function} fn The filter factory function which is injectable.
- */
-
-
-/**
- * @ngdoc function
- * @name ng.$filter
- * @function
- * @description
- * Filters are used for formatting data displayed to the user.
- *
- * The general syntax in templates is as follows:
- *
- * {{ expression | [ filter_name ] }}
- *
- * @param {String} name Name of the filter function to retrieve
- * @return {Function} the filter function
- */
-$FilterProvider.$inject = ['$provide'];
-function $FilterProvider($provide) {
- var suffix = 'Filter';
-
- function register(name, factory) {
- return $provide.factory(name + suffix, factory);
- }
- this.register = register;
-
- this.$get = ['$injector', function($injector) {
- return function(name) {
- return $injector.get(name + suffix);
- }
- }];
-
- ////////////////////////////////////////
-
- register('currency', currencyFilter);
- register('date', dateFilter);
- register('filter', filterFilter);
- register('json', jsonFilter);
- register('limitTo', limitToFilter);
- register('lowercase', lowercaseFilter);
- register('number', numberFilter);
- register('orderBy', orderByFilter);
- register('uppercase', uppercaseFilter);
-}
-
-/**
- * @ngdoc filter
- * @name ng.filter:filter
- * @function
- *
- * @description
- * Selects a subset of items from `array` and returns it as a new array.
- *
- * Note: This function is used to augment the `Array` type in Angular expressions. See
- * {@link ng.$filter} for more information about Angular arrays.
- *
- * @param {Array} array The source array.
- * @param {string|Object|function()} expression The predicate to be used for selecting items from
- * `array`.
- *
- * Can be one of:
- *
- * - `string`: Predicate that results in a substring match using the value of `expression`
- * string. All strings or objects with string properties in `array` that contain this string
- * will be returned. The predicate can be negated by prefixing the string with `!`.
- *
- * - `Object`: A pattern object can be used to filter specific properties on objects contained
- * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
- * which have property `name` containing "M" and property `phone` containing "1". A special
- * property name `$` can be used (as in `{$:"text"}`) to accept a match against any
- * property of the object. That's equivalent to the simple substring match with a `string`
- * as described above.
- *
- * - `function`: A predicate function can be used to write arbitrary filters. The function is
- * called for each element of `array`. The final result is an array of those elements that
- * the predicate returned true for.
- *
- * @example
-
-
-
-
- Search:
-
- Name Phone
-
- {{friend.name}}
- {{friend.phone}}
-
-
-
- Any:
- Name only
- Phone only
-
- Name Phone
-
- {{friend.name}}
- {{friend.phone}}
-
-
-
-
- it('should search across all fields when filtering with a string', function() {
- input('searchText').enter('m');
- expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
- toEqual(['Mary', 'Mike', 'Adam']);
-
- input('searchText').enter('76');
- expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
- toEqual(['John', 'Julie']);
- });
-
- it('should search in specific fields when filtering with a predicate object', function() {
- input('search.$').enter('i');
- expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')).
- toEqual(['Mary', 'Mike', 'Julie']);
- });
-
-
- */
-function filterFilter() {
- return function(array, expression) {
- if (!(array instanceof Array)) return array;
- var predicates = [];
- predicates.check = function(value) {
- for (var j = 0; j < predicates.length; j++) {
- if(!predicates[j](value)) {
- return false;
- }
- }
- return true;
- };
- var search = function(obj, text){
- if (text.charAt(0) === '!') {
- return !search(obj, text.substr(1));
- }
- switch (typeof obj) {
- case "boolean":
- case "number":
- case "string":
- return ('' + obj).toLowerCase().indexOf(text) > -1;
- case "object":
- for ( var objKey in obj) {
- if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
- return true;
- }
- }
- return false;
- case "array":
- for ( var i = 0; i < obj.length; i++) {
- if (search(obj[i], text)) {
- return true;
- }
- }
- return false;
- default:
- return false;
- }
- };
- switch (typeof expression) {
- case "boolean":
- case "number":
- case "string":
- expression = {$:expression};
- case "object":
- for (var key in expression) {
- if (key == '$') {
- (function() {
- var text = (''+expression[key]).toLowerCase();
- if (!text) return;
- predicates.push(function(value) {
- return search(value, text);
- });
- })();
- } else {
- (function() {
- var path = key;
- var text = (''+expression[key]).toLowerCase();
- if (!text) return;
- predicates.push(function(value) {
- return search(getter(value, path), text);
- });
- })();
- }
- }
- break;
- case 'function':
- predicates.push(expression);
- break;
- default:
- return array;
- }
- var filtered = [];
- for ( var j = 0; j < array.length; j++) {
- var value = array[j];
- if (predicates.check(value)) {
- filtered.push(value);
- }
- }
- return filtered;
- }
-}
-
-/**
- * @ngdoc filter
- * @name ng.filter:currency
- * @function
- *
- * @description
- * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
- * symbol for current locale is used.
- *
- * @param {number} amount Input to filter.
- * @param {string=} symbol Currency symbol or identifier to be displayed.
- * @returns {string} Formatted number.
- *
- *
- * @example
-
-
-
-
-
- default currency symbol ($): {{amount | currency}}
- custom currency identifier (USD$): {{amount | currency:"USD$"}}
-
-
-
- it('should init with 1234.56', function() {
- expect(binding('amount | currency')).toBe('$1,234.56');
- expect(binding('amount | currency:"USD$"')).toBe('USD$1,234.56');
- });
- it('should update', function() {
- input('amount').enter('-1234');
- expect(binding('amount | currency')).toBe('($1,234.00)');
- expect(binding('amount | currency:"USD$"')).toBe('(USD$1,234.00)');
- });
-
-
- */
-currencyFilter.$inject = ['$locale'];
-function currencyFilter($locale) {
- var formats = $locale.NUMBER_FORMATS;
- return function(amount, currencySymbol){
- if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM;
- return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2).
- replace(/\u00A4/g, currencySymbol);
- };
-}
-
-/**
- * @ngdoc filter
- * @name ng.filter:number
- * @function
- *
- * @description
- * Formats a number as text.
- *
- * If the input is not a number an empty string is returned.
- *
- * @param {number|string} number Number to format.
- * @param {(number|string)=} [fractionSize=2] Number of decimal places to round the number to.
- * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit.
- *
- * @example
-
-
-
-
- Enter number:
- Default formatting: {{val | number}}
- No fractions: {{val | number:0}}
- Negative number: {{-val | number:4}}
-
-
-
- it('should format numbers', function() {
- expect(binding('val | number')).toBe('1,234.568');
- expect(binding('val | number:0')).toBe('1,235');
- expect(binding('-val | number:4')).toBe('-1,234.5679');
- });
-
- it('should update', function() {
- input('val').enter('3374.333');
- expect(binding('val | number')).toBe('3,374.333');
- expect(binding('val | number:0')).toBe('3,374');
- expect(binding('-val | number:4')).toBe('-3,374.3330');
- });
-
-
- */
-
-
-numberFilter.$inject = ['$locale'];
-function numberFilter($locale) {
- var formats = $locale.NUMBER_FORMATS;
- return function(number, fractionSize) {
- return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP,
- fractionSize);
- };
-}
-
-var DECIMAL_SEP = '.';
-function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
- if (isNaN(number) || !isFinite(number)) return '';
-
- var isNegative = number < 0;
- number = Math.abs(number);
- var numStr = number + '',
- formatedText = '',
- parts = [];
-
- if (numStr.indexOf('e') !== -1) {
- formatedText = numStr;
- } else {
- var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
-
- // determine fractionSize if it is not specified
- if (isUndefined(fractionSize)) {
- fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac);
- }
-
- var pow = Math.pow(10, fractionSize);
- number = Math.round(number * pow) / pow;
- var fraction = ('' + number).split(DECIMAL_SEP);
- var whole = fraction[0];
- fraction = fraction[1] || '';
-
- var pos = 0,
- lgroup = pattern.lgSize,
- group = pattern.gSize;
-
- if (whole.length >= (lgroup + group)) {
- pos = whole.length - lgroup;
- for (var i = 0; i < pos; i++) {
- if ((pos - i)%group === 0 && i !== 0) {
- formatedText += groupSep;
- }
- formatedText += whole.charAt(i);
- }
- }
-
- for (i = pos; i < whole.length; i++) {
- if ((whole.length - i)%lgroup === 0 && i !== 0) {
- formatedText += groupSep;
- }
- formatedText += whole.charAt(i);
- }
-
- // format fraction part.
- while(fraction.length < fractionSize) {
- fraction += '0';
- }
-
- if (fractionSize) formatedText += decimalSep + fraction.substr(0, fractionSize);
- }
-
- parts.push(isNegative ? pattern.negPre : pattern.posPre);
- parts.push(formatedText);
- parts.push(isNegative ? pattern.negSuf : pattern.posSuf);
- return parts.join('');
-}
-
-function padNumber(num, digits, trim) {
- var neg = '';
- if (num < 0) {
- neg = '-';
- num = -num;
- }
- num = '' + num;
- while(num.length < digits) num = '0' + num;
- if (trim)
- num = num.substr(num.length - digits);
- return neg + num;
-}
-
-
-function dateGetter(name, size, offset, trim) {
- return function(date) {
- var value = date['get' + name]();
- if (offset > 0 || value > -offset)
- value += offset;
- if (value === 0 && offset == -12 ) value = 12;
- return padNumber(value, size, trim);
- };
-}
-
-function dateStrGetter(name, shortForm) {
- return function(date, formats) {
- var value = date['get' + name]();
- var get = uppercase(shortForm ? ('SHORT' + name) : name);
-
- return formats[get][value];
- };
-}
-
-function timeZoneGetter(date) {
- var offset = date.getTimezoneOffset();
- return padNumber(offset / 60, 2) + padNumber(Math.abs(offset % 60), 2);
-}
-
-function ampmGetter(date, formats) {
- return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
-}
-
-var DATE_FORMATS = {
- yyyy: dateGetter('FullYear', 4),
- yy: dateGetter('FullYear', 2, 0, true),
- y: dateGetter('FullYear', 1),
- MMMM: dateStrGetter('Month'),
- MMM: dateStrGetter('Month', true),
- MM: dateGetter('Month', 2, 1),
- M: dateGetter('Month', 1, 1),
- dd: dateGetter('Date', 2),
- d: dateGetter('Date', 1),
- HH: dateGetter('Hours', 2),
- H: dateGetter('Hours', 1),
- hh: dateGetter('Hours', 2, -12),
- h: dateGetter('Hours', 1, -12),
- mm: dateGetter('Minutes', 2),
- m: dateGetter('Minutes', 1),
- ss: dateGetter('Seconds', 2),
- s: dateGetter('Seconds', 1),
- EEEE: dateStrGetter('Day'),
- EEE: dateStrGetter('Day', true),
- a: ampmGetter,
- Z: timeZoneGetter
-};
-
-var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,
- NUMBER_STRING = /^\d+$/;
-
-/**
- * @ngdoc filter
- * @name ng.filter:date
- * @function
- *
- * @description
- * Formats `date` to a string based on the requested `format`.
- *
- * `format` string can be composed of the following elements:
- *
- * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
- * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
- * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
- * * `'MMMM'`: Month in year (January-December)
- * * `'MMM'`: Month in year (Jan-Dec)
- * * `'MM'`: Month in year, padded (01-12)
- * * `'M'`: Month in year (1-12)
- * * `'dd'`: Day in month, padded (01-31)
- * * `'d'`: Day in month (1-31)
- * * `'EEEE'`: Day in Week,(Sunday-Saturday)
- * * `'EEE'`: Day in Week, (Sun-Sat)
- * * `'HH'`: Hour in day, padded (00-23)
- * * `'H'`: Hour in day (0-23)
- * * `'hh'`: Hour in am/pm, padded (01-12)
- * * `'h'`: Hour in am/pm, (1-12)
- * * `'mm'`: Minute in hour, padded (00-59)
- * * `'m'`: Minute in hour (0-59)
- * * `'ss'`: Second in minute, padded (00-59)
- * * `'s'`: Second in minute (0-59)
- * * `'a'`: am/pm marker
- * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-1200)
- *
- * `format` string can also be one of the following predefined
- * {@link guide/i18n localizable formats}:
- *
- * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale
- * (e.g. Sep 3, 2010 12:05:08 pm)
- * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 pm)
- * * `'fullDate'`: equivalent to `'EEEE, MMMM d,y'` for en_US locale
- * (e.g. Friday, September 3, 2010)
- * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010
- * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010)
- * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10)
- * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm)
- * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm)
- *
- * `format` string can contain literal values. These need to be quoted with single quotes (e.g.
- * `"h 'in the morning'"`). In order to output single quote, use two single quotes in a sequence
- * (e.g. `"h o''clock"`).
- *
- * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
- * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and it's
- * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ).
- * @param {string=} format Formatting rules (see Description). If not specified,
- * `mediumDate` is used.
- * @returns {string} Formatted string or the input if input is not recognized as date/millis.
- *
- * @example
-
-
- {{1288323623006 | date:'medium'}} :
- {{1288323623006 | date:'medium'}}
- {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}} :
- {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}
- {{1288323623006 | date:'MM/dd/yyyy @ h:mma'}} :
- {{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}
-
-
- it('should format date', function() {
- expect(binding("1288323623006 | date:'medium'")).
- toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
- expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).
- toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} \-?\d{4}/);
- expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).
- toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
- });
-
-
- */
-dateFilter.$inject = ['$locale'];
-function dateFilter($locale) {
-
-
- var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;
- function jsonStringToDate(string){
- var match;
- if (match = string.match(R_ISO8601_STR)) {
- var date = new Date(0),
- tzHour = 0,
- tzMin = 0;
- if (match[9]) {
- tzHour = int(match[9] + match[10]);
- tzMin = int(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));
- return date;
- }
- return string;
- }
-
-
- return function(date, format) {
- var text = '',
- parts = [],
- fn, match;
-
- format = format || 'mediumDate';
- format = $locale.DATETIME_FORMATS[format] || format;
- if (isString(date)) {
- if (NUMBER_STRING.test(date)) {
- date = int(date);
- } else {
- date = jsonStringToDate(date);
- }
- }
-
- if (isNumber(date)) {
- date = new Date(date);
- }
-
- if (!isDate(date)) {
- return date;
- }
-
- while(format) {
- match = DATE_FORMATS_SPLIT.exec(format);
- if (match) {
- parts = concat(parts, match, 1);
- format = parts.pop();
- } else {
- parts.push(format);
- format = null;
- }
- }
-
- forEach(parts, function(value){
- fn = DATE_FORMATS[value];
- text += fn ? fn(date, $locale.DATETIME_FORMATS)
- : value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
- });
-
- return text;
- };
-}
-
-
-/**
- * @ngdoc filter
- * @name ng.filter:json
- * @function
- *
- * @description
- * Allows you to convert a JavaScript object into JSON string.
- *
- * This filter is mostly useful for debugging. When using the double curly {{value}} notation
- * the binding is automatically converted to JSON.
- *
- * @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
- * @returns {string} JSON string.
- *
- *
- * @example:
-
-
- {{ {'name':'value'} | json }}
-
-
- it('should jsonify filtered objects', function() {
- expect(binding("{'name':'value'}")).toMatch(/\{\n "name": ?"value"\n}/);
- });
-
-
- *
- */
-function jsonFilter() {
- return function(object) {
- return toJson(object, true);
- };
-}
-
-
-/**
- * @ngdoc filter
- * @name ng.filter:lowercase
- * @function
- * @description
- * Converts string to lowercase.
- * @see angular.lowercase
- */
-var lowercaseFilter = valueFn(lowercase);
-
-
-/**
- * @ngdoc filter
- * @name ng.filter:uppercase
- * @function
- * @description
- * Converts string to uppercase.
- * @see angular.uppercase
- */
-var uppercaseFilter = valueFn(uppercase);
-
-/**
- * @ngdoc function
- * @name ng.filter:limitTo
- * @function
- *
- * @description
- * Creates a new array containing only a specified number of elements in an array. The elements
- * are taken from either the beginning or the end of the source array, as specified by the
- * value and sign (positive or negative) of `limit`.
- *
- * Note: This function is used to augment the `Array` type in Angular expressions. See
- * {@link ng.$filter} for more information about Angular arrays.
- *
- * @param {Array} array Source array to be limited.
- * @param {string|Number} limit The length of the returned array. If the `limit` number is
- * positive, `limit` number of items from the beginning of the source array are copied.
- * If the number is negative, `limit` number of items from the end of the source array are
- * copied. The `limit` will be trimmed if it exceeds `array.length`
- * @returns {Array} A new sub-array of length `limit` or less if input array had less than `limit`
- * elements.
- *
- * @example
-
-
-
-
- Limit {{numbers}} to:
-
Output: {{ numbers | limitTo:limit }}
-
-
-
- it('should limit the numer array to first three items', function() {
- expect(element('.doc-example-live input[ng-model=limit]').val()).toBe('3');
- expect(binding('numbers | limitTo:limit')).toEqual('[1,2,3]');
- });
-
- it('should update the output when -3 is entered', function() {
- input('limit').enter(-3);
- expect(binding('numbers | limitTo:limit')).toEqual('[7,8,9]');
- });
-
- it('should not exceed the maximum size of input array', function() {
- input('limit').enter(100);
- expect(binding('numbers | limitTo:limit')).toEqual('[1,2,3,4,5,6,7,8,9]');
- });
-
-
- */
-function limitToFilter(){
- return function(array, limit) {
- if (!(array instanceof Array)) return array;
- limit = int(limit);
- var out = [],
- i, n;
-
- // check that array is iterable
- if (!array || !(array instanceof Array))
- return out;
-
- // if abs(limit) exceeds maximum length, trim it
- if (limit > array.length)
- limit = array.length;
- else if (limit < -array.length)
- limit = -array.length;
-
- if (limit > 0) {
- i = 0;
- n = limit;
- } else {
- i = array.length + limit;
- n = array.length;
- }
-
- for (; i} expression A predicate to be
- * used by the comparator to determine the order of elements.
- *
- * Can be one of:
- *
- * - `function`: Getter function. The result of this function will be sorted using the
- * `<`, `=`, `>` operator.
- * - `string`: An Angular expression which evaluates to an object to order by, such as 'name'
- * to sort by a property called 'name'. Optionally prefixed with `+` or `-` to control
- * ascending or descending sort order (for example, +name or -name).
- * - `Array`: An array of function or string predicates. The first predicate in the array
- * is used for sorting, but when two items are equivalent, the next predicate is used.
- *
- * @param {boolean=} reverse Reverse the order the array.
- * @returns {Array} Sorted copy of the source array.
- *
- * @example
-
-
-
-
-
Sorting predicate = {{predicate}}; reverse = {{reverse}}
-
- [
unsorted ]
-
-
-
-
- it('should be reverse ordered by aged', function() {
- expect(binding('predicate')).toBe('-age');
- expect(repeater('table.friend', 'friend in friends').column('friend.age')).
- toEqual(['35', '29', '21', '19', '10']);
- expect(repeater('table.friend', 'friend in friends').column('friend.name')).
- toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']);
- });
-
- it('should reorder the table when user selects different predicate', function() {
- element('.doc-example-live a:contains("Name")').click();
- expect(repeater('table.friend', 'friend in friends').column('friend.name')).
- toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']);
- expect(repeater('table.friend', 'friend in friends').column('friend.age')).
- toEqual(['35', '10', '29', '19', '21']);
-
- element('.doc-example-live a:contains("Phone")').click();
- expect(repeater('table.friend', 'friend in friends').column('friend.phone')).
- toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']);
- expect(repeater('table.friend', 'friend in friends').column('friend.name')).
- toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']);
- });
-
-
- */
-orderByFilter.$inject = ['$parse'];
-function orderByFilter($parse){
- return function(array, sortPredicate, reverseOrder) {
- if (!(array instanceof Array)) return array;
- if (!sortPredicate) return array;
- sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate];
- sortPredicate = map(sortPredicate, function(predicate){
- var descending = false, get = predicate || identity;
- if (isString(predicate)) {
- if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) {
- descending = predicate.charAt(0) == '-';
- predicate = predicate.substring(1);
- }
- get = $parse(predicate);
- }
- return reverseComparator(function(a,b){
- return compare(get(a),get(b));
- }, descending);
- });
- var arrayCopy = [];
- for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); }
- return arrayCopy.sort(reverseComparator(comparator, reverseOrder));
-
- function comparator(o1, o2){
- for ( var i = 0; i < sortPredicate.length; i++) {
- var comp = sortPredicate[i](o1, o2);
- if (comp !== 0) return comp;
- }
- return 0;
- }
- function reverseComparator(comp, descending) {
- return toBoolean(descending)
- ? function(a,b){return comp(b,a);}
- : comp;
- }
- function compare(v1, v2){
- var t1 = typeof v1;
- var t2 = typeof v2;
- if (t1 == t2) {
- if (t1 == "string") v1 = v1.toLowerCase();
- if (t1 == "string") v2 = v2.toLowerCase();
- if (v1 === v2) return 0;
- return v1 < v2 ? -1 : 1;
- } else {
- return t1 < t2 ? -1 : 1;
- }
- }
- }
-}
-
-function ngDirective(directive) {
- if (isFunction(directive)) {
- directive = {
- link: directive
- }
- }
- directive.restrict = directive.restrict || 'AC';
- return valueFn(directive);
-}
-
-/**
- * @ngdoc directive
- * @name ng.directive:a
- * @restrict E
- *
- * @description
- * Modifies the default behavior of html A tag, so that the default action is prevented when href
- * attribute is empty.
- *
- * The reasoning for this change is to allow easy creation of action links with `ngClick` directive
- * without changing the location or causing page reloads, e.g.:
- * Save
- */
-var htmlAnchorDirective = valueFn({
- restrict: 'E',
- compile: function(element, attr) {
- // turn link into a link in IE
- // but only if it doesn't have name attribute, in which case it's an anchor
- if (!attr.href) {
- attr.$set('href', '');
- }
-
- return function(scope, element) {
- element.bind('click', function(event){
- // if we have no href url, then don't navigate anywhere.
- if (!element.attr('href')) {
- event.preventDefault();
- }
- });
- }
- }
-});
-
-/**
- * @ngdoc directive
- * @name ng.directive:ngHref
- * @restrict A
- *
- * @description
- * Using Angular markup like {{hash}} in an href attribute makes
- * the page open to a wrong URL, if the user clicks that link before
- * angular has a chance to replace the {{hash}} with actual URL, the
- * link will be broken and will most likely return a 404 error.
- * The `ngHref` directive solves this problem.
- *
- * The buggy way to write it:
- *
- *
- *
- *
- * The correct way to write it:
- *
- *
- *
- *
- * @element A
- * @param {template} ngHref any string which can contain `{{}}` markup.
- *
- * @example
- * This example uses `link` variable inside `href` attribute:
-
-
-
- link 1 (link, don't reload)
- link 2 (link, don't reload)
- link 3 (link, reload!)
- anchor (link, don't reload)
- anchor (no link)
- link (link, change location)
-
-
- it('should execute ng-click but not reload when href without value', function() {
- element('#link-1').click();
- expect(input('value').val()).toEqual('1');
- expect(element('#link-1').attr('href')).toBe("");
- });
-
- it('should execute ng-click but not reload when href empty string', function() {
- element('#link-2').click();
- expect(input('value').val()).toEqual('2');
- expect(element('#link-2').attr('href')).toBe("");
- });
-
- it('should execute ng-click and change url when ng-href specified', function() {
- expect(element('#link-3').attr('href')).toBe("/123");
-
- element('#link-3').click();
- expect(browser().window().path()).toEqual('/123');
- });
-
- it('should execute ng-click but not reload when href empty string and name specified', function() {
- element('#link-4').click();
- expect(input('value').val()).toEqual('4');
- expect(element('#link-4').attr('href')).toBe('');
- });
-
- it('should execute ng-click but not reload when no href but name specified', function() {
- element('#link-5').click();
- expect(input('value').val()).toEqual('5');
- expect(element('#link-5').attr('href')).toBe('');
- });
-
- it('should only change url when only ng-href', function() {
- input('value').enter('6');
- expect(element('#link-6').attr('href')).toBe('6');
-
- element('#link-6').click();
- expect(browser().location().url()).toEqual('/6');
- });
-
-
- */
-
-/**
- * @ngdoc directive
- * @name ng.directive:ngSrc
- * @restrict A
- *
- * @description
- * Using Angular markup like `{{hash}}` in a `src` attribute doesn't
- * work right: The browser will fetch from the URL with the literal
- * text `{{hash}}` until Angular replaces the expression inside
- * `{{hash}}`. The `ngSrc` directive solves this problem.
- *
- * The buggy way to write it:
- *
- *
- *
- *
- * The correct way to write it:
- *
- *
- *
- *
- * @element IMG
- * @param {template} ngSrc any string which can contain `{{}}` markup.
- */
-
-/**
- * @ngdoc directive
- * @name ng.directive:ngDisabled
- * @restrict A
- *
- * @description
- *
- * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs:
- *
- *
- * Disabled
- *
- *
- *
- * The HTML specs do not require browsers to preserve the special attributes such as disabled.
- * (The presence of them means true and absence means false)
- * This prevents the angular compiler from correctly retrieving the binding expression.
- * To solve this problem, we introduce the `ngDisabled` directive.
- *
- * @example
-
-
- Click me to toggle:
- Button
-
-
- it('should toggle button', function() {
- expect(element('.doc-example-live :button').prop('disabled')).toBeFalsy();
- input('checked').check();
- expect(element('.doc-example-live :button').prop('disabled')).toBeTruthy();
- });
-
-
- *
- * @element INPUT
- * @param {expression} ngDisabled Angular expression that will be evaluated.
- */
-
-
-/**
- * @ngdoc directive
- * @name ng.directive:ngChecked
- * @restrict A
- *
- * @description
- * The HTML specs do not require browsers to preserve the special attributes such as checked.
- * (The presence of them means true and absence means false)
- * This prevents the angular compiler from correctly retrieving the binding expression.
- * To solve this problem, we introduce the `ngChecked` directive.
- * @example
-
-
- Check me to check both:
-
-
-
- it('should check both checkBoxes', function() {
- expect(element('.doc-example-live #checkSlave').prop('checked')).toBeFalsy();
- input('master').check();
- expect(element('.doc-example-live #checkSlave').prop('checked')).toBeTruthy();
- });
-
-
- *
- * @element INPUT
- * @param {expression} ngChecked Angular expression that will be evaluated.
- */
-
-
-/**
- * @ngdoc directive
- * @name ng.directive:ngMultiple
- * @restrict A
- *
- * @description
- * The HTML specs do not require browsers to preserve the special attributes such as multiple.
- * (The presence of them means true and absence means false)
- * This prevents the angular compiler from correctly retrieving the binding expression.
- * To solve this problem, we introduce the `ngMultiple` directive.
- *
- * @example
-
-
- Check me check multiple:
-
- Misko
- Igor
- Vojta
- Di
-
-
-
- it('should toggle multiple', function() {
- expect(element('.doc-example-live #select').prop('multiple')).toBeFalsy();
- input('checked').check();
- expect(element('.doc-example-live #select').prop('multiple')).toBeTruthy();
- });
-
-
- *
- * @element SELECT
- * @param {expression} ngMultiple Angular expression that will be evaluated.
- */
-
-
-/**
- * @ngdoc directive
- * @name ng.directive:ngReadonly
- * @restrict A
- *
- * @description
- * The HTML specs do not require browsers to preserve the special attributes such as readonly.
- * (The presence of them means true and absence means false)
- * This prevents the angular compiler from correctly retrieving the binding expression.
- * To solve this problem, we introduce the `ngReadonly` directive.
- * @example
-
-
- Check me to make text readonly:
-
-
-
- it('should toggle readonly attr', function() {
- expect(element('.doc-example-live :text').prop('readonly')).toBeFalsy();
- input('checked').check();
- expect(element('.doc-example-live :text').prop('readonly')).toBeTruthy();
- });
-
-
- *
- * @element INPUT
- * @param {string} expression Angular expression that will be evaluated.
- */
-
-
-/**
- * @ngdoc directive
- * @name ng.directive:ngSelected
- * @restrict A
- *
- * @description
- * The HTML specs do not require browsers to preserve the special attributes such as selected.
- * (The presence of them means true and absence means false)
- * This prevents the angular compiler from correctly retrieving the binding expression.
- * To solve this problem, we introduced the `ngSelected` directive.
- * @example
-
-
- Check me to select:
-
- Hello!
- Greetings!
-
-
-
- it('should select Greetings!', function() {
- expect(element('.doc-example-live #greet').prop('selected')).toBeFalsy();
- input('selected').check();
- expect(element('.doc-example-live #greet').prop('selected')).toBeTruthy();
- });
-
-
- *
- * @element OPTION
- * @param {string} expression Angular expression that will be evaluated.
- */
-
-
-var ngAttributeAliasDirectives = {};
-
-
-// boolean attrs are evaluated
-forEach(BOOLEAN_ATTR, function(propName, attrName) {
- var normalized = directiveNormalize('ng-' + attrName);
- ngAttributeAliasDirectives[normalized] = function() {
- return {
- priority: 100,
- compile: function() {
- return function(scope, element, attr) {
- scope.$watch(attr[normalized], function(value) {
- attr.$set(attrName, !!value);
- });
- };
- }
- };
- };
-});
-
-
-// ng-src, ng-href are interpolated
-forEach(['src', 'href'], function(attrName) {
- var normalized = directiveNormalize('ng-' + attrName);
- ngAttributeAliasDirectives[normalized] = function() {
- return {
- priority: 99, // it needs to run after the attributes are interpolated
- link: function(scope, element, attr) {
- attr.$observe(normalized, function(value) {
- attr.$set(attrName, value);
-
- // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
- // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
- // to set the property as well to achieve the desired effect
- if (msie) element.prop(attrName, value);
- });
- }
- };
- };
-});
-
-var nullFormCtrl = {
- $addControl: noop,
- $removeControl: noop,
- $setValidity: noop,
- $setDirty: noop
-};
-
-/**
- * @ngdoc object
- * @name ng.directive:form.FormController
- *
- * @property {boolean} $pristine True if user has not interacted with the form yet.
- * @property {boolean} $dirty True if user has already interacted with the form.
- * @property {boolean} $valid True if all of the containg forms and controls are valid.
- * @property {boolean} $invalid True if at least one containing control or form is invalid.
- *
- * @property {Object} $error Is an object hash, containing references to all invalid controls or
- * forms, where:
- *
- * - keys are validation tokens (error names) — such as `REQUIRED`, `URL` or `EMAIL`),
- * - values are arrays of controls or forms that are invalid with given error.
- *
- * @description
- * `FormController` keeps track of all its controls and nested forms as well as state of them,
- * such as being valid/invalid or dirty/pristine.
- *
- * Each {@link ng.directive:form form} directive creates an instance
- * of `FormController`.
- *
- */
-//asks for $scope to fool the BC controller module
-FormController.$inject = ['$element', '$attrs', '$scope'];
-function FormController(element, attrs) {
- var form = this,
- parentForm = element.parent().controller('form') || nullFormCtrl,
- invalidCount = 0, // used to easily determine if we are valid
- errors = form.$error = {};
-
- // init state
- form.$name = attrs.name;
- form.$dirty = false;
- form.$pristine = true;
- form.$valid = true;
- form.$invalid = false;
-
- parentForm.$addControl(form);
-
- // Setup initial state of the control
- element.addClass(PRISTINE_CLASS);
- toggleValidCss(true);
-
- // convenience method for easy toggling of classes
- function toggleValidCss(isValid, validationErrorKey) {
- validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
- element.
- removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey).
- addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
- }
-
- form.$addControl = function(control) {
- if (control.$name && !form.hasOwnProperty(control.$name)) {
- form[control.$name] = control;
- }
- };
-
- form.$removeControl = function(control) {
- if (control.$name && form[control.$name] === control) {
- delete form[control.$name];
- }
- forEach(errors, function(queue, validationToken) {
- form.$setValidity(validationToken, true, control);
- });
- };
-
- form.$setValidity = function(validationToken, isValid, control) {
- var queue = errors[validationToken];
-
- if (isValid) {
- if (queue) {
- arrayRemove(queue, control);
- if (!queue.length) {
- invalidCount--;
- if (!invalidCount) {
- toggleValidCss(isValid);
- form.$valid = true;
- form.$invalid = false;
- }
- errors[validationToken] = false;
- toggleValidCss(true, validationToken);
- parentForm.$setValidity(validationToken, true, form);
- }
- }
-
- } else {
- if (!invalidCount) {
- toggleValidCss(isValid);
- }
- if (queue) {
- if (includes(queue, control)) return;
- } else {
- errors[validationToken] = queue = [];
- invalidCount++;
- toggleValidCss(false, validationToken);
- parentForm.$setValidity(validationToken, false, form);
- }
- queue.push(control);
-
- form.$valid = false;
- form.$invalid = true;
- }
- };
-
- form.$setDirty = function() {
- element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS);
- form.$dirty = true;
- form.$pristine = false;
- };
-
-}
-
-
-/**
- * @ngdoc directive
- * @name ng.directive:ngForm
- * @restrict EAC
- *
- * @description
- * Nestable alias of {@link ng.directive:form `form`} directive. HTML
- * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
- * sub-group of controls needs to be determined.
- *
- * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into
- * related scope, under this name.
- *
- */
-
- /**
- * @ngdoc directive
- * @name ng.directive:form
- * @restrict E
- *
- * @description
- * Directive that instantiates
- * {@link ng.directive:form.FormController FormController}.
- *
- * If `name` attribute is specified, the form controller is published onto the current scope under
- * this name.
- *
- * # Alias: {@link ng.directive:ngForm `ngForm`}
- *
- * In angular forms can be nested. This means that the outer form is valid when all of the child
- * forms are valid as well. However browsers do not allow nesting of `
- */
- 'email': emailInputType,
+ extend(config, requestConfig);
+ config.headers = headers;
+ config.method = uppercase(config.method);
+ var xsrfValue = urlIsSameOrigin(config.url)
+ ? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName]
+ : undefined;
+ if (xsrfValue) {
+ headers[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
+ }
- /**
- * @ngdoc inputType
- * @name ng.directive:input.radio
- *
- * @description
- * HTML radio button.
- *
- * @param {string} ngModel Assignable angular expression to data-bind to.
- * @param {string} value The value to which the expression should be set when selected.
- * @param {string=} name Property name of the form under which the control is published.
- * @param {string=} ngChange Angular expression to be executed when input changes due to user
- * interaction with the input element.
- *
- * @example
-
-
-
-
- Red
- Green
- Blue
- color = {{color}}
-
-
-
- it('should change state', function() {
- expect(binding('color')).toEqual('blue');
-
- input('color').select('red');
- expect(binding('color')).toEqual('red');
- });
-
-
- */
- 'radio': radioInputType,
+ var serverRequest = function(config) {
+ headers = config.headers;
+ var reqData = transformData(config.data, headersGetter(headers), config.transformRequest);
- /**
- * @ngdoc inputType
- * @name ng.directive:input.checkbox
- *
- * @description
- * HTML checkbox.
- *
- * @param {string} ngModel Assignable angular expression to data-bind to.
- * @param {string=} name Property name of the form under which the control is published.
- * @param {string=} ngTrueValue The value to which the expression should be set when selected.
- * @param {string=} ngFalseValue The value to which the expression should be set when not selected.
- * @param {string=} ngChange Angular expression to be executed when input changes due to user
- * interaction with the input element.
- *
- * @example
-
-
-
-
- Value1:
- Value2:
- value1 = {{value1}}
- value2 = {{value2}}
-
-
-
- it('should change state', function() {
- expect(binding('value1')).toEqual('true');
- expect(binding('value2')).toEqual('YES');
-
- input('value1').check();
- input('value2').check();
- expect(binding('value1')).toEqual('false');
- expect(binding('value2')).toEqual('NO');
+ // strip content-type if data is undefined
+ if (isUndefined(config.data)) {
+ forEach(headers, function(value, header) {
+ if (lowercase(header) === 'content-type') {
+ delete headers[header];
+ }
});
-
-
- */
- 'checkbox': checkboxInputType,
-
- 'hidden': noop,
- 'button': noop,
- 'submit': noop,
- 'reset': noop
-};
-
-
-function isEmpty(value) {
- return isUndefined(value) || value === '' || value === null || value !== value;
-}
+ }
+ if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
+ config.withCredentials = defaults.withCredentials;
+ }
-function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
+ // send request
+ return sendReq(config, reqData, headers).then(transformResponse, transformResponse);
+ };
- var listener = function() {
- var value = trim(element.val());
+ var chain = [serverRequest, undefined];
+ var promise = $q.when(config);
- if (ctrl.$viewValue !== value) {
- scope.$apply(function() {
- ctrl.$setViewValue(value);
+ // apply interceptors
+ forEach(reversedInterceptors, function(interceptor) {
+ if (interceptor.request || interceptor.requestError) {
+ chain.unshift(interceptor.request, interceptor.requestError);
+ }
+ if (interceptor.response || interceptor.responseError) {
+ chain.push(interceptor.response, interceptor.responseError);
+ }
});
- }
- };
-
- // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the
- // input event on backspace, delete or cut
- if ($sniffer.hasEvent('input')) {
- element.bind('input', listener);
- } else {
- var timeout;
-
- element.bind('keydown', function(event) {
- var key = event.keyCode;
- // ignore
- // command modifiers arrows
- if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;
+ while(chain.length) {
+ var thenFn = chain.shift();
+ var rejectFn = chain.shift();
- if (!timeout) {
- timeout = $browser.defer(function() {
- listener();
- timeout = null;
- });
+ promise = promise.then(thenFn, rejectFn);
}
- });
-
- // if user paste into input using mouse, we need "change" event to catch it
- element.bind('change', listener);
- }
-
-
- ctrl.$render = function() {
- element.val(isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue);
- };
-
- // pattern validator
- var pattern = attr.ngPattern,
- patternValidator;
-
- var validate = function(regexp, value) {
- if (isEmpty(value) || regexp.test(value)) {
- ctrl.$setValidity('pattern', true);
- return value;
- } else {
- ctrl.$setValidity('pattern', false);
- return undefined;
- }
- };
- if (pattern) {
- if (pattern.match(/^\/(.*)\/$/)) {
- pattern = new RegExp(pattern.substr(1, pattern.length - 2));
- patternValidator = function(value) {
- return validate(pattern, value)
+ promise.success = function(fn) {
+ promise.then(function(response) {
+ fn(response.data, response.status, response.headers, config);
+ });
+ return promise;
};
- } else {
- patternValidator = function(value) {
- var patternObj = scope.$eval(pattern);
- if (!patternObj || !patternObj.test) {
- throw new Error('Expected ' + pattern + ' to be a RegExp but was ' + patternObj);
- }
- return validate(patternObj, value);
+ promise.error = function(fn) {
+ promise.then(null, function(response) {
+ fn(response.data, response.status, response.headers, config);
+ });
+ return promise;
};
- }
- ctrl.$formatters.push(patternValidator);
- ctrl.$parsers.push(patternValidator);
- }
+ return promise;
- // min length validator
- if (attr.ngMinlength) {
- var minlength = int(attr.ngMinlength);
- var minLengthValidator = function(value) {
- if (!isEmpty(value) && value.length < minlength) {
- ctrl.$setValidity('minlength', false);
- return undefined;
- } else {
- ctrl.$setValidity('minlength', true);
- return value;
+ function transformResponse(response) {
+ // make a copy since the response must be cacheable
+ var resp = extend({}, response, {
+ data: transformData(response.data, response.headers, config.transformResponse)
+ });
+ return (isSuccess(response.status))
+ ? resp
+ : $q.reject(resp);
}
- };
- ctrl.$parsers.push(minLengthValidator);
- ctrl.$formatters.push(minLengthValidator);
- }
+ function mergeHeaders(config) {
+ var defHeaders = defaults.headers,
+ reqHeaders = extend({}, config.headers),
+ defHeaderName, lowercaseDefHeaderName, reqHeaderName;
- // max length validator
- if (attr.ngMaxlength) {
- var maxlength = int(attr.ngMaxlength);
- var maxLengthValidator = function(value) {
- if (!isEmpty(value) && value.length > maxlength) {
- ctrl.$setValidity('maxlength', false);
- return undefined;
- } else {
- ctrl.$setValidity('maxlength', true);
- return value;
- }
- };
+ defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]);
- ctrl.$parsers.push(maxLengthValidator);
- ctrl.$formatters.push(maxLengthValidator);
- }
-}
+ // execute if header value is function
+ execHeaders(defHeaders);
+ execHeaders(reqHeaders);
-function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
- textInputType(scope, element, attr, ctrl, $sniffer, $browser);
+ // using for-in instead of forEach to avoid unecessary iteration after header has been found
+ defaultHeadersIteration:
+ for (defHeaderName in defHeaders) {
+ lowercaseDefHeaderName = lowercase(defHeaderName);
- ctrl.$parsers.push(function(value) {
- var empty = isEmpty(value);
- if (empty || NUMBER_REGEXP.test(value)) {
- ctrl.$setValidity('number', true);
- return value === '' ? null : (empty ? value : parseFloat(value));
- } else {
- ctrl.$setValidity('number', false);
- return undefined;
- }
- });
+ for (reqHeaderName in reqHeaders) {
+ if (lowercase(reqHeaderName) === lowercaseDefHeaderName) {
+ continue defaultHeadersIteration;
+ }
+ }
- ctrl.$formatters.push(function(value) {
- return isEmpty(value) ? '' : '' + value;
- });
+ reqHeaders[defHeaderName] = defHeaders[defHeaderName];
+ }
- if (attr.min) {
- var min = parseFloat(attr.min);
- var minValidator = function(value) {
- if (!isEmpty(value) && value < min) {
- ctrl.$setValidity('min', false);
- return undefined;
- } else {
- ctrl.$setValidity('min', true);
- return value;
- }
- };
+ return reqHeaders;
- ctrl.$parsers.push(minValidator);
- ctrl.$formatters.push(minValidator);
- }
+ function execHeaders(headers) {
+ var headerContent;
- if (attr.max) {
- var max = parseFloat(attr.max);
- var maxValidator = function(value) {
- if (!isEmpty(value) && value > max) {
- ctrl.$setValidity('max', false);
- return undefined;
- } else {
- ctrl.$setValidity('max', true);
- return value;
+ forEach(headers, function(headerFn, header) {
+ if (isFunction(headerFn)) {
+ headerContent = headerFn();
+ if (headerContent != null) {
+ headers[header] = headerContent;
+ } else {
+ delete headers[header];
+ }
+ }
+ });
+ }
}
- };
-
- ctrl.$parsers.push(maxValidator);
- ctrl.$formatters.push(maxValidator);
- }
-
- ctrl.$formatters.push(function(value) {
-
- if (isEmpty(value) || isNumber(value)) {
- ctrl.$setValidity('number', true);
- return value;
- } else {
- ctrl.$setValidity('number', false);
- return undefined;
- }
- });
-}
-
-function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
- textInputType(scope, element, attr, ctrl, $sniffer, $browser);
-
- var urlValidator = function(value) {
- if (isEmpty(value) || URL_REGEXP.test(value)) {
- ctrl.$setValidity('url', true);
- return value;
- } else {
- ctrl.$setValidity('url', false);
- return undefined;
- }
- };
-
- ctrl.$formatters.push(urlValidator);
- ctrl.$parsers.push(urlValidator);
-}
-
-function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
- textInputType(scope, element, attr, ctrl, $sniffer, $browser);
-
- var emailValidator = function(value) {
- if (isEmpty(value) || EMAIL_REGEXP.test(value)) {
- ctrl.$setValidity('email', true);
- return value;
- } else {
- ctrl.$setValidity('email', false);
- return undefined;
}
- };
- ctrl.$formatters.push(emailValidator);
- ctrl.$parsers.push(emailValidator);
-}
+ $http.pendingRequests = [];
-function radioInputType(scope, element, attr, ctrl) {
- // make the name unique, if not defined
- if (isUndefined(attr.name)) {
- element.attr('name', nextUid());
- }
+ /**
+ * @ngdoc method
+ * @name ng.$http#get
+ * @methodOf ng.$http
+ *
+ * @description
+ * Shortcut method to perform `GET` request.
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request
+ * @param {Object=} config Optional configuration object
+ * @returns {HttpPromise} Future object
+ */
- element.bind('click', function() {
- if (element[0].checked) {
- scope.$apply(function() {
- ctrl.$setViewValue(attr.value);
- });
- }
- });
+ /**
+ * @ngdoc method
+ * @name ng.$http#delete
+ * @methodOf ng.$http
+ *
+ * @description
+ * Shortcut method to perform `DELETE` request.
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request
+ * @param {Object=} config Optional configuration object
+ * @returns {HttpPromise} Future object
+ */
- ctrl.$render = function() {
- var value = attr.value;
- element[0].checked = (value == ctrl.$viewValue);
- };
+ /**
+ * @ngdoc method
+ * @name ng.$http#head
+ * @methodOf ng.$http
+ *
+ * @description
+ * Shortcut method to perform `HEAD` request.
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request
+ * @param {Object=} config Optional configuration object
+ * @returns {HttpPromise} Future object
+ */
- attr.$observe('value', ctrl.$render);
-}
+ /**
+ * @ngdoc method
+ * @name ng.$http#jsonp
+ * @methodOf ng.$http
+ *
+ * @description
+ * Shortcut method to perform `JSONP` request.
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request.
+ * Should contain `JSON_CALLBACK` string.
+ * @param {Object=} config Optional configuration object
+ * @returns {HttpPromise} Future object
+ */
+ createShortMethods('get', 'delete', 'head', 'jsonp');
-function checkboxInputType(scope, element, attr, ctrl) {
- var trueValue = attr.ngTrueValue,
- falseValue = attr.ngFalseValue;
+ /**
+ * @ngdoc method
+ * @name ng.$http#post
+ * @methodOf ng.$http
+ *
+ * @description
+ * Shortcut method to perform `POST` request.
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request
+ * @param {*} data Request content
+ * @param {Object=} config Optional configuration object
+ * @returns {HttpPromise} Future object
+ */
- if (!isString(trueValue)) trueValue = true;
- if (!isString(falseValue)) falseValue = false;
+ /**
+ * @ngdoc method
+ * @name ng.$http#put
+ * @methodOf ng.$http
+ *
+ * @description
+ * Shortcut method to perform `PUT` request.
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request
+ * @param {*} data Request content
+ * @param {Object=} config Optional configuration object
+ * @returns {HttpPromise} Future object
+ */
+ createShortMethodsWithData('post', 'put');
- element.bind('click', function() {
- scope.$apply(function() {
- ctrl.$setViewValue(element[0].checked);
- });
- });
+ /**
+ * @ngdoc property
+ * @name ng.$http#defaults
+ * @propertyOf ng.$http
+ *
+ * @description
+ * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
+ * default headers, withCredentials as well as request and response transformations.
+ *
+ * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
+ */
+ $http.defaults = defaults;
- ctrl.$render = function() {
- element[0].checked = ctrl.$viewValue;
- };
- ctrl.$formatters.push(function(value) {
- return value === trueValue;
- });
+ return $http;
- ctrl.$parsers.push(function(value) {
- return value ? trueValue : falseValue;
- });
-}
+ function createShortMethods(names) {
+ forEach(arguments, function(name) {
+ $http[name] = function(url, config) {
+ return $http(extend(config || {}, {
+ method: name,
+ url: url
+ }));
+ };
+ });
+ }
-/**
- * @ngdoc directive
- * @name ng.directive:textarea
- * @restrict E
- *
- * @description
- * HTML textarea element control with angular data-binding. The data-binding and validation
- * properties of this element are exactly the same as those of the
- * {@link ng.directive:input input element}.
- *
- * @param {string} ngModel Assignable angular expression to data-bind to.
- * @param {string=} name Property name of the form under which the control is published.
- * @param {string=} required Sets `required` validation error key if the value is not entered.
- * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
- * minlength.
- * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
- * maxlength.
- * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
- * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
- * patterns defined as scope expressions.
- * @param {string=} ngChange Angular expression to be executed when input changes due to user
- * interaction with the input element.
- */
+ function createShortMethodsWithData(name) {
+ forEach(arguments, function(name) {
+ $http[name] = function(url, data, config) {
+ return $http(extend(config || {}, {
+ method: name,
+ url: url,
+ data: data
+ }));
+ };
+ });
+ }
-/**
- * @ngdoc directive
- * @name ng.directive:input
- * @restrict E
- *
- * @description
- * HTML input element control with angular data-binding. Input control follows HTML5 input types
- * and polyfills the HTML5 validation behavior for older browsers.
- *
- * @param {string} ngModel Assignable angular expression to data-bind to.
- * @param {string=} name Property name of the form under which the control is published.
- * @param {string=} required Sets `required` validation error key if the value is not entered.
- * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
- * minlength.
- * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
- * maxlength.
- * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
- * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
- * patterns defined as scope expressions.
- * @param {string=} ngChange Angular expression to be executed when input changes due to user
- * interaction with the input element.
- *
- * @example
-
-
-
-
-
- User name:
-
- Required!
- Last name:
-
- Too short!
-
- Too long!
-
-
- user = {{user}}
- myForm.userName.$valid = {{myForm.userName.$valid}}
- myForm.userName.$error = {{myForm.userName.$error}}
- myForm.lastName.$valid = {{myForm.lastName.$valid}}
- myForm.userName.$error = {{myForm.lastName.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
- myForm.$error.minlength = {{!!myForm.$error.minlength}}
- myForm.$error.maxlength = {{!!myForm.$error.maxlength}}
-
-
-
- it('should initialize to model', function() {
- expect(binding('user')).toEqual('{"name":"guest","last":"visitor"}');
- expect(binding('myForm.userName.$valid')).toEqual('true');
- expect(binding('myForm.$valid')).toEqual('true');
- });
- it('should be invalid if empty when required', function() {
- input('user.name').enter('');
- expect(binding('user')).toEqual('{"last":"visitor"}');
- expect(binding('myForm.userName.$valid')).toEqual('false');
- expect(binding('myForm.$valid')).toEqual('false');
- });
+ /**
+ * Makes the request.
+ *
+ * !!! ACCESSES CLOSURE VARS:
+ * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests
+ */
+ function sendReq(config, reqData, reqHeaders) {
+ var deferred = $q.defer(),
+ promise = deferred.promise,
+ cache,
+ cachedResp,
+ url = buildUrl(config.url, config.params);
- it('should be valid if empty when min length is set', function() {
- input('user.last').enter('');
- expect(binding('user')).toEqual('{"name":"guest","last":""}');
- expect(binding('myForm.lastName.$valid')).toEqual('true');
- expect(binding('myForm.$valid')).toEqual('true');
- });
+ $http.pendingRequests.push(config);
+ promise.then(removePendingReq, removePendingReq);
- it('should be invalid if less than required min length', function() {
- input('user.last').enter('xx');
- expect(binding('user')).toEqual('{"name":"guest"}');
- expect(binding('myForm.lastName.$valid')).toEqual('false');
- expect(binding('myForm.lastName.$error')).toMatch(/minlength/);
- expect(binding('myForm.$valid')).toEqual('false');
- });
- it('should be invalid if longer than max length', function() {
- input('user.last').enter('some ridiculously long name');
- expect(binding('user'))
- .toEqual('{"name":"guest"}');
- expect(binding('myForm.lastName.$valid')).toEqual('false');
- expect(binding('myForm.lastName.$error')).toMatch(/maxlength/);
- expect(binding('myForm.$valid')).toEqual('false');
- });
-
-
- */
-var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) {
- return {
- restrict: 'E',
- require: '?ngModel',
- link: function(scope, element, attr, ctrl) {
- if (ctrl) {
- (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer,
- $browser);
+ if ((config.cache || defaults.cache) && config.cache !== false && config.method == 'GET') {
+ cache = isObject(config.cache) ? config.cache
+ : isObject(defaults.cache) ? defaults.cache
+ : defaultCache;
}
- }
- };
-}];
-var VALID_CLASS = 'ng-valid',
- INVALID_CLASS = 'ng-invalid',
- PRISTINE_CLASS = 'ng-pristine',
- DIRTY_CLASS = 'ng-dirty';
-
-/**
- * @ngdoc object
- * @name ng.directive:ngModel.NgModelController
- *
- * @property {string} $viewValue Actual string value in the view.
- * @property {*} $modelValue The value in the model, that the control is bound to.
- * @property {Array.} $parsers Whenever the control reads value from the DOM, it executes
- * all of these functions to sanitize / convert the value as well as validate.
- *
- * @property {Array.} $formatters Whenever the model value changes, it executes all of
- * these functions to convert the value as well as validate.
- *
- * @property {Object} $error An bject hash with all errors as keys.
- *
- * @property {boolean} $pristine True if user has not interacted with the control yet.
- * @property {boolean} $dirty True if user has already interacted with the control.
- * @property {boolean} $valid True if there is no error.
- * @property {boolean} $invalid True if at least one error on the control.
- *
- * @description
- *
- * `NgModelController` provides API for the `ng-model` directive. The controller contains
- * services for data-binding, validation, CSS update, value formatting and parsing. It
- * specifically does not contain any logic which deals with DOM rendering or listening to
- * DOM events. The `NgModelController` is meant to be extended by other directives where, the
- * directive provides DOM manipulation and the `NgModelController` provides the data-binding.
- *
- * This example shows how to use `NgModelController` with a custom control to achieve
- * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
- * collaborate together to achieve the desired result.
- *
- *
-
- [contenteditable] {
- border: 1px solid black;
- background-color: white;
- min-height: 20px;
+ if (cache) {
+ cachedResp = cache.get(url);
+ if (isDefined(cachedResp)) {
+ if (cachedResp.then) {
+ // cached request has already been sent, but there is no response yet
+ cachedResp.then(removePendingReq, removePendingReq);
+ return cachedResp;
+ } else {
+ // serving from cache
+ if (isArray(cachedResp)) {
+ resolvePromise(cachedResp[1], cachedResp[0], copy(cachedResp[2]));
+ } else {
+ resolvePromise(cachedResp, 200, {});
+ }
+ }
+ } else {
+ // put the promise for the non-transformed response into cache as a placeholder
+ cache.put(url, promise);
+ }
}
- .ng-invalid {
- border: 1px solid red;
+ // if we won't have the response in cache, send the request to the backend
+ if (isUndefined(cachedResp)) {
+ $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
+ config.withCredentials, config.responseType);
}
-
-
- angular.module('customControl', []).
- directive('contenteditable', function() {
- return {
- restrict: 'A', // only activate on element attribute
- require: '?ngModel', // get a hold of NgModelController
- link: function(scope, element, attrs, ngModel) {
- if(!ngModel) return; // do nothing if no ng-model
-
- // Specify how UI should be updated
- ngModel.$render = function() {
- element.html(ngModel.$viewValue || '');
- };
-
- // Listen for change events to enable binding
- element.bind('blur keyup change', function() {
- scope.$apply(read);
- });
- read(); // initialize
-
- // Write data to the model
- function read() {
- ngModel.$setViewValue(element.html());
- }
- }
- };
- });
-
-
-
- Change me!
- Required!
-
-
-
-
-
- it('should data-bind and become invalid', function() {
- var contentEditable = element('[contenteditable]');
-
- expect(contentEditable.text()).toEqual('Change me!');
- input('userContent').enter('');
- expect(contentEditable.text()).toEqual('');
- expect(contentEditable.prop('className')).toMatch(/ng-invalid-required/);
- });
-
- *
- *
- */
-var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse',
- function($scope, $exceptionHandler, $attr, $element, $parse) {
- this.$viewValue = Number.NaN;
- this.$modelValue = Number.NaN;
- this.$parsers = [];
- this.$formatters = [];
- this.$viewChangeListeners = [];
- this.$pristine = true;
- this.$dirty = false;
- this.$valid = true;
- this.$invalid = false;
- this.$name = $attr.name;
-
- var ngModelGet = $parse($attr.ngModel),
- ngModelSet = ngModelGet.assign;
-
- if (!ngModelSet) {
- throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + $attr.ngModel +
- ' (' + startingTag($element) + ')');
- }
+ return promise;
- /**
- * @ngdoc function
- * @name ng.directive:ngModel.NgModelController#$render
- * @methodOf ng.directive:ngModel.NgModelController
- *
- * @description
- * Called when the view needs to be updated. It is expected that the user of the ng-model
- * directive will implement this method.
- */
- this.$render = noop;
- var parentForm = $element.inheritedData('$formController') || nullFormCtrl,
- invalidCount = 0, // used to easily determine if we are valid
- $error = this.$error = {}; // keep invalid keys here
+ /**
+ * Callback registered to $httpBackend():
+ * - caches the response if desired
+ * - resolves the raw $http promise
+ * - calls $apply
+ */
+ function done(status, response, headersString) {
+ if (cache) {
+ if (isSuccess(status)) {
+ cache.put(url, [status, response, parseHeaders(headersString)]);
+ } else {
+ // remove promise from the cache
+ cache.remove(url);
+ }
+ }
+ resolvePromise(response, status, headersString);
+ if (!$rootScope.$$phase) $rootScope.$apply();
+ }
- // Setup initial state of the control
- $element.addClass(PRISTINE_CLASS);
- toggleValidCss(true);
- // convenience method for easy toggling of classes
- function toggleValidCss(isValid, validationErrorKey) {
- validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
- $element.
- removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey).
- addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
- }
+ /**
+ * Resolves the raw $http promise.
+ */
+ function resolvePromise(response, status, headers) {
+ // normalize internal statuses to 0
+ status = Math.max(status, 0);
- /**
- * @ngdoc function
- * @name ng.directive:ngModel.NgModelController#$setValidity
- * @methodOf ng.directive:ngModel.NgModelController
- *
- * @description
- * Change the validity state, and notifies the form when the control changes validity. (i.e. it
- * does not notify form if given validator is already marked as invalid).
- *
- * This method should be called by validators - i.e. the parser or formatter functions.
- *
- * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign
- * to `$error[validationErrorKey]=isValid` so that it is available for data-binding.
- * The `validationErrorKey` should be in camelCase and will get converted into dash-case
- * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
- * class and can be bound to as `{{someForm.someControl.$error.myError}}` .
- * @param {boolean} isValid Whether the current state is valid (true) or invalid (false).
- */
- this.$setValidity = function(validationErrorKey, isValid) {
- if ($error[validationErrorKey] === !isValid) return;
-
- if (isValid) {
- if ($error[validationErrorKey]) invalidCount--;
- if (!invalidCount) {
- toggleValidCss(true);
- this.$valid = true;
- this.$invalid = false;
+ (isSuccess(status) ? deferred.resolve : deferred.reject)({
+ data: response,
+ status: status,
+ headers: headersGetter(headers),
+ config: config
+ });
}
- } else {
- toggleValidCss(false);
- this.$invalid = true;
- this.$valid = false;
- invalidCount++;
- }
-
- $error[validationErrorKey] = !isValid;
- toggleValidCss(isValid, validationErrorKey);
-
- parentForm.$setValidity(validationErrorKey, isValid, this);
- };
- /**
- * @ngdoc function
- * @name ng.directive:ngModel.NgModelController#$setViewValue
- * @methodOf ng.directive:ngModel.NgModelController
- *
- * @description
- * Read a value from view.
- *
- * This method should be called from within a DOM event handler.
- * For example {@link ng.directive:input input} or
- * {@link ng.directive:select select} directives call it.
- *
- * It internally calls all `formatters` and if resulted value is valid, updates the model and
- * calls all registered change listeners.
- *
- * @param {string} value Value from the view.
- */
- this.$setViewValue = function(value) {
- this.$viewValue = value;
-
- // change to dirty
- if (this.$pristine) {
- this.$dirty = true;
- this.$pristine = false;
- $element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS);
- parentForm.$setDirty();
+ function removePendingReq() {
+ var idx = indexOf($http.pendingRequests, config);
+ if (idx !== -1) $http.pendingRequests.splice(idx, 1);
+ }
}
- forEach(this.$parsers, function(fn) {
- value = fn(value);
- });
-
- if (this.$modelValue !== value) {
- this.$modelValue = value;
- ngModelSet($scope, value);
- forEach(this.$viewChangeListeners, function(listener) {
- try {
- listener();
- } catch(e) {
- $exceptionHandler(e);
- }
- })
- }
- };
- // model -> value
- var ctrl = this;
- $scope.$watch(ngModelGet, function(value) {
+ function buildUrl(url, params) {
+ if (!params) return url;
+ var parts = [];
+ forEachSorted(params, function(value, key) {
+ if (value === null || isUndefined(value)) return;
+ if (!isArray(value)) value = [value];
- // ignore change from view
- if (ctrl.$modelValue === value) return;
+ forEach(value, function(v) {
+ if (isObject(v)) {
+ v = toJson(v);
+ }
+ parts.push(encodeUriQuery(key) + '=' +
+ encodeUriQuery(v));
+ });
+ });
+ return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&');
+ }
- var formatters = ctrl.$formatters,
- idx = formatters.length;
- ctrl.$modelValue = value;
- while(idx--) {
- value = formatters[idx](value);
- }
+ }];
+}
- if (ctrl.$viewValue !== value) {
- ctrl.$viewValue = value;
- ctrl.$render();
- }
- });
-}];
+var XHR = window.XMLHttpRequest || function() {
+ /* global ActiveXObject */
+ try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {}
+ try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {}
+ try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {}
+ throw minErr('$httpBackend')('noxhr', "This browser does not support XMLHttpRequest.");
+};
/**
- * @ngdoc directive
- * @name ng.directive:ngModel
- *
- * @element input
+ * @ngdoc object
+ * @name ng.$httpBackend
+ * @requires $browser
+ * @requires $window
+ * @requires $document
*
* @description
- * Is directive that tells Angular to do two-way data binding. It works together with `input`,
- * `select`, `textarea`. You can easily write your own directives to use `ngModel` as well.
- *
- * `ngModel` is responsible for:
- *
- * - binding the view into the model, which other directives such as `input`, `textarea` or `select`
- * require,
- * - providing validation behavior (i.e. required, number, email, url),
- * - keeping state of the control (valid/invalid, dirty/pristine, validation errors),
- * - setting related css class onto the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`),
- * - register the control with parent {@link ng.directive:form form}.
- *
- * For basic examples, how to use `ngModel`, see:
- *
- * - {@link ng.directive:input input}
- * - {@link ng.directive:input.text text}
- * - {@link ng.directive:input.checkbox checkbox}
- * - {@link ng.directive:input.radio radio}
- * - {@link ng.directive:input.number number}
- * - {@link ng.directive:input.email email}
- * - {@link ng.directive:input.url url}
- * - {@link ng.directive:select select}
- * - {@link ng.directive:textarea textarea}
+ * HTTP backend used by the {@link ng.$http service} that delegates to
+ * XMLHttpRequest object or JSONP and deals with browser incompatibilities.
+ *
+ * You should never need to use this service directly, instead use the higher-level abstractions:
+ * {@link ng.$http $http} or {@link ngResource.$resource $resource}.
*
+ * During testing this implementation is swapped with {@link ngMock.$httpBackend mock
+ * $httpBackend} which can be trained with responses.
*/
-var ngModelDirective = function() {
- return {
- require: ['ngModel', '^?form'],
- controller: NgModelController,
- link: function(scope, element, attr, ctrls) {
- // notify others, especially parent forms
-
- var modelCtrl = ctrls[0],
- formCtrl = ctrls[1] || nullFormCtrl;
+function $HttpBackendProvider() {
+ this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) {
+ return createHttpBackend($browser, XHR, $browser.defer, $window.angular.callbacks, $document[0]);
+ }];
+}
- formCtrl.$addControl(modelCtrl);
+function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument) {
+ var ABORTED = -1;
- element.bind('$destroy', function() {
- formCtrl.$removeControl(modelCtrl);
- });
- }
- };
-};
+ // TODO(vojta): fix the signature
+ return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
+ var status;
+ $browser.$$incOutstandingRequestCount();
+ url = url || $browser.url();
+ if (lowercase(method) == 'jsonp') {
+ var callbackId = '_' + (callbacks.counter++).toString(36);
+ callbacks[callbackId] = function(data) {
+ callbacks[callbackId].data = data;
+ };
-/**
- * @ngdoc directive
- * @name ng.directive:ngChange
- * @restrict E
- *
- * @description
- * Evaluate given expression when user changes the input.
- * The expression is not evaluated when the value change is coming from the model.
- *
- * Note, this directive requires `ngModel` to be present.
- *
- * @element input
- *
- * @example
- *
- *
- *
- *
- *
- *
- * Confirmed
- * debug = {{confirmed}}
- * counter = {{counter}}
- *
- *
- *
- * it('should evaluate the expression if changing from view', function() {
- * expect(binding('counter')).toEqual('0');
- * element('#ng-change-example1').click();
- * expect(binding('counter')).toEqual('1');
- * expect(binding('confirmed')).toEqual('true');
- * });
- *
- * it('should not evaluate the expression if changing from model', function() {
- * element('#ng-change-example2').click();
- * expect(binding('counter')).toEqual('0');
- * expect(binding('confirmed')).toEqual('true');
- * });
- *
- *
- */
-var ngChangeDirective = valueFn({
- require: 'ngModel',
- link: function(scope, element, attr, ctrl) {
- ctrl.$viewChangeListeners.push(function() {
- scope.$eval(attr.ngChange);
- });
- }
-});
+ var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId),
+ function() {
+ if (callbacks[callbackId].data) {
+ completeRequest(callback, 200, callbacks[callbackId].data);
+ } else {
+ completeRequest(callback, status || -2);
+ }
+ delete callbacks[callbackId];
+ });
+ } else {
+ var xhr = new XHR();
+ xhr.open(method, url, true);
+ forEach(headers, function(value, key) {
+ if (isDefined(value)) {
+ xhr.setRequestHeader(key, value);
+ }
+ });
+ // In IE6 and 7, this might be called synchronously when xhr.send below is called and the
+ // response is in the cache. the promise api will ensure that to the app code the api is
+ // always async
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ var responseHeaders = null,
+ response = null;
-var requiredDirective = function() {
- return {
- require: '?ngModel',
- link: function(scope, elm, attr, ctrl) {
- if (!ctrl) return;
- attr.required = true; // force truthy in case we are on non input element
+ if(status !== ABORTED) {
+ responseHeaders = xhr.getAllResponseHeaders();
+ response = xhr.responseType ? xhr.response : xhr.responseText;
+ }
- var validator = function(value) {
- if (attr.required && (isEmpty(value) || value === false)) {
- ctrl.$setValidity('required', false);
- return;
- } else {
- ctrl.$setValidity('required', true);
- return value;
+ // responseText is the old-school way of retrieving response (supported by IE8 & 9)
+ // response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
+ completeRequest(callback,
+ status || xhr.status,
+ response,
+ responseHeaders);
}
};
- ctrl.$formatters.push(validator);
- ctrl.$parsers.unshift(validator);
+ if (withCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ if (responseType) {
+ xhr.responseType = responseType;
+ }
- attr.$observe('required', function() {
- validator(ctrl.$viewValue);
- });
+ xhr.send(post || null);
}
- };
-};
+ if (timeout > 0) {
+ var timeoutId = $browserDefer(timeoutRequest, timeout);
+ } else if (timeout && timeout.then) {
+ timeout.then(timeoutRequest);
+ }
-/**
- * @ngdoc directive
- * @name ng.directive:ngList
- *
- * @description
- * Text input that converts between comma-seperated string into an array of strings.
- *
- * @element input
- * @param {string=} ngList optional delimiter that should be used to split the value. If
- * specified in form `/something/` then the value will be converted into a regular expression.
- *
- * @example
-
-
-
-
- List:
-
- Required!
- names = {{names}}
- myForm.namesInput.$valid = {{myForm.namesInput.$valid}}
- myForm.namesInput.$error = {{myForm.namesInput.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
-
-
-
- it('should initialize to model', function() {
- expect(binding('names')).toEqual('["igor","misko","vojta"]');
- expect(binding('myForm.namesInput.$valid')).toEqual('true');
- });
- it('should be invalid if empty', function() {
- input('names').enter('');
- expect(binding('names')).toEqual('[]');
- expect(binding('myForm.namesInput.$valid')).toEqual('false');
- });
-
-
- */
-var ngListDirective = function() {
- return {
- require: 'ngModel',
- link: function(scope, element, attr, ctrl) {
- var match = /\/(.*)\//.exec(attr.ngList),
- separator = match && new RegExp(match[1]) || attr.ngList || ',';
-
- var parse = function(viewValue) {
- var list = [];
-
- if (viewValue) {
- forEach(viewValue.split(separator), function(value) {
- if (value) list.push(trim(value));
- });
- }
+ function timeoutRequest() {
+ status = ABORTED;
+ jsonpDone && jsonpDone();
+ xhr && xhr.abort();
+ }
- return list;
- };
+ function completeRequest(callback, status, response, headersString) {
+ var protocol = urlResolve(url).protocol;
- ctrl.$parsers.push(parse);
- ctrl.$formatters.push(function(value) {
- if (isArray(value)) {
- return value.join(', ');
- }
+ // cancel timeout and subsequent timeout promise resolution
+ timeoutId && $browserDefer.cancel(timeoutId);
+ jsonpDone = xhr = null;
- return undefined;
- });
+ // fix status code for file protocol (it's always 0)
+ status = (protocol == 'file' && status === 0) ? (response ? 200 : 404) : status;
+
+ // normalize IE bug (http://bugs.jquery.com/ticket/1450)
+ status = status == 1223 ? 204 : status;
+
+ callback(status, response, headersString);
+ $browser.$$completeOutstandingRequest(noop);
}
};
-};
+ function jsonpReq(url, done) {
+ // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.:
+ // - fetches local scripts via XHR and evals them
+ // - adds and immediately removes script elements from the document
+ var script = rawDocument.createElement('script'),
+ doneWrapper = function() {
+ script.onreadystatechange = script.onload = script.onerror = null;
+ rawDocument.body.removeChild(script);
+ if (done) done();
+ };
-var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
+ script.type = 'text/javascript';
+ script.src = url;
-var ngValueDirective = function() {
- return {
- priority: 100,
- compile: function(tpl, tplAttr) {
- if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) {
- return function(scope, elm, attr) {
- attr.$set('value', scope.$eval(attr.ngValue));
- };
- } else {
- return function(scope, elm, attr) {
- scope.$watch(attr.ngValue, function(value) {
- attr.$set('value', value, false);
- });
- };
- }
+ if (msie && msie <= 8) {
+ script.onreadystatechange = function() {
+ if (/loaded|complete/.test(script.readyState)) {
+ doneWrapper();
+ }
+ };
+ } else {
+ script.onload = script.onerror = function() {
+ doneWrapper();
+ };
}
- };
-};
+
+ rawDocument.body.appendChild(script);
+ return doneWrapper;
+ }
+}
+
+var $interpolateMinErr = minErr('$interpolate');
/**
- * @ngdoc directive
- * @name ng.directive:ngBind
+ * @ngdoc object
+ * @name ng.$interpolateProvider
+ * @function
*
* @description
- * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element
- * with the value of a given expression, and to update the text content when the value of that
- * expression changes.
- *
- * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like
- * `{{ expression }}` which is similar but less verbose.
- *
- * Once scenario in which the use of `ngBind` is prefered over `{{ expression }}` binding is when
- * it's desirable to put bindings into template that is momentarily displayed by the browser in its
- * raw state before Angular compiles it. Since `ngBind` is an element attribute, it makes the
- * bindings invisible to the user while the page is loading.
- *
- * An alternative solution to this problem would be using the
- * {@link ng.directive:ngCloak ngCloak} directive.
*
- *
- * @element ANY
- * @param {expression} ngBind {@link guide/expression Expression} to evaluate.
+ * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
*
* @example
- * Enter a name in the Live Preview text box; the greeting below the text box changes instantly.
-
-
-
-
- Enter name:
- Hello !
-
-
-
- it('should check ng-bind', function() {
- expect(using('.doc-example-live').binding('name')).toBe('Whirled');
- using('.doc-example-live').input('name').enter('world');
- expect(using('.doc-example-live').binding('name')).toBe('world');
- });
-
-
- */
-var ngBindDirective = ngDirective(function(scope, element, attr) {
- element.addClass('ng-binding').data('$binding', attr.ngBind);
- scope.$watch(attr.ngBind, function(value) {
- element.text(value == undefined ? '' : value);
+
+
+
-
-
-
- it('should check ng-bind', function() {
- expect(using('.doc-example-live').binding('salutation')).
- toBe('Hello');
- expect(using('.doc-example-live').binding('name')).
- toBe('World');
- using('.doc-example-live').input('salutation').enter('Greetings');
- using('.doc-example-live').input('name').enter('user');
- expect(using('.doc-example-live').binding('salutation')).
- toBe('Greetings');
- expect(using('.doc-example-live').binding('name')).
- toBe('user');
- });
-
-
+ customInterpolationApp.controller('DemoController', function DemoController() {
+ this.label = "This binding is brought you by // interpolation symbols.";
+ });
+
+
+ //demo.label//
+
+
+
+ it('should interpolate binding with custom symbols', function() {
+ expect(binding('demo.label')).toBe('This binding is brought you by // interpolation symbols.');
+ });
+
+
*/
-var ngBindTemplateDirective = ['$interpolate', function($interpolate) {
- return function(scope, element, attr) {
- // TODO: move this to scenario runner
- var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
- element.addClass('ng-binding').data('$binding', interpolateFn);
- attr.$observe('ngBindTemplate', function(value) {
- element.text(value);
- });
- }
-}];
+function $InterpolateProvider() {
+ var startSymbol = '{{';
+ var endSymbol = '}}';
+ /**
+ * @ngdoc method
+ * @name ng.$interpolateProvider#startSymbol
+ * @methodOf ng.$interpolateProvider
+ * @description
+ * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
+ *
+ * @param {string=} value new value to set the starting symbol to.
+ * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
+ */
+ this.startSymbol = function(value){
+ if (value) {
+ startSymbol = value;
+ return this;
+ } else {
+ return startSymbol;
+ }
+ };
-/**
- * @ngdoc directive
- * @name ng.directive:ngBindHtmlUnsafe
- *
- * @description
- * Creates a binding that will innerHTML the result of evaluating the `expression` into the current
- * element. *The innerHTML-ed content will not be sanitized!* You should use this directive only if
- * {@link ngSanitize.directive:ngBindHtml ngBindHtml} directive is too
- * restrictive and when you absolutely trust the source of the content you are binding to.
- *
- * See {@link ngSanitize.$sanitize $sanitize} docs for examples.
- *
- * @element ANY
- * @param {expression} ngBindHtmlUnsafe {@link guide/expression Expression} to evaluate.
- */
-var ngBindHtmlUnsafeDirective = [function() {
- return function(scope, element, attr) {
- element.addClass('ng-binding').data('$binding', attr.ngBindHtmlUnsafe);
- scope.$watch(attr.ngBindHtmlUnsafe, function(value) {
- element.html(value || '');
- });
+ /**
+ * @ngdoc method
+ * @name ng.$interpolateProvider#endSymbol
+ * @methodOf ng.$interpolateProvider
+ * @description
+ * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
+ *
+ * @param {string=} value new value to set the ending symbol to.
+ * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
+ */
+ this.endSymbol = function(value){
+ if (value) {
+ endSymbol = value;
+ return this;
+ } else {
+ return endSymbol;
+ }
};
-}];
-function classDirective(name, selector) {
- name = 'ngClass' + name;
- return ngDirective(function(scope, element, attr) {
- scope.$watch(attr[name], function(newVal, oldVal) {
- if (selector === true || scope.$index % 2 === selector) {
- if (oldVal && (newVal !== oldVal)) {
- if (isObject(oldVal) && !isArray(oldVal))
- oldVal = map(oldVal, function(v, k) { if (v) return k });
- element.removeClass(isArray(oldVal) ? oldVal.join(' ') : oldVal);
- }
- if (isObject(newVal) && !isArray(newVal))
- newVal = map(newVal, function(v, k) { if (v) return k });
- if (newVal) element.addClass(isArray(newVal) ? newVal.join(' ') : newVal); }
- }, true);
- });
-}
-/**
- * @ngdoc directive
- * @name ng.directive:ngClass
- *
- * @description
- * The `ngClass` allows you to set CSS class on HTML element dynamically by databinding an
- * expression that represents all classes to be added.
- *
- * The directive won't add duplicate classes if a particular class was already set.
- *
- * When the expression changes, the previously added classes are removed and only then the classes
- * new classes are added.
- *
- * @element ANY
- * @param {expression} ngClass {@link guide/expression Expression} to eval. The result
- * of the evaluation can be a string representing space delimited class
- * names, an array, or a map of class names to boolean values.
- *
- * @example
-
-
-
-
-
- Sample Text
-
-
- .my-class {
- color: red;
- }
-
-
- it('should check ng-class', function() {
- expect(element('.doc-example-live span').prop('className')).not().
- toMatch(/my-class/);
+ this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) {
+ var startSymbolLength = startSymbol.length,
+ endSymbolLength = endSymbol.length;
+
+ /**
+ * @ngdoc function
+ * @name ng.$interpolate
+ * @function
+ *
+ * @requires $parse
+ * @requires $sce
+ *
+ * @description
+ *
+ * Compiles a string with markup into an interpolation function. This service is used by the
+ * HTML {@link ng.$compile $compile} service for data binding. See
+ * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
+ * interpolation markup.
+ *
+ *
+
+ var $interpolate = ...; // injected
+ var exp = $interpolate('Hello {{name}}!');
+ expect(exp({name:'Angular'}).toEqual('Hello Angular!');
+
+ *
+ *
+ * @param {string} text The text with markup to interpolate.
+ * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
+ * embedded expression in order to return an interpolation function. Strings with no
+ * embedded expression will return null for the interpolation function.
+ * @param {string=} trustedContext when provided, the returned function passes the interpolated
+ * result through {@link ng.$sce#methods_getTrusted $sce.getTrusted(interpolatedResult,
+ * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that
+ * provides Strict Contextual Escaping for details.
+ * @returns {function(context)} an interpolation function which is used to compute the
+ * interpolated string. The function has these parameters:
+ *
+ * * `context`: an object against which any expressions embedded in the strings are evaluated
+ * against.
+ *
+ */
+ function $interpolate(text, mustHaveExpression, trustedContext) {
+ var startIndex,
+ endIndex,
+ index = 0,
+ parts = [],
+ length = text.length,
+ hasInterpolation = false,
+ fn,
+ exp,
+ concat = [];
+
+ while(index < length) {
+ if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) &&
+ ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) {
+ (index != startIndex) && parts.push(text.substring(index, startIndex));
+ parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex)));
+ fn.exp = exp;
+ index = endIndex + endSymbolLength;
+ hasInterpolation = true;
+ } else {
+ // we did not find anything, so we have to add the remainder to the parts array
+ (index != length) && parts.push(text.substring(index));
+ index = length;
+ }
+ }
+
+ if (!(length = parts.length)) {
+ // we added, nothing, must have been an empty string.
+ parts.push('');
+ length = 1;
+ }
+
+ // Concatenating expressions makes it hard to reason about whether some combination of
+ // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a
+ // single expression be used for iframe[src], object[src], etc., we ensure that the value
+ // that's used is assigned or constructed by some JS code somewhere that is more testable or
+ // make it obvious that you bound the value to some user controlled value. This helps reduce
+ // the load when auditing for XSS issues.
+ if (trustedContext && parts.length > 1) {
+ throw $interpolateMinErr('noconcat',
+ "Error while interpolating: {0}\nStrict Contextual Escaping disallows " +
+ "interpolations that concatenate multiple expressions when a trusted value is " +
+ "required. See http://docs.angularjs.org/api/ng.$sce", text);
+ }
+
+ if (!mustHaveExpression || hasInterpolation) {
+ concat.length = length;
+ fn = function(context) {
+ try {
+ for(var i = 0, ii = length, part; i
-
- */
-var ngClassDirective = classDirective('', true);
+ /**
+ * @ngdoc method
+ * @name ng.$interpolate#endSymbol
+ * @methodOf ng.$interpolate
+ * @description
+ * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
+ *
+ * Use {@link ng.$interpolateProvider#endSymbol $interpolateProvider#endSymbol} to change
+ * the symbol.
+ *
+ * @returns {string} start symbol.
+ */
+ $interpolate.endSymbol = function() {
+ return endSymbol;
+ };
-/**
- * @ngdoc directive
- * @name ng.directive:ngClassOdd
- *
- * @description
- * The `ngClassOdd` and `ngClassEven` directives work exactly as
- * {@link ng.directive:ngClass ngClass}, except it works in
- * conjunction with `ngRepeat` and takes affect only on odd (even) rows.
- *
- * This directive can be applied only within a scope of an
- * {@link ng.directive:ngRepeat ngRepeat}.
- *
- * @element ANY
- * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result
- * of the evaluation can be a string representing space delimited class names or an array.
- *
- * @example
-
-
-
-
-
- {{name}}
-
-
-
-
-
- .odd {
- color: red;
- }
- .even {
- color: blue;
- }
-
-
- it('should check ng-class-odd and ng-class-even', function() {
- expect(element('.doc-example-live li:first span').prop('className')).
- toMatch(/odd/);
- expect(element('.doc-example-live li:last span').prop('className')).
- toMatch(/even/);
- });
-
-
- */
-var ngClassOddDirective = classDirective('Odd', 0);
+ return $interpolate;
+ }];
+}
-/**
- * @ngdoc directive
- * @name ng.directive:ngClassEven
- *
- * @description
- * The `ngClassOdd` and `ngClassEven` works exactly as
- * {@link ng.directive:ngClass ngClass}, except it works in
- * conjunction with `ngRepeat` and takes affect only on odd (even) rows.
- *
- * This directive can be applied only within a scope of an
- * {@link ng.directive:ngRepeat ngRepeat}.
- *
- * @element ANY
- * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The
- * result of the evaluation can be a string representing space delimited class names or an array.
- *
- * @example
-
-
-
-
-
- {{name}}
-
-
-
-
-
- .odd {
- color: red;
- }
- .even {
- color: blue;
- }
-
-
- it('should check ng-class-odd and ng-class-even', function() {
- expect(element('.doc-example-live li:first span').prop('className')).
- toMatch(/odd/);
- expect(element('.doc-example-live li:last span').prop('className')).
- toMatch(/even/);
- });
-
-
- */
-var ngClassEvenDirective = classDirective('Even', 1);
+function $IntervalProvider() {
+ this.$get = ['$rootScope', '$window', '$q',
+ function($rootScope, $window, $q) {
+ var intervals = {};
-/**
- * @ngdoc directive
- * @name ng.directive:ngCloak
- *
- * @description
- * The `ngCloak` directive is used to prevent the Angular html template from being briefly
- * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this
- * directive to avoid the undesirable flicker effect caused by the html template display.
- *
- * The directive can be applied to the `` element, but typically a fine-grained application is
- * prefered in order to benefit from progressive rendering of the browser view.
- *
- * `ngCloak` works in cooperation with a css rule that is embedded within `angular.js` and
- * `angular.min.js` files. Following is the css rule:
- *
- *
- * [ng\:cloak], [ng-cloak], .ng-cloak {
- * display: none;
- * }
- *
- *
- * When this css rule is loaded by the browser, all html elements (including their children) that
- * are tagged with the `ng-cloak` directive are hidden. When Angular comes across this directive
- * during the compilation of the template it deletes the `ngCloak` element attribute, which
- * makes the compiled element visible.
- *
- * For the best result, `angular.js` script must be loaded in the head section of the html file;
- * alternatively, the css rule (above) must be included in the external stylesheet of the
- * application.
- *
- * Legacy browsers, like IE7, do not provide attribute selector support (added in CSS 2.1) so they
- * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css
- * class `ngCloak` in addition to `ngCloak` directive as shown in the example below.
- *
- * @element ANY
- *
- * @example
-
-
- {{ 'hello' }}
- {{ 'hello IE7' }}
-
-
- it('should remove the template directive and css class', function() {
- expect(element('.doc-example-live #template1').attr('ng-cloak')).
- not().toBeDefined();
- expect(element('.doc-example-live #template2').attr('ng-cloak')).
- not().toBeDefined();
- });
-
-
- *
- */
-var ngCloakDirective = ngDirective({
- compile: function(element, attr) {
- attr.$set('ngCloak', undefined);
- element.removeClass('ng-cloak');
- }
-});
-/**
- * @ngdoc directive
- * @name ng.directive:ngController
- *
- * @description
- * The `ngController` directive assigns behavior to a scope. This is a key aspect of how angular
- * supports the principles behind the Model-View-Controller design pattern.
- *
- * MVC components in angular:
- *
- * * Model — The Model is data in scope properties; scopes are attached to the DOM.
- * * View — The template (HTML with data bindings) is rendered into the View.
- * * Controller — The `ngController` directive specifies a Controller class; the class has
- * methods that typically express the business logic behind the application.
- *
- * Note that an alternative way to define controllers is via the `{@link ng.$route}`
- * service.
- *
- * @element ANY
- * @scope
- * @param {expression} ngController Name of a globally accessible constructor function or an
- * {@link guide/expression expression} that on the current scope evaluates to a
- * constructor function.
- *
- * @example
- * Here is a simple form for editing user contact information. Adding, removing, clearing, and
- * greeting are methods declared on the controller (see source tab). These methods can
- * easily be called from the angular markup. Notice that the scope becomes the `this` for the
- * controller's instance. This allows for easy access to the view data from the controller. Also
- * notice that any changes to the data are automatically reflected in the View without the need
- * for a manual update.
-
-
-
-
- Name:
- [
greet ]
- Contact:
-
-
-
-
- it('should check controller', function() {
- expect(element('.doc-example-live div>:input').val()).toBe('John Smith');
- expect(element('.doc-example-live li:nth-child(1) input').val())
- .toBe('408 555 1212');
- expect(element('.doc-example-live li:nth-child(2) input').val())
- .toBe('john.smith@example.org');
-
- element('.doc-example-live li:first a:contains("clear")').click();
- expect(element('.doc-example-live li:first input').val()).toBe('');
-
- element('.doc-example-live li:last a:contains("add")').click();
- expect(element('.doc-example-live li:nth-child(3) input').val())
- .toBe('yourname@example.org');
- });
-
-
- */
-var ngControllerDirective = [function() {
- return {
- scope: true,
- controller: '@'
- };
-}];
-/**
- * @ngdoc directive
- * @name ng.directive:ngCsp
- * @priority 1000
- *
- * @description
- * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support.
- * This directive should be used on the root element of the application (typically the ``
- * element or other element with the {@link ng.directive:ngApp ngApp}
- * directive).
- *
- * If enabled the performance of template expression evaluator will suffer slightly, so don't enable
- * this mode unless you need it.
- *
- * @element html
- */
+ if (!skipApply) $rootScope.$apply();
-var ngCspDirective = ['$sniffer', function($sniffer) {
- return {
- priority: 1000,
- compile: function() {
- $sniffer.csp = true;
- }
- };
-}];
+ }, delay);
-/**
- * @ngdoc directive
- * @name ng.directive:ngClick
- *
- * @description
- * The ngClick allows you to specify custom behavior when
- * element is clicked.
- *
- * @element ANY
- * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon
- * click. (Event object is available as `$event`)
- *
- * @example
-
-
-
- Increment
-
- count: {{count}}
-
-
- it('should check ng-click', function() {
- expect(binding('count')).toBe('0');
- element('.doc-example-live :button').click();
- expect(binding('count')).toBe('1');
- });
-
-
- */
-/*
- * A directive that allows creation of custom onclick handlers that are defined as angular
- * expressions and are compiled and executed within the current scope.
- *
- * Events that are handled via these handler are always configured not to propagate further.
- */
-var ngEventDirectives = {};
-forEach(
- 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave'.split(' '),
- function(name) {
- var directiveName = directiveNormalize('ng-' + name);
- ngEventDirectives[directiveName] = ['$parse', function($parse) {
- return function(scope, element, attr) {
- var fn = $parse(attr[directiveName]);
- element.bind(lowercase(name), function(event) {
- scope.$apply(function() {
- fn(scope, {$event:event});
- });
- });
- };
- }];
- }
-);
+ intervals[promise.$$intervalId] = deferred;
-/**
- * @ngdoc directive
- * @name ng.directive:ngDblclick
- *
- * @description
- * The `ngDblclick` directive allows you to specify custom behavior on dblclick event.
- *
- * @element ANY
- * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon
- * dblclick. (Event object is available as `$event`)
- *
- * @example
- * See {@link ng.directive:ngClick ngClick}
- */
+ return promise;
+ }
-/**
- * @ngdoc directive
- * @name ng.directive:ngMousedown
- *
- * @description
- * The ngMousedown directive allows you to specify custom behavior on mousedown event.
- *
- * @element ANY
- * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon
- * mousedown. (Event object is available as `$event`)
- *
- * @example
- * See {@link ng.directive:ngClick ngClick}
- */
+ /**
+ * @ngdoc function
+ * @name ng.$interval#cancel
+ * @methodOf ng.$interval
+ *
+ * @description
+ * Cancels a task associated with the `promise`.
+ *
+ * @param {number} promise Promise returned by the `$interval` function.
+ * @returns {boolean} Returns `true` if the task was successfully canceled.
+ */
+ interval.cancel = function(promise) {
+ if (promise && promise.$$intervalId in intervals) {
+ intervals[promise.$$intervalId].reject('canceled');
+ clearInterval(promise.$$intervalId);
+ delete intervals[promise.$$intervalId];
+ return true;
+ }
+ return false;
+ };
+ return interval;
+ }];
+}
/**
- * @ngdoc directive
- * @name ng.directive:ngMouseup
+ * @ngdoc object
+ * @name ng.$locale
*
* @description
- * Specify custom behavior on mouseup event.
- *
- * @element ANY
- * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon
- * mouseup. (Event object is available as `$event`)
+ * $locale service provides localization rules for various Angular components. As of right now the
+ * only public api is:
*
- * @example
- * See {@link ng.directive:ngClick ngClick}
+ * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`)
*/
+function $LocaleProvider(){
+ this.$get = function() {
+ return {
+ id: 'en-us',
-/**
- * @ngdoc directive
- * @name ng.directive:ngMouseover
- *
- * @description
- * Specify custom behavior on mouseover event.
- *
- * @element ANY
- * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon
- * mouseover. (Event object is available as `$event`)
- *
- * @example
- * See {@link ng.directive:ngClick ngClick}
- */
+ NUMBER_FORMATS: {
+ DECIMAL_SEP: '.',
+ GROUP_SEP: ',',
+ PATTERNS: [
+ { // Decimal Pattern
+ minInt: 1,
+ minFrac: 0,
+ maxFrac: 3,
+ posPre: '',
+ posSuf: '',
+ negPre: '-',
+ negSuf: '',
+ gSize: 3,
+ lgSize: 3
+ },{ //Currency Pattern
+ minInt: 1,
+ minFrac: 2,
+ maxFrac: 2,
+ posPre: '\u00A4',
+ posSuf: '',
+ negPre: '(\u00A4',
+ negSuf: ')',
+ gSize: 3,
+ lgSize: 3
+ }
+ ],
+ CURRENCY_SYM: '$'
+ },
+ DATETIME_FORMATS: {
+ MONTH:
+ 'January,February,March,April,May,June,July,August,September,October,November,December'
+ .split(','),
+ SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','),
+ DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','),
+ SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','),
+ AMPMS: ['AM','PM'],
+ medium: 'MMM d, y h:mm:ss a',
+ short: 'M/d/yy h:mm a',
+ fullDate: 'EEEE, MMMM d, y',
+ longDate: 'MMMM d, y',
+ mediumDate: 'MMM d, y',
+ shortDate: 'M/d/yy',
+ mediumTime: 'h:mm:ss a',
+ shortTime: 'h:mm a'
+ },
-/**
- * @ngdoc directive
- * @name ng.directive:ngMouseenter
- *
- * @description
- * Specify custom behavior on mouseenter event.
- *
- * @element ANY
- * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon
- * mouseenter. (Event object is available as `$event`)
- *
- * @example
- * See {@link ng.directive:ngClick ngClick}
- */
+ pluralCat: function(num) {
+ if (num === 1) {
+ return 'one';
+ }
+ return 'other';
+ }
+ };
+ };
+}
+
+var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/,
+ DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21};
+var $locationMinErr = minErr('$location');
/**
- * @ngdoc directive
- * @name ng.directive:ngMouseleave
- *
- * @description
- * Specify custom behavior on mouseleave event.
- *
- * @element ANY
- * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon
- * mouseleave. (Event object is available as `$event`)
+ * Encode path using encodeUriSegment, ignoring forward slashes
*
- * @example
- * See {@link ng.directive:ngClick ngClick}
+ * @param {string} path Path to encode
+ * @returns {string}
*/
+function encodePath(path) {
+ var segments = path.split('/'),
+ i = segments.length;
+ while (i--) {
+ segments[i] = encodeUriSegment(segments[i]);
+ }
-/**
- * @ngdoc directive
- * @name ng.directive:ngMousemove
- *
- * @description
- * Specify custom behavior on mousemove event.
- *
- * @element ANY
- * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon
- * mousemove. (Event object is available as `$event`)
- *
- * @example
- * See {@link ng.directive:ngClick ngClick}
- */
+ return segments.join('/');
+}
+function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) {
+ var parsedUrl = urlResolve(absoluteUrl, appBase);
+
+ locationObj.$$protocol = parsedUrl.protocol;
+ locationObj.$$host = parsedUrl.hostname;
+ locationObj.$$port = int(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
+}
+
+
+function parseAppUrl(relativeUrl, locationObj, appBase) {
+ var prefixed = (relativeUrl.charAt(0) !== '/');
+ if (prefixed) {
+ relativeUrl = '/' + relativeUrl;
+ }
+ var match = urlResolve(relativeUrl, appBase);
+ locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ?
+ match.pathname.substring(1) : match.pathname);
+ locationObj.$$search = parseKeyValue(match.search);
+ locationObj.$$hash = decodeURIComponent(match.hash);
+
+ // make sure path starts with '/';
+ if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') {
+ locationObj.$$path = '/' + locationObj.$$path;
+ }
+}
-/**
- * @ngdoc directive
- * @name ng.directive:ngSubmit
- *
- * @description
- * Enables binding angular expressions to onsubmit events.
- *
- * Additionally it prevents the default action (which for form means sending the request to the
- * server and reloading the current page).
- *
- * @element form
- * @param {expression} ngSubmit {@link guide/expression Expression} to eval.
- *
- * @example
-
-
-
-
- Enter text and hit enter:
-
-
- list={{list}}
-
-
-
- it('should check ng-submit', function() {
- expect(binding('list')).toBe('[]');
- element('.doc-example-live #submit').click();
- expect(binding('list')).toBe('["hello"]');
- expect(input('text').val()).toBe('');
- });
- it('should ignore empty strings', function() {
- expect(binding('list')).toBe('[]');
- element('.doc-example-live #submit').click();
- element('.doc-example-live #submit').click();
- expect(binding('list')).toBe('["hello"]');
- });
-
-
- */
-var ngSubmitDirective = ngDirective(function(scope, element, attrs) {
- element.bind('submit', function() {
- scope.$apply(attrs.ngSubmit);
- });
-});
/**
- * @ngdoc directive
- * @name ng.directive:ngInclude
- * @restrict ECA
- *
- * @description
- * Fetches, compiles and includes an external HTML fragment.
- *
- * Keep in mind that Same Origin Policy applies to included resources
- * (e.g. ngInclude won't work for cross-domain requests on all browsers and for
- * file:// access on some browsers).
*
- * @scope
- *
- * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant,
- * make sure you wrap it in quotes, e.g. `src="'myPartialTemplate.html'"`.
- * @param {string=} onload Expression to evaluate when a new partial is loaded.
- *
- * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll
- * $anchorScroll} to scroll the viewport after the content is loaded.
- *
- * - If the attribute is not set, disable scrolling.
- * - If the attribute is set without value, enable scrolling.
- * - Otherwise enable scrolling only if the expression evaluates to truthy value.
- *
- * @example
-
-
-
-
- (blank)
-
- url of the template:
{{template.url}}
-
-
-
-
-
- function Ctrl($scope) {
- $scope.templates =
- [ { name: 'template1.html', url: 'template1.html'}
- , { name: 'template2.html', url: 'template2.html'} ];
- $scope.template = $scope.templates[0];
- }
-
-
- Content of template1.html
-
-
- Content of template2.html
-
-
- it('should load template1.html', function() {
- expect(element('.doc-example-live [ng-include]').text()).
- toMatch(/Content of template1.html/);
- });
- it('should load template2.html', function() {
- select('template').option('1');
- expect(element('.doc-example-live [ng-include]').text()).
- toMatch(/Content of template2.html/);
- });
- it('should change to blank', function() {
- select('template').option('');
- expect(element('.doc-example-live [ng-include]').text()).toEqual('');
- });
-
-
+ * @param {string} begin
+ * @param {string} whole
+ * @returns {string} returns text from whole after begin or undefined if it does not begin with
+ * expected string.
*/
+function beginsWith(begin, whole) {
+ if (whole.indexOf(begin) === 0) {
+ return whole.substr(begin.length);
+ }
+}
+
+
+function stripHash(url) {
+ var index = url.indexOf('#');
+ return index == -1 ? url : url.substr(0, index);
+}
+
+
+function stripFile(url) {
+ return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
+}
+
+/* return the server only (scheme://host:port) */
+function serverBase(url) {
+ return url.substring(0, url.indexOf('/', url.indexOf('//') + 2));
+}
/**
- * @ngdoc event
- * @name ng.directive:ngInclude#$includeContentLoaded
- * @eventOf ng.directive:ngInclude
- * @eventType emit on the current ngInclude scope
- * @description
- * Emitted every time the ngInclude content is reloaded.
+ * LocationHtml5Url represents an url
+ * This object is exposed as $location service when HTML5 mode is enabled and supported
+ *
+ * @constructor
+ * @param {string} appBase application base URL
+ * @param {string} basePrefix url path prefix
*/
-var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile',
- function($http, $templateCache, $anchorScroll, $compile) {
- return {
- restrict: 'ECA',
- terminal: true,
- compile: function(element, attr) {
- var srcExp = attr.ngInclude || attr.src,
- onloadExp = attr.onload || '',
- autoScrollExp = attr.autoscroll;
-
- return function(scope, element) {
- var changeCounter = 0,
- childScope;
-
- var clearContent = function() {
- if (childScope) {
- childScope.$destroy();
- childScope = null;
- }
+function LocationHtml5Url(appBase, basePrefix) {
+ this.$$html5 = true;
+ basePrefix = basePrefix || '';
+ var appBaseNoFile = stripFile(appBase);
+ parseAbsoluteUrl(appBase, this, appBase);
- element.html('');
- };
- scope.$watch(srcExp, function(src) {
- var thisChangeId = ++changeCounter;
+ /**
+ * Parse given html5 (regular) url string into properties
+ * @param {string} newAbsoluteUrl HTML5 url
+ * @private
+ */
+ this.$$parse = function(url) {
+ var pathUrl = beginsWith(appBaseNoFile, url);
+ if (!isString(pathUrl)) {
+ throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url,
+ appBaseNoFile);
+ }
- if (src) {
- $http.get(src, {cache: $templateCache}).success(function(response) {
- if (thisChangeId !== changeCounter) return;
+ parseAppUrl(pathUrl, this, appBase);
- if (childScope) childScope.$destroy();
- childScope = scope.$new();
+ if (!this.$$path) {
+ this.$$path = '/';
+ }
- element.html(response);
- $compile(element.contents())(childScope);
+ this.$$compose();
+ };
- if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
- $anchorScroll();
- }
+ /**
+ * Compose url and update `absUrl` property
+ * @private
+ */
+ this.$$compose = function() {
+ var search = toKeyValue(this.$$search),
+ hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
- childScope.$emit('$includeContentLoaded');
- scope.$eval(onloadExp);
- }).error(function() {
- if (thisChangeId === changeCounter) clearContent();
- });
- } else clearContent();
- });
- };
- }
+ this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
+ this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/'
};
-}];
-/**
- * @ngdoc directive
- * @name ng.directive:ngInit
- *
- * @description
- * The `ngInit` directive specifies initialization tasks to be executed
- * before the template enters execution mode during bootstrap.
- *
- * @element ANY
- * @param {expression} ngInit {@link guide/expression Expression} to eval.
- *
- * @example
-
-
-
- {{greeting}} {{person}}!
-
-
-
- it('should check greeting', function() {
- expect(binding('greeting')).toBe('Hello');
- expect(binding('person')).toBe('World');
- });
-
-
- */
-var ngInitDirective = ngDirective({
- compile: function() {
- return {
- pre: function(scope, element, attrs) {
- scope.$eval(attrs.ngInit);
+ this.$$rewrite = function(url) {
+ var appUrl, prevAppUrl;
+
+ if ( (appUrl = beginsWith(appBase, url)) !== undefined ) {
+ prevAppUrl = appUrl;
+ if ( (appUrl = beginsWith(basePrefix, appUrl)) !== undefined ) {
+ return appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
+ } else {
+ return appBase + prevAppUrl;
}
+ } else if ( (appUrl = beginsWith(appBaseNoFile, url)) !== undefined ) {
+ return appBaseNoFile + appUrl;
+ } else if (appBaseNoFile == url + '/') {
+ return appBaseNoFile;
}
- }
-});
+ };
+}
-/**
- * @ngdoc directive
- * @name ng.directive:ngNonBindable
- * @priority 1000
- *
- * @description
- * Sometimes it is necessary to write code which looks like bindings but which should be left alone
- * by angular. Use `ngNonBindable` to make angular ignore a chunk of HTML.
- *
- * @element ANY
- *
- * @example
- * In this example there are two location where a simple binding (`{{}}`) is present, but the one
- * wrapped in `ngNonBindable` is left alone.
- *
- * @example
-
-
- Normal: {{1 + 2}}
- Ignored: {{1 + 2}}
-
-
- it('should check ng-non-bindable', function() {
- expect(using('.doc-example-live').binding('1 + 2')).toBe('3');
- expect(using('.doc-example-live').element('div:last').text()).
- toMatch(/1 \+ 2/);
- });
-
-
- */
-var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
/**
- * @ngdoc directive
- * @name ng.directive:ngPluralize
- * @restrict EA
- *
- * @description
- * # Overview
- * `ngPluralize` is a directive that displays messages according to en-US localization rules.
- * These rules are bundled with angular.js and the rules can be overridden
- * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive
- * by specifying the mappings between
- * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
- * plural categories} and the strings to be displayed.
- *
- * # Plural categories and explicit number rules
- * There are two
- * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
- * plural categories} in Angular's default en-US locale: "one" and "other".
- *
- * While a pural category may match many numbers (for example, in en-US locale, "other" can match
- * any number that is not 1), an explicit number rule can only match one number. For example, the
- * explicit number rule for "3" matches the number 3. You will see the use of plural categories
- * and explicit number rules throughout later parts of this documentation.
- *
- * # Configuring ngPluralize
- * You configure ngPluralize by providing 2 attributes: `count` and `when`.
- * You can also provide an optional attribute, `offset`.
- *
- * The value of the `count` attribute can be either a string or an {@link guide/expression
- * Angular expression}; these are evaluated on the current scope for its bound value.
- *
- * The `when` attribute specifies the mappings between plural categories and the actual
- * string to be displayed. The value of the attribute should be a JSON object so that Angular
- * can interpret it correctly.
- *
- * The following example shows how to configure ngPluralize:
- *
- *
- *
- *
- *
- *
- * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not
- * specify this rule, 0 would be matched to the "other" category and "0 people are viewing"
- * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for
- * other numbers, for example 12, so that instead of showing "12 people are viewing", you can
- * show "a dozen people are viewing".
- *
- * You can use a set of closed braces(`{}`) as a placeholder for the number that you want substituted
- * into pluralized strings. In the previous example, Angular will replace `{}` with
- * `{{personCount}}` . The closed braces `{}` is a placeholder
- * for {{numberExpression}} .
- *
- * # Configuring ngPluralize with offset
- * The `offset` attribute allows further customization of pluralized text, which can result in
- * a better user experience. For example, instead of the message "4 people are viewing this document",
- * you might display "John, Kate and 2 others are viewing this document".
- * The offset attribute allows you to offset a number by any desired value.
- * Let's take a look at an example:
- *
- *
- *
- *
- *
- *
- * Notice that we are still using two plural categories(one, other), but we added
- * three explicit number rules 0, 1 and 2.
- * When one person, perhaps John, views the document, "John is viewing" will be shown.
- * When three people view the document, no explicit number rule is found, so
- * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category.
- * In this case, plural category 'one' is matched and "John, Marry and one other person are viewing"
- * is shown.
- *
- * Note that when you specify offsets, you must provide explicit number rules for
- * numbers from 0 up to and including the offset. If you use an offset of 3, for example,
- * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for
- * plural categories "one" and "other".
- *
- * @param {string|expression} count The variable to be bounded to.
- * @param {string} when The mapping between plural category to its correspoding strings.
- * @param {number=} offset Offset to deduct from the total number.
+ * LocationHashbangUrl represents url
+ * This object is exposed as $location service when developer doesn't opt into html5 mode.
+ * It also serves as the base class for html5 mode fallback on legacy browsers.
*
- * @example
-
-
-
-
- Person 1:
- Person 2:
- Number of People:
-
-
- Without Offset:
-
-
-
-
- With Offset(2):
-
-
-
-
-
- it('should show correct pluralized string', function() {
- expect(element('.doc-example-live ng-pluralize:first').text()).
- toBe('1 person is viewing.');
- expect(element('.doc-example-live ng-pluralize:last').text()).
- toBe('Igor is viewing.');
-
- using('.doc-example-live').input('personCount').enter('0');
- expect(element('.doc-example-live ng-pluralize:first').text()).
- toBe('Nobody is viewing.');
- expect(element('.doc-example-live ng-pluralize:last').text()).
- toBe('Nobody is viewing.');
-
- using('.doc-example-live').input('personCount').enter('2');
- expect(element('.doc-example-live ng-pluralize:first').text()).
- toBe('2 people are viewing.');
- expect(element('.doc-example-live ng-pluralize:last').text()).
- toBe('Igor and Misko are viewing.');
-
- using('.doc-example-live').input('personCount').enter('3');
- expect(element('.doc-example-live ng-pluralize:first').text()).
- toBe('3 people are viewing.');
- expect(element('.doc-example-live ng-pluralize:last').text()).
- toBe('Igor, Misko and one other person are viewing.');
-
- using('.doc-example-live').input('personCount').enter('4');
- expect(element('.doc-example-live ng-pluralize:first').text()).
- toBe('4 people are viewing.');
- expect(element('.doc-example-live ng-pluralize:last').text()).
- toBe('Igor, Misko and 2 other people are viewing.');
- });
+ * @constructor
+ * @param {string} appBase application base URL
+ * @param {string} hashPrefix hashbang prefix
+ */
+function LocationHashbangUrl(appBase, hashPrefix) {
+ var appBaseNoFile = stripFile(appBase);
- it('should show data-binded names', function() {
- using('.doc-example-live').input('personCount').enter('4');
- expect(element('.doc-example-live ng-pluralize:last').text()).
- toBe('Igor, Misko and 2 other people are viewing.');
+ parseAbsoluteUrl(appBase, this, appBase);
- using('.doc-example-live').input('person1').enter('Di');
- using('.doc-example-live').input('person2').enter('Vojta');
- expect(element('.doc-example-live ng-pluralize:last').text()).
- toBe('Di, Vojta and 2 other people are viewing.');
- });
-
-
- */
-var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) {
- var BRACE = /{}/g;
- return {
- restrict: 'EA',
- link: function(scope, element, attr) {
- var numberExp = attr.count,
- whenExp = element.attr(attr.$attr.when), // this is because we have {{}} in attrs
- offset = attr.offset || 0,
- whens = scope.$eval(whenExp),
- whensExpFns = {},
- startSymbol = $interpolate.startSymbol(),
- endSymbol = $interpolate.endSymbol();
-
- forEach(whens, function(expression, key) {
- whensExpFns[key] =
- $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' +
- offset + endSymbol));
- });
- scope.$watch(function() {
- var value = parseFloat(scope.$eval(numberExp));
+ /**
+ * Parse given hashbang url into properties
+ * @param {string} url Hashbang url
+ * @private
+ */
+ this.$$parse = function(url) {
+ var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);
+ var withoutHashUrl = withoutBaseUrl.charAt(0) == '#'
+ ? beginsWith(hashPrefix, withoutBaseUrl)
+ : (this.$$html5)
+ ? withoutBaseUrl
+ : '';
- if (!isNaN(value)) {
- //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise,
- //check it against pluralization rules in $locale service
- if (!whens[value]) value = $locale.pluralCat(value - offset);
- return whensExpFns[value](scope, element, true);
- } else {
- return '';
- }
- }, function(newVal) {
- element.text(newVal);
- });
+ if (!isString(withoutHashUrl)) {
+ throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url,
+ hashPrefix);
}
- };
-}];
+ parseAppUrl(withoutHashUrl, this, appBase);
-/**
- * @ngdoc directive
- * @name ng.directive:ngRepeat
- *
- * @description
- * The `ngRepeat` directive instantiates a template once per item from a collection. Each template
- * instance gets its own scope, where the given loop variable is set to the current collection item,
- * and `$index` is set to the item index or key.
- *
- * Special properties are exposed on the local scope of each template instance, including:
- *
- * * `$index` – `{number}` – iterator offset of the repeated element (0..length-1)
- * * `$first` – `{boolean}` – true if the repeated element is first in the iterator.
- * * `$middle` – `{boolean}` – true if the repeated element is between the first and last in the iterator.
- * * `$last` – `{boolean}` – true if the repeated element is last in the iterator.
- *
- *
- * @element ANY
- * @scope
- * @priority 1000
- * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. Two
- * formats are currently supported:
- *
- * * `variable in expression` – where variable is the user defined loop variable and `expression`
- * is a scope expression giving the collection to enumerate.
- *
- * For example: `track in cd.tracks`.
- *
- * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers,
- * and `expression` is the scope expression giving the collection to enumerate.
- *
- * For example: `(name, age) in {'adam':10, 'amalie':12}`.
- *
- * @example
- * This example initializes the scope to a list of names and
- * then uses `ngRepeat` to display every person:
-
-
-
- I have {{friends.length}} friends. They are:
-
-
- [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
-
-
-
-
-
- it('should check ng-repeat', function() {
- var r = using('.doc-example-live').repeater('ul li');
- expect(r.count()).toBe(2);
- expect(r.row(0)).toEqual(["1","John","25"]);
- expect(r.row(1)).toEqual(["2","Mary","28"]);
- });
-
-
- */
-var ngRepeatDirective = ngDirective({
- transclude: 'element',
- priority: 1000,
- terminal: true,
- compile: function(element, attr, linker) {
- return function(scope, iterStartElement, attr){
- var expression = attr.ngRepeat;
- var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/),
- lhs, rhs, valueIdent, keyIdent;
- if (! match) {
- throw Error("Expected ngRepeat in form of '_item_ in _collection_' but got '" +
- expression + "'.");
- }
- lhs = match[1];
- rhs = match[2];
- match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);
- if (!match) {
- throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '" +
- lhs + "'.");
- }
- valueIdent = match[3] || match[1];
- keyIdent = match[2];
-
- // Store a list of elements from previous run. This is a hash where key is the item from the
- // iterator, and the value is an array of objects with following properties.
- // - scope: bound scope
- // - element: previous element.
- // - index: position
- // We need an array of these objects since the same object can be returned from the iterator.
- // We expect this to be a rare case.
- var lastOrder = new HashQueueMap();
- scope.$watch(function(scope){
- var index, length,
- collection = scope.$eval(rhs),
- collectionLength = size(collection, true),
- childScope,
- // Same as lastOrder but it has the current state. It will become the
- // lastOrder on the next iteration.
- nextOrder = new HashQueueMap(),
- key, value, // key/value of iteration
- array, last, // last object information {scope, element, index}
- cursor = iterStartElement; // current position of the node
-
- if (!isArray(collection)) {
- // if object, extract keys, sort them and use to determine order of iteration over obj props
- array = [];
- for(key in collection) {
- if (collection.hasOwnProperty(key) && key.charAt(0) != '$') {
- array.push(key);
- }
- }
- array.sort();
- } else {
- array = collection || [];
- }
+ this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
- // we are not using forEach for perf reasons (trying to avoid #call)
- for (index = 0, length = array.length; index < length; index++) {
- key = (collection === array) ? index : array[index];
- value = collection[key];
- last = lastOrder.shift(value);
- if (last) {
- // if we have already seen this object, then we need to reuse the
- // associated scope/element
- childScope = last.scope;
- nextOrder.push(value, last);
-
- if (index === last.index) {
- // do nothing
- cursor = last.element;
- } else {
- // existing item which got moved
- last.index = index;
- // This may be a noop, if the element is next, but I don't know of a good way to
- // figure this out, since it would require extra DOM access, so let's just hope that
- // the browsers realizes that it is noop, and treats it as such.
- cursor.after(last.element);
- cursor = last.element;
- }
- } else {
- // new item which we don't know about
- childScope = scope.$new();
- }
+ this.$$compose();
- childScope[valueIdent] = value;
- if (keyIdent) childScope[keyIdent] = key;
- childScope.$index = index;
-
- childScope.$first = (index === 0);
- childScope.$last = (index === (collectionLength - 1));
- childScope.$middle = !(childScope.$first || childScope.$last);
-
- if (!last) {
- linker(childScope, function(clone){
- cursor.after(clone);
- last = {
- scope: childScope,
- element: (cursor = clone),
- index: index
- };
- nextOrder.push(value, last);
- });
- }
- }
+ /*
+ * In Windows, on an anchor node on documents loaded from
+ * the filesystem, the browser will return a pathname
+ * prefixed with the drive name ('/C:/path') when a
+ * pathname without a drive is set:
+ * * a.setAttribute('href', '/foo')
+ * * a.pathname === '/C:/foo' //true
+ *
+ * Inside of Angular, we're always using pathnames that
+ * do not include drive names for routing.
+ */
+ function removeWindowsDriveName (path, url, base) {
+ /*
+ Matches paths for file protocol on windows,
+ such as /C:/foo/bar, and captures only /foo/bar.
+ */
+ var windowsFilePathExp = /^\/?.*?:(\/.*)/;
- //shrink children
- for (key in lastOrder) {
- if (lastOrder.hasOwnProperty(key)) {
- array = lastOrder[key];
- while(array.length) {
- value = array.pop();
- value.element.remove();
- value.scope.$destroy();
- }
- }
- }
+ var firstPathSegmentMatch;
- lastOrder = nextOrder;
- });
- };
- }
-});
+ //Get the relative path from the input URL.
+ if (url.indexOf(base) === 0) {
+ url = url.replace(base, '');
+ }
-/**
- * @ngdoc directive
- * @name ng.directive:ngShow
- *
- * @description
- * The `ngShow` and `ngHide` directives show or hide a portion of the DOM tree (HTML)
- * conditionally.
- *
- * @element ANY
- * @param {expression} ngShow If the {@link guide/expression expression} is truthy
- * then the element is shown or hidden respectively.
- *
- * @example
-
-
- Click me:
- Show: I show up when your checkbox is checked.
- Hide: I hide when your checkbox is checked.
-
-
- it('should check ng-show / ng-hide', function() {
- expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
- expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
-
- input('checked').check();
-
- expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
- expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
- });
-
-
- */
-//TODO(misko): refactor to remove element from the DOM
-var ngShowDirective = ngDirective(function(scope, element, attr){
- scope.$watch(attr.ngShow, function(value){
- element.css('display', toBoolean(value) ? '' : 'none');
- });
-});
+ /*
+ * The input URL intentionally contains a
+ * first path segment that ends with a colon.
+ */
+ if (windowsFilePathExp.exec(url)) {
+ return path;
+ }
+ firstPathSegmentMatch = windowsFilePathExp.exec(path);
+ return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path;
+ }
+ };
+
+ /**
+ * Compose hashbang url and update `absUrl` property
+ * @private
+ */
+ this.$$compose = function() {
+ var search = toKeyValue(this.$$search),
+ hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
+
+ this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
+ this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : '');
+ };
+
+ this.$$rewrite = function(url) {
+ if(stripHash(appBase) == stripHash(url)) {
+ return url;
+ }
+ };
+}
-/**
- * @ngdoc directive
- * @name ng.directive:ngHide
- *
- * @description
- * The `ngHide` and `ngShow` directives hide or show a portion
- * of the HTML conditionally.
- *
- * @element ANY
- * @param {expression} ngHide If the {@link guide/expression expression} truthy then
- * the element is shown or hidden respectively.
- *
- * @example
-
-
- Click me:
- Show: I show up when you checkbox is checked?
- Hide: I hide when you checkbox is checked?
-
-
- it('should check ng-show / ng-hide', function() {
- expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
- expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
-
- input('checked').check();
-
- expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
- expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
- });
-
-
- */
-//TODO(misko): refactor to remove element from the DOM
-var ngHideDirective = ngDirective(function(scope, element, attr){
- scope.$watch(attr.ngHide, function(value){
- element.css('display', toBoolean(value) ? 'none' : '');
- });
-});
/**
- * @ngdoc directive
- * @name ng.directive:ngStyle
- *
- * @description
- * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally.
- *
- * @element ANY
- * @param {expression} ngStyle {@link guide/expression Expression} which evals to an
- * object whose keys are CSS style names and values are corresponding values for those CSS
- * keys.
+ * LocationHashbangUrl represents url
+ * This object is exposed as $location service when html5 history api is enabled but the browser
+ * does not support it.
*
- * @example
-
-
-
-
-
- Sample Text
- myStyle={{myStyle}}
-
-
- span {
- color: black;
- }
-
-
- it('should check ng-style', function() {
- expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
- element('.doc-example-live :button[value=set]').click();
- expect(element('.doc-example-live span').css('color')).toBe('rgb(255, 0, 0)');
- element('.doc-example-live :button[value=clear]').click();
- expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
- });
-
-
+ * @constructor
+ * @param {string} appBase application base URL
+ * @param {string} hashPrefix hashbang prefix
*/
-var ngStyleDirective = ngDirective(function(scope, element, attr) {
- scope.$watch(attr.ngStyle, function(newStyles, oldStyles) {
- if (oldStyles && (newStyles !== oldStyles)) {
- forEach(oldStyles, function(val, style) { element.css(style, '');});
+function LocationHashbangInHtml5Url(appBase, hashPrefix) {
+ this.$$html5 = true;
+ LocationHashbangUrl.apply(this, arguments);
+
+ var appBaseNoFile = stripFile(appBase);
+
+ this.$$rewrite = function(url) {
+ var appUrl;
+
+ if ( appBase == stripHash(url) ) {
+ return url;
+ } else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) {
+ return appBase + hashPrefix + appUrl;
+ } else if ( appBaseNoFile === url + '/') {
+ return appBaseNoFile;
}
- if (newStyles) element.css(newStyles);
- }, true);
-});
+ };
+}
-/**
- * @ngdoc directive
- * @name ng.directive:ngSwitch
- * @restrict EA
- *
- * @description
- * Conditionally change the DOM structure.
- *
- * @usageContent
- * ...
- * ...
- * ...
- * ...
- *
- * @scope
- * @param {*} ngSwitch|on expression to match against ng-switch-when .
- * @paramDescription
- * On child elments add:
- *
- * * `ngSwitchWhen`: the case statement to match against. If match then this
- * case will be displayed.
- * * `ngSwitchDefault`: the default case when no other casses match.
- *
- * @example
-
-
-
-
-
-
-
selection={{selection}}
-
-
-
Settings Div
-
Home Span
-
default
-
-
-
-
- it('should start in settings', function() {
- expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Settings Div/);
- });
- it('should change to home', function() {
- select('selection').option('home');
- expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Home Span/);
- });
- it('should select deafault', function() {
- select('selection').option('other');
- expect(element('.doc-example-live [ng-switch]').text()).toMatch(/default/);
- });
-
-
- */
-var NG_SWITCH = 'ng-switch';
-var ngSwitchDirective = valueFn({
- restrict: 'EA',
- compile: function(element, attr) {
- var watchExpr = attr.ngSwitch || attr.on,
- cases = {};
-
- element.data(NG_SWITCH, cases);
- return function(scope, element){
- var selectedTransclude,
- selectedElement,
- selectedScope;
-
- scope.$watch(watchExpr, function(value) {
- if (selectedElement) {
- selectedScope.$destroy();
- selectedElement.remove();
- selectedElement = selectedScope = null;
- }
- if ((selectedTransclude = cases['!' + value] || cases['?'])) {
- scope.$eval(attr.change);
- selectedScope = scope.$new();
- selectedTransclude(selectedScope, function(caseElement) {
- selectedElement = caseElement;
- element.append(caseElement);
- });
- }
- });
- };
- }
-});
-var ngSwitchWhenDirective = ngDirective({
- transclude: 'element',
- priority: 500,
- compile: function(element, attrs, transclude) {
- var cases = element.inheritedData(NG_SWITCH);
- assertArg(cases);
- cases['!' + attrs.ngSwitchWhen] = transclude;
- }
-});
+LocationHashbangInHtml5Url.prototype =
+ LocationHashbangUrl.prototype =
+ LocationHtml5Url.prototype = {
-var ngSwitchDefaultDirective = ngDirective({
- transclude: 'element',
- priority: 500,
- compile: function(element, attrs, transclude) {
- var cases = element.inheritedData(NG_SWITCH);
- assertArg(cases);
- cases['?'] = transclude;
- }
-});
+ /**
+ * Are we in html5 mode?
+ * @private
+ */
+ $$html5: false,
-/**
- * @ngdoc directive
- * @name ng.directive:ngTransclude
- *
- * @description
- * Insert the transcluded DOM here.
- *
- * @element ANY
- *
- * @example
-
-
-
-
-
-
- it('should have transcluded', function() {
- input('title').enter('TITLE');
- input('text').enter('TEXT');
- expect(binding('title')).toEqual('TITLE');
- expect(binding('text')).toEqual('TEXT');
- });
-
-
- *
- */
-var ngTranscludeDirective = ngDirective({
- controller: ['$transclude', '$element', function($transclude, $element) {
- $transclude(function(clone) {
- $element.append(clone);
- });
- }]
-});
+ /**
+ * @ngdoc method
+ * @name ng.$location#absUrl
+ * @methodOf ng.$location
+ *
+ * @description
+ * This method is getter only.
+ *
+ * Return full url representation with all segments encoded according to rules specified in
+ * {@link http://www.ietf.org/rfc/rfc3986.txt RFC 3986}.
+ *
+ * @return {string} full url
+ */
+ absUrl: locationGetter('$$absUrl'),
-/**
- * @ngdoc directive
- * @name ng.directive:ngView
- * @restrict ECA
- *
- * @description
- * # Overview
- * `ngView` is a directive that complements the {@link ng.$route $route} service by
- * including the rendered template of the current route into the main layout (`index.html`) file.
- * Every time the current route changes, the included view changes with it according to the
- * configuration of the `$route` service.
- *
- * @scope
- * @example
-
-
-
- Choose:
-
Moby |
-
Moby: Ch1 |
-
Gatsby |
-
Gatsby: Ch4 |
-
Scarlet Letter
-
-
-
-
-
$location.path() = {{$location.path()}}
-
$route.current.template = {{$route.current.template}}
-
$route.current.params = {{$route.current.params}}
-
$route.current.scope.name = {{$route.current.scope.name}}
-
$routeParams = {{$routeParams}}
-
-
-
-
- controller: {{name}}
- Book Id: {{params.bookId}}
-
-
-
- controller: {{name}}
- Book Id: {{params.bookId}}
- Chapter Id: {{params.chapterId}}
-
-
-
- angular.module('ngView', [], function($routeProvider, $locationProvider) {
- $routeProvider.when('/Book/:bookId', {
- templateUrl: 'book.html',
- controller: BookCntl
- });
- $routeProvider.when('/Book/:bookId/ch/:chapterId', {
- templateUrl: 'chapter.html',
- controller: ChapterCntl
- });
+ /**
+ * @ngdoc method
+ * @name ng.$location#url
+ * @methodOf ng.$location
+ *
+ * @description
+ * This method is getter / setter.
+ *
+ * Return url (e.g. `/path?a=b#hash`) when called without any parameter.
+ *
+ * Change path, search and hash, when called with parameter and return `$location`.
+ *
+ * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
+ * @param {string=} replace The path that will be changed
+ * @return {string} url
+ */
+ url: function(url, replace) {
+ if (isUndefined(url))
+ return this.$$url;
- // configure html5 to get links working on jsfiddle
- $locationProvider.html5Mode(true);
- });
+ var match = PATH_MATCH.exec(url);
+ if (match[1]) this.path(decodeURIComponent(match[1]));
+ if (match[2] || match[1]) this.search(match[3] || '');
+ this.hash(match[5] || '', replace);
- function MainCntl($scope, $route, $routeParams, $location) {
- $scope.$route = $route;
- $scope.$location = $location;
- $scope.$routeParams = $routeParams;
- }
+ return this;
+ },
- function BookCntl($scope, $routeParams) {
- $scope.name = "BookCntl";
- $scope.params = $routeParams;
- }
+ /**
+ * @ngdoc method
+ * @name ng.$location#protocol
+ * @methodOf ng.$location
+ *
+ * @description
+ * This method is getter only.
+ *
+ * Return protocol of current url.
+ *
+ * @return {string} protocol of current url
+ */
+ protocol: locationGetter('$$protocol'),
- function ChapterCntl($scope, $routeParams) {
- $scope.name = "ChapterCntl";
- $scope.params = $routeParams;
- }
-
-
-
- it('should load and compile correct template', function() {
- element('a:contains("Moby: Ch1")').click();
- var content = element('.doc-example-live [ng-view]').text();
- expect(content).toMatch(/controller\: ChapterCntl/);
- expect(content).toMatch(/Book Id\: Moby/);
- expect(content).toMatch(/Chapter Id\: 1/);
-
- element('a:contains("Scarlet")').click();
- content = element('.doc-example-live [ng-view]').text();
- expect(content).toMatch(/controller\: BookCntl/);
- expect(content).toMatch(/Book Id\: Scarlet/);
- });
-
-
- */
+ /**
+ * @ngdoc method
+ * @name ng.$location#host
+ * @methodOf ng.$location
+ *
+ * @description
+ * This method is getter only.
+ *
+ * Return host of current url.
+ *
+ * @return {string} host of current url.
+ */
+ host: locationGetter('$$host'),
+
+ /**
+ * @ngdoc method
+ * @name ng.$location#port
+ * @methodOf ng.$location
+ *
+ * @description
+ * This method is getter only.
+ *
+ * Return port of current url.
+ *
+ * @return {Number} port
+ */
+ port: locationGetter('$$port'),
+ /**
+ * @ngdoc method
+ * @name ng.$location#path
+ * @methodOf ng.$location
+ *
+ * @description
+ * This method is getter / setter.
+ *
+ * Return path of current url when called without any parameter.
+ *
+ * Change path when called with parameter and return `$location`.
+ *
+ * Note: Path should always begin with forward slash (/), this method will add the forward slash
+ * if it is missing.
+ *
+ * @param {string=} path New path
+ * @return {string} path
+ */
+ path: locationGetterSetter('$$path', function(path) {
+ return path.charAt(0) == '/' ? path : '/' + path;
+ }),
-/**
- * @ngdoc event
- * @name ng.directive:ngView#$viewContentLoaded
- * @eventOf ng.directive:ngView
- * @eventType emit on the current ngView scope
- * @description
- * Emitted every time the ngView content is reloaded.
- */
-var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$compile',
- '$controller',
- function($http, $templateCache, $route, $anchorScroll, $compile,
- $controller) {
- return {
- restrict: 'ECA',
- terminal: true,
- link: function(scope, element, attr) {
- var lastScope,
- onloadExp = attr.onload || '';
-
- scope.$on('$routeChangeSuccess', update);
- update();
-
-
- function destroyLastScope() {
- if (lastScope) {
- lastScope.$destroy();
- lastScope = null;
+ /**
+ * @ngdoc method
+ * @name ng.$location#search
+ * @methodOf ng.$location
+ *
+ * @description
+ * This method is getter / setter.
+ *
+ * Return search part (as object) of current url when called without any parameter.
+ *
+ * Change search part when called with parameter and return `$location`.
+ *
+ * @param {string|Object.|Object.>} search New search params - string or
+ * hash object. Hash object may contain an array of values, which will be decoded as duplicates in
+ * the url.
+ *
+ * @param {(string|Array)=} paramValue If `search` is a string, then `paramValue` will override only a
+ * single search parameter. If `paramValue` is an array, it will set the parameter as a
+ * comma-separated value. If `paramValue` is `null`, the parameter will be deleted.
+ *
+ * @return {string} search
+ */
+ search: function(search, paramValue) {
+ switch (arguments.length) {
+ case 0:
+ return this.$$search;
+ case 1:
+ if (isString(search)) {
+ this.$$search = parseKeyValue(search);
+ } else if (isObject(search)) {
+ this.$$search = search;
+ } else {
+ throw $locationMinErr('isrcharg',
+ 'The first argument of the `$location#search()` call must be a string or an object.');
}
- }
+ break;
+ default:
+ if (isUndefined(paramValue) || paramValue === null) {
+ delete this.$$search[search];
+ } else {
+ this.$$search[search] = paramValue;
+ }
+ }
- function clearContent() {
- element.html('');
- destroyLastScope();
- }
+ this.$$compose();
+ return this;
+ },
+
+ /**
+ * @ngdoc method
+ * @name ng.$location#hash
+ * @methodOf ng.$location
+ *
+ * @description
+ * This method is getter / setter.
+ *
+ * Return hash fragment when called without any parameter.
+ *
+ * Change hash fragment when called with parameter and return `$location`.
+ *
+ * @param {string=} hash New hash fragment
+ * @return {string} hash
+ */
+ hash: locationGetterSetter('$$hash', identity),
- function update() {
- var locals = $route.current && $route.current.locals,
- template = locals && locals.$template;
+ /**
+ * @ngdoc method
+ * @name ng.$location#replace
+ * @methodOf ng.$location
+ *
+ * @description
+ * If called, all changes to $location during current `$digest` will be replacing current history
+ * record, instead of adding new one.
+ */
+ replace: function() {
+ this.$$replace = true;
+ return this;
+ }
+};
- if (template) {
- element.html(template);
- destroyLastScope();
+function locationGetter(property) {
+ return function() {
+ return this[property];
+ };
+}
- var link = $compile(element.contents()),
- current = $route.current,
- controller;
- lastScope = current.scope = scope.$new();
- if (current.controller) {
- locals.$scope = lastScope;
- controller = $controller(current.controller, locals);
- element.contents().data('$ngControllerController', controller);
- }
+function locationGetterSetter(property, preprocess) {
+ return function(value) {
+ if (isUndefined(value))
+ return this[property];
- link(lastScope);
- lastScope.$emit('$viewContentLoaded');
- lastScope.$eval(onloadExp);
+ this[property] = preprocess(value);
+ this.$$compose();
- // $anchorScroll might listen on event...
- $anchorScroll();
- } else {
- clearContent();
- }
- }
- }
+ return this;
};
-}];
+}
+
/**
- * @ngdoc directive
- * @name ng.directive:script
+ * @ngdoc object
+ * @name ng.$location
+ *
+ * @requires $browser
+ * @requires $sniffer
+ * @requires $rootElement
*
* @description
- * Load content of a script tag, with type `text/ng-template`, into `$templateCache`, so that the
- * template can be used by `ngInclude`, `ngView` or directive templates.
+ * The $location service parses the URL in the browser address bar (based on the
+ * {@link https://developer.mozilla.org/en/window.location window.location}) and makes the URL
+ * available to your application. Changes to the URL in the address bar are reflected into
+ * $location service and changes to $location are reflected into the browser address bar.
*
- * @restrict E
- * @param {'text/ng-template'} type must be set to `'text/ng-template'`
+ * **The $location service:**
*
- * @example
-
-
-
-
- Load inlined template
-
-
-
- it('should load template defined inside script tag', function() {
- element('#tpl-link').click();
- expect(element('#tpl-content').text()).toMatch(/Content of the template/);
- });
-
-
+ * - Exposes the current URL in the browser address bar, so you can
+ * - Watch and observe the URL.
+ * - Change the URL.
+ * - Synchronizes the URL with the browser when the user
+ * - Changes the address bar.
+ * - Clicks the back or forward button (or clicks a History link).
+ * - Clicks on a link.
+ * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
+ *
+ * For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular
+ * Services: Using $location}
*/
-var scriptDirective = ['$templateCache', function($templateCache) {
- return {
- restrict: 'E',
- terminal: true,
- compile: function(element, attr) {
- if (attr.type == 'text/ng-template') {
- var templateUrl = attr.id,
- // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent
- text = element[0].text;
-
- $templateCache.put(templateUrl, text);
- }
- }
- };
-}];
/**
- * @ngdoc directive
- * @name ng.directive:select
- * @restrict E
- *
+ * @ngdoc object
+ * @name ng.$locationProvider
* @description
- * HTML `SELECT` element with angular data-binding.
- *
- * # `ngOptions`
- *
- * Optionally `ngOptions` attribute can be used to dynamically generate a list of ``
- * elements for a `` element using an array or an object obtained by evaluating the
- * `ngOptions` expression.
- *˝˝
- * When an item in the select menu is select, the value of array element or object property
- * represented by the selected option will be bound to the model identified by the `ngModel`
- * directive of the parent select element.
- *
- * Optionally, a single hard-coded `` element, with the value set to an empty string, can
- * be nested into the `` element. This element will then represent `null` or "not selected"
- * option. See example below for demonstration.
- *
- * Note: `ngOptions` provides iterator facility for `` element which should be used instead
- * of {@link ng.directive:ngRepeat ngRepeat} when you want the
- * `select` model to be bound to a non-string value. This is because an option element can currently
- * be bound to string values only.
- *
- * @param {string} name assignable expression to data-bind to.
- * @param {string=} required The control is considered valid only if value is entered.
- * @param {comprehension_expression=} ngOptions in one of the following forms:
- *
- * * for array data sources:
- * * `label` **`for`** `value` **`in`** `array`
- * * `select` **`as`** `label` **`for`** `value` **`in`** `array`
- * * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
- * * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array`
- * * for object data sources:
- * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
- * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
- * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
- * * `select` **`as`** `label` **`group by`** `group`
- * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
- *
- * Where:
- *
- * * `array` / `object`: an expression which evaluates to an array / object to iterate over.
- * * `value`: local variable which will refer to each item in the `array` or each property value
- * of `object` during iteration.
- * * `key`: local variable which will refer to a property name in `object` during iteration.
- * * `label`: The result of this expression will be the label for ` ` element. The
- * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
- * * `select`: The result of this expression will be bound to the model of the parent ``
- * element. If not specified, `select` expression will default to `value`.
- * * `group`: The result of this expression will be used to group options using the ``
- * DOM element.
- *
- * @example
-
-
-
-
-
-
- Color (null not allowed):
-
-
- Color (null allowed):
-
-
- -- chose color --
-
-
-
- Color grouped by shade:
-
-
-
-
- Select
bogus .
-
- Currently selected: {{ {selected_color:color} }}
-
-
-
-
-
- it('should check ng-options', function() {
- expect(binding('{selected_color:color}')).toMatch('red');
- select('color').option('0');
- expect(binding('{selected_color:color}')).toMatch('black');
- using('.nullable').select('color').option('');
- expect(binding('{selected_color:color}')).toMatch('null');
- });
-
-
+ * Use the `$locationProvider` to configure how the application deep linking paths are stored.
*/
+function $LocationProvider(){
+ var hashPrefix = '',
+ html5Mode = false;
-var ngOptionsDirective = valueFn({ terminal: true });
-var selectDirective = ['$compile', '$parse', function($compile, $parse) {
- //00001111100000000000222200000000000000000000003333000000000000044444444444444444000000000555555555555555550000000666666666666666660000000000000007777
- var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*)$/,
- nullModelCtrl = {$setViewValue: noop};
-
- return {
- restrict: 'E',
- require: ['select', '?ngModel'],
- controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
- var self = this,
- optionsMap = {},
- ngModelCtrl = nullModelCtrl,
- nullOption,
- unknownOption;
-
-
- self.databound = $attrs.ngModel;
-
-
- self.init = function(ngModelCtrl_, nullOption_, unknownOption_) {
- ngModelCtrl = ngModelCtrl_;
- nullOption = nullOption_;
- unknownOption = unknownOption_;
- }
+ /**
+ * @ngdoc property
+ * @name ng.$locationProvider#hashPrefix
+ * @methodOf ng.$locationProvider
+ * @description
+ * @param {string=} prefix Prefix for hash part (containing path and search)
+ * @returns {*} current value if used as getter or itself (chaining) if used as setter
+ */
+ this.hashPrefix = function(prefix) {
+ if (isDefined(prefix)) {
+ hashPrefix = prefix;
+ return this;
+ } else {
+ return hashPrefix;
+ }
+ };
+ /**
+ * @ngdoc property
+ * @name ng.$locationProvider#html5Mode
+ * @methodOf ng.$locationProvider
+ * @description
+ * @param {boolean=} mode Use HTML5 strategy if available.
+ * @returns {*} current value if used as getter or itself (chaining) if used as setter
+ */
+ this.html5Mode = function(mode) {
+ if (isDefined(mode)) {
+ html5Mode = mode;
+ return this;
+ } else {
+ return html5Mode;
+ }
+ };
- self.addOption = function(value) {
- optionsMap[value] = true;
+ /**
+ * @ngdoc event
+ * @name ng.$location#$locationChangeStart
+ * @eventOf ng.$location
+ * @eventType broadcast on root scope
+ * @description
+ * Broadcasted before a URL will change. This change can be prevented by calling
+ * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
+ * details about event object. Upon successful change
+ * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired.
+ *
+ * @param {Object} angularEvent Synthetic event object.
+ * @param {string} newUrl New URL
+ * @param {string=} oldUrl URL that was before it was changed.
+ */
- if (ngModelCtrl.$viewValue == value) {
- $element.val(value);
- if (unknownOption.parent()) unknownOption.remove();
- }
- };
+ /**
+ * @ngdoc event
+ * @name ng.$location#$locationChangeSuccess
+ * @eventOf ng.$location
+ * @eventType broadcast on root scope
+ * @description
+ * Broadcasted after a URL was changed.
+ *
+ * @param {Object} angularEvent Synthetic event object.
+ * @param {string} newUrl New URL
+ * @param {string=} oldUrl URL that was before it was changed.
+ */
+ this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement',
+ function( $rootScope, $browser, $sniffer, $rootElement) {
+ var $location,
+ LocationMode,
+ baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to ''
+ initialUrl = $browser.url(),
+ appBase;
- self.removeOption = function(value) {
- if (this.hasOption(value)) {
- delete optionsMap[value];
- if (ngModelCtrl.$viewValue == value) {
- this.renderUnknownOption(value);
- }
- }
- };
+ if (html5Mode) {
+ appBase = serverBase(initialUrl) + (baseHref || '/');
+ LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url;
+ } else {
+ appBase = stripHash(initialUrl);
+ LocationMode = LocationHashbangUrl;
+ }
+ $location = new LocationMode(appBase, '#' + hashPrefix);
+ $location.$$parse($location.$$rewrite(initialUrl));
+ $rootElement.on('click', function(event) {
+ // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
+ // currently we open nice url link and redirect then
- self.renderUnknownOption = function(val) {
- var unknownVal = '? ' + hashKey(val) + ' ?';
- unknownOption.val(unknownVal);
- $element.prepend(unknownOption);
- $element.val(unknownVal);
- unknownOption.prop('selected', true); // needed for IE
- }
+ if (event.ctrlKey || event.metaKey || event.which == 2) return;
+ var elm = jqLite(event.target);
- self.hasOption = function(value) {
- return optionsMap.hasOwnProperty(value);
+ // traverse the DOM up to find first A tag
+ while (lowercase(elm[0].nodeName) !== 'a') {
+ // ignore rewriting if no A tag (reached root element, or no parent - removed from document)
+ if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return;
}
- $scope.$on('$destroy', function() {
- // disable unknown option so that we don't do work when the whole select is being destroyed
- self.renderUnknownOption = noop;
- });
- }],
+ var absHref = elm.prop('href');
+ var rewrittenUrl = $location.$$rewrite(absHref);
- link: function(scope, element, attr, ctrls) {
- // if ngModel is not defined, we don't need to do anything
- if (!ctrls[1]) return;
-
- var selectCtrl = ctrls[0],
- ngModelCtrl = ctrls[1],
- multiple = attr.multiple,
- optionsExp = attr.ngOptions,
- nullOption = false, // if false, user will not be able to select it (used by ngOptions)
- emptyOption,
- // we can't just jqLite('') since jqLite is not smart enough
- // to create it in and IE barfs otherwise.
- optionTemplate = jqLite(document.createElement('option')),
- optGroupTemplate =jqLite(document.createElement('optgroup')),
- unknownOption = optionTemplate.clone();
-
- // find "null" option
- for(var i = 0, children = element.children(), ii = children.length; i < ii; i++) {
- if (children[i].value == '') {
- emptyOption = nullOption = children.eq(i);
- break;
+ if (absHref && !elm.attr('target') && rewrittenUrl && !event.isDefaultPrevented()) {
+ event.preventDefault();
+ if (rewrittenUrl != $browser.url()) {
+ // update location manually
+ $location.$$parse(rewrittenUrl);
+ $rootScope.$apply();
+ // hack to work around FF6 bug 684208 when scenario runner clicks on links
+ window.angular['ff-684208-preventDefault'] = true;
}
}
+ });
- selectCtrl.init(ngModelCtrl, nullOption, unknownOption);
- // required validator
- if (multiple && (attr.required || attr.ngRequired)) {
- var requiredValidator = function(value) {
- ngModelCtrl.$setValidity('required', !attr.required || (value && value.length));
- return value;
- };
+ // rewrite hashbang url <> html5 url
+ if ($location.absUrl() != initialUrl) {
+ $browser.url($location.absUrl(), true);
+ }
- ngModelCtrl.$parsers.push(requiredValidator);
- ngModelCtrl.$formatters.unshift(requiredValidator);
+ // update $location when $browser url changes
+ $browser.onUrlChange(function(newUrl) {
+ if ($location.absUrl() != newUrl) {
+ if ($rootScope.$broadcast('$locationChangeStart', newUrl,
+ $location.absUrl()).defaultPrevented) {
+ $browser.url($location.absUrl());
+ return;
+ }
+ $rootScope.$evalAsync(function() {
+ var oldUrl = $location.absUrl();
- attr.$observe('required', function() {
- requiredValidator(ngModelCtrl.$viewValue);
+ $location.$$parse(newUrl);
+ afterLocationChange(oldUrl);
});
+ if (!$rootScope.$$phase) $rootScope.$digest();
}
+ });
- if (optionsExp) Options(scope, element, ngModelCtrl);
- else if (multiple) Multiple(scope, element, ngModelCtrl);
- else Single(scope, element, ngModelCtrl, selectCtrl);
-
-
- ////////////////////////////
-
-
-
- function Single(scope, selectElement, ngModelCtrl, selectCtrl) {
- ngModelCtrl.$render = function() {
- var viewValue = ngModelCtrl.$viewValue;
+ // update browser
+ var changeCounter = 0;
+ $rootScope.$watch(function $locationWatch() {
+ var oldUrl = $browser.url();
+ var currentReplace = $location.$$replace;
- if (selectCtrl.hasOption(viewValue)) {
- if (unknownOption.parent()) unknownOption.remove();
- selectElement.val(viewValue);
- if (viewValue === '') emptyOption.prop('selected', true); // to make IE9 happy
+ if (!changeCounter || oldUrl != $location.absUrl()) {
+ changeCounter++;
+ $rootScope.$evalAsync(function() {
+ if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl).
+ defaultPrevented) {
+ $location.$$parse(oldUrl);
} else {
- if (isUndefined(viewValue) && emptyOption) {
- selectElement.val('');
- } else {
- selectCtrl.renderUnknownOption(viewValue);
- }
- }
- };
-
- selectElement.bind('change', function() {
- scope.$apply(function() {
- if (unknownOption.parent()) unknownOption.remove();
- ngModelCtrl.$setViewValue(selectElement.val());
- });
- });
- }
-
- function Multiple(scope, selectElement, ctrl) {
- var lastView;
- ctrl.$render = function() {
- var items = new HashMap(ctrl.$viewValue);
- forEach(selectElement.children(), function(option) {
- option.selected = isDefined(items.get(option.value));
- });
- };
-
- // we have to do it on each watch since ngModel watches reference, but
- // we need to work of an array, so we need to see if anything was inserted/removed
- scope.$watch(function() {
- if (!equals(lastView, ctrl.$viewValue)) {
- lastView = copy(ctrl.$viewValue);
- ctrl.$render();
+ $browser.url($location.absUrl(), currentReplace);
+ afterLocationChange(oldUrl);
}
});
-
- selectElement.bind('change', function() {
- scope.$apply(function() {
- var array = [];
- forEach(selectElement.children(), function(option) {
- if (option.selected) {
- array.push(option.value);
- }
- });
- ctrl.$setViewValue(array);
- });
- });
- }
-
- function Options(scope, selectElement, ctrl) {
- var match;
-
- if (! (match = optionsExp.match(NG_OPTIONS_REGEXP))) {
- throw Error(
- "Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
- " but got '" + optionsExp + "'.");
- }
-
- var displayFn = $parse(match[2] || match[1]),
- valueName = match[4] || match[6],
- keyName = match[5],
- groupByFn = $parse(match[3] || ''),
- valueFn = $parse(match[2] ? match[1] : valueName),
- valuesFn = $parse(match[7]),
- // This is an array of array of existing option groups in DOM. We try to reuse these if possible
- // optionGroupsCache[0] is the options with no option group
- // optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element
- optionGroupsCache = [[{element: selectElement, label:''}]];
-
- if (nullOption) {
- // compile the element since there might be bindings in it
- $compile(nullOption)(scope);
-
- // remove the class, which is added automatically because we recompile the element and it
- // becomes the compilation root
- nullOption.removeClass('ng-scope');
-
- // we need to remove it before calling selectElement.html('') because otherwise IE will
- // remove the label from the element. wtf?
- nullOption.remove();
- }
-
- // clear contents, we'll add what's needed based on the model
- selectElement.html('');
-
- selectElement.bind('change', function() {
- scope.$apply(function() {
- var optionGroup,
- collection = valuesFn(scope) || [],
- locals = {},
- key, value, optionElement, index, groupIndex, length, groupLength;
-
- if (multiple) {
- value = [];
- for (groupIndex = 0, groupLength = optionGroupsCache.length;
- groupIndex < groupLength;
- groupIndex++) {
- // list of options for that group. (first item has the parent)
- optionGroup = optionGroupsCache[groupIndex];
-
- for(index = 1, length = optionGroup.length; index < length; index++) {
- if ((optionElement = optionGroup[index].element)[0].selected) {
- key = optionElement.val();
- if (keyName) locals[keyName] = key;
- locals[valueName] = collection[key];
- value.push(valueFn(scope, locals));
- }
- }
- }
- } else {
- key = selectElement.val();
- if (key == '?') {
- value = undefined;
- } else if (key == ''){
- value = null;
- } else {
- locals[valueName] = collection[key];
- if (keyName) locals[keyName] = key;
- value = valueFn(scope, locals);
- }
- }
- ctrl.$setViewValue(value);
- });
- });
-
- ctrl.$render = render;
-
- // TODO(vojta): can't we optimize this ?
- scope.$watch(render);
-
- function render() {
- var optionGroups = {'':[]}, // Temporary location for the option groups before we render them
- optionGroupNames = [''],
- optionGroupName,
- optionGroup,
- option,
- existingParent, existingOptions, existingOption,
- modelValue = ctrl.$modelValue,
- values = valuesFn(scope) || [],
- keys = keyName ? sortedKeys(values) : values,
- groupLength, length,
- groupIndex, index,
- locals = {},
- selected,
- selectedSet = false, // nothing is selected yet
- lastElement,
- element;
-
- if (multiple) {
- selectedSet = new HashMap(modelValue);
- } else if (modelValue === null || nullOption) {
- // if we are not multiselect, and we are null then we have to add the nullOption
- optionGroups[''].push({selected:modelValue === null, id:'', label:''});
- selectedSet = true;
- }
-
- // We now build up the list of options we need (we merge later)
- for (index = 0; length = keys.length, index < length; index++) {
- locals[valueName] = values[keyName ? locals[keyName]=keys[index]:index];
- optionGroupName = groupByFn(scope, locals) || '';
- if (!(optionGroup = optionGroups[optionGroupName])) {
- optionGroup = optionGroups[optionGroupName] = [];
- optionGroupNames.push(optionGroupName);
- }
- if (multiple) {
- selected = selectedSet.remove(valueFn(scope, locals)) != undefined;
- } else {
- selected = modelValue === valueFn(scope, locals);
- selectedSet = selectedSet || selected; // see if at least one item is selected
- }
- optionGroup.push({
- id: keyName ? keys[index] : index, // either the index into array or key from object
- label: displayFn(scope, locals) || '', // what will be seen by the user
- selected: selected // determine if we should be selected
- });
- }
- if (!multiple && !selectedSet) {
- // nothing was selected, we have to insert the undefined item
- optionGroups[''].unshift({id:'?', label:'', selected:true});
- }
-
- // Now we need to update the list of DOM nodes to match the optionGroups we computed above
- for (groupIndex = 0, groupLength = optionGroupNames.length;
- groupIndex < groupLength;
- groupIndex++) {
- // current option group name or '' if no group
- optionGroupName = optionGroupNames[groupIndex];
-
- // list of options for that group. (first item has the parent)
- optionGroup = optionGroups[optionGroupName];
-
- if (optionGroupsCache.length <= groupIndex) {
- // we need to grow the optionGroups
- existingParent = {
- element: optGroupTemplate.clone().attr('label', optionGroupName),
- label: optionGroup.label
- };
- existingOptions = [existingParent];
- optionGroupsCache.push(existingOptions);
- selectElement.append(existingParent.element);
- } else {
- existingOptions = optionGroupsCache[groupIndex];
- existingParent = existingOptions[0]; // either SELECT (no group) or OPTGROUP element
-
- // update the OPTGROUP label if not the same.
- if (existingParent.label != optionGroupName) {
- existingParent.element.attr('label', existingParent.label = optionGroupName);
- }
- }
-
- lastElement = null; // start at the beginning
- for(index = 0, length = optionGroup.length; index < length; index++) {
- option = optionGroup[index];
- if ((existingOption = existingOptions[index+1])) {
- // reuse elements
- lastElement = existingOption.element;
- if (existingOption.label !== option.label) {
- lastElement.text(existingOption.label = option.label);
- }
- if (existingOption.id !== option.id) {
- lastElement.val(existingOption.id = option.id);
- }
- if (existingOption.element.selected !== option.selected) {
- lastElement.prop('selected', (existingOption.selected = option.selected));
- }
- } else {
- // grow elements
-
- // if it's a null option
- if (option.id === '' && nullOption) {
- // put back the pre-compiled element
- element = nullOption;
- } else {
- // jQuery(v1.4.2) Bug: We should be able to chain the method calls, but
- // in this version of jQuery on some browser the .text() returns a string
- // rather then the element.
- (element = optionTemplate.clone())
- .val(option.id)
- .attr('selected', option.selected)
- .text(option.label);
- }
-
- existingOptions.push(existingOption = {
- element: element,
- label: option.label,
- id: option.id,
- selected: option.selected
- });
- if (lastElement) {
- lastElement.after(element);
- } else {
- existingParent.element.append(element);
- }
- lastElement = element;
- }
- }
- // remove any excessive OPTIONs in a group
- index++; // increment since the existingOptions[0] is parent element not OPTION
- while(existingOptions.length > index) {
- existingOptions.pop().element.remove();
- }
- }
- // remove any excessive OPTGROUPs from select
- while(optionGroupsCache.length > groupIndex) {
- optionGroupsCache.pop()[0].element.remove();
- }
- }
- }
- }
- }
-}];
-
-var optionDirective = ['$interpolate', function($interpolate) {
- var nullSelectCtrl = {
- addOption: noop,
- removeOption: noop
- };
-
- return {
- restrict: 'E',
- priority: 100,
- compile: function(element, attr) {
- if (isUndefined(attr.value)) {
- var interpolateFn = $interpolate(element.text(), true);
- if (!interpolateFn) {
- attr.$set('value', element.text());
- }
}
+ $location.$$replace = false;
- return function (scope, element, attr) {
- var selectCtrlName = '$selectController',
- parent = element.parent(),
- selectCtrl = parent.data(selectCtrlName) ||
- parent.parent().data(selectCtrlName); // in case we are in optgroup
-
- if (selectCtrl && selectCtrl.databound) {
- // For some reason Opera defaults to true and if not overridden this messes up the repeater.
- // We don't want the view to drive the initialization of the model anyway.
- element.prop('selected', false);
- } else {
- selectCtrl = nullSelectCtrl;
- }
+ return changeCounter;
+ });
- if (interpolateFn) {
- scope.$watch(interpolateFn, function(newVal, oldVal) {
- attr.$set('value', newVal);
- if (newVal !== oldVal) selectCtrl.removeOption(oldVal);
- selectCtrl.addOption(newVal);
- });
- } else {
- selectCtrl.addOption(attr.value);
- }
+ return $location;
- element.bind('$destroy', function() {
- selectCtrl.removeOption(attr.value);
- });
- };
+ function afterLocationChange(oldUrl) {
+ $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl);
}
- }
}];
+}
-var styleDirective = valueFn({
- restrict: 'E',
- terminal: true
-});
- //try to bind to jquery now so that one can write angular.element().read()
- //but we will rebind on bootstrap again.
- bindJQuery();
-
- publishExternalAPI(angular);
-
- jqLite(document).ready(function() {
- angularInit(document, bootstrap);
- });
-
-})(window, document);
-angular.element(document).find('head').append('');
\ No newline at end of file
+/**
+ * @ngdoc object
+ * @name ng.$log
+ * @requires $window
+ *
+ * @description
+ * Simple service for logging. Default implementation safely writes the message
+ * into the browser's console (if present).
+ *
+ * The main purpose of this service is to simplify debugging and troubleshooting.
+ *
+ * The default is to log `debug` messages. You can use
+ * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this.
\ No newline at end of file
diff --git a/vendor/assets/javascripts/angular.min.js b/vendor/assets/javascripts/angular.min.js
index accc48e..264ac1e 100644
--- a/vendor/assets/javascripts/angular.min.js
+++ b/vendor/assets/javascripts/angular.min.js
@@ -1,158 +1,201 @@
/*
- AngularJS v1.0.2
- (c) 2010-2012 Google, Inc. http://angularjs.org
+ AngularJS v1.2.3
+ (c) 2010-2014 Google, Inc. http://angularjs.org
License: MIT
*/
-(function(T,ba,p){'use strict';function m(b,a,c){var d;if(b)if(M(b))for(d in b)d!="prototype"&&d!="length"&&d!="name"&&b.hasOwnProperty(d)&&a.call(c,b[d],d);else if(b.forEach&&b.forEach!==m)b.forEach(a,c);else if(I(b)&&wa(b.length))for(d=0;d=0&&b.splice(c,1);return a}function U(b,a){if(oa(b)||b&&b.$evalAsync&&b.$watch)throw A("Can't copy Window or Scope");if(a){if(b===
-a)throw A("Can't copy equivalent objects or arrays");if(K(b)){for(;a.length;)a.pop();for(var c=0;c2?ha.call(arguments,2):[];return M(a)&&!(a instanceof RegExp)?c.length?
-function(){return arguments.length?a.apply(b,c.concat(ha.call(arguments,0))):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}:a}function jc(b,a){var c=a;/^\$+/.test(b)?c=p:oa(a)?c="$WINDOW":a&&ba===a?c="$DOCUMENT":a&&a.$evalAsync&&a.$watch&&(c="$SCOPE");return c}function ca(b,a){return JSON.stringify(b,jc,a?" ":null)}function ob(b){return F(b)?JSON.parse(b):b}function Xa(b){b&&b.length!==0?(b=E(""+b),b=!(b=="f"||b=="0"||b=="false"||b=="no"||b=="n"||b=="[]")):b=!1;
-return b}function pa(b){b=y(b).clone();try{b.html("")}catch(a){}return y("").append(b).html().match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+E(b)})}function Ya(b){var a={},c,d;m((b||"").split("&"),function(b){b&&(c=b.split("="),d=decodeURIComponent(c[0]),a[d]=u(c[1])?decodeURIComponent(c[1]):!0)});return a}function pb(b){var a=[];m(b,function(b,d){a.push(Za(d,!0)+(b===!0?"":"="+Za(b,!0)))});return a.length?a.join("&"):""}function $a(b){return Za(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,
-"=").replace(/%2B/gi,"+")}function Za(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(a?null:/%20/g,"+")}function kc(b,a){function c(a){a&&d.push(a)}var d=[b],e,g,i=["ng:app","ng-app","x-ng-app","data-ng-app"],f=/\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;m(i,function(a){i[a]=!0;c(ba.getElementById(a));a=a.replace(":","\\:");b.querySelectorAll&&(m(b.querySelectorAll("."+a),c),m(b.querySelectorAll("."+a+"\\:"),c),m(b.querySelectorAll("["+
-a+"]"),c))});m(d,function(a){if(!e){var b=f.exec(" "+a.className+" ");b?(e=a,g=(b[2]||"").replace(/\s+/g,",")):m(a.attributes,function(b){if(!e&&i[b.name])e=a,g=b.value})}});e&&a(e,g?[g]:[])}function qb(b,a){b=y(b);a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);a.unshift("ng");var c=rb(a);c.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,i){a.$apply(function(){b.data("$injector",i);c(b)(a)})}]);return c}function ab(b,a){a=a||"_";return b.replace(lc,
-function(b,d){return(d?a:"")+b.toLowerCase()})}function qa(b,a,c){if(!b)throw new A("Argument '"+(a||"?")+"' is "+(c||"required"));return b}function ra(b,a,c){c&&K(b)&&(b=b[b.length-1]);qa(M(b),a,"not a function, got "+(b&&typeof b=="object"?b.constructor.name||"Object":typeof b));return b}function mc(b){function a(a,b,e){return a[b]||(a[b]=e())}return a(a(b,"angular",Object),"module",function(){var b={};return function(d,e,g){e&&b.hasOwnProperty(d)&&(b[d]=null);return a(b,d,function(){function a(c,
-d,e){return function(){b[e||"push"]([c,d,arguments]);return j}}if(!e)throw A("No module: "+d);var b=[],c=[],k=a("$injector","invoke"),j={_invokeQueue:b,_runBlocks:c,requires:e,name:d,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider","directive"),config:k,run:function(a){c.push(a);
-return this}};g&&k(g);return j})}})}function sb(b){return b.replace(nc,function(a,b,d,e){return e?d.toUpperCase():d}).replace(oc,"Moz$1")}function bb(b,a){function c(){var e;for(var b=[this],c=a,i,f,h,k,j,l,n;b.length;){i=b.shift();f=0;for(h=i.length;f
"+b;a.removeChild(a.firstChild);cb(this,a.childNodes);this.remove()}else cb(this,b)}function db(b){return b.cloneNode(!0)}function sa(b){tb(b);for(var a=0,b=b.childNodes||[];a
--1}function wb(b,a){a&&m(a.split(" "),function(a){b.className=Q((" "+b.className+" ").replace(/[\n\t]/g," ").replace(" "+Q(a)+" "," "))})}function xb(b,a){a&&m(a.split(" "),function(a){if(!Ca(b,a))b.className=Q(b.className+" "+Q(a))})}function cb(b,a){if(a)for(var a=!a.nodeName&&u(a.length)&&!oa(a)?a:[a],c=0;c4096&&c.warn("Cookie '"+a+"' possibly not set or overflowed because it was too large ("+d+" > 4096 bytes)!"),V.length>20&&c.warn("Cookie '"+a+"' possibly not set or overflowed because too many cookies were already set ("+
-V.length+" > 20 )")}else{if(h.cookie!==s){s=h.cookie;d=s.split("; ");V={};for(f=0;f0&&(V[unescape(e.substring(0,g))]=unescape(e.substring(g+1)))}return V}};f.defer=function(a,b){var c;o++;c=l(function(){delete r[c];e(a)},b||0);r[c]=!0;return c};f.defer.cancel=function(a){return r[a]?(delete r[a],n(a),e(D),!0):!1}}function xc(){this.$get=["$window","$log","$sniffer","$document",function(b,a,c,d){return new wc(b,d,a,c)}]}function yc(){this.$get=function(){function b(b,
-d){function e(a){if(a!=l){if(n){if(n==a)n=a.n}else n=a;g(a.n,a.p);g(a,l);l=a;l.n=null}}function g(a,b){if(a!=b){if(a)a.p=b;if(b)b.n=a}}if(b in a)throw A("cacheId "+b+" taken");var i=0,f=x({},d,{id:b}),h={},k=d&&d.capacity||Number.MAX_VALUE,j={},l=null,n=null;return a[b]={put:function(a,b){var c=j[a]||(j[a]={key:a});e(c);t(b)||(a in h||i++,h[a]=b,i>k&&this.remove(n.key))},get:function(a){var b=j[a];if(b)return e(b),h[a]},remove:function(a){var b=j[a];if(b==l)l=b.p;if(b==n)n=b.n;g(b.n,b.p);delete j[a];
-delete h[a];i--},removeAll:function(){h={};i=0;j={};l=n=null},destroy:function(){j=f=h=null;delete a[b]},info:function(){return x({},f,{size:i})}}}var a={};b.info=function(){var b={};m(a,function(a,e){b[e]=a.info()});return b};b.get=function(b){return a[b]};return b}}function zc(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function Cb(b){var a={},c="Directive",d=/^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,e=/(([\d\w\-_]+)(?:\:([^;]+))?;?)/,g="Template must have exactly one root element. was: ";
-this.directive=function f(d,e){F(d)?(qa(e,"directive"),a.hasOwnProperty(d)||(a[d]=[],b.factory(d+c,["$injector","$exceptionHandler",function(b,c){var e=[];m(a[d],function(a){try{var f=b.invoke(a);if(M(f))f={compile:J(f)};else if(!f.compile&&f.link)f.compile=J(f.link);f.priority=f.priority||0;f.name=f.name||d;f.require=f.require||f.controller&&f.name;f.restrict=f.restrict||"A";e.push(f)}catch(g){c(g)}});return e}])),a[d].push(e)):m(d,nb(f));return this};this.$get=["$injector","$interpolate","$exceptionHandler",
-"$http","$templateCache","$parse","$controller","$rootScope",function(b,h,k,j,l,n,r,o){function w(a,b,c){a instanceof y||(a=y(a));m(a,function(b,c){b.nodeType==3&&(a[c]=y(b).wrap(" ").parent()[0])});var d=v(a,b,a,c);return function(b,c){qa(b,"scope");var e=c?ua.clone.call(a):a;e.data("$scope",b);q(e,"ng-scope");c&&c(e,b);d&&d(b,e,e);return e}}function q(a,b){try{a.addClass(b)}catch(c){}}function v(a,b,c,d){function e(a,c,d,g){for(var k,h,n,j,o,l=0,r=0,q=f.length;lC.priority)break;if(X=C.scope)N("isolated scope",B,C,s),I(X)&&(q(s,"ng-isolate-scope"),B=C),q(s,"ng-scope"),v=v||C;H=C.name;if(X=C.controller)u=u||{},N("'"+H+"' controller",u[H],C,s),u[H]=C;if(X=C.transclude)N("transclusion",D,C,s),D=C,o=C.priority,X=="element"?(W=y(b),s=c.$$element=y("<\!-- "+H+": "+c[H]+" --\>"),b=s[0],Ga(e,y(W[0]),b),t=w(W,d,o)):(W=y(db(b)).contents(),s.html(""),t=w(W,d));if(X=C.template)if(N("template",z,C,s),z=C,X=Ia(X),C.replace){W=y(""+Q(X)+"
").contents();
-b=W[0];if(W.length!=1||b.nodeType!==1)throw new A(g+X);Ga(e,s,b);H={$attr:{}};a=a.concat(Y(b,a.splice(E+1,a.length-(E+1)),H));L(c,H);G=a.length}else s.html(X);if(C.templateUrl)N("template",z,C,s),z=C,j=V(a.splice(E,a.length-E),j,s,c,e,C.replace,t),G=a.length;else if(C.compile)try{x=C.compile(s,c,t),M(x)?f(null,x):x&&f(x.pre,x.post)}catch(J){k(J,pa(s))}if(C.terminal)j.terminal=!0,o=Math.max(o,C.priority)}j.scope=v&&v.scope;j.transclude=D&&t;return j}function z(d,e,g,h){var j=!1;if(a.hasOwnProperty(e))for(var n,
-e=b.get(e+c),o=0,l=e.length;on.priority)&&n.restrict.indexOf(g)!=-1)d.push(n),j=!0}catch(r){k(r)}return j}function L(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;m(a,function(d,e){e.charAt(0)!="$"&&(b[e]&&(d+=(e==="style"?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});m(b,function(b,f){f=="class"?(q(e,b),a["class"]=(a["class"]?a["class"]+" ":"")+b):f=="style"?e.attr("style",e.attr("style")+";"+b):f.charAt(0)!="$"&&!a.hasOwnProperty(f)&&(a[f]=b,d[f]=c[f])})}function V(a,b,c,d,e,
-f,k){var h=[],n,o,r=c[0],q=a.shift(),w=x({},q,{controller:null,templateUrl:null,transclude:null});c.html("");j.get(q.templateUrl,{cache:l}).success(function(j){var l,q,j=Ia(j);if(f){q=y(""+Q(j)+"
").contents();l=q[0];if(q.length!=1||l.nodeType!==1)throw new A(g+j);j={$attr:{}};Ga(e,c,l);Y(l,a,j);L(d,j)}else l=r,c.html(j);a.unshift(w);n=B(a,c,d,k);for(o=v(c.contents(),k);h.length;){var aa=h.pop(),j=h.pop();q=h.pop();var s=h.pop(),m=l;q!==r&&(m=db(l),Ga(j,y(q),m));n(function(){b(o,s,m,e,aa)},
-s,m,e,aa)}h=null}).error(function(a,b,c,d){throw A("Failed to load template: "+d.url);});return function(a,c,d,e,f){h?(h.push(c),h.push(d),h.push(e),h.push(f)):n(function(){b(o,c,d,e,f)},c,d,e,f)}}function s(a,b){return b.priority-a.priority}function N(a,b,c,d){if(b)throw A("Multiple directives ["+b.name+", "+c.name+"] asking for "+a+" on: "+pa(d));}function H(a,b){var c=h(b,!0);c&&a.push({priority:0,compile:J(function(a,b){var d=b.parent(),e=d.data("$binding")||[];e.push(c);q(d.data("$binding",e),
-"ng-binding");a.$watch(c,function(a){b[0].nodeValue=a})})})}function W(a,b,c,d){var e=h(c,!0);e&&b.push({priority:100,compile:J(function(a,b,c){b=c.$$observers||(c.$$observers={});d==="class"&&(e=h(c[d],!0));c[d]=p;(b[d]||(b[d]=[])).$$inter=!0;(c.$$observers&&c.$$observers[d].$$scope||a).$watch(e,function(a){c.$set(d,a)})})})}function Ga(a,b,c){var d=b[0],e=d.parentNode,f,g;if(a){f=0;for(g=a.length;f0){var f=N[0],e=f.text;if(e==a||e==b||e==c||e==d||!a&&!b&&!c&&!d)return f}return!1}function f(b,c,d,f){return(b=i(b,c,d,f))?(a&&!b.json&&e("is not valid json",b),N.shift(),b):!1}function h(a){f(a)||e("is unexpected, expecting ["+a+"]",i())}function k(a,b){return function(c,d){return a(c,d,b)}}function j(a,b,c){return function(d,f){return b(d,f,a,c)}}function l(){for(var a=[];;)if(N.length>0&&!i("}",")",";","]")&&a.push(t()),!f(";"))return a.length==1?a[0]:function(b,c){for(var d,
-f=0;f","<=",">="))a=j(a,b.fn,q());return a}function v(){for(var a=m(),b;b=f("*","/","%");)a=j(a,b.fn,m());return a}function m(){var a;return f("+")?B():(a=f("-"))?j(V,a.fn,m()):(a=f("!"))?k(a.fn,m()):B()}function B(){var a;if(f("("))a=t(),h(")");else if(f("["))a=z();else if(f("{"))a=L();else{var b=f();(a=b.fn)||e("not a primary expression",b)}for(var c;b=f("(","[",".");)b.text==="("?(a=y(a,c),c=null):b.text==="["?(c=a,a=ea(a)):b.text==="."?(c=a,a=u(a)):e("IMPOSSIBLE");
-return a}function z(){var a=[];if(g().text!="]"){do a.push(H());while(f(","))}h("]");return function(b,c){for(var d=[],f=0;f1;d++){var e=a.shift(),g=
-b[e];g||(g={},b[e]=g);b=g}return b[a.shift()]=c}function gb(b,a,c){if(!a)return b;for(var a=a.split("."),d,e=b,g=a.length,i=0;i7),hasEvent:function(c){if(c=="input"&&$==9)return!1;if(t(a[c])){var e=b.document.createElement("div");a[c]="on"+c in e}return a[c]},csp:!1}}]}function Uc(){this.$get=J(T)}function Nb(b){var a={},c,d,e;if(!b)return a;m(b.split("\n"),function(b){e=b.indexOf(":");c=E(Q(b.substr(0,e)));d=Q(b.substr(e+1));c&&(a[c]?a[c]+=", "+d:a[c]=d)});return a}function Ob(b){var a=I(b)?b:p;return function(c){a||(a=Nb(b));return c?a[E(c)]||null:a}}function Pb(b,a,c){if(M(c))return c(b,a);m(c,
-function(c){b=c(b,a)});return b}function Vc(){var b=/^\s*(\[|\{[^\{])/,a=/[\}\]]\s*$/,c=/^\)\]\}',?\n/,d=this.defaults={transformResponse:[function(d){F(d)&&(d=d.replace(c,""),b.test(d)&&a.test(d)&&(d=ob(d,!0)));return d}],transformRequest:[function(a){return I(a)&&Ta.apply(a)!=="[object File]"?ca(a):a}],headers:{common:{Accept:"application/json, text/plain, */*","X-Requested-With":"XMLHttpRequest"},post:{"Content-Type":"application/json;charset=utf-8"},put:{"Content-Type":"application/json;charset=utf-8"}}},
-e=this.responseInterceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(a,b,c,h,k,j){function l(a){function c(a){var b=x({},a,{data:Pb(a.data,a.headers,f)});return 200<=a.status&&a.status<300?b:k.reject(b)}a.method=la(a.method);var e=a.transformRequest||d.transformRequest,f=a.transformResponse||d.transformResponse,g=d.headers,g=x({"X-XSRF-TOKEN":b.cookies()["XSRF-TOKEN"]},g.common,g[E(a.method)],a.headers),e=Pb(a.data,Ob(g),e),h;t(a.data)&&delete g["Content-Type"];
-h=n(a,e,g);h=h.then(c,c);m(w,function(a){h=a(h)});h.success=function(b){h.then(function(c){b(c.data,c.status,c.headers,a)});return h};h.error=function(b){h.then(null,function(c){b(c.data,c.status,c.headers,a)});return h};return h}function n(b,c,d){function e(a,b,c){m&&(200<=a&&a<300?m.put(w,[a,b,Nb(c)]):m.remove(w));f(b,a,c);h.$apply()}function f(a,c,d){c=Math.max(c,0);(200<=c&&c<300?j.resolve:j.reject)({data:a,status:c,headers:Ob(d),config:b})}function i(){var a=Va(l.pendingRequests,b);a!==-1&&l.pendingRequests.splice(a,
-1)}var j=k.defer(),n=j.promise,m,p,w=r(b.url,b.params);l.pendingRequests.push(b);n.then(i,i);b.cache&&b.method=="GET"&&(m=I(b.cache)?b.cache:o);if(m)if(p=m.get(w))if(p.then)return p.then(i,i),p;else K(p)?f(p[1],p[0],U(p[2])):f(p,200,{});else m.put(w,n);p||a(b.method,w,c,e,d,b.timeout,b.withCredentials);return n}function r(a,b){if(!b)return a;var c=[];fc(b,function(a,b){a==null||a==p||(I(a)&&(a=ca(a)),c.push(encodeURIComponent(b)+"="+encodeURIComponent(a)))});return a+(a.indexOf("?")==-1?"?":"&")+
-c.join("&")}var o=c("$http"),w=[];m(e,function(a){w.push(F(a)?j.get(a):j.invoke(a))});l.pendingRequests=[];(function(a){m(arguments,function(a){l[a]=function(b,c){return l(x(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){m(arguments,function(a){l[a]=function(b,c,d){return l(x(d||{},{method:a,url:b,data:c}))}})})("post","put");l.defaults=d;return l}]}function Wc(){this.$get=["$browser","$window","$document",function(b,a,c){return Xc(b,Yc,b.defer,a.angular.callbacks,c[0],
-a.location.protocol.replace(":",""))}]}function Xc(b,a,c,d,e,g){function i(a,b){var c=e.createElement("script"),d=function(){e.body.removeChild(c);b&&b()};c.type="text/javascript";c.src=a;$?c.onreadystatechange=function(){/loaded|complete/.test(c.readyState)&&d()}:c.onload=c.onerror=d;e.body.appendChild(c)}return function(e,h,k,j,l,n,r){function o(a,c,d,e){c=(h.match(Gb)||["",g])[1]=="file"?d?200:404:c;a(c==1223?204:c,d,e);b.$$completeOutstandingRequest(D)}b.$$incOutstandingRequestCount();h=h||b.url();
-if(E(e)=="jsonp"){var p="_"+(d.counter++).toString(36);d[p]=function(a){d[p].data=a};i(h.replace("JSON_CALLBACK","angular.callbacks."+p),function(){d[p].data?o(j,200,d[p].data):o(j,-2);delete d[p]})}else{var q=new a;q.open(e,h,!0);m(l,function(a,b){a&&q.setRequestHeader(b,a)});var v;q.onreadystatechange=function(){q.readyState==4&&o(j,v||q.status,q.responseText,q.getAllResponseHeaders())};if(r)q.withCredentials=!0;q.send(k||"");n>0&&c(function(){v=-1;q.abort()},n)}}}function Zc(){this.$get=function(){return{id:"en-us",
-NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January,February,March,April,May,June,July,August,September,October,November,December".split(","),SHORTMONTH:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(","),DAY:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(","),
-SHORTDAY:"Sun,Mon,Tue,Wed,Thu,Fri,Sat".split(","),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return b===1?"one":"other"}}}}function $c(){this.$get=["$rootScope","$browser","$q","$exceptionHandler",function(b,a,c,d){function e(e,f,h){var k=c.defer(),j=k.promise,l=u(h)&&!h,f=a.defer(function(){try{k.resolve(e())}catch(a){k.reject(a),
-d(a)}l||b.$apply()},f),h=function(){delete g[j.$$timeoutId]};j.$$timeoutId=f;g[f]=k;j.then(h,h);return j}var g={};e.cancel=function(b){return b&&b.$$timeoutId in g?(g[b.$$timeoutId].reject("canceled"),a.defer.cancel(b.$$timeoutId)):!1};return e}]}function Qb(b){function a(a,e){return b.factory(a+c,e)}var c="Filter";this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+c)}}];a("currency",Rb);a("date",Sb);a("filter",ad);a("json",bd);a("limitTo",cd);a("lowercase",dd);a("number",
-Tb);a("orderBy",Ub);a("uppercase",ed)}function ad(){return function(b,a){if(!(b instanceof Array))return b;var c=[];c.check=function(a){for(var b=0;b-1;case "object":for(var c in a)if(c.charAt(0)!=="$"&&d(a[c],b))return!0;return!1;case "array":for(c=0;c=j+l)for(var k=i.length-j,n=0;n0||e>-c)e+=c;e===0&&c==-12&&(e=
-12);return jb(e,a,d)}}function Ma(b,a){return function(c,d){var e=c["get"+b](),g=la(a?"SHORT"+b:b);return d[g][e]}}function Sb(b){function a(a){var b;if(b=a.match(c)){var a=new Date(0),g=0,i=0;b[9]&&(g=G(b[9]+b[10]),i=G(b[9]+b[11]));a.setUTCFullYear(G(b[1]),G(b[2])-1,G(b[3]));a.setUTCHours(G(b[4]||0)-g,G(b[5]||0)-i,G(b[6]||0),G(b[7]||0))}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;return function(c,e){var g="",i=[],f,h,e=e||
-"mediumDate",e=b.DATETIME_FORMATS[e]||e;F(c)&&(c=fd.test(c)?G(c):a(c));wa(c)&&(c=new Date(c));if(!na(c))return c;for(;e;)(h=gd.exec(e))?(i=i.concat(ha.call(h,1)),e=i.pop()):(i.push(e),e=null);m(i,function(a){f=hd[a];g+=f?f(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function bd(){return function(b){return ca(b,!0)}}function cd(){return function(b,a){if(!(b instanceof Array))return b;var a=G(a),c=[],d,e;if(!b||!(b instanceof Array))return c;a>b.length?a=b.length:a<
--b.length&&(a=-b.length);a>0?(d=0,e=a):(d=b.length+a,e=b.length);for(;dl?(d.$setValidity("maxlength",!1),p):(d.$setValidity("maxlength",!0),a)};d.$parsers.push(c);d.$formatters.push(c)}}function kb(b,a){b="ngClass"+b;return R(function(c,d,e){c.$watch(e[b],function(b,e){if(a===!0||c.$index%2===a)e&&b!==e&&(I(e)&&!K(e)&&(e=Ua(e,function(a,b){if(a)return b})),d.removeClass(K(e)?e.join(" "):e)),I(b)&&!K(b)&&(b=Ua(b,
-function(a,b){if(a)return b})),b&&d.addClass(K(b)?b.join(" "):b)},!0)})}var E=function(b){return F(b)?b.toLowerCase():b},la=function(b){return F(b)?b.toUpperCase():b},A=T.Error,$=G((/msie (\d+)/.exec(E(navigator.userAgent))||[])[1]),y,ia,ha=[].slice,Sa=[].push,Ta=Object.prototype.toString,Zb=T.angular||(T.angular={}),ta,Db,Z=["0","0","0"];D.$inject=[];ma.$inject=[];Db=$<9?function(b){b=b.nodeName?b:b[0];return b.scopeName&&b.scopeName!="HTML"?la(b.scopeName+":"+b.nodeName):b.nodeName}:function(b){return b.nodeName?
-b.nodeName:b[0].nodeName};var lc=/[A-Z]/g,id={full:"1.0.2",major:1,minor:0,dot:2,codeName:"debilitating-awesomeness"},Ba=P.cache={},Aa=P.expando="ng-"+(new Date).getTime(),pc=1,$b=T.document.addEventListener?function(b,a,c){b.addEventListener(a,c,!1)}:function(b,a,c){b.attachEvent("on"+a,c)},eb=T.document.removeEventListener?function(b,a,c){b.removeEventListener(a,c,!1)}:function(b,a,c){b.detachEvent("on"+a,c)},nc=/([\:\-\_]+(.))/g,oc=/^moz([A-Z])/,ua=P.prototype={ready:function(b){function a(){c||
-(c=!0,b())}var c=!1;this.bind("DOMContentLoaded",a);P(T).bind("load",a)},toString:function(){var b=[];m(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return b>=0?y(this[b]):y(this[this.length+b])},length:0,push:Sa,sort:[].sort,splice:[].splice},Ea={};m("multiple,selected,checked,disabled,readOnly,required".split(","),function(b){Ea[E(b)]=b});var Ab={};m("input,select,option,textarea,button,form".split(","),function(b){Ab[la(b)]=!0});m({data:vb,inheritedData:Da,scope:function(b){return Da(b,
-"$scope")},controller:yb,injector:function(b){return Da(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:Ca,css:function(b,a,c){a=sb(a);if(u(c))b.style[a]=c;else{var d;$<=8&&(d=b.currentStyle&&b.currentStyle[a],d===""&&(d="auto"));d=d||b.style[a];$<=8&&(d=d===""?p:d);return d}},attr:function(b,a,c){var d=E(a);if(Ea[d])if(u(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||D).specified?d:p;else if(u(c))b.setAttribute(a,
-c);else if(b.getAttribute)return b=b.getAttribute(a,2),b===null?p:b},prop:function(b,a,c){if(u(c))b[a]=c;else return b[a]},text:x($<9?function(b,a){if(b.nodeType==1){if(t(a))return b.innerText;b.innerText=a}else{if(t(a))return b.nodeValue;b.nodeValue=a}}:function(b,a){if(t(a))return b.textContent;b.textContent=a},{$dv:""}),val:function(b,a){if(t(a))return b.value;b.value=a},html:function(b,a){if(t(a))return b.innerHTML;for(var c=0,d=b.childNodes;c":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a,c)&
-e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},"!":function(a,c,d){return!d(a,c)}},Lc={n:"\n",f:"\u000c",r:"\r",t:"\t",v:"\u000b","'":"'",'"':'"'},ib={},Yc=T.XMLHttpRequest||function(){try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(a){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(c){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(d){}throw new A("This browser does not support XMLHttpRequest.");};Qb.$inject=["$provide"];Rb.$inject=["$locale"];Tb.$inject=["$locale"];
-var Wb=".",hd={yyyy:O("FullYear",4),yy:O("FullYear",2,0,!0),y:O("FullYear",1),MMMM:Ma("Month"),MMM:Ma("Month",!0),MM:O("Month",2,1),M:O("Month",1,1),dd:O("Date",2),d:O("Date",1),HH:O("Hours",2),H:O("Hours",1),hh:O("Hours",2,-12),h:O("Hours",1,-12),mm:O("Minutes",2),m:O("Minutes",1),ss:O("Seconds",2),s:O("Seconds",1),EEEE:Ma("Day"),EEE:Ma("Day",!0),a:function(a,c){return a.getHours()<12?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){a=a.getTimezoneOffset();return jb(a/60,2)+jb(Math.abs(a%60),2)}},gd=/((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,
-fd=/^\d+$/;Sb.$inject=["$locale"];var dd=J(E),ed=J(la);Ub.$inject=["$parse"];var jd=J({restrict:"E",compile:function(a,c){c.href||c.$set("href","");return function(a,c){c.bind("click",function(a){c.attr("href")||a.preventDefault()})}}}),lb={};m(Ea,function(a,c){var d=fa("ng-"+c);lb[d]=function(){return{priority:100,compile:function(){return function(a,g,i){a.$watch(i[d],function(a){i.$set(c,!!a)})}}}}});m(["src","href"],function(a){var c=fa("ng-"+a);lb[c]=function(){return{priority:99,link:function(d,
-e,g){g.$observe(c,function(c){g.$set(a,c);$&&e.prop(a,c)})}}}});var Pa={$addControl:D,$removeControl:D,$setValidity:D,$setDirty:D};Xb.$inject=["$element","$attrs","$scope"];var Sa=function(a){return["$timeout",function(c){var d={name:"form",restrict:"E",controller:Xb,compile:function(){return{pre:function(a,d,i,f){if(!i.action){var h=function(a){a.preventDefault?a.preventDefault():a.returnValue=!1};$b(d[0],"submit",h);d.bind("$destroy",function(){c(function(){eb(d[0],"submit",h)},0,!1)})}var k=d.parent().controller("form"),
-j=i.name||i.ngForm;j&&(a[j]=f);k&&d.bind("$destroy",function(){k.$removeControl(f);j&&(a[j]=p);x(f,Pa)})}}}};return a?x(U(d),{restrict:"EAC"}):d}]},kd=Sa(),ld=Sa(!0),md=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,nd=/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/,od=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,bc={text:Ra,number:function(a,c,d,e,g,i){Ra(a,c,d,e,g,i);e.$parsers.push(function(a){var c=S(a);return c||od.test(a)?(e.$setValidity("number",!0),a===""?
-null:c?a:parseFloat(a)):(e.$setValidity("number",!1),p)});e.$formatters.push(function(a){return S(a)?"":""+a});if(d.min){var f=parseFloat(d.min),a=function(a){return!S(a)&&ah?(e.$setValidity("max",!1),p):(e.$setValidity("max",!0),a)};e.$parsers.push(d);e.$formatters.push(d)}e.$formatters.push(function(a){return S(a)||wa(a)?(e.$setValidity("number",
-!0),a):(e.$setValidity("number",!1),p)})},url:function(a,c,d,e,g,i){Ra(a,c,d,e,g,i);a=function(a){return S(a)||md.test(a)?(e.$setValidity("url",!0),a):(e.$setValidity("url",!1),p)};e.$formatters.push(a);e.$parsers.push(a)},email:function(a,c,d,e,g,i){Ra(a,c,d,e,g,i);a=function(a){return S(a)||nd.test(a)?(e.$setValidity("email",!0),a):(e.$setValidity("email",!1),p)};e.$formatters.push(a);e.$parsers.push(a)},radio:function(a,c,d,e){t(d.name)&&c.attr("name",xa());c.bind("click",function(){c[0].checked&&
-a.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e){var g=d.ngTrueValue,i=d.ngFalseValue;F(g)||(g=!0);F(i)||(i=!1);c.bind("click",function(){a.$apply(function(){e.$setViewValue(c[0].checked)})});e.$render=function(){c[0].checked=e.$viewValue};e.$formatters.push(function(a){return a===g});e.$parsers.push(function(a){return a?g:i})},hidden:D,button:D,submit:D,reset:D},cc=["$browser","$sniffer",
-function(a,c){return{restrict:"E",require:"?ngModel",link:function(d,e,g,i){i&&(bc[E(g.type)]||bc.text)(d,e,g,i,c,a)}}}],Oa="ng-valid",Na="ng-invalid",Qa="ng-pristine",Yb="ng-dirty",pd=["$scope","$exceptionHandler","$attrs","$element","$parse",function(a,c,d,e,g){function i(a,c){c=c?"-"+ab(c,"-"):"";e.removeClass((a?Na:Oa)+c).addClass((a?Oa:Na)+c)}this.$modelValue=this.$viewValue=Number.NaN;this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=!0;this.$dirty=!1;this.$valid=
-!0;this.$invalid=!1;this.$name=d.name;var g=g(d.ngModel),f=g.assign;if(!f)throw A(Eb+d.ngModel+" ("+pa(e)+")");this.$render=D;var h=e.inheritedData("$formController")||Pa,k=0,j=this.$error={};e.addClass(Qa);i(!0);this.$setValidity=function(a,c){if(j[a]!==!c){if(c){if(j[a]&&k--,!k)i(!0),this.$valid=!0,this.$invalid=!1}else i(!1),this.$invalid=!0,this.$valid=!1,k++;j[a]=!c;i(c,a);h.$setValidity(a,c,this)}};this.$setViewValue=function(d){this.$viewValue=d;if(this.$pristine)this.$dirty=!0,this.$pristine=
-!1,e.removeClass(Qa).addClass(Yb),h.$setDirty();m(this.$parsers,function(a){d=a(d)});if(this.$modelValue!==d)this.$modelValue=d,f(a,d),m(this.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}})};var l=this;a.$watch(g,function(a){if(l.$modelValue!==a){var c=l.$formatters,d=c.length;for(l.$modelValue=a;d--;)a=c[d](a);if(l.$viewValue!==a)l.$viewValue=a,l.$render()}})}],qd=function(){return{require:["ngModel","^?form"],controller:pd,link:function(a,c,d,e){var g=e[0],i=e[1]||Pa;i.$addControl(g);
-c.bind("$destroy",function(){i.$removeControl(g)})}}},rd=J({require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),dc=function(){return{require:"?ngModel",link:function(a,c,d,e){if(e){d.required=!0;var g=function(a){if(d.required&&(S(a)||a===!1))e.$setValidity("required",!1);else return e.$setValidity("required",!0),a};e.$formatters.push(g);e.$parsers.unshift(g);d.$observe("required",function(){g(e.$viewValue)})}}}},sd=function(){return{require:"ngModel",
-link:function(a,c,d,e){var g=(a=/\/(.*)\//.exec(d.ngList))&&RegExp(a[1])||d.ngList||",";e.$parsers.push(function(a){var c=[];a&&m(a.split(g),function(a){a&&c.push(Q(a))});return c});e.$formatters.push(function(a){return K(a)?a.join(", "):p})}}},td=/^(true|false|\d+)$/,ud=function(){return{priority:100,compile:function(a,c){return td.test(c.ngValue)?function(a,c,g){g.$set("value",a.$eval(g.ngValue))}:function(a,c,g){a.$watch(g.ngValue,function(a){g.$set("value",a,!1)})}}}},vd=R(function(a,c,d){c.addClass("ng-binding").data("$binding",
-d.ngBind);a.$watch(d.ngBind,function(a){c.text(a==p?"":a)})}),wd=["$interpolate",function(a){return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));d.addClass("ng-binding").data("$binding",c);e.$observe("ngBindTemplate",function(a){d.text(a)})}}],xd=[function(){return function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBindHtmlUnsafe);a.$watch(d.ngBindHtmlUnsafe,function(a){c.html(a||"")})}}],yd=kb("",!0),zd=kb("Odd",0),Ad=kb("Even",1),Bd=R({compile:function(a,c){c.$set("ngCloak",p);
-a.removeClass("ng-cloak")}}),Cd=[function(){return{scope:!0,controller:"@"}}],Dd=["$sniffer",function(a){return{priority:1E3,compile:function(){a.csp=!0}}}],ec={};m("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave".split(" "),function(a){var c=fa("ng-"+a);ec[c]=["$parse",function(d){return function(e,g,i){var f=d(i[c]);g.bind(E(a),function(a){e.$apply(function(){f(e,{$event:a})})})}}]});var Ed=R(function(a,c,d){c.bind("submit",function(){a.$apply(d.ngSubmit)})}),
-Fd=["$http","$templateCache","$anchorScroll","$compile",function(a,c,d,e){return{restrict:"ECA",terminal:!0,compile:function(g,i){var f=i.ngInclude||i.src,h=i.onload||"",k=i.autoscroll;return function(g,i){var n=0,m,o=function(){m&&(m.$destroy(),m=null);i.html("")};g.$watch(f,function(f){var q=++n;f?a.get(f,{cache:c}).success(function(a){q===n&&(m&&m.$destroy(),m=g.$new(),i.html(a),e(i.contents())(m),u(k)&&(!k||g.$eval(k))&&d(),m.$emit("$includeContentLoaded"),g.$eval(h))}).error(function(){q===n&&
-o()}):o()})}}}}],Gd=R({compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),Hd=R({terminal:!0,priority:1E3}),Id=["$locale","$interpolate",function(a,c){var d=/{}/g;return{restrict:"EA",link:function(e,g,i){var f=i.count,h=g.attr(i.$attr.when),k=i.offset||0,j=e.$eval(h),l={},n=c.startSymbol(),r=c.endSymbol();m(j,function(a,e){l[e]=c(a.replace(d,n+f+"-"+k+r))});e.$watch(function(){var c=parseFloat(e.$eval(f));return isNaN(c)?"":(j[c]||(c=a.pluralCat(c-k)),l[c](e,g,!0))},function(a){g.text(a)})}}}],
-Jd=R({transclude:"element",priority:1E3,terminal:!0,compile:function(a,c,d){return function(a,c,i){var f=i.ngRepeat,i=f.match(/^\s*(.+)\s+in\s+(.*)\s*$/),h,k,j;if(!i)throw A("Expected ngRepeat in form of '_item_ in _collection_' but got '"+f+"'.");f=i[1];h=i[2];i=f.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!i)throw A("'item' in 'item in collection' should be identifier or (key, value) but got '"+f+"'.");k=i[3]||i[1];j=i[2];var l=new fb;a.$watch(function(a){var e,f,i=a.$eval(h),m=hc(i,
-!0),p,y=new fb,B,z,t,u,s=c;if(K(i))t=i||[];else{t=[];for(B in i)i.hasOwnProperty(B)&&B.charAt(0)!="$"&&t.push(B);t.sort()}e=0;for(f=t.length;ex;)u.pop().element.remove()}for(;v.length>w;)v.pop()[0].element.remove()}var i;if(!(i=w.match(d)))throw A("Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_' but got '"+w+"'.");var j=c(i[2]||i[1]),k=i[4]||i[6],l=i[5],m=c(i[3]||""),n=c(i[2]?i[1]:k),r=c(i[7]),v=[[{element:f,label:""}]];q&&(a(q)(e),q.removeClass("ng-scope"),
-q.remove());f.html("");f.bind("change",function(){e.$apply(function(){var a,c=r(e)||[],d={},h,i,j,m,q,s;if(o){i=[];m=0;for(s=v.length;m@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none;}ng\\:form{display:block;}');
+(function(X,O,r){'use strict';function A(b){return function(){var a=arguments[0],c,a="["+(b?b+":":"")+a+"] http://errors.angularjs.org/1.2.3/"+(b?b+"/":"")+a;for(c=1;c").append(b).html();try{return 3===b[0].nodeType?t(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+t(b)})}catch(d){return t(c)}}function Ub(b){try{return decodeURIComponent(b)}catch(a){}}
+function Vb(b){var a={},c,d;q((b||"").split("&"),function(b){b&&(c=b.split("="),d=Ub(c[0]),B(d)&&(b=B(c[1])?Ub(c[1]):!0,a[d]?K(a[d])?a[d].push(b):a[d]=[a[d],b]:a[d]=b))});return a}function Wb(b){var a=[];q(b,function(b,d){K(b)?q(b,function(b){a.push(ua(d,!0)+(!0===b?"":"="+ua(b,!0)))}):a.push(ua(d,!0)+(!0===b?"":"="+ua(b,!0)))});return a.length?a.join("&"):""}function sb(b){return ua(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function ua(b,a){return encodeURIComponent(b).replace(/%40/gi,
+"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,a?"%20":"+")}function Rc(b,a){function c(a){a&&d.push(a)}var d=[b],e,g,f=["ng:app","ng-app","x-ng-app","data-ng-app"],h=/\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;q(f,function(a){f[a]=!0;c(O.getElementById(a));a=a.replace(":","\\:");b.querySelectorAll&&(q(b.querySelectorAll("."+a),c),q(b.querySelectorAll("."+a+"\\:"),c),q(b.querySelectorAll("["+a+"]"),c))});q(d,function(a){if(!e){var b=h.exec(" "+a.className+" ");b?(e=a,g=
+(b[2]||"").replace(/\s+/g,",")):q(a.attributes,function(b){!e&&f[b.name]&&(e=a,g=b.value)})}});e&&a(e,g?[g]:[])}function Xb(b,a){var c=function(){b=x(b);if(b.injector()){var c=b[0]===O?"document":ha(b);throw Ma("btstrpd",c);}a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);a.unshift("ng");c=Yb(a);c.invoke(["$rootScope","$rootElement","$compile","$injector","$animate",function(a,b,c,d,e){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return c},d=/^NG_DEFER_BOOTSTRAP!/;
+if(X&&!d.test(X.name))return c();X.name=X.name.replace(d,"");cb.resumeBootstrap=function(b){q(b,function(b){a.push(b)});c()}}function db(b,a){a=a||"_";return b.replace(Sc,function(b,d){return(d?a:"")+b.toLowerCase()})}function tb(b,a,c){if(!b)throw Ma("areq",a||"?",c||"required");return b}function Oa(b,a,c){c&&K(b)&&(b=b[b.length-1]);tb(I(b),a,"not a function, got "+(b&&"object"==typeof b?b.constructor.name||"Object":typeof b));return b}function va(b,a){if("hasOwnProperty"===b)throw Ma("badname",
+a);}function ub(b,a,c){if(!a)return b;a=a.split(".");for(var d,e=b,g=a.length,f=0;f "+b;a.removeChild(a.firstChild);yb(this,a.childNodes);x(O.createDocumentFragment()).append(this)}else yb(this,b)}function zb(b){return b.cloneNode(!0)}function Qa(b){Zb(b);var a=0;for(b=b.childNodes||
+[];a=M?(c.preventDefault=
+null,c.stopPropagation=null,c.isDefaultPrevented=null):(delete c.preventDefault,delete c.stopPropagation,delete c.isDefaultPrevented)};c.elem=b;return c}function Ca(b){var a=typeof b,c;"object"==a&&null!==b?"function"==typeof(c=b.$$hashKey)?c=b.$$hashKey():c===r&&(c=b.$$hashKey=Za()):c=b;return a+":"+c}function Sa(b){q(b,this.put,this)}function ec(b){var a,c;"function"==typeof b?(a=b.$inject)||(a=[],b.length&&(c=b.toString().replace(Yc,""),c=c.match(Zc),q(c[1].split($c),function(b){b.replace(ad,function(b,
+c,d){a.push(d)})})),b.$inject=a):K(b)?(c=b.length-1,Oa(b[c],"fn"),a=b.slice(0,c)):Oa(b,"fn",!0);return a}function Yb(b){function a(a){return function(b,c){if(V(b))q(b,Pb(a));else return a(b,c)}}function c(a,b){va(a,"service");if(I(b)||K(b))b=n.instantiate(b);if(!b.$get)throw Ta("pget",a);return l[a+h]=b}function d(a,b){return c(a,{$get:b})}function e(a){var b=[],c,d,h,g;q(a,function(a){if(!k.get(a)){k.put(a,!0);try{if(w(a))for(c=Ua(a),b=b.concat(e(c.requires)).concat(c._runBlocks),d=c._invokeQueue,
+h=0,g=d.length;h 4096 bytes)!"));else{if(m.cookie!==Z)for(Z=m.cookie,d=Z.split("; "),E={},h=0;hk&&this.remove(p.key),b},get:function(a){var b=l[a];if(b)return e(b),m[a]},remove:function(a){var b=l[a];b&&(b==n&&(n=b.p),b==p&&(p=b.n),g(b.n,b.p),delete l[a],delete m[a],f--)},removeAll:function(){m={};f=0;l={};n=p=null},destroy:function(){l=h=m=null;delete a[b]},info:function(){return F({},
+h,{size:f})}}}var a={};b.info=function(){var b={};q(a,function(a,e){b[e]=a.info()});return b};b.get=function(b){return a[b]};return b}}function fd(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function gc(b,a){var c={},d="Directive",e=/^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,g=/(([\d\w\-_]+)(?:\:([^;]+))?;?)/,f=/^(on[a-z]+|formaction)$/;this.directive=function m(a,e){va(a,"directive");w(a)?(tb(e,"directiveFactory"),c.hasOwnProperty(a)||(c[a]=[],b.factory(a+d,["$injector","$exceptionHandler",
+function(b,d){var e=[];q(c[a],function(c,g){try{var f=b.invoke(c);I(f)?f={compile:ba(f)}:!f.compile&&f.link&&(f.compile=ba(f.link));f.priority=f.priority||0;f.index=g;f.name=f.name||a;f.require=f.require||f.controller&&f.name;f.restrict=f.restrict||"A";e.push(f)}catch(m){d(m)}});return e}])),c[a].push(e)):q(a,Pb(m));return this};this.aHrefSanitizationWhitelist=function(b){return B(b)?(a.aHrefSanitizationWhitelist(b),this):a.aHrefSanitizationWhitelist()};this.imgSrcSanitizationWhitelist=function(b){return B(b)?
+(a.imgSrcSanitizationWhitelist(b),this):a.imgSrcSanitizationWhitelist()};this.$get=["$injector","$interpolate","$exceptionHandler","$http","$templateCache","$parse","$controller","$rootScope","$document","$sce","$animate","$$sanitizeUri",function(a,b,l,n,p,s,D,u,Q,z,Y,H){function y(a,b,c,d,e){a instanceof x||(a=x(a));q(a,function(b,c){3==b.nodeType&&b.nodeValue.match(/\S+/)&&(a[c]=x(b).wrap(" ").parent()[0])});var g=R(a,b,a,c,d,e);return function(b,c,d){tb(b,"scope");var e=c?Da.clone.call(a):
+a;q(d,function(a,b){e.data("$"+b+"Controller",a)});d=0;for(var f=e.length;darguments.length&&(b=a,a=r);Ga&&(c=P);return p(a,b,c)}var u,ca,H,R,da,J,P={},Z;u=c===g?d:Pc(d,new Eb(x(g),d.$attr));ca=u.$$element;if(E){var T=/^\s*([@=&])(\??)\s*(\w*)\s*$/;f=x(g);J=e.$new(!0);N&&N===E.$$originalDirective?f.data("$isolateScope",J):f.data("$isolateScopeNoTemplate",J);aa(f,"ng-isolate-scope");q(E.scope,function(a,c){var d=a.match(T)||
+[],g=d[3]||c,f="?"==d[2],d=d[1],m,l,n;J.$$isolateBindings[c]=d+g;switch(d){case "@":u.$observe(g,function(a){J[c]=a});u.$$observers[g].$$scope=e;u[g]&&(J[c]=b(u[g])(e));break;case "=":if(f&&!u[g])break;l=s(u[g]);n=l.assign||function(){m=J[c]=l(e);throw ia("nonassign",u[g],E.name);};m=J[c]=l(e);J.$watch(function(){var a=l(e);a!==J[c]&&(a!==m?m=J[c]=a:n(e,a=m=J[c]));return a});break;case "&":l=s(u[g]);J[c]=function(a){return l(e,a)};break;default:throw ia("iscp",E.name,c,a);}})}Z=p&&y;z&&q(z,function(a){var b=
+{$scope:a===E||a.$$isolateScope?J:e,$element:ca,$attrs:u,$transclude:Z},c;da=a.controller;"@"==da&&(da=u[a.name]);c=D(da,b);P[a.name]=c;Ga||ca.data("$"+a.name+"Controller",c);a.controllerAs&&(b.$scope[a.controllerAs]=c)});f=0;for(H=m.length;fG.priority)break;if(t=G.scope)R=R||G,G.templateUrl||(C("new/isolated scope",E,G,v),V(t)&&(E=G));ea=G.name;!G.templateUrl&&G.controller&&(t=G.controller,z=z||{},C("'"+ea+"' controller",z[ea],G,v),
+z[ea]=G);if(t=G.transclude)la=!0,G.$$tlb||(C("transclusion",p,G,v),p=G),"element"==t?(Ga=!0,H=G.priority,t=da(c,Va,Fa),v=d.$$element=x(O.createComment(" "+ea+": "+d[ea]+" ")),c=v[0],S(g,x(ta.call(t,0)),c),F=y(t,e,H,f&&f.name,{nonTlbTranscludeDirective:p})):(t=x(zb(c)).contents(),v.html(""),F=y(t,e));if(G.template)if(C("template",N,G,v),N=G,t=I(G.template)?G.template(v,d):G.template,t=hc(t),G.replace){f=G;t=x(""+$(t)+"
").contents();c=t[0];if(1!=t.length||1!==c.nodeType)throw ia("tplrt",
+ea,"");S(g,v,c);M={$attr:{}};t=Z(c,[],M);var W=a.splice(L+1,a.length-(L+1));E&&T(t);a=a.concat(t).concat(W);fc(d,M);M=a.length}else v.html(t);if(G.templateUrl)C("template",N,G,v),N=G,G.replace&&(f=G),Q=B(a.splice(L,a.length-L),v,d,g,F,m,n,{controllerDirectives:z,newIsolateScopeDirective:E,templateDirective:N,nonTlbTranscludeDirective:p}),M=a.length;else if(G.compile)try{A=G.compile(v,d,F),I(A)?u(null,A,Va,Fa):A&&u(A.pre,A.post,Va,Fa)}catch(X){l(X,ha(v))}G.terminal&&(Q.terminal=!0,H=Math.max(H,G.priority))}Q.scope=
+R&&!0===R.scope;Q.transclude=la&&F;return Q}function T(a){for(var b=0,c=a.length;bs.priority)&&-1!=s.restrict.indexOf(g)&&(n&&(s=Rb(s,{$$start:n,$$end:p})),b.push(s),k=s)}catch(y){l(y)}}return k}function fc(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;q(a,function(d,e){"$"!=e.charAt(0)&&(b[e]&&(d+=("style"===e?
+";":" ")+b[e]),a.$set(e,d,!0,c[e]))});q(b,function(b,g){"class"==g?(aa(e,b),a["class"]=(a["class"]?a["class"]+" ":"")+b):"style"==g?(e.attr("style",e.attr("style")+";"+b),a.style=(a.style?a.style+";":"")+b):"$"==g.charAt(0)||a.hasOwnProperty(g)||(a[g]=b,d[g]=c[g])})}function B(a,b,c,d,e,g,f,m){var k=[],l,s,D=b[0],u=a.shift(),y=F({},u,{templateUrl:null,transclude:null,replace:null,$$originalDirective:u}),Y=I(u.templateUrl)?u.templateUrl(b,c):u.templateUrl;b.html("");n.get(z.getTrustedResourceUrl(Y),
+{cache:p}).success(function(n){var p,Q;n=hc(n);if(u.replace){n=x(""+$(n)+"
").contents();p=n[0];if(1!=n.length||1!==p.nodeType)throw ia("tplrt",u.name,Y);n={$attr:{}};S(d,b,p);var H=Z(p,[],n);V(u.scope)&&T(H);a=H.concat(a);fc(c,n)}else p=D,b.html(n);a.unshift(y);l=N(a,p,c,e,b,u,g,f,m);q(d,function(a,c){a==p&&(d[c]=b[0])});for(s=R(b[0].childNodes,e);k.length;){n=k.shift();Q=k.shift();var aa=k.shift(),z=k.shift(),H=b[0];Q!==D&&(H=zb(p),S(aa,x(Q),H));Q=l.transclude?E(n,l.transclude):z;l(s,
+n,H,d,Q)}k=null}).error(function(a,b,c,d){throw ia("tpload",d.url);});return function(a,b,c,d,e){k?(k.push(b),k.push(c),k.push(d),k.push(e)):l(s,b,c,d,e)}}function v(a,b){var c=b.priority-a.priority;return 0!==c?c:a.name!==b.name?a.namea.status?b:n.reject(b)}var d={transformRequest:e.transformRequest,
+transformResponse:e.transformResponse},g=function(a){function b(a){var c;q(a,function(b,d){I(b)&&(c=b(),null!=c?a[d]=c:delete a[d])})}var c=e.headers,d=F({},a.headers),g,f,c=F({},c.common,c[t(a.method)]);b(c);b(d);a:for(g in c){a=t(g);for(f in d)if(t(f)===a)continue a;d[g]=c[g]}return d}(a);F(d,a);d.headers=g;d.method=Ha(d.method);(a=Fb(d.url)?b.cookies()[d.xsrfCookieName||e.xsrfCookieName]:r)&&(g[d.xsrfHeaderName||e.xsrfHeaderName]=a);var h=[function(a){g=a.headers;var b=lc(a.data,kc(g),a.transformRequest);
+C(a.data)&&q(g,function(a,b){"content-type"===t(b)&&delete g[b]});C(a.withCredentials)&&!C(e.withCredentials)&&(a.withCredentials=e.withCredentials);return D(a,b,g).then(c,c)},r],f=n.when(d);for(q(z,function(a){(a.request||a.requestError)&&h.unshift(a.request,a.requestError);(a.response||a.responseError)&&h.push(a.response,a.responseError)});h.length;){a=h.shift();var k=h.shift(),f=f.then(a,k)}f.success=function(a){f.then(function(b){a(b.data,b.status,b.headers,d)});return f};f.error=function(a){f.then(null,
+function(b){a(b.data,b.status,b.headers,d)});return f};return f}function D(b,c,g){function f(a,b,c){q&&(200<=a&&300>a?q.put(r,[a,b,jc(c)]):q.remove(r));m(b,a,c);d.$$phase||d.$apply()}function m(a,c,d){c=Math.max(c,0);(200<=c&&300>c?p.resolve:p.reject)({data:a,status:c,headers:kc(d),config:b})}function k(){var a=bb(s.pendingRequests,b);-1!==a&&s.pendingRequests.splice(a,1)}var p=n.defer(),D=p.promise,q,z,r=u(b.url,b.params);s.pendingRequests.push(b);D.then(k,k);(b.cache||e.cache)&&(!1!==b.cache&&"GET"==
+b.method)&&(q=V(b.cache)?b.cache:V(e.cache)?e.cache:Q);if(q)if(z=q.get(r),B(z)){if(z.then)return z.then(k,k),z;K(z)?m(z[1],z[0],ga(z[2])):m(z,200,{})}else q.put(r,D);C(z)&&a(b.method,r,c,f,g,b.timeout,b.withCredentials,b.responseType);return D}function u(a,b){if(!b)return a;var c=[];Mc(b,function(a,b){null===a||C(a)||(K(a)||(a=[a]),q(a,function(a){V(a)&&(a=oa(a));c.push(ua(b)+"="+ua(a))}))});return a+(-1==a.indexOf("?")?"?":"&")+c.join("&")}var Q=c("$http"),z=[];q(g,function(a){z.unshift(w(a)?p.get(a):
+p.invoke(a))});q(f,function(a,b){var c=w(a)?p.get(a):p.invoke(a);z.splice(b,0,{response:function(a){return c(n.when(a))},responseError:function(a){return c(n.reject(a))}})});s.pendingRequests=[];(function(a){q(arguments,function(a){s[a]=function(b,c){return s(F(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){q(arguments,function(a){s[a]=function(b,c,d){return s(F(d||{},{method:a,url:b,data:c}))}})})("post","put");s.defaults=e;return s}]}function ld(){this.$get=["$browser",
+"$window","$document",function(b,a,c){return md(b,nd,b.defer,a.angular.callbacks,c[0])}]}function md(b,a,c,d,e){function g(a,b){var c=e.createElement("script"),d=function(){c.onreadystatechange=c.onload=c.onerror=null;e.body.removeChild(c);b&&b()};c.type="text/javascript";c.src=a;M&&8>=M?c.onreadystatechange=function(){/loaded|complete/.test(c.readyState)&&d()}:c.onload=c.onerror=function(){d()};e.body.appendChild(c);return d}var f=-1;return function(e,m,k,l,n,p,s,D){function u(){z=f;H&&H();y&&y.abort()}
+function Q(a,d,e,g){var f=wa(m).protocol;aa&&c.cancel(aa);H=y=null;d="file"==f&&0===d?e?200:404:d;a(1223==d?204:d,e,g);b.$$completeOutstandingRequest(v)}var z;b.$$incOutstandingRequestCount();m=m||b.url();if("jsonp"==t(e)){var r="_"+(d.counter++).toString(36);d[r]=function(a){d[r].data=a};var H=g(m.replace("JSON_CALLBACK","angular.callbacks."+r),function(){d[r].data?Q(l,200,d[r].data):Q(l,z||-2);delete d[r]})}else{var y=new a;y.open(e,m,!0);q(n,function(a,b){B(a)&&y.setRequestHeader(b,a)});y.onreadystatechange=
+function(){if(4==y.readyState){var a=null,b=null;z!==f&&(a=y.getAllResponseHeaders(),b=y.responseType?y.response:y.responseText);Q(l,z||y.status,b,a)}};s&&(y.withCredentials=!0);D&&(y.responseType=D);y.send(k||null)}if(0=h&&(n.resolve(s),l(p.$$intervalId),delete e[p.$$intervalId]);D||b.$apply()},f);e[p.$$intervalId]=n;return p}
+var e={};d.cancel=function(a){return a&&a.$$intervalId in e?(e[a.$$intervalId].reject("canceled"),clearInterval(a.$$intervalId),delete e[a.$$intervalId],!0):!1};return d}]}function qd(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January February March April May June July August September October November December".split(" "),
+SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return 1===b?"one":"other"}}}}function nc(b){b=b.split("/");for(var a=b.length;a--;)b[a]=
+sb(b[a]);return b.join("/")}function oc(b,a,c){b=wa(b,c);a.$$protocol=b.protocol;a.$$host=b.hostname;a.$$port=S(b.port)||rd[b.protocol]||null}function pc(b,a,c){var d="/"!==b.charAt(0);d&&(b="/"+b);b=wa(b,c);a.$$path=decodeURIComponent(d&&"/"===b.pathname.charAt(0)?b.pathname.substring(1):b.pathname);a.$$search=Vb(b.search);a.$$hash=decodeURIComponent(b.hash);a.$$path&&"/"!=a.$$path.charAt(0)&&(a.$$path="/"+a.$$path)}function na(b,a){if(0===a.indexOf(b))return a.substr(b.length)}function Wa(b){var a=
+b.indexOf("#");return-1==a?b:b.substr(0,a)}function Gb(b){return b.substr(0,Wa(b).lastIndexOf("/")+1)}function qc(b,a){this.$$html5=!0;a=a||"";var c=Gb(b);oc(b,this,b);this.$$parse=function(a){var e=na(c,a);if(!w(e))throw Hb("ipthprfx",a,c);pc(e,this,b);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=Wb(this.$$search),b=this.$$hash?"#"+sb(this.$$hash):"";this.$$url=nc(this.$$path)+(a?"?"+a:"")+b;this.$$absUrl=c+this.$$url.substr(1)};this.$$rewrite=function(d){var e;
+if((e=na(b,d))!==r)return d=e,(e=na(a,e))!==r?c+(na("/",e)||e):b+d;if((e=na(c,d))!==r)return c+e;if(c==d+"/")return c}}function Ib(b,a){var c=Gb(b);oc(b,this,b);this.$$parse=function(d){var e=na(b,d)||na(c,d),e="#"==e.charAt(0)?na(a,e):this.$$html5?e:"";if(!w(e))throw Hb("ihshprfx",d,a);pc(e,this,b);d=this.$$path;var g=/^\/?.*?:(\/.*)/;0===e.indexOf(b)&&(e=e.replace(b,""));g.exec(e)||(d=(e=g.exec(d))?e[1]:d);this.$$path=d;this.$$compose()};this.$$compose=function(){var c=Wb(this.$$search),e=this.$$hash?
+"#"+sb(this.$$hash):"";this.$$url=nc(this.$$path)+(c?"?"+c:"")+e;this.$$absUrl=b+(this.$$url?a+this.$$url:"")};this.$$rewrite=function(a){if(Wa(b)==Wa(a))return a}}function rc(b,a){this.$$html5=!0;Ib.apply(this,arguments);var c=Gb(b);this.$$rewrite=function(d){var e;if(b==Wa(d))return d;if(e=na(c,d))return b+a+e;if(c===d+"/")return c}}function hb(b){return function(){return this[b]}}function sc(b,a){return function(c){if(C(c))return this[b];this[b]=a(c);this.$$compose();return this}}function sd(){var b=
+"",a=!1;this.hashPrefix=function(a){return B(a)?(b=a,this):b};this.html5Mode=function(b){return B(b)?(a=b,this):a};this.$get=["$rootScope","$browser","$sniffer","$rootElement",function(c,d,e,g){function f(a){c.$broadcast("$locationChangeSuccess",h.absUrl(),a)}var h,m=d.baseHref(),k=d.url();a?(m=k.substring(0,k.indexOf("/",k.indexOf("//")+2))+(m||"/"),e=e.history?qc:rc):(m=Wa(k),e=Ib);h=new e(m,"#"+b);h.$$parse(h.$$rewrite(k));g.on("click",function(a){if(!a.ctrlKey&&!a.metaKey&&2!=a.which){for(var b=
+x(a.target);"a"!==t(b[0].nodeName);)if(b[0]===g[0]||!(b=b.parent())[0])return;var e=b.prop("href"),f=h.$$rewrite(e);e&&(!b.attr("target")&&f&&!a.isDefaultPrevented())&&(a.preventDefault(),f!=d.url()&&(h.$$parse(f),c.$apply(),X.angular["ff-684208-preventDefault"]=!0))}});h.absUrl()!=k&&d.url(h.absUrl(),!0);d.onUrlChange(function(a){h.absUrl()!=a&&(c.$broadcast("$locationChangeStart",a,h.absUrl()).defaultPrevented?d.url(h.absUrl()):(c.$evalAsync(function(){var b=h.absUrl();h.$$parse(a);f(b)}),c.$$phase||
+c.$digest()))});var l=0;c.$watch(function(){var a=d.url(),b=h.$$replace;l&&a==h.absUrl()||(l++,c.$evalAsync(function(){c.$broadcast("$locationChangeStart",h.absUrl(),a).defaultPrevented?h.$$parse(a):(d.url(h.absUrl(),b),f(a))}));h.$$replace=!1;return l});return h}]}function td(){var b=!0,a=this;this.debugEnabled=function(a){return B(a)?(b=a,this):b};this.$get=["$window",function(c){function d(a){a instanceof Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:
+a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=c.console||{},e=b[a]||b.log||v;return e.apply?function(){var a=[];q(arguments,function(b){a.push(d(b))});return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){b&&c.apply(a,arguments)}}()}}]}function pa(b,a){if("constructor"===b)throw xa("isecfld",a);return b}function Xa(b,a){if(b&&b.constructor===
+b)throw xa("isecfn",a);if(b&&b.document&&b.location&&b.alert&&b.setInterval)throw xa("isecwindow",a);if(b&&(b.nodeName||b.on&&b.find))throw xa("isecdom",a);return b}function ib(b,a,c,d,e){e=e||{};a=a.split(".");for(var g,f=0;1e?tc(d[0],d[1],d[2],d[3],d[4],c,a):function(b,g){var f=0,h;do h=tc(d[f++],d[f++],d[f++],d[f++],d[f++],c,a)(b,g),g=r,b=h;while(fa)for(b in g++,d)d.hasOwnProperty(b)&&!f.hasOwnProperty(b)&&(l--,delete d[b])}else d!==f&&(d=f,g++);return g},function(){b(f,d,c)})},$digest:function(){var c,e,f,g,m=this.$$asyncQueue,q=this.$$postDigestQueue,r,t,H=b,y,v=[],x,E,Z;h("$digest");do{t=!1;for(y=this;m.length;)try{Z=m.shift(),Z.scope.$eval(Z.expression)}catch(B){d(B)}do{if(g=y.$$watchers)for(r=g.length;r--;)try{(c=
+g[r])&&((e=c.get(y))!==(f=c.last)&&!(c.eq?Aa(e,f):"number"==typeof e&&"number"==typeof f&&isNaN(e)&&isNaN(f)))&&(t=!0,c.last=c.eq?ga(e):e,c.fn(e,f===k?e:f,y),5>H&&(x=4-H,v[x]||(v[x]=[]),E=I(c.exp)?"fn: "+(c.exp.name||c.exp.toString()):c.exp,E+="; newVal: "+oa(e)+"; oldVal: "+oa(f),v[x].push(E)))}catch(P){d(P)}if(!(g=y.$$childHead||y!==this&&y.$$nextSibling))for(;y!==this&&!(g=y.$$nextSibling);)y=y.$parent}while(y=g);if(t&&!H--)throw l.$$phase=null,a("infdig",b,oa(v));}while(t||m.length);for(l.$$phase=
+null;q.length;)try{q.shift()()}catch(N){d(N)}},$destroy:function(){if(l!=this&&!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;a.$$childHead==this&&(a.$$childHead=this.$$nextSibling);a.$$childTail==this&&(a.$$childTail=this.$$prevSibling);this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling);this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling);this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=
+null}},$eval:function(a,b){return e(a)(this,b)},$evalAsync:function(a){l.$$phase||l.$$asyncQueue.length||g.defer(function(){l.$$asyncQueue.length&&l.$digest()});this.$$asyncQueue.push({scope:this,expression:a})},$$postDigest:function(a){this.$$postDigestQueue.push(a)},$apply:function(a){try{return h("$apply"),this.$eval(a)}catch(b){d(b)}finally{l.$$phase=null;try{l.$digest()}catch(c){throw d(c),c;}}},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);return function(){c[bb(c,
+b)]=null}},$emit:function(a,b){var c=[],e,f=this,g=!1,h={name:a,targetScope:f,stopPropagation:function(){g=!0},preventDefault:function(){h.defaultPrevented=!0},defaultPrevented:!1},k=[h].concat(ta.call(arguments,1)),m,l;do{e=f.$$listeners[a]||c;h.currentScope=f;m=0;for(l=e.length;mc.msieDocumentMode)throw ra("iequirks");var e=ga(fa);e.isEnabled=function(){return b};e.trustAs=d.trustAs;e.getTrusted=d.getTrusted;e.valueOf=d.valueOf;b||(e.trustAs=e.getTrusted=function(a,b){return b},e.valueOf=za);e.parseAs=function(b,c){var d=a(c);return d.literal&&d.constant?d:function(a,c){return e.getTrusted(b,d(a,c))}};var g=e.parseAs,
+f=e.getTrusted,h=e.trustAs;q(fa,function(a,b){var c=t(b);e[Pa("parse_as_"+c)]=function(b){return g(a,b)};e[Pa("get_trusted_"+c)]=function(b){return f(a,b)};e[Pa("trust_as_"+c)]=function(b){return h(a,b)}});return e}]}function Cd(){this.$get=["$window","$document",function(b,a){var c={},d=S((/android (\d+)/.exec(t((b.navigator||{}).userAgent))||[])[1]),e=/Boxee/i.test((b.navigator||{}).userAgent),g=a[0]||{},f=g.documentMode,h,m=/^(Moz|webkit|O|ms)(?=[A-Z])/,k=g.body&&g.body.style,l=!1,n=!1;if(k){for(var p in k)if(l=
+m.exec(p)){h=l[0];h=h.substr(0,1).toUpperCase()+h.substr(1);break}h||(h="WebkitOpacity"in k&&"webkit");l=!!("transition"in k||h+"Transition"in k);n=!!("animation"in k||h+"Animation"in k);!d||l&&n||(l=w(g.body.style.webkitTransition),n=w(g.body.style.webkitAnimation))}return{history:!(!b.history||!b.history.pushState||4>d||e),hashchange:"onhashchange"in b&&(!f||7b;b=Math.abs(b);var f=b+"",h="",m=[],k=!1;if(-1!==f.indexOf("e")){var l=f.match(/([\d\.]+)e(-?)(\d+)/);l&&"-"==l[2]&&l[3]>e+1?f="0":(h=f,k=!0)}if(k)0b)&&(h=b.toFixed(e));else{f=(f.split(Ec)[1]||"").length;C(e)&&(e=Math.min(Math.max(a.minFrac,f),a.maxFrac));
+f=Math.pow(10,e);b=Math.round(b*f)/f;b=(""+b).split(Ec);f=b[0];b=b[1]||"";var l=0,n=a.lgSize,p=a.gSize;if(f.length>=n+p)for(l=f.length-n,k=0;kb&&(d="-",b=-b);for(b=""+b;b.length-c)e+=c;0===e&&-12==c&&(e=12);return Lb(e,a,d)}}function jb(b,a){return function(c,d){var e=c["get"+b](),g=Ha(a?"SHORT"+b:b);return d[g][e]}}function Ac(b){function a(a){var b;if(b=a.match(c)){a=new Date(0);var g=0,f=0,h=b[8]?a.setUTCFullYear:a.setFullYear,m=b[8]?a.setUTCHours:a.setHours;b[9]&&(g=S(b[9]+b[10]),f=S(b[9]+b[11]));h.call(a,S(b[1]),S(b[2])-1,S(b[3]));g=S(b[4]||0)-g;f=S(b[5]||0)-f;h=S(b[6]||0);b=Math.round(1E3*
+parseFloat("0."+(b[7]||0)));m.call(a,g,f,h,b)}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,e){var g="",f=[],h,m;e=e||"mediumDate";e=b.DATETIME_FORMATS[e]||e;w(c)&&(c=Kd.test(c)?S(c):a(c));qb(c)&&(c=new Date(c));if(!Ka(c))return c;for(;e;)(m=Ld.exec(e))?(f=f.concat(ta.call(m,1)),e=f.pop()):(f.push(e),e=null);q(f,function(a){h=Md[a];g+=h?h(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}
+function Gd(){return function(b){return oa(b,!0)}}function Hd(){return function(b,a){if(!K(b)&&!w(b))return b;a=S(a);if(w(b))return a?0<=a?b.slice(0,a):b.slice(a,b.length):"";var c=[],d,e;a>b.length?a=b.length:a<-b.length&&(a=-b.length);0 a||37<=a&&40>=a)||k()});if(e.hasEvent("paste"))a.on("paste cut",k)}a.on("change",h);d.$render=function(){a.val(d.$isEmpty(d.$viewValue)?"":d.$viewValue)};var l=c.ngPattern,
+n=function(a,b){if(d.$isEmpty(b)||a.test(b))return d.$setValidity("pattern",!0),b;d.$setValidity("pattern",!1);return r};l&&((e=l.match(/^\/(.*)\/([gim]*)$/))?(l=RegExp(e[1],e[2]),e=function(a){return n(l,a)}):e=function(c){var d=b.$eval(l);if(!d||!d.test)throw A("ngPattern")("noregexp",l,d,ha(a));return n(d,c)},d.$formatters.push(e),d.$parsers.push(e));if(c.ngMinlength){var p=S(c.ngMinlength);e=function(a){if(!d.$isEmpty(a)&&a.lengths)return d.$setValidity("maxlength",!1),r;d.$setValidity("maxlength",!0);return a};d.$parsers.push(e);d.$formatters.push(e)}}function Mb(b,a){b="ngClass"+b;return function(){return{restrict:"AC",link:function(c,d,e){function g(b){if(!0===a||c.$index%2===a){var d=f(b||"");h?Aa(b,h)||e.$updateClass(d,f(h)):e.$addClass(d)}h=ga(b)}function f(a){if(K(a))return a.join(" ");
+if(V(a)){var b=[];q(a,function(a,c){a&&b.push(c)});return b.join(" ")}return a}var h;c.$watch(e[b],g,!0);e.$observe("class",function(a){g(c.$eval(e[b]))});"ngClass"!==b&&c.$watch("$index",function(d,g){var h=d&1;if(h!==g&1){var n=f(c.$eval(e[b]));h===a?e.$addClass(n):e.$removeClass(n)}})}}}}var t=function(b){return w(b)?b.toLowerCase():b},Ha=function(b){return w(b)?b.toUpperCase():b},M,x,Ba,ta=[].slice,Nd=[].push,$a=Object.prototype.toString,Ma=A("ng"),cb=X.angular||(X.angular={}),Ua,Ea,ja=["0","0",
+"0"];M=S((/msie (\d+)/.exec(t(navigator.userAgent))||[])[1]);isNaN(M)&&(M=S((/trident\/.*; rv:(\d+)/.exec(t(navigator.userAgent))||[])[1]));v.$inject=[];za.$inject=[];var $=function(){return String.prototype.trim?function(b){return w(b)?b.trim():b}:function(b){return w(b)?b.replace(/^\s\s*/,"").replace(/\s\s*$/,""):b}}();Ea=9>M?function(b){b=b.nodeName?b:b[0];return b.scopeName&&"HTML"!=b.scopeName?Ha(b.scopeName+":"+b.nodeName):b.nodeName}:function(b){return b.nodeName?b.nodeName:b[0].nodeName};
+var Sc=/[A-Z]/g,Od={full:"1.2.3",major:1,minor:2,dot:3,codeName:"unicorn-zapper"},Ra=L.cache={},eb=L.expando="ng-"+(new Date).getTime(),Wc=1,Gc=X.document.addEventListener?function(b,a,c){b.addEventListener(a,c,!1)}:function(b,a,c){b.attachEvent("on"+a,c)},Ab=X.document.removeEventListener?function(b,a,c){b.removeEventListener(a,c,!1)}:function(b,a,c){b.detachEvent("on"+a,c)},Uc=/([\:\-\_]+(.))/g,Vc=/^moz([A-Z])/,xb=A("jqLite"),Da=L.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;
+"complete"===O.readyState?setTimeout(a):(this.on("DOMContentLoaded",a),L(X).on("load",a))},toString:function(){var b=[];q(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return 0<=b?x(this[b]):x(this[this.length+b])},length:0,push:Nd,sort:[].sort,splice:[].splice},gb={};q("multiple selected checked disabled readOnly required open".split(" "),function(b){gb[t(b)]=b});var dc={};q("input select option textarea button form details".split(" "),function(b){dc[Ha(b)]=!0});q({data:ac,
+inheritedData:fb,scope:function(b){return x(b).data("$scope")||fb(b.parentNode||b,["$isolateScope","$scope"])},isolateScope:function(b){return x(b).data("$isolateScope")||x(b).data("$isolateScopeNoTemplate")},controller:bc,injector:function(b){return fb(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:Bb,css:function(b,a,c){a=Pa(a);if(B(c))b.style[a]=c;else{var d;8>=M&&(d=b.currentStyle&&b.currentStyle[a],""===d&&(d="auto"));d=d||b.style[a];8>=M&&(d=""===d?r:d);return d}},attr:function(b,
+a,c){var d=t(a);if(gb[d])if(B(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||v).specified?d:r;else if(B(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),null===b?r:b},prop:function(b,a,c){if(B(c))b[a]=c;else return b[a]},text:function(){function b(b,d){var e=a[b.nodeType];if(C(d))return e?b[e]:"";b[e]=d}var a=[];9>M?(a[1]="innerText",a[3]="nodeValue"):a[1]=a[3]="textContent";b.$dv="";return b}(),val:function(b,
+a){if(C(a)){if("SELECT"===Ea(b)&&b.multiple){var c=[];q(b.options,function(a){a.selected&&c.push(a.value||a.text)});return 0===c.length?null:c}return b.value}b.value=a},html:function(b,a){if(C(a))return b.innerHTML;for(var c=0,d=b.childNodes;c":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a,c)&e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},"!":function(a,c,d){return!d(a,c)}},Sd={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},Kb=function(a){this.options=a};Kb.prototype={constructor:Kb,lex:function(a){this.text=a;
+this.index=0;this.ch=r;this.lastCh=":";this.tokens=[];var c;for(a=[];this.index=a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdent:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isExpOperator:function(a){return"-"===
+a||"+"===a||this.isNumber(a)},throwError:function(a,c,d){d=d||this.index;c=B(c)?"s "+c+"-"+this.index+" ["+this.text.substring(c,d)+"]":" "+d;throw xa("lexerr",a,c,this.text);},readNumber:function(){for(var a="",c=this.index;this.index","<=",">="))a=this.binaryFn(a,c.fn,this.relational());return a},additive:function(){for(var a=this.multiplicative(),c;c=this.expect("+",
+"-");)a=this.binaryFn(a,c.fn,this.multiplicative());return a},multiplicative:function(){for(var a=this.unary(),c;c=this.expect("*","/","%");)a=this.binaryFn(a,c.fn,this.unary());return a},unary:function(){var a;return this.expect("+")?this.primary():(a=this.expect("-"))?this.binaryFn(Ya.ZERO,a.fn,this.unary()):(a=this.expect("!"))?this.unaryFn(a.fn,this.unary()):this.primary()},fieldAccess:function(a){var c=this,d=this.expect().text,e=uc(d,this.options,this.text);return F(function(c,d,h){return e(h||
+a(c,d),d)},{assign:function(e,f,h){return ib(a(e,h),d,f,c.text,c.options)}})},objectIndex:function(a){var c=this,d=this.expression();this.consume("]");return F(function(e,g){var f=a(e,g),h=d(e,g),m;if(!f)return r;(f=Xa(f[h],c.text))&&(f.then&&c.options.unwrapPromises)&&(m=f,"$$v"in f||(m.$$v=r,m.then(function(a){m.$$v=a})),f=f.$$v);return f},{assign:function(e,g,f){var h=d(e,f);return Xa(a(e,f),c.text)[h]=g}})},functionCall:function(a,c){var d=[];if(")"!==this.peekToken().text){do d.push(this.expression());
+while(this.expect(","))}this.consume(")");var e=this;return function(g,f){for(var h=[],m=c?c(g,f):g,k=0;ka.getHours()?
+c.AMPMS[0]:c.AMPMS[1]},Z:function(a){a=-1*a.getTimezoneOffset();return a=(0<=a?"+":"")+(Lb(Math[0=M&&(c.href||c.name||c.$set("href",""),a.append(O.createComment("IE fix")));return function(a,c){c.on("click",function(a){c.attr("href")||a.preventDefault()})}}}),
+Nb={};q(gb,function(a,c){if("multiple"!=a){var d=ma("ng-"+c);Nb[d]=function(){return{priority:100,compile:function(){return function(a,g,f){a.$watch(f[d],function(a){f.$set(c,!!a)})}}}}}});q(["src","srcset","href"],function(a){var c=ma("ng-"+a);Nb[c]=function(){return{priority:99,link:function(d,e,g){g.$observe(c,function(c){c&&(g.$set(a,c),M&&e.prop(a,g[a]))})}}}});var mb={$addControl:v,$removeControl:v,$setValidity:v,$setDirty:v,$setPristine:v};Fc.$inject=["$element","$attrs","$scope"];var Hc=function(a){return["$timeout",
+function(c){return{name:"form",restrict:a?"EAC":"E",controller:Fc,compile:function(){return{pre:function(a,e,g,f){if(!g.action){var h=function(a){a.preventDefault?a.preventDefault():a.returnValue=!1};Gc(e[0],"submit",h);e.on("$destroy",function(){c(function(){Ab(e[0],"submit",h)},0,!1)})}var m=e.parent().controller("form"),k=g.name||g.ngForm;k&&ib(a,k,f,k);if(m)e.on("$destroy",function(){m.$removeControl(f);k&&ib(a,k,r,k);F(f,mb)})}}}}}]},Ud=Hc(),Vd=Hc(!0),Wd=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,
+Xd=/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/,Yd=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,Ic={text:ob,number:function(a,c,d,e,g,f){ob(a,c,d,e,g,f);e.$parsers.push(function(a){var c=e.$isEmpty(a);if(c||Yd.test(a))return e.$setValidity("number",!0),""===a?null:c?a:parseFloat(a);e.$setValidity("number",!1);return r});e.$formatters.push(function(a){return e.$isEmpty(a)?"":""+a});d.min&&(a=function(a){var c=parseFloat(d.min);if(!e.$isEmpty(a)&&ac)return e.$setValidity("max",!1),r;e.$setValidity("max",!0);return a},e.$parsers.push(a),e.$formatters.push(a));e.$formatters.push(function(a){if(e.$isEmpty(a)||qb(a))return e.$setValidity("number",!0),a;e.$setValidity("number",!1);return r})},url:function(a,c,d,e,g,f){ob(a,c,d,e,g,f);a=function(a){if(e.$isEmpty(a)||Wd.test(a))return e.$setValidity("url",!0),a;e.$setValidity("url",
+!1);return r};e.$formatters.push(a);e.$parsers.push(a)},email:function(a,c,d,e,g,f){ob(a,c,d,e,g,f);a=function(a){if(e.$isEmpty(a)||Xd.test(a))return e.$setValidity("email",!0),a;e.$setValidity("email",!1);return r};e.$formatters.push(a);e.$parsers.push(a)},radio:function(a,c,d,e){C(d.name)&&c.attr("name",Za());c.on("click",function(){c[0].checked&&a.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,
+c,d,e){var g=d.ngTrueValue,f=d.ngFalseValue;w(g)||(g=!0);w(f)||(f=!1);c.on("click",function(){a.$apply(function(){e.$setViewValue(c[0].checked)})});e.$render=function(){c[0].checked=e.$viewValue};e.$isEmpty=function(a){return a!==g};e.$formatters.push(function(a){return a===g});e.$parsers.push(function(a){return a?g:f})},hidden:v,button:v,submit:v,reset:v},Jc=["$browser","$sniffer",function(a,c){return{restrict:"E",require:"?ngModel",link:function(d,e,g,f){f&&(Ic[t(g.type)]||Ic.text)(d,e,g,f,c,a)}}}],
+lb="ng-valid",kb="ng-invalid",Ia="ng-pristine",nb="ng-dirty",Zd=["$scope","$exceptionHandler","$attrs","$element","$parse",function(a,c,d,e,g){function f(a,c){c=c?"-"+db(c,"-"):"";e.removeClass((a?kb:lb)+c).addClass((a?lb:kb)+c)}this.$modelValue=this.$viewValue=Number.NaN;this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$name=d.name;var h=g(d.ngModel),m=h.assign;if(!m)throw A("ngModel")("nonassign",d.ngModel,ha(e));
+this.$render=v;this.$isEmpty=function(a){return C(a)||""===a||null===a||a!==a};var k=e.inheritedData("$formController")||mb,l=0,n=this.$error={};e.addClass(Ia);f(!0);this.$setValidity=function(a,c){n[a]!==!c&&(c?(n[a]&&l--,l||(f(!0),this.$valid=!0,this.$invalid=!1)):(f(!1),this.$invalid=!0,this.$valid=!1,l++),n[a]=!c,f(c,a),k.$setValidity(a,c,this))};this.$setPristine=function(){this.$dirty=!1;this.$pristine=!0;e.removeClass(nb).addClass(Ia)};this.$setViewValue=function(d){this.$viewValue=d;this.$pristine&&
+(this.$dirty=!0,this.$pristine=!1,e.removeClass(Ia).addClass(nb),k.$setDirty());q(this.$parsers,function(a){d=a(d)});this.$modelValue!==d&&(this.$modelValue=d,m(a,d),q(this.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}}))};var p=this;a.$watch(function(){var c=h(a);if(p.$modelValue!==c){var d=p.$formatters,e=d.length;for(p.$modelValue=c;e--;)c=d[e](c);p.$viewValue!==c&&(p.$viewValue=c,p.$render())}})}],$d=function(){return{require:["ngModel","^?form"],controller:Zd,link:function(a,c,d,e){var g=
+e[0],f=e[1]||mb;f.$addControl(g);a.$on("$destroy",function(){f.$removeControl(g)})}}},ae=ba({require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),Kc=function(){return{require:"?ngModel",link:function(a,c,d,e){if(e){d.required=!0;var g=function(a){if(d.required&&e.$isEmpty(a))e.$setValidity("required",!1);else return e.$setValidity("required",!0),a};e.$formatters.push(g);e.$parsers.unshift(g);d.$observe("required",function(){g(e.$viewValue)})}}}},
+be=function(){return{require:"ngModel",link:function(a,c,d,e){var g=(a=/\/(.*)\//.exec(d.ngList))&&RegExp(a[1])||d.ngList||",";e.$parsers.push(function(a){if(!C(a)){var c=[];a&&q(a.split(g),function(a){a&&c.push($(a))});return c}});e.$formatters.push(function(a){return K(a)?a.join(", "):r});e.$isEmpty=function(a){return!a||!a.length}}}},ce=/^(true|false|\d+)$/,de=function(){return{priority:100,compile:function(a,c){return ce.test(c.ngValue)?function(a,c,g){g.$set("value",a.$eval(g.ngValue))}:function(a,
+c,g){a.$watch(g.ngValue,function(a){g.$set("value",a)})}}}},ee=sa(function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBind);a.$watch(d.ngBind,function(a){c.text(a==r?"":a)})}),fe=["$interpolate",function(a){return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));d.addClass("ng-binding").data("$binding",c);e.$observe("ngBindTemplate",function(a){d.text(a)})}}],ge=["$sce","$parse",function(a,c){return function(d,e,g){e.addClass("ng-binding").data("$binding",g.ngBindHtml);var f=c(g.ngBindHtml);
+d.$watch(function(){return(f(d)||"").toString()},function(c){e.html(a.getTrustedHtml(f(d))||"")})}}],he=Mb("",!0),ie=Mb("Odd",0),je=Mb("Even",1),ke=sa({compile:function(a,c){c.$set("ngCloak",r);a.removeClass("ng-cloak")}}),le=[function(){return{scope:!0,controller:"@",priority:500}}],Lc={};q("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var c=ma("ng-"+a);Lc[c]=["$parse",function(d){return{compile:function(e,
+g){var f=d(g[c]);return function(c,d,e){d.on(t(a),function(a){c.$apply(function(){f(c,{$event:a})})})}}}}]});var me=["$animate",function(a){return{transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(c,d,e,g,f){var h,m;c.$watch(e.ngIf,function(g){Na(g)?m||(m=c.$new(),f(m,function(c){h={startNode:c[0],endNode:c[c.length++]=O.createComment(" end ngIf: "+e.ngIf+" ")};a.enter(c,d.parent(),d)})):(m&&(m.$destroy(),m=null),h&&(a.leave(vb(h)),h=null))})}}}],ne=["$http","$templateCache",
+"$anchorScroll","$compile","$animate","$sce",function(a,c,d,e,g,f){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",compile:function(h,m){var k=m.ngInclude||m.src,l=m.onload||"",n=m.autoscroll;return function(h,m,q,r,t){var z=0,x,H,y=function(){x&&(x.$destroy(),x=null);H&&(g.leave(H),H=null)};h.$watch(f.parseAsResourceUrl(k),function(f){var k=function(){!B(n)||n&&!h.$eval(n)||d()},q=++z;f?(a.get(f,{cache:c}).success(function(a){if(q===z){var c=h.$new(),d=t(c,v);y();x=c;H=d;H.html(a);
+g.enter(H,null,m,k);e(H.contents())(x);x.$emit("$includeContentLoaded");h.$eval(l)}}).error(function(){q===z&&y()}),h.$emit("$includeContentRequested")):y()})}}}}],oe=sa({compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),pe=sa({terminal:!0,priority:1E3}),qe=["$locale","$interpolate",function(a,c){var d=/{}/g;return{restrict:"EA",link:function(e,g,f){var h=f.count,m=f.$attr.when&&g.attr(f.$attr.when),k=f.offset||0,l=e.$eval(m)||{},n={},p=c.startSymbol(),s=c.endSymbol(),r=/^when(Minus)?(.+)$/;
+q(f,function(a,c){r.test(c)&&(l[t(c.replace("when","").replace("Minus","-"))]=g.attr(f.$attr[c]))});q(l,function(a,e){n[e]=c(a.replace(d,p+h+"-"+k+s))});e.$watch(function(){var c=parseFloat(e.$eval(h));if(isNaN(c))return"";c in l||(c=a.pluralCat(c-k));return n[c](e,g,!0)},function(a){g.text(a)})}}}],re=["$parse","$animate",function(a,c){var d=A("ngRepeat");return{transclude:"element",priority:1E3,terminal:!0,$$tlb:!0,link:function(e,g,f,h,m){var k=f.ngRepeat,l=k.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/),
+n,p,s,r,u,t,v={$id:Ca};if(!l)throw d("iexp",k);f=l[1];h=l[2];(l=l[4])?(n=a(l),p=function(a,c,d){t&&(v[t]=a);v[u]=c;v.$index=d;return n(e,v)}):(s=function(a,c){return Ca(c)},r=function(a){return a});l=f.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!l)throw d("iidexp",f);u=l[3]||l[1];t=l[2];var B={};e.$watchCollection(h,function(a){var f,h,l=g[0],n,v={},z,P,N,T,C,w,F=[];if(pb(a))C=a,n=p||s;else{n=p||r;C=[];for(N in a)a.hasOwnProperty(N)&&"$"!=N.charAt(0)&&C.push(N);C.sort()}z=C.length;
+h=F.length=C.length;for(f=0;fJ;)u.pop().element.remove()}for(;y.length>A;)y.pop()[0].element.remove()}var k;if(!(k=t.match(d)))throw Ae("iexp",t,ha(f));var m=c(k[2]||k[1]),l=k[4]||k[6],n=k[5],p=c(k[3]||""),q=c(k[2]?k[1]:l),s=c(k[7]),x=k[8]?c(k[8]):null,y=[[{element:f,label:""}]];w&&(a(w)(e),w.removeClass("ng-scope"),w.remove());f.html("");f.on("change",
+function(){e.$apply(function(){var a,c=s(e)||[],d={},h,k,m,p,t,u,w;if(v)for(k=[],p=0,u=y.length;p@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide{display:none !important;}ng\\:form{display:block;}.ng-animate-start{border-spacing:1px 1px;-ms-zoom:1.0001;}.ng-animate-active{border-spacing:0px 0px;-ms-zoom:1;}');
+//# sourceMappingURL=angular.min.js.map
\ No newline at end of file