diff options
| author | Misko Hevery | 2011-11-02 16:32:46 -0700 |
|---|---|---|
| committer | Misko Hevery | 2011-11-14 16:39:33 -0800 |
| commit | bd04316a89a0909e7a4e483839d573ce857f7622 (patch) | |
| tree | 0378c33976ba524b7090a3665cd7729638f1545e /src | |
| parent | ed36b9da3be338fe9eb36f3eeea901d6f51cd768 (diff) | |
| download | angular.js-bd04316a89a0909e7a4e483839d573ce857f7622.tar.bz2 | |
refactor(services): migrate angular.service -> module
Diffstat (limited to 'src')
| -rw-r--r-- | src/Angular.js | 58 | ||||
| -rw-r--r-- | src/AngularPublic.js | 11 | ||||
| -rw-r--r-- | src/Browser.js | 7 | ||||
| -rw-r--r-- | src/Injector.js | 10 | ||||
| -rw-r--r-- | src/angular-mocks.js | 127 | ||||
| -rw-r--r-- | src/service/compiler.js | 595 | ||||
| -rw-r--r-- | src/service/cookieStore.js | 92 | ||||
| -rw-r--r-- | src/service/cookies.js | 122 | ||||
| -rw-r--r-- | src/service/defer.js | 24 | ||||
| -rw-r--r-- | src/service/document.js | 8 | ||||
| -rw-r--r-- | src/service/exceptionHandler.js | 13 | ||||
| -rw-r--r-- | src/service/formFactory.js | 520 | ||||
| -rw-r--r-- | src/service/locale.js | 108 | ||||
| -rw-r--r-- | src/service/location.js | 151 | ||||
| -rw-r--r-- | src/service/log.js | 126 | ||||
| -rw-r--r-- | src/service/resource.js | 10 | ||||
| -rw-r--r-- | src/service/route.js | 473 | ||||
| -rw-r--r-- | src/service/routeParams.js | 6 | ||||
| -rw-r--r-- | src/service/scope.js | 1182 | ||||
| -rw-r--r-- | src/service/sniffer.js | 20 | ||||
| -rw-r--r-- | src/service/window.js | 4 | ||||
| -rw-r--r-- | src/service/xhr.bulk.js | 139 | ||||
| -rw-r--r-- | src/service/xhr.cache.js | 148 | ||||
| -rw-r--r-- | src/service/xhr.error.js | 12 | ||||
| -rw-r--r-- | src/service/xhr.js | 101 |
25 files changed, 2075 insertions, 1992 deletions
diff --git a/src/Angular.js b/src/Angular.js index 4344425a..931a3e5a 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -99,8 +99,8 @@ var _undefined = undefined, : noop, /** @name angular */ - angular = window[$angular] || (window[$angular] = {}), - angularModules = angular.modules || (angular.modules = {}), + angular = window.angular || (window.angular = {}), + angularModule = angular.module || (angular.module = {}), /** @name angular.markup */ angularTextMarkup = extensionMap(angular, 'markup'), /** @name angular.attrMarkup */ @@ -114,7 +114,6 @@ var _undefined = undefined, /** @name angular.service */ angularInputType = extensionMap(angular, 'inputType', lowercase), /** @name angular.service */ - angularService = extensionMap(angular, 'service'), angularCallbacks = extensionMap(angular, 'callbacks'), nodeName_, uid = ['0', '0', '0'], @@ -188,18 +187,6 @@ function forEachSorted(obj, iterator, context) { } -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; -} - /** * A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric * characters such as '012ABC'. The reason why we are not using simply a number counter is that @@ -957,7 +944,7 @@ function angularInit(config, document){ modules.push(module); } }); - createInjector(modules, angularModules)(['$rootScope', '$compile', function(scope, compile){ + createInjector(modules, angularModule)(['$rootScope', '$compile', '$injector', function(scope, compile, injector){ scope.$apply(function(){ compile(isString(autobind) ? document.getElementById(autobind) : document)(scope); }); @@ -1030,7 +1017,7 @@ function publishExternalAPI(angular){ 'extend': extend, 'equals': equals, 'forEach': forEach, - 'injector': function(){ return createInjector(arguments, angularModules); }, + 'injector': function(){ return createInjector(arguments, angularModule); }, 'noop':noop, 'bind':bind, 'toJson': toJson, @@ -1049,14 +1036,39 @@ function publishExternalAPI(angular){ 'uppercase': uppercase }); - angularModules.NG = ngModule; + angularModule.NG = ngModule; } -ngModule.$inject = ['$provide']; -function ngModule($provide) { - forEach(angularService, function(factory, name){ - $provide.factory(name, factory); - }); +ngModule.$inject = ['$provide', '$injector']; +function ngModule($provide, $injector) { +// TODO(misko): temporary services to get the compiler working; + $provide.value('$textMarkup', angularTextMarkup); + $provide.value('$attrMarkup', angularAttrMarkup); + $provide.value('$directive', angularDirective); + $provide.value('$widget', angularWidget); + + $provide.service('$browser', $BrowserProvider); + $provide.service('$compile', $CompileProvider); + $provide.service('$cookies', $CookiesProvider); + $provide.service('$cookieStore', $CookieStoreProvider); + $provide.service('$defer', $DeferProvider); + $provide.service('$document', $DocumentProvider); + $provide.service('$exceptionHandler', $ExceptionHandlerProvider); + $provide.service('$formFactory', $FormFactoryProvider); + $provide.service('$locale', $LocaleProvider); + $provide.service('$location', $LocationProvider); + $provide.service('$locationConfig', $LocationConfigProvider); + $provide.service('$log', $LogProvider); + $provide.service('$resource', $ResourceProvider); + $provide.service('$route', $RouteProvider); + $provide.service('$routeParams', $RouteParamsProvider); + $provide.service('$rootScope', $RootScopeProvider); + $provide.service('$sniffer', $SnifferProvider); + $provide.service('$window', $WindowProvider); + $provide.service('$xhr.bulk', $XhrBulkProvider); + $provide.service('$xhr.cache', $XhrCacheProvider); + $provide.service('$xhr.error', $XhrErrorProvider); + $provide.service('$xhr', $XhrProvider); } diff --git a/src/AngularPublic.js b/src/AngularPublic.js index 600f1ef8..64a7bf4d 100644 --- a/src/AngularPublic.js +++ b/src/AngularPublic.js @@ -1,16 +1,5 @@ 'use strict'; -var browserSingleton; - -angularService('$browser', function($log, $sniffer) { - if (!browserSingleton) { - browserSingleton = new Browser(window, jqLite(window.document), jqLite(window.document.body), - XHR, $log, $sniffer); - } - return browserSingleton; -}, {$inject: ['$log', '$sniffer']}); - - publishExternalAPI(angular); //try to bind to jquery now so that one can write angular.element().read() diff --git a/src/Browser.js b/src/Browser.js index 71252050..d4faebc8 100644 --- a/src/Browser.js +++ b/src/Browser.js @@ -473,3 +473,10 @@ function Browser(window, document, body, XHR, $log, $sniffer) { return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : href; }; } + +function $BrowserProvider(){ + this.$get = ['$window', '$log', '$sniffer', '$document', + function( $window, $log, $sniffer, $document){ + return new Browser($window, $document, $document.find('body'), XHR, $log, $sniffer); + }]; +} diff --git a/src/Injector.js b/src/Injector.js index 12c2ffa6..838a911d 100644 --- a/src/Injector.js +++ b/src/Injector.js @@ -33,10 +33,6 @@ * `injector.eager()` */ -function angularServiceInject(name, fn, inject, eager) { - angularService(name, fn, {$inject:inject, $eager:eager}); -} - /** * @returns the $inject property of function. If not found the @@ -177,7 +173,11 @@ function createInjector(modulesToLoad, moduleRegistry) { forEach(modulesToLoad, function(module){ if (isString(module)) { - module = moduleRegistry[module]; + if (moduleRegistry[module]) { + module = moduleRegistry[module]; + } else { + throw Error("Module '" + module + "' is not defined!"); + } } if (isFunction(module) || isArray(module)) { $injector(module); diff --git a/src/angular-mocks.js b/src/angular-mocks.js index 679a78a3..b4b2fd19 100644 --- a/src/angular-mocks.js +++ b/src/angular-mocks.js @@ -1,4 +1,3 @@ -'use strict'; /** * @license AngularJS v"NG_VERSION_FULL" @@ -8,30 +7,6 @@ /* - - NUGGGGGH MUST TONGUE WANGS - \ - ..... - C C / - /< / - ___ __________/_#__=o - /(- /(\_\________ \ - \ ) \ )_ \o \ - /|\ /|\ |' | - | _| - /o __\ - / ' | - / / | - /_/\______| - ( _( < - \ \ \ - \ \ | - \____\____\ - ____\_\__\_\ - /` /` o\ - |___ |_______|.. . b'ger - - IN THE FINAL BUILD THIS FILE DOESN'T HAVE DIRECT ACCESS TO GLOBAL FUNCTIONS DEFINED IN Angular.js YOU *MUST* REFER TO THEM VIA angular OBJECT (e.g. angular.forEach(...)) AND MAKE SURE THAT THE GIVEN FUNCTION IS EXPORTED @@ -56,8 +31,15 @@ * the angular service exception handler. * * {@link angular.mock.service.$log $log } - A mock implementation of the angular service log. */ -angular.mock = {}; +window.angular = window.angular || {}; +angular.module = angular.module || {}; +angular.mock = angular.mock || {}; +angular.module.NG_MOCK = ['$provide', function($provide){ + $provide.service('$browser', angular.mock.$BrowserProvider); + $provide.service('$exceptionHandler', angular.mock.$ExceptionHandlerProvider); + $provide.service('$log', angular.mock.$LogProvider); +}]; /** * @ngdoc service @@ -81,7 +63,12 @@ angular.mock = {}; * - $browser.defer — enables testing of code that uses * {@link angular.service.$defer $defer service} for executing functions via the `setTimeout` api. */ -function MockBrowser() { +angular.mock.$BrowserProvider = function(){ + this.$get = function(){ + return new angular.mock.$Browser(); + }; +}; +angular.mock.$Browser = function() { var self = this, expectations = {}, requests = []; @@ -309,7 +296,7 @@ function MockBrowser() { return this.$$baseHref; }; } -MockBrowser.prototype = { +angular.mock.$Browser.prototype = { /** * @name angular.mock.service.$browser#poll @@ -360,10 +347,6 @@ MockBrowser.prototype = { addJs: function() {} }; -angular.service('$browser', function() { - return new MockBrowser(); -}); - /** * @ngdoc service @@ -376,9 +359,29 @@ angular.service('$browser', function() { * * See {@link angular.mock} for more info on angular mocks. */ -angular.service('$exceptionHandler', function() { - return function(e) { throw e; }; -}); +angular.mock.$ExceptionHandlerProvider = function(){ + var handler; + + this.mode = function(mode){ + handler = { + rethrow: function(e) { + throw e; + }, + log: angular.extend(function log(e) { + log.errors.push(e); + }, {errors:[]}) + }[mode]; + if (!handler) { + throw Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!"); + } + }; + + this.$get = function(){ + return handler; + }; + + this.mode('rethrow'); +}; /** @@ -392,23 +395,43 @@ angular.service('$exceptionHandler', function() { * * See {@link angular.mock} for more info on angular mocks. */ -angular.service('$log', MockLogFactory); - -function MockLogFactory() { - var $log = { - log: function() { $log.log.logs.push(arguments); }, - warn: function() { $log.warn.logs.push(arguments); }, - info: function() { $log.info.logs.push(arguments); }, - error: function() { $log.error.logs.push(arguments); } - }; +angular.mock.$LogProvider = function(){ + this.$get = function () { + var $log = { + log: function() { $log.log.logs.push(concat([], arguments, 0)); }, + warn: function() { $log.warn.logs.push(concat([], arguments, 0)); }, + info: function() { $log.info.logs.push(concat([], arguments, 0)); }, + error: function() { $log.error.logs.push(concat([], arguments, 0)); } + }; - $log.log.logs = []; - $log.warn.logs = []; - $log.info.logs = []; - $log.error.logs = []; + $log.reset = function (){ + $log.log.logs = []; + $log.warn.logs = []; + $log.info.logs = []; + $log.error.logs = []; + }; - return $log; -} + $log.assertEmpty = function(){ + var errors = []; + angular.forEach(['error', 'warn', 'info', 'log'], function(logLevel) { + angular.forEach($log[logLevel].logs, function(log) { + angular.forEach(log, function (logItem) { + errors.push('MOCK $log (' + logLevel + '): ' + (logItem.stack || logItem)); + }); + }); + }); + if (errors.length) { + errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or an expected " + + "log message was not checked and removed:"); + errors.push('') + throw new Error(errors.join('\n---------\n')); + } + }; + + $log.reset(); + return $log; + }; +}; /** @@ -441,7 +464,7 @@ function MockLogFactory() { * </pre> * */ -function TzDate(offset, timestamp) { +angular.mock.TzDate = function (offset, timestamp) { if (angular.isString(timestamp)) { var tsStr = timestamp; @@ -545,4 +568,4 @@ function TzDate(offset, timestamp) { } //make "tzDateInstance instanceof Date" return true -TzDate.prototype = Date.prototype; +angular.mock.TzDate.prototype = Date.prototype; diff --git a/src/service/compiler.js b/src/service/compiler.js index 0bd3e54d..220d9c39 100644 --- a/src/service/compiler.js +++ b/src/service/compiler.js @@ -1,325 +1,322 @@ 'use strict'; -// TODO(misko): temporary services to get the compiler working; -angularService('$textMarkup', valueFn(angularTextMarkup)); -angularService('$attrMarkup', valueFn(angularAttrMarkup)); -angularService('$directive', valueFn(angularDirective)); -angularService('$widget', valueFn(angularWidget)); -angularServiceInject('$compile', function($injector, $exceptionHandler, $textMarkup, $attrMarkup, $directive, $widget){ - /** - * Template provides directions an how to bind to a given element. - * It contains a list of init functions which need to be called to - * bind to a new instance of elements. It also provides a list - * of child paths which contain child templates - */ - function Template() { - this.paths = []; - this.children = []; - this.linkFns = []; - this.newScope = false; - } - - Template.prototype = { - link: function(element, scope) { - var childScope = scope, - locals = {$element: element}; - if (this.newScope) { - childScope = isFunction(this.newScope) ? scope.$new(this.newScope(scope)) : scope.$new(); - element.data($$scope, childScope); - } - forEach(this.linkFns, function(fn) { - try { - $injector.invoke(childScope, fn, locals); - } catch (e) { - $exceptionHandler(e); - } - }); - var i, - childNodes = element[0].childNodes, - children = this.children, - paths = this.paths, - length = paths.length; - for (i = 0; i < length; i++) { - // sometimes `element` can be modified by one of the linker functions in `this.linkFns` - // and childNodes may be added or removed - // TODO: element structure needs to be re-evaluated if new children added - // if the childNode still exists - if (childNodes[paths[i]]) - children[i].link(jqLite(childNodes[paths[i]]), childScope); - else - delete paths[i]; // if child no longer available, delete path +function $CompileProvider(){ + this.$get = ['$injector', '$exceptionHandler', '$textMarkup', '$attrMarkup', '$directive', '$widget', + function( $injector, $exceptionHandler, $textMarkup, $attrMarkup, $directive, $widget){ + /** + * Template provides directions an how to bind to a given element. + * It contains a list of init functions which need to be called to + * bind to a new instance of elements. It also provides a list + * of child paths which contain child templates + */ + function Template() { + this.paths = []; + this.children = []; + this.linkFns = []; + this.newScope = false; } - }, - - addLinkFn:function(linkingFn) { - if (linkingFn) { - //TODO(misko): temporary hack. - if (isFunction(linkingFn) && !linkingFn.$inject) { - linkingFn.$inject = ['$element']; - } - this.linkFns.push(linkingFn); - } - }, + Template.prototype = { + link: function(element, scope) { + var childScope = scope, + locals = {$element: element}; + if (this.newScope) { + childScope = isFunction(this.newScope) ? scope.$new(this.newScope(scope)) : scope.$new(); + element.data($$scope, childScope); + } + forEach(this.linkFns, function(fn) { + try { + $injector.invoke(childScope, fn, locals); + } catch (e) { + $exceptionHandler(e); + } + }); + var i, + childNodes = element[0].childNodes, + children = this.children, + paths = this.paths, + length = paths.length; + for (i = 0; i < length; i++) { + // sometimes `element` can be modified by one of the linker functions in `this.linkFns` + // and childNodes may be added or removed + // TODO: element structure needs to be re-evaluated if new children added + // if the childNode still exists + if (childNodes[paths[i]]) + children[i].link(jqLite(childNodes[paths[i]]), childScope); + else + delete paths[i]; // if child no longer available, delete path + } + }, - addChild: function(index, template) { - if (template) { - this.paths.push(index); - this.children.push(template); - } - }, + addLinkFn:function(linkingFn) { + if (linkingFn) { + //TODO(misko): temporary hack. + if (isFunction(linkingFn) && !linkingFn.$inject) { + linkingFn.$inject = ['$element']; + } + this.linkFns.push(linkingFn); + } + }, - empty: function() { - return this.linkFns.length === 0 && this.paths.length === 0; - } - }; - /////////////////////////////////// - //Compiler - ////////////////////////////////// + addChild: function(index, template) { + if (template) { + this.paths.push(index); + this.children.push(template); + } + }, - /** - * @ngdoc function - * @name angular.compile - * @function - * - * @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 angular.scope scope} and the template together. - * - * The compilation is a process of walking the DOM tree and trying to match DOM elements to - * {@link angular.markup markup}, {@link angular.attrMarkup attrMarkup}, - * {@link angular.widget widgets}, and {@link angular.directive directives}. For each match it - * executes corresponding markup, attrMarkup, widget or directive template function and collects the - * instance functions into a single template function which is then returned. - * - * The template function can then be used once to produce the view or as it is the case with - * {@link angular.widget.@ng:repeat repeater} many-times, in which case each call results in a view - * that is a DOM clone of the original template. - * - <pre> - // compile the entire window.document and give me the scope bound to this template. - var rootScope = angular.compile(window.document)(); + empty: function() { + return this.linkFns.length === 0 && this.paths.length === 0; + } + }; - // compile a piece of html - var rootScope2 = angular.compile('<div ng:click="clicked = true">click me</div>')(); + /////////////////////////////////// + //Compiler + ////////////////////////////////// + /** + * @ngdoc function + * @name angular.compile + * @function + * + * @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 angular.scope scope} and the template together. + * + * The compilation is a process of walking the DOM tree and trying to match DOM elements to + * {@link angular.markup markup}, {@link angular.attrMarkup attrMarkup}, + * {@link angular.widget widgets}, and {@link angular.directive directives}. For each match it + * executes corresponding markup, attrMarkup, widget or directive template function and collects the + * instance functions into a single template function which is then returned. + * + * The template function can then be used once to produce the view or as it is the case with + * {@link angular.widget.@ng:repeat repeater} many-times, in which case each call results in a view + * that is a DOM clone of the original template. + * + <pre> + // compile the entire window.document and give me the scope bound to this template. + var rootScope = angular.compile(window.document)(); - // compile a piece of html and retain reference to both the dom and scope - var template = angular.element('<div ng:click="clicked = true">click me</div>'), - scope = angular.compile(template)(); - // at this point template was transformed into a view - </pre> - * - * - * @param {string|DOMElement} element Element or HTML to compile into a template function. - * @returns {function(scope[, cloneAttachFn])} a template function which is used to bind template - * (a DOM element/tree) to a scope. Where: - * - * * `scope` - A {@link angular.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: <br/> `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 template 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. - * - * It is important to understand that the returned scope is "linked" to the view DOM, but no linking - * (instance) functions registered by {@link angular.directive directives} or - * {@link angular.widget widgets} found in the template have been executed yet. This means that the - * view is likely empty and doesn't contain any values that result from evaluation on the scope. To - * bring the view to life, the scope needs to run through a $digest phase which typically is done by - * Angular automatically, except for the case when an application is being - * {@link guide/dev_guide.bootstrap.manual_bootstrap} manually bootstrapped, in which case the - * $digest phase must be invoked by calling {@link angular.scope.$apply}. - * - * If you need access to the bound view, there are two ways to do it: - * - * - If you are not asking the linking function to clone the template, create the DOM element(s) - * before you send them to the compiler and keep this reference around. - * <pre> - * var scope = angular.injector()('$rootScope'); - * var element = angular.compile('<p>{{total}}</p>')(scope); - * </pre> - * - * - if on the other hand, you need the element to be cloned, the view reference from the original - * example would not point to the clone, but rather to the original template that was cloned. In - * this case, you can access the clone via the cloneAttachFn: - * <pre> - * var original = angular.element('<p>{{total}}</p>'), - * scope = someParentScope.$new(), - * clone; - * - * angular.compile(original)(scope, function(clonedElement, scope) { - * clone = clonedElement; - * //attach the clone to DOM document at the right place - * }); - * - * //now we have reference to the cloned DOM via `clone` - * </pre> - * - * - * Compiler Methods For Widgets and Directives: - * - * The following methods are available for use when you write your own widgets, directives, - * and markup. (Recall that the compile function's this is a reference to the compiler.) - * - * `compile(element)` - returns linker - - * Invoke a new instance of the compiler to compile a DOM element and return a linker function. - * You can apply the linker function to the original element or a clone of the original element. - * The linker function returns a scope. - * - * * `comment(commentText)` - returns element - Create a comment element. - * - * * `element(elementName)` - returns element - Create an element by name. - * - * * `text(text)` - returns element - Create a text element. - * - * * `descend([set])` - returns descend state (true or false). Get or set the current descend - * state. If true the compiler will descend to children elements. - * - * * `directives([set])` - returns directive state (true or false). Get or set the current - * directives processing state. The compiler will process directives only when directives set to - * true. - * - * For information on how the compiler works, see the - * {@link guide/dev_guide.compiler Angular HTML Compiler} section of the Developer Guide. - */ - function Compiler(markup, attrMarkup, directives, widgets){ - this.markup = markup; - this.attrMarkup = attrMarkup; - this.directives = directives; - this.widgets = widgets; - } + // compile a piece of html + var rootScope2 = angular.compile('<div ng:click="clicked = true">click me</div>')(); - Compiler.prototype = { - compile: function(templateElement) { - templateElement = jqLite(templateElement); - var index = 0, - template, - parent = templateElement.parent(); - if (templateElement.length > 1) { - // https://github.com/angular/angular.js/issues/338 - throw Error("Cannot compile multiple element roots: " + - jqLite('<div>').append(templateElement.clone()).html()); - } - if (parent && parent[0]) { - parent = parent[0]; - for(var i = 0; i < parent.childNodes.length; i++) { - if (parent.childNodes[i] == templateElement[0]) { - index = i; - } - } + // compile a piece of html and retain reference to both the dom and scope + var template = angular.element('<div ng:click="clicked = true">click me</div>'), + scope = angular.compile(template)(); + // at this point template was transformed into a view + </pre> + * + * + * @param {string|DOMElement} element Element or HTML to compile into a template function. + * @returns {function(scope[, cloneAttachFn])} a template function which is used to bind template + * (a DOM element/tree) to a scope. Where: + * + * * `scope` - A {@link angular.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: <br/> `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 template 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. + * + * It is important to understand that the returned scope is "linked" to the view DOM, but no linking + * (instance) functions registered by {@link angular.directive directives} or + * {@link angular.widget widgets} found in the template have been executed yet. This means that the + * view is likely empty and doesn't contain any values that result from evaluation on the scope. To + * bring the view to life, the scope needs to run through a $digest phase which typically is done by + * Angular automatically, except for the case when an application is being + * {@link guide/dev_guide.bootstrap.manual_bootstrap} manually bootstrapped, in which case the + * $digest phase must be invoked by calling {@link angular.scope.$apply}. + * + * If you need access to the bound view, there are two ways to do it: + * + * - If you are not asking the linking function to clone the template, create the DOM element(s) + * before you send them to the compiler and keep this reference around. + * <pre> + * var scope = angular.injector()('$rootScope'); + * var element = angular.compile('<p>{{total}}</p>')(scope); + * </pre> + * + * - if on the other hand, you need the element to be cloned, the view reference from the original + * example would not point to the clone, but rather to the original template that was cloned. In + * this case, you can access the clone via the cloneAttachFn: + * <pre> + * var original = angular.element('<p>{{total}}</p>'), + * scope = someParentScope.$new(), + * clone; + * + * angular.compile(original)(scope, function(clonedElement, scope) { + * clone = clonedElement; + * //attach the clone to DOM document at the right place + * }); + * + * //now we have reference to the cloned DOM via `clone` + * </pre> + * + * + * Compiler Methods For Widgets and Directives: + * + * The following methods are available for use when you write your own widgets, directives, + * and markup. (Recall that the compile function's this is a reference to the compiler.) + * + * `compile(element)` - returns linker - + * Invoke a new instance of the compiler to compile a DOM element and return a linker function. + * You can apply the linker function to the original element or a clone of the original element. + * The linker function returns a scope. + * + * * `comment(commentText)` - returns element - Create a comment element. + * + * * `element(elementName)` - returns element - Create an element by name. + * + * * `text(text)` - returns element - Create a text element. + * + * * `descend([set])` - returns descend state (true or false). Get or set the current descend + * state. If true the compiler will descend to children elements. + * + * * `directives([set])` - returns directive state (true or false). Get or set the current + * directives processing state. The compiler will process directives only when directives set to + * true. + * + * For information on how the compiler works, see the + * {@link guide/dev_guide.compiler Angular HTML Compiler} section of the Developer Guide. + */ + function Compiler(markup, attrMarkup, directives, widgets){ + this.markup = markup; + this.attrMarkup = attrMarkup; + this.directives = directives; + this.widgets = widgets; } - template = this.templatize(templateElement, index) || new Template(); - return function(scope, cloneConnectFn){ - 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 element = cloneConnectFn - ? JQLitePrototype.clone.call(templateElement) // IMPORTANT!!! - : templateElement; - element.data($$scope, scope); - scope.$element = element; - (cloneConnectFn||noop)(element, scope); - template.link(element, scope); - return element; - }; - }, - templatize: function(element, elementIndex){ - var self = this, - widget, - fn, - directiveFns = self.directives, - descend = true, - directives = true, - elementName = nodeName_(element), - elementNamespace = elementName.indexOf(':') > 0 ? lowercase(elementName).replace(':', '-') : '', - template, - selfApi = { - compile: bind(self, self.compile), - descend: function(value){ if(isDefined(value)) descend = value; return descend;}, - directives: function(value){ if(isDefined(value)) directives = value; return directives;}, - scope: function(value){ if(isDefined(value)) template.newScope = template.newScope || value; return template.newScope;} - }; - element.addClass(elementNamespace); - template = new Template(); - eachAttribute(element, function(value, name){ - if (!widget) { - if ((widget = self.widgets('@' + name))) { - element.addClass('ng-attr-widget'); - widget = bind(selfApi, widget, value, element); + Compiler.prototype = { + compile: function(templateElement) { + templateElement = jqLite(templateElement); + var index = 0, + template, + parent = templateElement.parent(); + if (templateElement.length > 1) { + // https://github.com/angular/angular.js/issues/338 + throw Error("Cannot compile multiple element roots: " + + jqLite('<div>').append(templateElement.clone()).html()); } - } - }); - if (!widget) { - if ((widget = self.widgets(elementName))) { - if (elementNamespace) - element.addClass('ng-widget'); - widget = bind(selfApi, widget, element); - } - } - if (widget) { - descend = false; - directives = false; - var parent = element.parent(); - template.addLinkFn(widget.call(selfApi, element)); - if (parent && parent[0]) { - element = jqLite(parent[0].childNodes[elementIndex]); - } - } - if (descend){ - // process markup for text nodes only - for(var i=0, child=element[0].childNodes; - i<child.length; i++) { - if (isTextNode(child[i])) { - forEach(self.markup, function(markup){ - if (i<child.length) { - var textNode = jqLite(child[i]); - markup.call(selfApi, textNode.text(), textNode, element); + if (parent && parent[0]) { + parent = parent[0]; + for(var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i] == templateElement[0]) { + index = i; } - }); + } } - } - } + template = this.templatize(templateElement, index) || new Template(); + return function(scope, cloneConnectFn){ + 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 element = cloneConnectFn + ? JQLitePrototype.clone.call(templateElement) // IMPORTANT!!! + : templateElement; + element.data($$scope, scope); + scope.$element = element; + (cloneConnectFn||noop)(element, scope); + template.link(element, scope); + return element; + }; + }, - if (directives) { - // Process attributes/directives - eachAttribute(element, function(value, name){ - forEach(self.attrMarkup, function(markup){ - markup.call(selfApi, value, name, element); + templatize: function(element, elementIndex){ + var self = this, + widget, + fn, + directiveFns = self.directives, + descend = true, + directives = true, + elementName = nodeName_(element), + elementNamespace = elementName.indexOf(':') > 0 ? lowercase(elementName).replace(':', '-') : '', + template, + selfApi = { + compile: bind(self, self.compile), + descend: function(value){ if(isDefined(value)) descend = value; return descend;}, + directives: function(value){ if(isDefined(value)) directives = value; return directives;}, + scope: function(value){ if(isDefined(value)) template.newScope = template.newScope || value; return template.newScope;} + }; + element.addClass(elementNamespace); + template = new Template(); + eachAttribute(element, function(value, name){ + if (!widget) { + if ((widget = self.widgets('@' + name))) { + element.addClass('ng-attr-widget'); + widget = bind(selfApi, widget, value, element); + } + } }); - }); - eachAttribute(element, function(value, name){ - name = lowercase(name); - fn = directiveFns[name]; - if (fn) { - element.addClass('ng-directive'); - template.addLinkFn((directiveFns[name]).call(selfApi, value, element)); + if (!widget) { + if ((widget = self.widgets(elementName))) { + if (elementNamespace) + element.addClass('ng-widget'); + widget = bind(selfApi, widget, element); + } } - }); - } - // Process non text child nodes - if (descend) { - eachNode(element, function(child, i){ - template.addChild(i, self.templatize(child, i)); - }); - } - return template.empty() ? null : template; - } - }; + if (widget) { + descend = false; + directives = false; + var parent = element.parent(); + template.addLinkFn(widget.call(selfApi, element)); + if (parent && parent[0]) { + element = jqLite(parent[0].childNodes[elementIndex]); + } + } + if (descend){ + // process markup for text nodes only + for(var i=0, child=element[0].childNodes; + i<child.length; i++) { + if (isTextNode(child[i])) { + forEach(self.markup, function(markup){ + if (i<child.length) { + var textNode = jqLite(child[i]); + markup.call(selfApi, textNode.text(), textNode, element); + } + }); + } + } + } + + if (directives) { + // Process attributes/directives + eachAttribute(element, function(value, name){ + forEach(self.attrMarkup, function(markup){ + markup.call(selfApi, value, name, element); + }); + }); + eachAttribute(element, function(value, name){ + name = lowercase(name); + fn = directiveFns[name]; + if (fn) { + element.addClass('ng-directive'); + template.addLinkFn((directiveFns[name]).call(selfApi, value, element)); + } + }); + } + // Process non text child nodes + if (descend) { + eachNode(element, function(child, i){ + template.addChild(i, self.templatize(child, i)); + }); + } + return template.empty() ? null : template; + } + }; - ///////////////////////////////////////////////////////////////////// - var compiler = new Compiler($textMarkup, $attrMarkup, $directive, $widget); - return bind(compiler, compiler.compile); -}, ['$injector', '$exceptionHandler', '$textMarkup', '$attrMarkup', '$directive', '$widget']); + ///////////////////////////////////////////////////////////////////// + var compiler = new Compiler($textMarkup, $attrMarkup, $directive, $widget); + return bind(compiler, compiler.compile); + }]; +}; function eachNode(element, fn){ diff --git a/src/service/cookieStore.js b/src/service/cookieStore.js index 1b38cfac..0aa13ed2 100644 --- a/src/service/cookieStore.js +++ b/src/service/cookieStore.js @@ -11,52 +11,54 @@ * deserialized by angular's toJson/fromJson. * @example */ -angularServiceInject('$cookieStore', function($store) { +function $CookieStoreProvider(){ + this.$get = ['$cookies', function($cookies) { - return { - /** - * @ngdoc method - * @name angular.service.$cookieStore#get - * @methodOf angular.service.$cookieStore - * - * @description - * Returns the value of given cookie key - * - * @param {string} key Id to use for lookup. - * @returns {Object} Deserialized cookie value. - */ - get: function(key) { - return fromJson($store[key]); - }, + return { + /** + * @ngdoc method + * @name angular.service.$cookieStore#get + * @methodOf angular.service.$cookieStore + * + * @description + * Returns the value of given cookie key + * + * @param {string} key Id to use for lookup. + * @returns {Object} Deserialized cookie value. + */ + get: function(key) { + return fromJson($cookies[key]); + }, - /** - * @ngdoc method - * @name angular.service.$cookieStore#put - * @methodOf angular.service.$cookieStore - * - * @description - * Sets a value for given cookie key - * - * @param {string} key Id for the `value`. - * @param {Object} value Value to be stored. - */ - put: function(key, value) { - $store[key] = toJson(value); - }, + /** + * @ngdoc method + * @name angular.service.$cookieStore#put + * @methodOf angular.service.$cookieStore + * + * @description + * Sets a value for given cookie key + * + * @param {string} key Id for the `value`. + * @param {Object} value Value to be stored. + */ + put: function(key, value) { + $cookies[key] = toJson(value); + }, - /** - * @ngdoc method - * @name angular.service.$cookieStore#remove - * @methodOf angular.service.$cookieStore - * - * @description - * Remove given cookie - * - * @param {string} key Id of the key-value pair to delete. - */ - remove: function(key) { - delete $store[key]; - } - }; + /** + * @ngdoc method + * @name angular.service.$cookieStore#remove + * @methodOf angular.service.$cookieStore + * + * @description + * Remove given cookie + * + * @param {string} key Id of the key-value pair to delete. + */ + remove: function(key) { + delete $cookies[key]; + } + }; -}, ['$cookies']); + }]; +} diff --git a/src/service/cookies.js b/src/service/cookies.js index 2cd2b9d6..16e14847 100644 --- a/src/service/cookies.js +++ b/src/service/cookies.js @@ -13,80 +13,82 @@ * * @example */ -angularServiceInject('$cookies', function($rootScope, $browser) { - var cookies = {}, - lastCookies = {}, - lastBrowserCookies, - runEval = false; +function $CookiesProvider() { + this.$get = ['$rootScope', '$browser', function ($rootScope, $browser) { + var cookies = {}, + lastCookies = {}, + lastBrowserCookies, + runEval = false; - //creates a poller fn that copies all cookies from the $browser to service & inits the service - $browser.addPollFn(function() { - var currentCookies = $browser.cookies(); - if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl - lastBrowserCookies = currentCookies; - copy(currentCookies, lastCookies); - copy(currentCookies, cookies); - if (runEval) $rootScope.$apply(); - } - })(); + //creates a poller fn that copies all cookies from the $browser to service & inits the service + $browser.addPollFn(function() { + var currentCookies = $browser.cookies(); + if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl + lastBrowserCookies = currentCookies; + copy(currentCookies, lastCookies); + copy(currentCookies, cookies); + if (runEval) $rootScope.$apply(); + } + })(); - runEval = true; + runEval = true; - //at the end of each eval, push cookies - //TODO: this should happen before the "delayed" watches fire, because if some cookies are not - // strings or browser refuses to store some cookies, we update the model in the push fn. - $rootScope.$watch(push); + //at the end of each eval, push cookies + //TODO: this should happen before the "delayed" watches fire, because if some cookies are not + // strings or browser refuses to store some cookies, we update the model in the push fn. + $rootScope.$watch(push); - return cookies; + return cookies; - /** - * Pushes all the cookies from the service to the browser and verifies if all cookies were stored. - */ - function push() { - var name, - value, - browserCookies, - updated; + /** + * Pushes all the cookies from the service to the browser and verifies if all cookies were stored. + */ + function push() { + var name, + value, + browserCookies, + updated; - //delete any cookies deleted in $cookies - for (name in lastCookies) { - if (isUndefined(cookies[name])) { - $browser.cookies(name, undefined); + //delete any cookies deleted in $cookies + for (name in lastCookies) { + if (isUndefined(cookies[name])) { + $browser.cookies(name, undefined); + } } - } - //update all cookies updated in $cookies - for(name in cookies) { - value = cookies[name]; - if (!isString(value)) { - if (isDefined(lastCookies[name])) { - cookies[name] = lastCookies[name]; - } else { - delete cookies[name]; + //update all cookies updated in $cookies + for(name in cookies) { + value = cookies[name]; + if (!isString(value)) { + if (isDefined(lastCookies[name])) { + cookies[name] = lastCookies[name]; + } else { + delete cookies[name]; + } + } else if (value !== lastCookies[name]) { + $browser.cookies(name, value); + updated = true; } - } else if (value !== lastCookies[name]) { - $browser.cookies(name, value); - updated = true; } - } - //verify what was actually stored - if (updated){ - updated = false; - browserCookies = $browser.cookies(); + //verify what was actually stored + if (updated){ + updated = false; + browserCookies = $browser.cookies(); - for (name in cookies) { - if (cookies[name] !== browserCookies[name]) { - //delete or reset all cookies that the browser dropped from $cookies - if (isUndefined(browserCookies[name])) { - delete cookies[name]; - } else { - cookies[name] = browserCookies[name]; + for (name in cookies) { + if (cookies[name] !== browserCookies[name]) { + //delete or reset all cookies that the browser dropped from $cookies + if (isUndefined(browserCookies[name])) { + delete cookies[name]; + } else { + cookies[name] = browserCookies[name]; + } + updated = true; } - updated = true; } } } - } -}, ['$rootScope', '$browser']); + }]; +} diff --git a/src/service/defer.js b/src/service/defer.js index 07c98065..90e6f19f 100644 --- a/src/service/defer.js +++ b/src/service/defer.js @@ -28,16 +28,18 @@ * @param {*} deferId Token returned by the `$defer` function. * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfuly canceled. */ -angularServiceInject('$defer', function($rootScope, $browser) { - function defer(fn, delay) { - return $browser.defer(function() { - $rootScope.$apply(fn); - }, delay); - } +function $DeferProvider(){ + this.$get = ['$rootScope', '$browser', function($rootScope, $browser) { + function defer(fn, delay) { + return $browser.defer(function() { + $rootScope.$apply(fn); + }, delay); + } - defer.cancel = function(deferId) { - return $browser.defer.cancel(deferId); - }; + defer.cancel = function(deferId) { + return $browser.defer.cancel(deferId); + }; - return defer; -}, ['$rootScope', '$browser']); + return defer; + }]; +} diff --git a/src/service/document.js b/src/service/document.js index ae13c1ed..f71ac33a 100644 --- a/src/service/document.js +++ b/src/service/document.js @@ -9,6 +9,8 @@ * A {@link angular.element jQuery (lite)}-wrapped reference to the browser's `window.document` * element. */ -angularServiceInject("$document", function(window){ - return jqLite(window.document); -}, ['$window']); +function $DocumentProvider(){ + this.$get = ['$window', function(window){ + return jqLite(window.document); + }]; +} diff --git a/src/service/exceptionHandler.js b/src/service/exceptionHandler.js index 4bf6b6a6..b0c8e822 100644 --- a/src/service/exceptionHandler.js +++ b/src/service/exceptionHandler.js @@ -15,9 +15,10 @@ * * @example */ -var $exceptionHandlerFactory; //reference to be used only in tests -angularServiceInject('$exceptionHandler', $exceptionHandlerFactory = function($log){ - return function(e) { - $log.error(e); - }; -}, ['$log']); +function $ExceptionHandlerProvider(){ + this.$get = ['$log', function($log){ + return function(e) { + $log.error(e); + }; + }]; +} diff --git a/src/service/formFactory.js b/src/service/formFactory.js index fa6ad201..c9cb9c53 100644 --- a/src/service/formFactory.js +++ b/src/service/formFactory.js @@ -96,297 +96,299 @@ </doc:scenario> </doc:example> */ -angularServiceInject('$formFactory', function($rootScope) { +function $FormFactoryProvider() { + this.$get = ['$rootScope', function($rootScope) { + + /** + * @ngdoc proprety + * @name rootForm + * @propertyOf angular.service.$formFactory + * @description + * Static property on `$formFactory` + * + * Each application ({@link guide/dev_guide.scopes.internals root scope}) gets a root form which + * is the top-level parent of all forms. + */ + formFactory.rootForm = formFactory($rootScope); + + + /** + * @ngdoc method + * @name forElement + * @methodOf angular.service.$formFactory + * @description + * Static method on `$formFactory` service. + * + * Retrieve the closest form for a given element or defaults to the `root` form. Used by the + * {@link angular.widget.form form} element. + * @param {Element} element The element where the search for form should initiate. + */ + formFactory.forElement = function(element) { + return element.inheritedData('$form') || formFactory.rootForm; + }; + return formFactory; + + function formFactory(parent) { + return (parent || formFactory.rootForm).$new(FormController); + } + + }]; + + function propertiesUpdate(widget) { + widget.$valid = !(widget.$invalid = + !(widget.$readonly || widget.$disabled || equals(widget.$error, {}))); + } /** - * @ngdoc proprety - * @name rootForm + * @ngdoc property + * @name $error * @propertyOf angular.service.$formFactory * @description - * Static property on `$formFactory` + * Property of the form and widget instance. * - * Each application ({@link guide/dev_guide.scopes.internals root scope}) gets a root form which - * is the top-level parent of all forms. + * Summary of all of the errors on the page. If a widget emits `$invalid` with `REQUIRED` key, + * then the `$error` object will have a `REQUIRED` key with an array of widgets which have + * emitted this key. `form.$error.REQUIRED == [ widget ]`. */ - formFactory.rootForm = formFactory($rootScope); - /** - * @ngdoc method - * @name forElement - * @methodOf angular.service.$formFactory + * @ngdoc property + * @name $invalid + * @propertyOf angular.service.$formFactory * @description - * Static method on `$formFactory` service. + * Property of the form and widget instance. * - * Retrieve the closest form for a given element or defaults to the `root` form. Used by the - * {@link angular.widget.form form} element. - * @param {Element} element The element where the search for form should initiate. + * True if any of the widgets of the form are invalid. */ - formFactory.forElement = function(element) { - return element.inheritedData('$form') || formFactory.rootForm; - }; - return formFactory; - - function formFactory(parent) { - return (parent || formFactory.rootForm).$new(FormController); - } - -}, ['$rootScope']); -function propertiesUpdate(widget) { - widget.$valid = !(widget.$invalid = - !(widget.$readonly || widget.$disabled || equals(widget.$error, {}))); -} - -/** - * @ngdoc property - * @name $error - * @propertyOf angular.service.$formFactory - * @description - * Property of the form and widget instance. - * - * Summary of all of the errors on the page. If a widget emits `$invalid` with `REQUIRED` key, - * then the `$error` object will have a `REQUIRED` key with an array of widgets which have - * emitted this key. `form.$error.REQUIRED == [ widget ]`. - */ - -/** - * @ngdoc property - * @name $invalid - * @propertyOf angular.service.$formFactory - * @description - * Property of the form and widget instance. - * - * True if any of the widgets of the form are invalid. - */ - -/** - * @ngdoc property - * @name $valid - * @propertyOf angular.service.$formFactory - * @description - * Property of the form and widget instance. - * - * True if all of the widgets of the form are valid. - */ + /** + * @ngdoc property + * @name $valid + * @propertyOf angular.service.$formFactory + * @description + * Property of the form and widget instance. + * + * True if all of the widgets of the form are valid. + */ -/** - * @ngdoc event - * @name angular.service.$formFactory#$valid - * @eventOf angular.service.$formFactory - * @eventType listen on form - * @description - * Upon receiving the `$valid` event from the widget update the `$error`, `$valid` and `$invalid` - * properties of both the widget as well as the from. - * - * @param {String} validationKey The validation key to be used when updating the `$error` object. - * The validation key is what will allow the template to bind to a specific validation error - * such as `<div ng:show="form.$error.KEY">error for key</div>`. - */ + /** + * @ngdoc event + * @name angular.service.$formFactory#$valid + * @eventOf angular.service.$formFactory + * @eventType listen on form + * @description + * Upon receiving the `$valid` event from the widget update the `$error`, `$valid` and `$invalid` + * properties of both the widget as well as the from. + * + * @param {String} validationKey The validation key to be used when updating the `$error` object. + * The validation key is what will allow the template to bind to a specific validation error + * such as `<div ng:show="form.$error.KEY">error for key</div>`. + */ -/** - * @ngdoc event - * @name angular.service.$formFactory#$invalid - * @eventOf angular.service.$formFactory - * @eventType listen on form - * @description - * Upon receiving the `$invalid` event from the widget update the `$error`, `$valid` and `$invalid` - * properties of both the widget as well as the from. - * - * @param {String} validationKey The validation key to be used when updating the `$error` object. - * The validation key is what will allow the template to bind to a specific validation error - * such as `<div ng:show="form.$error.KEY">error for key</div>`. - */ + /** + * @ngdoc event + * @name angular.service.$formFactory#$invalid + * @eventOf angular.service.$formFactory + * @eventType listen on form + * @description + * Upon receiving the `$invalid` event from the widget update the `$error`, `$valid` and `$invalid` + * properties of both the widget as well as the from. + * + * @param {String} validationKey The validation key to be used when updating the `$error` object. + * The validation key is what will allow the template to bind to a specific validation error + * such as `<div ng:show="form.$error.KEY">error for key</div>`. + */ -/** - * @ngdoc event - * @name angular.service.$formFactory#$validate - * @eventOf angular.service.$formFactory - * @eventType emit on widget - * @description - * Emit the `$validate` event on the widget, giving a widget a chance to emit a - * `$valid` / `$invalid` event base on its state. The `$validate` event is triggered when the - * model or the view changes. - */ + /** + * @ngdoc event + * @name angular.service.$formFactory#$validate + * @eventOf angular.service.$formFactory + * @eventType emit on widget + * @description + * Emit the `$validate` event on the widget, giving a widget a chance to emit a + * `$valid` / `$invalid` event base on its state. The `$validate` event is triggered when the + * model or the view changes. + */ -/** - * @ngdoc event - * @name angular.service.$formFactory#$viewChange - * @eventOf angular.service.$formFactory - * @eventType listen on widget - * @description - * A widget is responsible for emitting this event whenever the view changes do to user interaction. - * The event takes a `$viewValue` parameter, which is the new value of the view. This - * event triggers a call to `$parseView()` as well as `$validate` event on widget. - * - * @param {*} viewValue The new value for the view which will be assigned to `widget.$viewValue`. - */ + /** + * @ngdoc event + * @name angular.service.$formFactory#$viewChange + * @eventOf angular.service.$formFactory + * @eventType listen on widget + * @description + * A widget is responsible for emitting this event whenever the view changes do to user interaction. + * The event takes a `$viewValue` parameter, which is the new value of the view. This + * event triggers a call to `$parseView()` as well as `$validate` event on widget. + * + * @param {*} viewValue The new value for the view which will be assigned to `widget.$viewValue`. + */ -function FormController() { - var form = this, - $error = form.$error = {}; + function FormController() { + var form = this, + $error = form.$error = {}; - form.$on('$destroy', function(event){ - var widget = event.targetScope; - if (widget.$widgetId) { - delete form[widget.$widgetId]; - } - forEach($error, removeWidget, widget); - }); + form.$on('$destroy', function(event){ + var widget = event.targetScope; + if (widget.$widgetId) { + delete form[widget.$widgetId]; + } + forEach($error, removeWidget, widget); + }); + + form.$on('$valid', function(event, error){ + var widget = event.targetScope; + delete widget.$error[error]; + propertiesUpdate(widget); + removeWidget($error[error], error, widget); + }); + + form.$on('$invalid', function(event, error){ + var widget = event.targetScope; + addWidget(error, widget); + widget.$error[error] = true; + propertiesUpdate(widget); + }); - form.$on('$valid', function(event, error){ - var widget = event.targetScope; - delete widget.$error[error]; - propertiesUpdate(widget); - removeWidget($error[error], error, widget); - }); + propertiesUpdate(form); - form.$on('$invalid', function(event, error){ - var widget = event.targetScope; - addWidget(error, widget); - widget.$error[error] = true; - propertiesUpdate(widget); - }); - - propertiesUpdate(form); - - function removeWidget(queue, errorKey, widget) { - if (queue) { - widget = widget || this; // so that we can be used in forEach; - for (var i = 0, length = queue.length; i < length; i++) { - if (queue[i] === widget) { - queue.splice(i, 1); - if (!queue.length) { - delete $error[errorKey]; + function removeWidget(queue, errorKey, widget) { + if (queue) { + widget = widget || this; // so that we can be used in forEach; + for (var i = 0, length = queue.length; i < length; i++) { + if (queue[i] === widget) { + queue.splice(i, 1); + if (!queue.length) { + delete $error[errorKey]; + } } } + propertiesUpdate(form); } - propertiesUpdate(form); } - } - function addWidget(errorKey, widget) { - var queue = $error[errorKey]; - if (queue) { - for (var i = 0, length = queue.length; i < length; i++) { - if (queue[i] === widget) { - return; + function addWidget(errorKey, widget) { + var queue = $error[errorKey]; + if (queue) { + for (var i = 0, length = queue.length; i < length; i++) { + if (queue[i] === widget) { + return; + } } + } else { + $error[errorKey] = queue = []; } - } else { - $error[errorKey] = queue = []; + queue.push(widget); + propertiesUpdate(form); } - queue.push(widget); - propertiesUpdate(form); } -} -/** - * @ngdoc method - * @name $createWidget - * @methodOf angular.service.$formFactory - * @description - * - * Use form's `$createWidget` instance method to create new widgets. The widgets can be created - * using an alias which makes the accessible from the form and available for data-binding, - * useful for displaying validation error messages. - * - * The creation of a widget sets up: - * - * - `$watch` of `expression` on `model` scope. This code path syncs the model to the view. - * The `$watch` listener will: - * - * - assign the new model value of `expression` to `widget.$modelValue`. - * - call `widget.$parseModel` method if present. The `$parseModel` is responsible for copying - * the `widget.$modelValue` to `widget.$viewValue` and optionally converting the data. - * (For example to convert a number into string) - * - emits `$validate` event on widget giving a widget a chance to emit `$valid` / `$invalid` - * event. - * - call `widget.$render()` method on widget. The `$render` method is responsible for - * reading the `widget.$viewValue` and updating the DOM. - * - * - Listen on `$viewChange` event from the `widget`. This code path syncs the view to the model. - * The `$viewChange` listener will: - * - * - assign the value to `widget.$viewValue`. - * - call `widget.$parseView` method if present. The `$parseView` is responsible for copying - * the `widget.$viewValue` to `widget.$modelValue` and optionally converting the data. - * (For example to convert a string into number) - * - emits `$validate` event on widget giving a widget a chance to emit `$valid` / `$invalid` - * event. - * - Assign the `widget.$modelValue` to the `expression` on the `model` scope. - * - * - Creates these set of properties on the `widget` which are updated as a response to the - * `$valid` / `$invalid` events: - * - * - `$error` - object - validation errors will be published as keys on this object. - * Data-binding to this property is useful for displaying the validation errors. - * - `$valid` - boolean - true if there are no validation errors - * - `$invalid` - boolean - opposite of `$valid`. - * @param {Object} params Named parameters: - * - * - `scope` - `{Scope}` - The scope to which the model for this widget is attached. - * - `model` - `{string}` - The name of the model property on model scope. - * - `controller` - {WidgetController} - The controller constructor function. - * The controller constructor should create these instance methods. - * - `$parseView()`: optional method responsible for copying `$viewVale` to `$modelValue`. - * The method may fire `$valid`/`$invalid` events. - * - `$parseModel()`: optional method responsible for copying `$modelVale` to `$viewValue`. - * The method may fire `$valid`/`$invalid` events. - * - `$render()`: required method which needs to update the DOM of the widget to match the - * `$viewValue`. - * - * - `controllerArgs` - `{Array}` (Optional) - Any extra arguments will be curried to the - * WidgetController constructor. - * - `onChange` - `{(string|function())}` (Optional) - Expression to execute when user changes the - * value. - * - `alias` - `{string}` (Optional) - The name of the form property under which the widget - * instance should be published. The name should be unique for each form. - * @returns {Widget} Instance of a widget scope. - */ -FormController.prototype.$createWidget = function(params) { - var form = this, - modelScope = params.scope, - onChange = params.onChange, - alias = params.alias, - scopeGet = parser(params.model).assignable(), - scopeSet = scopeGet.assign, - widget = this.$new(params.controller, params.controllerArgs); - - widget.$error = {}; - // Set the state to something we know will change to get the process going. - widget.$modelValue = Number.NaN; - // watch for scope changes and update the view appropriately - modelScope.$watch(scopeGet, function(scope, value) { - if (!equals(widget.$modelValue, value)) { - widget.$modelValue = value; - widget.$parseModel ? widget.$parseModel() : (widget.$viewValue = value); - widget.$emit('$validate'); - widget.$render && widget.$render(); - } - }); - - widget.$on('$viewChange', function(event, viewValue){ - if (!equals(widget.$viewValue, viewValue)) { - widget.$viewValue = viewValue; - widget.$parseView ? widget.$parseView() : (widget.$modelValue = widget.$viewValue); - scopeSet(modelScope, widget.$modelValue); - if (onChange) modelScope.$eval(onChange); - widget.$emit('$validate'); - } - }); + /** + * @ngdoc method + * @name $createWidget + * @methodOf angular.service.$formFactory + * @description + * + * Use form's `$createWidget` instance method to create new widgets. The widgets can be created + * using an alias which makes the accessible from the form and available for data-binding, + * useful for displaying validation error messages. + * + * The creation of a widget sets up: + * + * - `$watch` of `expression` on `model` scope. This code path syncs the model to the view. + * The `$watch` listener will: + * + * - assign the new model value of `expression` to `widget.$modelValue`. + * - call `widget.$parseModel` method if present. The `$parseModel` is responsible for copying + * the `widget.$modelValue` to `widget.$viewValue` and optionally converting the data. + * (For example to convert a number into string) + * - emits `$validate` event on widget giving a widget a chance to emit `$valid` / `$invalid` + * event. + * - call `widget.$render()` method on widget. The `$render` method is responsible for + * reading the `widget.$viewValue` and updating the DOM. + * + * - Listen on `$viewChange` event from the `widget`. This code path syncs the view to the model. + * The `$viewChange` listener will: + * + * - assign the value to `widget.$viewValue`. + * - call `widget.$parseView` method if present. The `$parseView` is responsible for copying + * the `widget.$viewValue` to `widget.$modelValue` and optionally converting the data. + * (For example to convert a string into number) + * - emits `$validate` event on widget giving a widget a chance to emit `$valid` / `$invalid` + * event. + * - Assign the `widget.$modelValue` to the `expression` on the `model` scope. + * + * - Creates these set of properties on the `widget` which are updated as a response to the + * `$valid` / `$invalid` events: + * + * - `$error` - object - validation errors will be published as keys on this object. + * Data-binding to this property is useful for displaying the validation errors. + * - `$valid` - boolean - true if there are no validation errors + * - `$invalid` - boolean - opposite of `$valid`. + * @param {Object} params Named parameters: + * + * - `scope` - `{Scope}` - The scope to which the model for this widget is attached. + * - `model` - `{string}` - The name of the model property on model scope. + * - `controller` - {WidgetController} - The controller constructor function. + * The controller constructor should create these instance methods. + * - `$parseView()`: optional method responsible for copying `$viewVale` to `$modelValue`. + * The method may fire `$valid`/`$invalid` events. + * - `$parseModel()`: optional method responsible for copying `$modelVale` to `$viewValue`. + * The method may fire `$valid`/`$invalid` events. + * - `$render()`: required method which needs to update the DOM of the widget to match the + * `$viewValue`. + * + * - `controllerArgs` - `{Array}` (Optional) - Any extra arguments will be curried to the + * WidgetController constructor. + * - `onChange` - `{(string|function())}` (Optional) - Expression to execute when user changes the + * value. + * - `alias` - `{string}` (Optional) - The name of the form property under which the widget + * instance should be published. The name should be unique for each form. + * @returns {Widget} Instance of a widget scope. + */ + FormController.prototype.$createWidget = function(params) { + var form = this, + modelScope = params.scope, + onChange = params.onChange, + alias = params.alias, + scopeGet = parser(params.model).assignable(), + scopeSet = scopeGet.assign, + widget = this.$new(params.controller, params.controllerArgs); + + widget.$error = {}; + // Set the state to something we know will change to get the process going. + widget.$modelValue = Number.NaN; + // watch for scope changes and update the view appropriately + modelScope.$watch(scopeGet, function(scope, value) { + if (!equals(widget.$modelValue, value)) { + widget.$modelValue = value; + widget.$parseModel ? widget.$parseModel() : (widget.$viewValue = value); + widget.$emit('$validate'); + widget.$render && widget.$render(); + } + }); + + widget.$on('$viewChange', function(event, viewValue){ + if (!equals(widget.$viewValue, viewValue)) { + widget.$viewValue = viewValue; + widget.$parseView ? widget.$parseView() : (widget.$modelValue = widget.$viewValue); + scopeSet(modelScope, widget.$modelValue); + if (onChange) modelScope.$eval(onChange); + widget.$emit('$validate'); + } + }); - propertiesUpdate(widget); + propertiesUpdate(widget); - // assign the widgetModel to the form - if (alias && !form.hasOwnProperty(alias)) { - form[alias] = widget; - widget.$widgetId = alias; - } else { - alias = null; - } + // assign the widgetModel to the form + if (alias && !form.hasOwnProperty(alias)) { + form[alias] = widget; + widget.$widgetId = alias; + } else { + alias = null; + } - return widget; -}; + return widget; + }; +} diff --git a/src/service/locale.js b/src/service/locale.js index 069a691e..aae325ae 100644 --- a/src/service/locale.js +++ b/src/service/locale.js @@ -10,61 +10,63 @@ * * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`) */ -angularServiceInject('$locale', function() { - return { - id: '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: '$' - }, + 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' - }, + 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'; + pluralCat: function(num) { + if (num === 1) { + return 'one'; + } + return 'other'; } - return 'other'; - } + }; }; -}); +} diff --git a/src/service/location.js b/src/service/location.js index c9b76122..7cc5c273 100644 --- a/src/service/location.js +++ b/src/service/location.js @@ -419,94 +419,99 @@ function locationGetterSetter(property, preprocess) { * * For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular Services: Using $location} */ -angularServiceInject('$location', function($rootScope, $browser, $sniffer, $locationConfig, $document) { - var currentUrl, - basePath = $browser.baseHref() || '/', - pathPrefix = pathPrefixFromBase(basePath), - hashPrefix = $locationConfig.hashPrefix || '', - initUrl = $browser.url(); - - if ($locationConfig.html5Mode) { - if ($sniffer.history) { - currentUrl = new LocationUrl(convertToHtml5Url(initUrl, basePath, hashPrefix), pathPrefix); - } else { - currentUrl = new LocationHashbangUrl(convertToHashbangUrl(initUrl, basePath, hashPrefix), - hashPrefix); - } - - // link rewriting - var u = currentUrl, - absUrlPrefix = composeProtocolHostPort(u.protocol(), u.host(), u.port()) + pathPrefix; +function $LocationProvider(){ + this.$get = ['$rootScope', '$browser', '$sniffer', '$locationConfig', '$document', + function( $rootScope, $browser, $sniffer, $locationConfig, $document) { + var currentUrl, + basePath = $browser.baseHref() || '/', + pathPrefix = pathPrefixFromBase(basePath), + hashPrefix = $locationConfig.hashPrefix || '', + initUrl = $browser.url(); - $document.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 ($locationConfig.html5Mode) { + if ($sniffer.history) { + currentUrl = new LocationUrl(convertToHtml5Url(initUrl, basePath, hashPrefix), pathPrefix); + } else { + currentUrl = new LocationHashbangUrl(convertToHashbangUrl(initUrl, basePath, hashPrefix), + hashPrefix); + } - if (event.ctrlKey || event.metaKey || event.which == 2) return; + // link rewriting + var u = currentUrl, + absUrlPrefix = composeProtocolHostPort(u.protocol(), u.host(), u.port()) + pathPrefix; - var elm = jqLite(event.target); + $document.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 - // traverse the DOM up to find first A tag - while (elm.length && lowercase(elm[0].nodeName) !== 'a') { - elm = elm.parent(); - } + if (event.ctrlKey || event.metaKey || event.which == 2) return; - var href = elm.attr('href'); - if (!href || isDefined(elm.attr('ng:ext-link')) || elm.attr('target')) return; + var elm = jqLite(event.target); - // remove same domain from full url links (IE7 always returns full hrefs) - href = href.replace(absUrlPrefix, ''); + // traverse the DOM up to find first A tag + while (elm.length && lowercase(elm[0].nodeName) !== 'a') { + elm = elm.parent(); + } - // link to different domain (or base path) - if (href.substr(0, 4) == 'http') return; + var href = elm.attr('href'); + if (!href || isDefined(elm.attr('ng:ext-link')) || elm.attr('target')) return; - // remove pathPrefix from absolute links - href = href.indexOf(pathPrefix) === 0 ? href.substr(pathPrefix.length) : href; + // remove same domain from full url links (IE7 always returns full hrefs) + href = href.replace(absUrlPrefix, ''); - currentUrl.url(href); - $rootScope.$apply(); - event.preventDefault(); - // hack to work around FF6 bug 684208 when scenario runner clicks on links - window.angular['ff-684208-preventDefault'] = true; - }); - } else { - currentUrl = new LocationHashbangUrl(initUrl, hashPrefix); - } + // link to different domain (or base path) + if (href.substr(0, 4) == 'http') return; - // rewrite hashbang url <> html5 url - if (currentUrl.absUrl() != initUrl) { - $browser.url(currentUrl.absUrl(), true); - } + // remove pathPrefix from absolute links + href = href.indexOf(pathPrefix) === 0 ? href.substr(pathPrefix.length) : href; - // update $location when $browser url changes - $browser.onUrlChange(function(newUrl) { - if (currentUrl.absUrl() != newUrl) { - currentUrl.$$parse(newUrl); - $rootScope.$apply(); - } - }); - - // update browser - var changeCounter = 0; - $rootScope.$watch(function() { - if ($browser.url() != currentUrl.absUrl()) { - changeCounter++; - $rootScope.$evalAsync(function() { - $browser.url(currentUrl.absUrl(), currentUrl.$$replace); - currentUrl.$$replace = false; + currentUrl.url(href); + $rootScope.$apply(); + event.preventDefault(); + // hack to work around FF6 bug 684208 when scenario runner clicks on links + window.angular['ff-684208-preventDefault'] = true; }); + } else { + currentUrl = new LocationHashbangUrl(initUrl, hashPrefix); } - return changeCounter; - }); + // rewrite hashbang url <> html5 url + if (currentUrl.absUrl() != initUrl) { + $browser.url(currentUrl.absUrl(), true); + } - return currentUrl; -}, ['$rootScope', '$browser', '$sniffer', '$locationConfig', '$document']); + // update $location when $browser url changes + $browser.onUrlChange(function(newUrl) { + if (currentUrl.absUrl() != newUrl) { + currentUrl.$$parse(newUrl); + $rootScope.$apply(); + } + }); + // update browser + var changeCounter = 0; + $rootScope.$watch(function() { + if ($browser.url() != currentUrl.absUrl()) { + changeCounter++; + $rootScope.$evalAsync(function() { + $browser.url(currentUrl.absUrl(), currentUrl.$$replace); + currentUrl.$$replace = false; + }); + } -angular.service('$locationConfig', function() { - return { - html5Mode: false, - hashPrefix: '' + return changeCounter; + }); + + return currentUrl; +}]; +} + +//TODO(misko): refactor to service +function $LocationConfigProvider(){ + this.$get = function() { + return { + html5Mode: false, + hashPrefix: '' + }; }; -}); +} diff --git a/src/service/log.js b/src/service/log.js index 29577166..59094c42 100644 --- a/src/service/log.js +++ b/src/service/log.js @@ -34,64 +34,78 @@ </doc:scenario> </doc:example> */ -var $logFactory; //reference to be used only in tests -angularServiceInject("$log", $logFactory = function($window){ - return { - /** - * @ngdoc method - * @name angular.service.$log#log - * @methodOf angular.service.$log - * - * @description - * Write a log message - */ - log: consoleLog('log'), - /** - * @ngdoc method - * @name angular.service.$log#warn - * @methodOf angular.service.$log - * - * @description - * Write a warning message - */ - warn: consoleLog('warn'), +function $LogProvider(){ + this.$get = ['$window', function($window){ + return { + /** + * @ngdoc method + * @name angular.service.$log#log + * @methodOf angular.service.$log + * + * @description + * Write a log message + */ + log: consoleLog('log'), - /** - * @ngdoc method - * @name angular.service.$log#info - * @methodOf angular.service.$log - * - * @description - * Write an information message - */ - info: consoleLog('info'), + /** + * @ngdoc method + * @name angular.service.$log#warn + * @methodOf angular.service.$log + * + * @description + * Write a warning message + */ + warn: consoleLog('warn'), - /** - * @ngdoc method - * @name angular.service.$log#error - * @methodOf angular.service.$log - * - * @description - * Write an error message - */ - error: consoleLog('error') - }; + /** + * @ngdoc method + * @name angular.service.$log#info + * @methodOf angular.service.$log + * + * @description + * Write an information message + */ + info: consoleLog('info'), - function consoleLog(type) { - var console = $window.console || {}; - var 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); - }; - } else { - // we are IE, in which case there is nothing we can do - return logFn; + /** + * @ngdoc method + * @name angular.service.$log#error + * @methodOf angular.service.$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 || {}; + var 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); + }; + } else { + // we are IE, in which case there is nothing we can do + return logFn; + } } - } -}, ['$window']); + }]; +} diff --git a/src/service/resource.js b/src/service/resource.js index 8d77a9e4..e396e521 100644 --- a/src/service/resource.js +++ b/src/service/resource.js @@ -200,7 +200,9 @@ </doc:scenario> </doc:example> */ -angularServiceInject('$resource', function($xhr){ - var resource = new ResourceFactory($xhr); - return bind(resource, resource.route); -}, ['$xhr.cache']); +function $ResourceProvider() { + this.$get = ['$xhr.cache', function($xhr){ + var resource = new ResourceFactory($xhr); + return bind(resource, resource.route); + }]; +} diff --git a/src/service/route.js b/src/service/route.js index 3918c251..f2ecbd2a 100644 --- a/src/service/route.js +++ b/src/service/route.js @@ -62,264 +62,267 @@ </doc:scenario> </doc:example> */ -angularServiceInject('$route', function($rootScope, $location, $routeParams) { - /** - * @ngdoc event - * @name angular.service.$route#$beforeRouteChange - * @eventOf angular.service.$route - * @eventType broadcast on root scope - * @description - * Broadcasted before a route change. - * - * @param {Route} next Future route information. - * @param {Route} current Current route information. - * - * The `Route` object extends the route definition with the following properties. - * - * * `scope` - The instance of the route controller. - * * `params` - The current {@link angular.service.$routeParams params}. - * - */ +function $RouteProvider(){ + this.$get = ['$rootScope', '$location', '$routeParams', + function( $rootScope, $location, $routeParams) { + /** + * @ngdoc event + * @name angular.service.$route#$beforeRouteChange + * @eventOf angular.service.$route + * @eventType broadcast on root scope + * @description + * Broadcasted before a route change. + * + * @param {Route} next Future route information. + * @param {Route} current Current route information. + * + * The `Route` object extends the route definition with the following properties. + * + * * `scope` - The instance of the route controller. + * * `params` - The current {@link angular.service.$routeParams params}. + * + */ - /** - * @ngdoc event - * @name angular.service.$route#$afterRouteChange - * @eventOf angular.service.$route - * @eventType broadcast on root scope - * @description - * Broadcasted after a route change. - * - * @param {Route} current Current route information. - * @param {Route} previous Previous route information. - * - * The `Route` object extends the route definition with the following properties. - * - * * `scope` - The instance of the route controller. - * * `params` - The current {@link angular.service.$routeParams params}. - * - */ + /** + * @ngdoc event + * @name angular.service.$route#$afterRouteChange + * @eventOf angular.service.$route + * @eventType broadcast on root scope + * @description + * Broadcasted after a route change. + * + * @param {Route} current Current route information. + * @param {Route} previous Previous route information. + * + * The `Route` object extends the route definition with the following properties. + * + * * `scope` - The instance of the route controller. + * * `params` - The current {@link angular.service.$routeParams params}. + * + */ - /** - * @ngdoc event - * @name angular.service.$route#$routeUpdate - * @eventOf angular.service.$route - * @eventType emit on the current route scope - * @description - * - * The `reloadOnSearch` property has been set to false, and we are reusing the same - * instance of the Controller. - */ + /** + * @ngdoc event + * @name angular.service.$route#$routeUpdate + * @eventOf angular.service.$route + * @eventType emit on the current route scope + * @description + * + * The `reloadOnSearch` property has been set to false, and we are reusing the same + * instance of the Controller. + */ - var routes = {}, - matcher = switchRouteMatcher, - parentScope = $rootScope, - dirty = 0, - forceReload = false, - $route = { - routes: routes, + var routes = {}, + matcher = switchRouteMatcher, + parentScope = $rootScope, + dirty = 0, + forceReload = false, + $route = { + routes: routes, - /** - * @ngdoc method - * @name angular.service.$route#parent - * @methodOf angular.service.$route - * - * @param {Scope} [scope=rootScope] Scope to be used as parent for newly created - * `$route.current.scope` scopes. - * - * @description - * Sets a scope to be used as the parent scope for scopes created on route change. If not - * set, defaults to the root scope. - */ - parent: function(scope) { - if (scope) parentScope = scope; - }, + /** + * @ngdoc method + * @name angular.service.$route#parent + * @methodOf angular.service.$route + * + * @param {Scope} [scope=rootScope] Scope to be used as parent for newly created + * `$route.current.scope` scopes. + * + * @description + * Sets a scope to be used as the parent scope for scopes created on route change. If not + * set, defaults to the root scope. + */ + parent: function(scope) { + if (scope) parentScope = scope; + }, - /** - * @ngdoc method - * @name angular.service.$route#when - * @methodOf angular.service.$route - * - * @param {string} path Route path (matched against `$location.hash`) - * @param {Object} route Mapping information to be assigned to `$route.current` on route - * match. - * - * Object properties: - * - * - `controller` – `{function()=}` – Controller fn that should be associated with newly - * created scope. - * - `template` – `{string=}` – path to an html template that should be used by - * {@link angular.widget.ng:view ng:view} or - * {@link angular.widget.ng:include ng:include} widgets. - * - `redirectTo` – {(string|function())=} – value to update - * {@link angular.service.$location $location} path with and trigger route redirection. - * - * If `redirectTo` is a function, it will be called with the following parameters: - * - * - `{Object.<string>}` - route parameters extracted from the current - * `$location.path()` by applying the current route template. - * - `{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 emited on the current route scope. You can use this event to - * react to {@link angular.service.$routeParams} changes: - * - * function MyCtrl($route, $routeParams) { - * this.$on('$routeUpdate', function() { - * // do stuff with $routeParams - * }); - * } - * - * @returns {Object} route object - * - * @description - * Adds a new route definition to the `$route` service. - */ - when: function(path, route) { - var routeDef = routes[path]; - if (!routeDef) routeDef = routes[path] = {reloadOnSearch: true}; - if (route) extend(routeDef, route); // TODO(im): what the heck? merge two route definitions? - dirty++; - return routeDef; - }, + /** + * @ngdoc method + * @name angular.service.$route#when + * @methodOf angular.service.$route + * + * @param {string} path Route path (matched against `$location.hash`) + * @param {Object} route Mapping information to be assigned to `$route.current` on route + * match. + * + * Object properties: + * + * - `controller` – `{function()=}` – Controller fn that should be associated with newly + * created scope. + * - `template` – `{string=}` – path to an html template that should be used by + * {@link angular.widget.ng:view ng:view} or + * {@link angular.widget.ng:include ng:include} widgets. + * - `redirectTo` – {(string|function())=} – value to update + * {@link angular.service.$location $location} path with and trigger route redirection. + * + * If `redirectTo` is a function, it will be called with the following parameters: + * + * - `{Object.<string>}` - route parameters extracted from the current + * `$location.path()` by applying the current route template. + * - `{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 emited on the current route scope. You can use this event to + * react to {@link angular.service.$routeParams} changes: + * + * function MyCtrl($route, $routeParams) { + * this.$on('$routeUpdate', function() { + * // do stuff with $routeParams + * }); + * } + * + * @returns {Object} route object + * + * @description + * Adds a new route definition to the `$route` service. + */ + when: function(path, route) { + var routeDef = routes[path]; + if (!routeDef) routeDef = routes[path] = {reloadOnSearch: true}; + if (route) extend(routeDef, route); // TODO(im): what the heck? merge two route definitions? + dirty++; + return routeDef; + }, - /** - * @ngdoc method - * @name angular.service.$route#otherwise - * @methodOf angular.service.$route - * - * @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`. - */ - otherwise: function(params) { - $route.when(null, params); - }, + /** + * @ngdoc method + * @name angular.service.$route#otherwise + * @methodOf angular.service.$route + * + * @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`. + */ + otherwise: function(params) { + $route.when(null, params); + }, - /** - * @ngdoc method - * @name angular.service.$route#reload - * @methodOf angular.service.$route - * - * @description - * Causes `$route` service to reload (and recreate the `$route.current` scope) upon the next - * eval even if {@link angular.service.$location $location} hasn't changed. - */ - reload: function() { - dirty++; - forceReload = true; - } - }; + /** + * @ngdoc method + * @name angular.service.$route#reload + * @methodOf angular.service.$route + * + * @description + * Causes `$route` service to reload (and recreate the `$route.current` scope) upon the next + * eval even if {@link angular.service.$location $location} hasn't changed. + */ + reload: function() { + dirty++; + forceReload = true; + } + }; - $rootScope.$watch(function() { return dirty + $location.url(); }, updateRoute); + $rootScope.$watch(function() { return dirty + $location.url(); }, updateRoute); - return $route; + 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); + 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]; }); + var match = on.match(new RegExp(regex)); + if (match) { + forEach(params, function(name, index) { + dst[name] = match[index + 1]; + }); + } + return match ? dst : null; } - return match ? dst : null; - } - function updateRoute() { - var next = parseRoute(), - last = $route.current, - Controller; + function updateRoute() { + var next = parseRoute(), + last = $route.current, + Controller; - if (next && last && next.$route === last.$route - && equals(next.pathParams, last.pathParams) && !next.reloadOnSearch && !forceReload) { - $route.current = next; - copy(next.params, $routeParams); - last.scope && last.scope.$emit('$routeUpdate'); - } else { - forceReload = false; - $rootScope.$broadcast('$beforeRouteChange', next, last); - last && last.scope && last.scope.$destroy(); - $route.current = next; - if (next) { - if (next.redirectTo) { - if (isString(next.redirectTo)) { - $location.path(interpolate(next.redirectTo, next.params)).search(next.params) - .replace(); + if (next && last && next.$route === last.$route + && equals(next.pathParams, last.pathParams) && !next.reloadOnSearch && !forceReload) { + $route.current = next; + copy(next.params, $routeParams); + last.scope && last.scope.$emit('$routeUpdate'); + } else { + forceReload = false; + $rootScope.$broadcast('$beforeRouteChange', next, last); + last && last.scope && last.scope.$destroy(); + $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(); + } } else { - $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search())) - .replace(); + copy(next.params, $routeParams); + (Controller = next.controller) && inferInjectionArgs(Controller); + next.scope = parentScope.$new(Controller); } - } else { - copy(next.params, $routeParams); - (Controller = next.controller) && inferInjectionArgs(Controller); - next.scope = parentScope.$new(Controller); } + $rootScope.$broadcast('$afterRouteChange', next, last); } - $rootScope.$broadcast('$afterRouteChange', next, last); } - } - /** - * @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 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(''); - } + /** + * @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(''); + } -}, ['$rootScope', '$location', '$routeParams']); + }]; +} diff --git a/src/service/routeParams.js b/src/service/routeParams.js index a9e201a3..df89c739 100644 --- a/src/service/routeParams.js +++ b/src/service/routeParams.js @@ -25,6 +25,6 @@ * $routeParams ==> {chapterId:1, sectionId:2, search:'moby'} * </pre> */ -angularService('$routeParams', function() { - return {}; -}); +function $RouteParamsProvider() { + this.$get = valueFn({}); +} diff --git a/src/service/scope.js b/src/service/scope.js index 94c41041..f78ac448 100644 --- a/src/service/scope.js +++ b/src/service/scope.js @@ -24,631 +24,633 @@ * implemented in the same way as watch. Watch requires return of initialization function which * are expensive to construct. */ - -angularServiceInject('$rootScope', function($injector, $exceptionHandler){ - /** - * @ngdoc function - * @name angular.scope - * - * @description - * A root scope can be created by calling {@link angular.scope angular.scope()}. Child scopes - * are created using the {@link angular.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. - * <pre> - var scope = angular.scope(); - 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!'); - * </pre> - * - * # Inheritance - * A scope can inherit from a parent scope, as in this example: - * <pre> - var parent = angular.scope(); - 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'); - * </pre> - * - * # Dependency Injection - * See {@link guide/dev_guide.di dependency injection}. - * - * - * @param {Object.<string, function()>=} providers Map of service factory which need to be provided - * for the current scope. Defaults to {@link angular.service}. - * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should - * append/override services provided by `providers`. This is handy when unit-testing and having - * the need to override a default service. - * @returns {Object} Newly created scope. - * - */ - function Scope() { - this.$id = nextUid(); - this.$$phase = this.$parent = this.$$watchers = - this.$$nextSibling = this.$$prevSibling = - this.$$childHead = this.$$childTail = null; - this.$destructor = noop; - this['this'] = this.$root = this; - this.$$asyncQueue = []; - this.$$listeners = {}; - } - - /** - * @ngdoc property - * @name angular.scope.$id - * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for - * debugging. - */ - - - Scope.prototype = { +function $RootScopeProvider(){ + this.$get = ['$injector', '$exceptionHandler', + function( $injector, $exceptionHandler){ /** * @ngdoc function - * @name angular.scope.$new - * @function + * @name angular.scope * * @description - * Creates a new child {@link angular.scope scope}. The new scope can optionally behave as a - * controller. The parent scope will propagate the {@link angular.scope.$digest $digest()} and - * {@link angular.scope.$digest $digest()} events. The scope can be removed from the scope - * hierarchy using {@link angular.scope.$destroy $destroy()}. - * - * {@link angular.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 {function()=} Class Constructor function which the scope should be applied to the scope. - * @param {...*} curryArguments Any additional arguments which are curried into the constructor. - * See {@link guide/dev_guide.di dependency injection}. - * @returns {Object} The newly created child scope. + * A root scope can be created by calling {@link angular.scope angular.scope()}. Child scopes + * are created using the {@link angular.scope.$new $new()} method. + * (Most scopes are created automatically when compiled HTML template is executed.) * - */ - $new: function(Class, curryArguments) { - var 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. - var child; - Child.prototype = this; - child = new Child(); - child['this'] = child; - child.$$listeners = {}; - child.$parent = this; - child.$id = nextUid(); - child.$$asyncQueue = []; - child.$$phase = 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; - } - // short circuit if we have no class - if (Class) { - // can't use forEach, we need speed! - var ClassPrototype = Class.prototype; - for(var key in ClassPrototype) { - child[key] = bind(child, ClassPrototype[key]); - } - $injector.invoke(child, Class, curryArguments); - } - return child; - }, + * Here is a simple scope snippet to show how you can interact with the scope. + * <pre> + var scope = angular.scope(); + scope.salutation = 'Hello'; + scope.name = 'World'; - /** - * @ngdoc function - * @name angular.scope.$watch - * @function - * - * @description - * Registers a `listener` callback to be executed whenever the `watchExpression` changes. - * - * - The `watchExpression` is called on every call to {@link angular.scope.$digest $digest()} and - * should return the value which will be watched. (Since {@link angular.scope.$digest $digest()} - * reruns when it detects changes the `watchExpression` can execute multiple times per - * {@link angular.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. 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 angular.scope.$digest $digest} is called, - * you can register an `watchExpression` function with no `listener`. (Since `watchExpression`, - * can execute multiple times per {@link angular.scope.$digest $digest} cycle when a change is - * detected, be prepared for multiple calls to your listener.) - * - * - * # Example - <pre> - var scope = angular.scope(); - scope.name = 'misko'; - scope.counter = 0; - - expect(scope.counter).toEqual(0); - scope.$watch('name', function(scope, 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); - </pre> + 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!'); + * </pre> * + * # Inheritance + * A scope can inherit from a parent scope, as in this example: + * <pre> + var parent = angular.scope(); + 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'); + * </pre> * + * # Dependency Injection + * See {@link guide/dev_guide.di dependency injection}. * - * @param {(function()|string)} watchExpression Expression that is evaluated on each - * {@link angular.scope.$digest $digest} cycle. A change in the return value triggers a - * call to the `listener`. * - * - `string`: Evaluated as {@link guide/dev_guide.expressions expression} - * - `function(scope)`: called with current `scope` as a parameter. - * @param {(function()|string)=} listener Callback called whenever the return value of - * the `watchExpression` changes. + * @param {Object.<string, function()>=} providers Map of service factory which need to be provided + * for the current scope. Defaults to {@link angular.service}. + * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should + * append/override services provided by `providers`. This is handy when unit-testing and having + * the need to override a default service. + * @returns {Object} Newly created scope. * - * - `string`: Evaluated as {@link guide/dev_guide.expressions expression} - * - `function(scope, newValue, oldValue)`: called with current `scope` an previous and - * current values as parameters. - * @returns {function()} Returns a deregistration function for this listener. */ - $watch: function(watchExp, listener) { - var scope = this, - get = compileToFn(watchExp, 'watch'), - listenFn = compileToFn(listener || noop, 'listener'), - array = scope.$$watchers, - watcher = { - fn: listenFn, - last: Number.NaN, // NaN !== NaN. We used this to force $watch to fire on first run. - get: get, - exp: watchExp - }; - - 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() { - angularArray.remove(array, watcher); - }; - }, + function Scope() { + this.$id = nextUid(); + this.$$phase = this.$parent = this.$$watchers = + this.$$nextSibling = this.$$prevSibling = + this.$$childHead = this.$$childTail = null; + this.$destructor = noop; + this['this'] = this.$root = this; + this.$$asyncQueue = []; + this.$$listeners = {}; + } /** - * @ngdoc function - * @name angular.scope.$digest - * @function - * - * @description - * Process all of the {@link angular.scope.$watch watchers} of the current scope and its children. - * Because a {@link angular.scope.$watch watcher}'s listener can change the model, the - * `$digest()` keeps calling the {@link angular.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 100. - * - * Usually you don't call `$digest()` directly in - * {@link angular.directive.ng:controller controllers} or in {@link angular.directive directives}. - * Instead a call to {@link angular.scope.$apply $apply()} (typically from within a - * {@link angular.directive directive}) will force a `$digest()`. - * - * If you want to be notified whenever `$digest()` is called, - * you can register a `watchExpression` function with {@link angular.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 - <pre> - var scope = angular.scope(); - scope.name = 'misko'; - scope.counter = 0; - - expect(scope.counter).toEqual(0); - scope.$digest('name', function(scope, 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); - </pre> - * + * @ngdoc property + * @name angular.scope.$id + * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for + * debugging. */ - $digest: function() { - var watch, value, last, - watchers, - asyncQueue, - length, - dirty, ttl = 100, - next, current, target = this, - watchLog = []; - - if (target.$$phase) { - throw Error(target.$$phase + ' already in progress'); - } - do { - dirty = false; - current = target; - do { - current.$$phase = '$digest'; - asyncQueue = current.$$asyncQueue; - while(asyncQueue.length) { - try { - current.$eval(asyncQueue.shift()); - } catch (e) { - current.$service('$exceptionHandler')(e); - } + + Scope.prototype = { + /** + * @ngdoc function + * @name angular.scope.$new + * @function + * + * @description + * Creates a new child {@link angular.scope scope}. The new scope can optionally behave as a + * controller. The parent scope will propagate the {@link angular.scope.$digest $digest()} and + * {@link angular.scope.$digest $digest()} events. The scope can be removed from the scope + * hierarchy using {@link angular.scope.$destroy $destroy()}. + * + * {@link angular.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 {function()=} Class Constructor function which the scope should be applied to the scope. + * @param {...*} curryArguments Any additional arguments which are curried into the constructor. + * See {@link guide/dev_guide.di dependency injection}. + * @returns {Object} The newly created child scope. + * + */ + $new: function(Class, curryArguments) { + var 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. + var child; + Child.prototype = this; + child = new Child(); + child['this'] = child; + child.$$listeners = {}; + child.$parent = this; + child.$id = nextUid(); + child.$$asyncQueue = []; + child.$$phase = 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; + } + // short circuit if we have no class + if (Class) { + // can't use forEach, we need speed! + var ClassPrototype = Class.prototype; + for(var key in ClassPrototype) { + child[key] = bind(child, ClassPrototype[key]); } - if ((watchers = current.$$watchers)) { - // process our watches - length = watchers.length; - while (length--) { + $injector.invoke(child, Class, curryArguments); + } + return child; + }, + + /** + * @ngdoc function + * @name angular.scope.$watch + * @function + * + * @description + * Registers a `listener` callback to be executed whenever the `watchExpression` changes. + * + * - The `watchExpression` is called on every call to {@link angular.scope.$digest $digest()} and + * should return the value which will be watched. (Since {@link angular.scope.$digest $digest()} + * reruns when it detects changes the `watchExpression` can execute multiple times per + * {@link angular.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. 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 angular.scope.$digest $digest} is called, + * you can register an `watchExpression` function with no `listener`. (Since `watchExpression`, + * can execute multiple times per {@link angular.scope.$digest $digest} cycle when a change is + * detected, be prepared for multiple calls to your listener.) + * + * + * # Example + <pre> + var scope = angular.scope(); + scope.name = 'misko'; + scope.counter = 0; + + expect(scope.counter).toEqual(0); + scope.$watch('name', function(scope, 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); + </pre> + * + * + * + * @param {(function()|string)} watchExpression Expression that is evaluated on each + * {@link angular.scope.$digest $digest} cycle. A change in the return value triggers a + * call to the `listener`. + * + * - `string`: Evaluated as {@link guide/dev_guide.expressions 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/dev_guide.expressions expression} + * - `function(scope, newValue, oldValue)`: called with current `scope` an previous and + * current values as parameters. + * @returns {function()} Returns a deregistration function for this listener. + */ + $watch: function(watchExp, listener) { + var scope = this, + get = compileToFn(watchExp, 'watch'), + listenFn = compileToFn(listener || noop, 'listener'), + array = scope.$$watchers, + watcher = { + fn: listenFn, + last: Number.NaN, // NaN !== NaN. We used this to force $watch to fire on first run. + get: get, + exp: watchExp + }; + + 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() { + angularArray.remove(array, watcher); + }; + }, + + /** + * @ngdoc function + * @name angular.scope.$digest + * @function + * + * @description + * Process all of the {@link angular.scope.$watch watchers} of the current scope and its children. + * Because a {@link angular.scope.$watch watcher}'s listener can change the model, the + * `$digest()` keeps calling the {@link angular.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 100. + * + * Usually you don't call `$digest()` directly in + * {@link angular.directive.ng:controller controllers} or in {@link angular.directive directives}. + * Instead a call to {@link angular.scope.$apply $apply()} (typically from within a + * {@link angular.directive directive}) will force a `$digest()`. + * + * If you want to be notified whenever `$digest()` is called, + * you can register a `watchExpression` function with {@link angular.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 + <pre> + var scope = angular.scope(); + scope.name = 'misko'; + scope.counter = 0; + + expect(scope.counter).toEqual(0); + scope.$digest('name', function(scope, 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); + </pre> + * + */ + $digest: function() { + var watch, value, last, + watchers, + asyncQueue, + length, + dirty, ttl = 100, + next, current, target = this, + watchLog = []; + + if (target.$$phase) { + throw Error(target.$$phase + ' already in progress'); + } + do { + + dirty = false; + current = target; + do { + current.$$phase = '$digest'; + asyncQueue = current.$$asyncQueue; + while(asyncQueue.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) && !equals(value, last)) { - dirty = true; - watch.last = copy(value); - watch.fn(current, value, last); - if (ttl < 5) { - if (!watchLog[4-ttl]) watchLog[4-ttl] = []; - if (isFunction(watch.exp)) { - watchLog[4-ttl].push('fn: ' + (watch.exp.name || watch.exp.toString())); - } else { - watchLog[4-ttl].push(watch.exp); + current.$eval(asyncQueue.shift()); + } catch (e) { + current.$service('$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) && !equals(value, last)) { + dirty = true; + watch.last = copy(value); + watch.fn(current, value, last); + if (ttl < 5) { + if (!watchLog[4-ttl]) watchLog[4-ttl] = []; + if (isFunction(watch.exp)) { + watchLog[4-ttl].push('fn: ' + (watch.exp.name || watch.exp.toString())); + } else { + watchLog[4-ttl].push(watch.exp); + } } } + } catch (e) { + $exceptionHandler(e); } - } catch (e) { - $exceptionHandler(e); } } + + current.$$phase = null; + + // 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(!(ttl--)) { + throw Error('100 $digest() iterations reached. Aborting!\n' + + 'Watchers fired in the last 5 iterations: ' + toJson(watchLog)); + } + } while (dirty); + }, + + /** + * @ngdoc function + * @name angular.scope.$destroy + * @function + * + * @description + * Remove the current scope (and all of its children) from the parent scope. Removal implies + * that calls to {@link angular.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 destructing scope emits an `$destroy` {@link angular.scope.$emit event}. + * + * The `$destroy()` is usually used by directives such as + * {@link angular.widget.@ng:repeat ng:repeat} for managing the unrolling of the loop. + * + */ + $destroy: function() { + if (this.$root == this) return; // we can't remove the root node; + this.$emit('$destroy'); + var parent = this.$parent; + + 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 angular.scope.$eval + * @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 + <pre> + var scope = angular.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); + </pre> + * + * @param {(string|function())=} expression An angular expression to be executed. + * + * - `string`: execute using the rules as defined in {@link guide/dev_guide.expressions expression}. + * - `function(scope)`: execute the function with the current `scope` parameter. + * + * @returns {*} The result of evaluating the expression. + */ + $eval: function(expr) { + var fn = isString(expr) + ? expressionCompile(expr) + : expr || noop; + return fn(this); + }, + + /** + * @ngdoc function + * @name angular.scope.$evalAsync + * @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 angular.scope.$digest $digest cycle} will be performed after + * `expression` execution. + * + * Any exceptions from the execution of the expression are forwarded to the + * {@link angular.service.$exceptionHandler $exceptionHandler} service. + * + * @param {(string|function())=} expression An angular expression to be executed. + * + * - `string`: execute using the rules as defined in {@link guide/dev_guide.expressions expression}. + * - `function(scope)`: execute the function with the current `scope` parameter. + * + */ + $evalAsync: function(expr) { + this.$$asyncQueue.push(expr); + }, + + /** + * @ngdoc function + * @name angular.scope.$apply + * @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 angular.service.$exceptionHandler exception handling}, + * {@link angular.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/dev_guide.expressions expression} is executed using the + * {@link angular.scope.$eval $eval()} method. + * 2. Any exceptions from the execution of the expression are forwarded to the + * {@link angular.service.$exceptionHandler $exceptionHandler} service. + * 3. The {@link angular.scope.$watch watch} listeners are fired immediately after the expression + * was executed using the {@link angular.scope.$digest $digest()} method. + * + * + * @param {(string|function())=} exp An angular expression to be executed. + * + * - `string`: execute using the rules as defined in {@link guide/dev_guide.expressions expression}. + * - `function(scope)`: execute the function with current `scope` parameter. + * + * @returns {*} The result of evaluating the expression. + */ + $apply: function(expr) { + try { + return this.$eval(expr); + } catch (e) { + $exceptionHandler(e); + } finally { + this.$root.$digest(); + } + }, + + /** + * @ngdoc function + * @name angular.scope.$on + * @function + * + * @description + * Listen on events of a given type. See {@link angular.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)`. 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. + * - `cancel` - {function=}: calling `cancel` function will cancel further event propagation + * (available only for events that were `$emit`-ed). + */ + $on: function(name, listener) { + var namedListeners = this.$$listeners[name]; + if (!namedListeners) { + this.$$listeners[name] = namedListeners = []; + } + namedListeners.push(listener); + + return function() { + angularArray.remove(namedListeners, listener); + }; + }, + + + /** + * @ngdoc function + * @name angular.scope.$emit + * @function + * + * @description + * Dispatches an event `name` upwards through the scope hierarchy notifying the + * registered {@link angular.scope.$on} listeners. + * + * The event life cycle starts at the scope on which `$emit` was called. All + * {@link angular.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 angular.scope.$on listeners} will be passed + * onto the {@link angular.service.$exceptionHandler $exceptionHandler} service. + * + * @param {string} name Event name to emit. + * @param {...*} args Optional set of arguments which will be passed onto the event listeners. + */ + $emit: function(name, args) { + var empty = [], + namedListeners, + canceled = false, + scope = this, + event = { + name: name, + targetScope: scope, + cancel: function() {canceled = true;} + }, + listenerArgs = concat([event], arguments, 1), + i, length; - current.$$phase = null; + do { + namedListeners = scope.$$listeners[name] || empty; + event.currentScope = scope; + for (i=0, length=namedListeners.length; i<length; i++) { + try { + namedListeners[i].apply(null, listenerArgs); + if (canceled) return; + } catch (e) { + $exceptionHandler(e); + } + } + //traverse upwards + scope = scope.$parent; + } while (scope); + }, + + + /** + * @ngdoc function + * @name angular.scope.$broadcast + * @function + * + * @description + * Dispatches an event `name` downwards to all child scopes (and their children) notifying the + * registered {@link angular.scope.$on} listeners. + * + * The event life cycle starts at the scope on which `$broadcast` was called. All + * {@link angular.scope.$on listeners} listening for `name` event on this scope get notified. + * Afterwards, the event propagates to all direct and indirect scopes of the current scope and + * calls all registered listeners along the way. The event cannot be canceled. + * + * Any exception emmited from the {@link angular.scope.$on listeners} will be passed + * onto the {@link angular.service.$exceptionHandler $exceptionHandler} service. + * + * @param {string} name Event name to emit. + * @param {...*} args Optional set of arguments which will be passed onto the event listeners. + */ + $broadcast: function(name, args) { + var target = this, + current = target, + next = target, + event = { name: name, + targetScope: target }, + listenerArgs = concat([event], arguments, 1); + + //down while you can, then up and next sibling or up and next sibling until back at root + do { + current = next; + event.currentScope = current; + forEach(current.$$listeners[name], function(listener) { + try { + listener.apply(null, listenerArgs); + } 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 + // this piece should be kept in sync with the traversal in $digest if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) { while(current !== target && !(next = current.$$nextSibling)) { current = current.$parent; } } } while ((current = next)); - - if(!(ttl--)) { - throw Error('100 $digest() iterations reached. Aborting!\n' + - 'Watchers fired in the last 5 iterations: ' + toJson(watchLog)); - } - } while (dirty); - }, - - /** - * @ngdoc function - * @name angular.scope.$destroy - * @function - * - * @description - * Remove the current scope (and all of its children) from the parent scope. Removal implies - * that calls to {@link angular.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 destructing scope emits an `$destroy` {@link angular.scope.$emit event}. - * - * The `$destroy()` is usually used by directives such as - * {@link angular.widget.@ng:repeat ng:repeat} for managing the unrolling of the loop. - * - */ - $destroy: function() { - if (this.$root == this) return; // we can't remove the root node; - this.$emit('$destroy'); - var parent = this.$parent; - - 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 angular.scope.$eval - * @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 - <pre> - var scope = angular.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); - </pre> - * - * @param {(string|function())=} expression An angular expression to be executed. - * - * - `string`: execute using the rules as defined in {@link guide/dev_guide.expressions expression}. - * - `function(scope)`: execute the function with the current `scope` parameter. - * - * @returns {*} The result of evaluating the expression. - */ - $eval: function(expr) { - var fn = isString(expr) - ? expressionCompile(expr) - : expr || noop; - return fn(this); - }, - - /** - * @ngdoc function - * @name angular.scope.$evalAsync - * @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 angular.scope.$digest $digest cycle} will be performed after - * `expression` execution. - * - * Any exceptions from the execution of the expression are forwarded to the - * {@link angular.service.$exceptionHandler $exceptionHandler} service. - * - * @param {(string|function())=} expression An angular expression to be executed. - * - * - `string`: execute using the rules as defined in {@link guide/dev_guide.expressions expression}. - * - `function(scope)`: execute the function with the current `scope` parameter. - * - */ - $evalAsync: function(expr) { - this.$$asyncQueue.push(expr); - }, - - /** - * @ngdoc function - * @name angular.scope.$apply - * @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 angular.service.$exceptionHandler exception handling}, - * {@link angular.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/dev_guide.expressions expression} is executed using the - * {@link angular.scope.$eval $eval()} method. - * 2. Any exceptions from the execution of the expression are forwarded to the - * {@link angular.service.$exceptionHandler $exceptionHandler} service. - * 3. The {@link angular.scope.$watch watch} listeners are fired immediately after the expression - * was executed using the {@link angular.scope.$digest $digest()} method. - * - * - * @param {(string|function())=} exp An angular expression to be executed. - * - * - `string`: execute using the rules as defined in {@link guide/dev_guide.expressions expression}. - * - `function(scope)`: execute the function with current `scope` parameter. - * - * @returns {*} The result of evaluating the expression. - */ - $apply: function(expr) { - try { - return this.$eval(expr); - } catch (e) { - $exceptionHandler(e); - } finally { - this.$root.$digest(); - } - }, - - /** - * @ngdoc function - * @name angular.scope.$on - * @function - * - * @description - * Listen on events of a given type. See {@link angular.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)`. 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. - * - `cancel` - {function=}: calling `cancel` function will cancel further event propagation - * (available only for events that were `$emit`-ed). - */ - $on: function(name, listener) { - var namedListeners = this.$$listeners[name]; - if (!namedListeners) { - this.$$listeners[name] = namedListeners = []; } - namedListeners.push(listener); - - return function() { - angularArray.remove(namedListeners, listener); - }; - }, - - - /** - * @ngdoc function - * @name angular.scope.$emit - * @function - * - * @description - * Dispatches an event `name` upwards through the scope hierarchy notifying the - * registered {@link angular.scope.$on} listeners. - * - * The event life cycle starts at the scope on which `$emit` was called. All - * {@link angular.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 angular.scope.$on listeners} will be passed - * onto the {@link angular.service.$exceptionHandler $exceptionHandler} service. - * - * @param {string} name Event name to emit. - * @param {...*} args Optional set of arguments which will be passed onto the event listeners. - */ - $emit: function(name, args) { - var empty = [], - namedListeners, - canceled = false, - scope = this, - event = { - name: name, - targetScope: scope, - cancel: function() {canceled = true;} - }, - listenerArgs = concat([event], arguments, 1), - i, length; - - do { - namedListeners = scope.$$listeners[name] || empty; - event.currentScope = scope; - for (i=0, length=namedListeners.length; i<length; i++) { - try { - namedListeners[i].apply(null, listenerArgs); - if (canceled) return; - } catch (e) { - $exceptionHandler(e); - } - } - //traverse upwards - scope = scope.$parent; - } while (scope); - }, - - - /** - * @ngdoc function - * @name angular.scope.$broadcast - * @function - * - * @description - * Dispatches an event `name` downwards to all child scopes (and their children) notifying the - * registered {@link angular.scope.$on} listeners. - * - * The event life cycle starts at the scope on which `$broadcast` was called. All - * {@link angular.scope.$on listeners} listening for `name` event on this scope get notified. - * Afterwards, the event propagates to all direct and indirect scopes of the current scope and - * calls all registered listeners along the way. The event cannot be canceled. - * - * Any exception emmited from the {@link angular.scope.$on listeners} will be passed - * onto the {@link angular.service.$exceptionHandler $exceptionHandler} service. - * - * @param {string} name Event name to emit. - * @param {...*} args Optional set of arguments which will be passed onto the event listeners. - */ - $broadcast: function(name, args) { - var target = this, - current = target, - next = target, - event = { name: name, - targetScope: target }, - listenerArgs = concat([event], arguments, 1); - - //down while you can, then up and next sibling or up and next sibling until back at root - do { - current = next; - event.currentScope = current; - forEach(current.$$listeners[name], function(listener) { - try { - listener.apply(null, listenerArgs); - } 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 $digest - if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) { - while(current !== target && !(next = current.$$nextSibling)) { - current = current.$parent; - } - } - } while ((current = next)); + }; + + // TODO(misko): remove this; + var scope = new Scope(); + scope.$service = $injector; + return scope; + + function compileToFn(exp, name) { + var fn = isString(exp) + ? expressionCompile(exp) + : exp; + assertArgFn(fn, name); + return fn; } - }; - - // TODO(misko): remove this; - var scope = new Scope(); - scope.$service = $injector; - return scope; - - function compileToFn(exp, name) { - var fn = isString(exp) - ? expressionCompile(exp) - : exp; - assertArgFn(fn, name); - return fn; - } -}, ['$injector', '$exceptionHandler']); + }]; +} diff --git a/src/service/sniffer.js b/src/service/sniffer.js index ed6d688d..8f6efeef 100644 --- a/src/service/sniffer.js +++ b/src/service/sniffer.js @@ -11,13 +11,15 @@ * @description * This is very simple implementation of testing browser's features. */ -angularServiceInject('$sniffer', function($window) { - if ($window.Modernizr) return $window.Modernizr; +function $SnifferProvider(){ + this.$get = ['$window', function($window){ + if ($window.Modernizr) return $window.Modernizr; - return { - history: !!($window.history && $window.history.pushState), - hashchange: 'onhashchange' in $window && - // IE8 compatible mode lies - (!$window.document.documentMode || $window.document.documentMode > 7) - }; -}, ['$window']); + return { + history: !!($window.history && $window.history.pushState), + hashchange: 'onhashchange' in $window && + // IE8 compatible mode lies + (!$window.document.documentMode || $window.document.documentMode > 7) + }; + }]; +} diff --git a/src/service/window.js b/src/service/window.js index bc264e30..d5c62146 100644 --- a/src/service/window.js +++ b/src/service/window.js @@ -23,4 +23,6 @@ </doc:scenario> </doc:example> */ -angularServiceInject("$window", bind(window, identity, window)); +function $WindowProvider(){ + this.$get = valueFn(window); +} diff --git a/src/service/xhr.bulk.js b/src/service/xhr.bulk.js index bf5a1d95..b75f4046 100644 --- a/src/service/xhr.bulk.js +++ b/src/service/xhr.bulk.js @@ -11,76 +11,79 @@ * * @example */ -angularServiceInject('$xhr.bulk', function($rootScope, $xhr, $error, $log){ - var requests = []; - function bulkXHR(method, url, post, success, error) { - if (isFunction(post)) { - error = success; - success = post; - post = null; - } - var currentQueue; - forEach(bulkXHR.urls, function(queue){ - if (isFunction(queue.match) ? queue.match(url) : queue.match.exec(url)) { - currentQueue = queue; +function $XhrBulkProvider() { + this.$get = ['$rootScope', '$xhr', '$xhr.error', '$log', + function( $rootScope, $xhr, $error, $log) { + var requests = []; + function bulkXHR(method, url, post, success, error) { + if (isFunction(post)) { + error = success; + success = post; + post = null; + } + var currentQueue; + forEach(bulkXHR.urls, function(queue){ + if (isFunction(queue.match) ? queue.match(url) : queue.match.exec(url)) { + currentQueue = queue; + } + }); + if (currentQueue) { + if (!currentQueue.requests) currentQueue.requests = []; + var request = { + method: method, + url: url, + data: post, + success: success}; + if (error) request.error = error; + currentQueue.requests.push(request); + } else { + $xhr(method, url, post, success, error); } - }); - if (currentQueue) { - if (!currentQueue.requests) currentQueue.requests = []; - var request = { - method: method, - url: url, - data: post, - success: success}; - if (error) request.error = error; - currentQueue.requests.push(request); - } else { - $xhr(method, url, post, success, error); } - } - bulkXHR.urls = {}; - bulkXHR.flush = function(success, errorback) { - assertArgFn(success = success || noop, 0); - assertArgFn(errorback = errorback || noop, 1); - forEach(bulkXHR.urls, function(queue, url) { - var currentRequests = queue.requests; - if (currentRequests && currentRequests.length) { - queue.requests = []; - queue.callbacks = []; - $xhr('POST', url, {requests: currentRequests}, - function(code, response) { - forEach(response, function(response, i) { - try { - if (response.status == 200) { - (currentRequests[i].success || noop)(response.status, response.response); - } else if (isFunction(currentRequests[i].error)) { - currentRequests[i].error(response.status, response.response); - } else { - $error(currentRequests[i], response); + bulkXHR.urls = {}; + bulkXHR.flush = function(success, errorback) { + assertArgFn(success = success || noop, 0); + assertArgFn(errorback = errorback || noop, 1); + forEach(bulkXHR.urls, function(queue, url) { + var currentRequests = queue.requests; + if (currentRequests && currentRequests.length) { + queue.requests = []; + queue.callbacks = []; + $xhr('POST', url, {requests: currentRequests}, + function(code, response) { + forEach(response, function(response, i) { + try { + if (response.status == 200) { + (currentRequests[i].success || noop)(response.status, response.response); + } else if (isFunction(currentRequests[i].error)) { + currentRequests[i].error(response.status, response.response); + } else { + $error(currentRequests[i], response); + } + } catch(e) { + $log.error(e); } - } catch(e) { - $log.error(e); - } - }); - success(); - }, - function(code, response) { - forEach(currentRequests, function(request, i) { - try { - if (isFunction(request.error)) { - request.error(code, response); - } else { - $error(request, response); + }); + success(); + }, + function(code, response) { + forEach(currentRequests, function(request, i) { + try { + if (isFunction(request.error)) { + request.error(code, response); + } else { + $error(request, response); + } + } catch(e) { + $log.error(e); } - } catch(e) { - $log.error(e); - } + }); + noop(); }); - noop(); - }); - } - }); - }; - $rootScope.$watch(function() { bulkXHR.flush(); }); - return bulkXHR; -}, ['$rootScope', '$xhr', '$xhr.error', '$log']); + } + }); + }; + $rootScope.$watch(function() { bulkXHR.flush(); }); + return bulkXHR; + }]; +} diff --git a/src/service/xhr.cache.js b/src/service/xhr.cache.js index 335c481d..a448bfe0 100644 --- a/src/service/xhr.cache.js +++ b/src/service/xhr.cache.js @@ -28,87 +28,91 @@ * cached entry. The `success` function will be called when the response is received. * @param {boolean=} [sync=false] in case of cache hit execute `success` synchronously. */ -angularServiceInject('$xhr.cache', function($xhr, $defer, $error, $log) { - var inflight = {}; - function cache(method, url, post, success, error, verifyCache, sync) { - if (isFunction(post)) { - if (!isFunction(success)) { - verifyCache = success; - sync = error; - error = null; - } else { +function $XhrCacheProvider() { + this.$get = ['$xhr.bulk', '$defer', '$xhr.error', '$log', + function($xhr, $defer, $error, $log) { + var inflight = {}; + function cache(method, url, post, success, error, verifyCache, sync) { + if (isFunction(post)) { + if (!isFunction(success)) { + verifyCache = success; + sync = error; + error = null; + } else { + sync = verifyCache; + verifyCache = error; + error = success; + } + success = post; + post = null; + } else if (!isFunction(error)) { sync = verifyCache; verifyCache = error; - error = success; + error = null; } - success = post; - post = null; - } else if (!isFunction(error)) { - sync = verifyCache; - verifyCache = error; - error = null; - } - if (method == 'GET') { - var data, dataCached; - if ((dataCached = cache.data[url])) { + if (method == 'GET') { + var data, dataCached; + if ((dataCached = cache.data[url])) { - if (sync) { - success(200, copy(dataCached.value)); - } else { - $defer(function() { success(200, copy(dataCached.value)); }); - } + if (sync) { + success(200, copy(dataCached.value)); + } else { + $defer(function() { success(200, copy(dataCached.value)); }); + } - if (!verifyCache) - return; - } + if (!verifyCache) + return; + } - if ((data = inflight[url])) { - data.successes.push(success); - data.errors.push(error); - } else { - inflight[url] = {successes: [success], errors: [error]}; - cache.delegate(method, url, post, - function(status, response) { - if (status == 200) - cache.data[url] = {value: response}; - var successes = inflight[url].successes; - delete inflight[url]; - forEach(successes, function(success) { - try { - (success||noop)(status, copy(response)); - } catch(e) { - $log.error(e); - } - }); - }, - function(status, response) { - var errors = inflight[url].errors, - successes = inflight[url].successes; - delete inflight[url]; + if ((data = inflight[url])) { + data.successes.push(success); + data.errors.push(error); + } else { + inflight[url] = {successes: [success], errors: [error]}; + cache.delegate(method, url, post, + function(status, response) { + if (status == 200) + cache.data[url] = {value: response}; + var successes = inflight[url].successes; + delete inflight[url]; + forEach(successes, function(success) { + try { + (success||noop)(status, copy(response)); + } catch(e) { + $log.error(e); + } + }); + }, + function(status, response) { + var errors = inflight[url].errors, + successes = inflight[url].successes; + delete inflight[url]; - forEach(errors, function(error, i) { - try { - if (isFunction(error)) { - error(status, copy(response)); - } else { - $error( - {method: method, url: url, data: post, success: successes[i]}, - {status: status, body: response}); + forEach(errors, function(error, i) { + try { + if (isFunction(error)) { + error(status, copy(response)); + } else { + $error( + {method: method, url: url, data: post, success: successes[i]}, + {status: status, body: response}); + } + } catch(e) { + $log.error(e); } - } catch(e) { - $log.error(e); - } + }); }); - }); - } + } - } else { - cache.data = {}; - cache.delegate(method, url, post, success, error); + } else { + cache.data = {}; + cache.delegate(method, url, post, success, error); + } } - } - cache.data = {}; - cache.delegate = $xhr; - return cache; -}, ['$xhr.bulk', '$defer', '$xhr.error', '$log']); + cache.data = {}; + cache.delegate = $xhr; + return cache; + }]; + +} diff --git a/src/service/xhr.error.js b/src/service/xhr.error.js index 01fb8fff..6bd2ac78 100644 --- a/src/service/xhr.error.js +++ b/src/service/xhr.error.js @@ -35,8 +35,10 @@ </doc:source> </doc:example> */ -angularServiceInject('$xhr.error', function($log){ - return function(request, response){ - $log.error('ERROR: XHR: ' + request.url, request, response); - }; -}, ['$log']); +function $XhrErrorProvider() { + this.$get = ['$log', function($log) { + return function(request, response){ + $log.error('ERROR: XHR: ' + request.url, request, response); + }; + }]; +} diff --git a/src/service/xhr.js b/src/service/xhr.js index b2a5bdf2..bd1217b9 100644 --- a/src/service/xhr.js +++ b/src/service/xhr.js @@ -171,58 +171,61 @@ </doc:scenario> </doc:example> */ -angularServiceInject('$xhr', function($rootScope, $browser, $error, $log){ - var xhrHeaderDefaults = { - common: { - "Accept": "application/json, text/plain, */*", - "X-Requested-With": "XMLHttpRequest" - }, - post: {'Content-Type': 'application/x-www-form-urlencoded'}, - get: {}, // all these empty properties are needed so that client apps can just do: - head: {}, // $xhr.defaults.headers.head.foo="bar" without having to create head object - put: {}, // it also means that if we add a header for these methods in the future, it - 'delete': {}, // won't be easily silently lost due to an object assignment. - patch: {} - }; +function $XhrProvider() { + this.$get = ['$rootScope', '$browser', '$xhr.error', '$log', + function( $rootScope, $browser, $error, $log){ + var xhrHeaderDefaults = { + common: { + "Accept": "application/json, text/plain, */*", + "X-Requested-With": "XMLHttpRequest" + }, + post: {'Content-Type': 'application/x-www-form-urlencoded'}, + get: {}, // all these empty properties are needed so that client apps can just do: + head: {}, // $xhr.defaults.headers.head.foo="bar" without having to create head object + put: {}, // it also means that if we add a header for these methods in the future, it + 'delete': {}, // won't be easily silently lost due to an object assignment. + patch: {} + }; - function xhr(method, url, post, success, error) { - if (isFunction(post)) { - error = success; - success = post; - post = null; - } - if (post && isObject(post)) { - post = toJson(post); - } + function xhr(method, url, post, success, error) { + if (isFunction(post)) { + error = success; + success = post; + post = null; + } + if (post && isObject(post)) { + post = toJson(post); + } - $browser.xhr(method, url, post, function(code, response){ - try { - if (isString(response)) { - if (response.match(/^\)\]\}',\n/)) response=response.substr(6); - if (/^\s*[\[\{]/.exec(response) && /[\}\]]\s*$/.exec(response)) { - response = fromJson(response, true); + $browser.xhr(method, url, post, function(code, response){ + try { + if (isString(response)) { + if (response.match(/^\)\]\}',\n/)) response=response.substr(6); + if (/^\s*[\[\{]/.exec(response) && /[\}\]]\s*$/.exec(response)) { + response = fromJson(response, true); + } } + $rootScope.$apply(function() { + if (200 <= code && code < 300) { + success(code, response); + } else if (isFunction(error)) { + error(code, response); + } else { + $error( + {method: method, url: url, data: post, success: success}, + {status: code, body: response}); + } + }); + } catch (e) { + $log.error(e); } - $rootScope.$apply(function() { - if (200 <= code && code < 300) { - success(code, response); - } else if (isFunction(error)) { - error(code, response); - } else { - $error( - {method: method, url: url, data: post, success: success}, - {status: code, body: response}); - } - }); - } catch (e) { - $log.error(e); - } - }, extend({'X-XSRF-TOKEN': $browser.cookies()['XSRF-TOKEN']}, - xhrHeaderDefaults.common, - xhrHeaderDefaults[lowercase(method)])); - } + }, extend({'X-XSRF-TOKEN': $browser.cookies()['XSRF-TOKEN']}, + xhrHeaderDefaults.common, + xhrHeaderDefaults[lowercase(method)])); + } - xhr.defaults = {headers: xhrHeaderDefaults}; + xhr.defaults = {headers: xhrHeaderDefaults}; - return xhr; -}, ['$rootScope', '$browser', '$xhr.error', '$log']); + return xhr; + }]; +} |
