refactor(services): migrate angular.service -> module

This commit is contained in:
Misko Hevery 2011-11-02 16:32:46 -07:00
parent ed36b9da3b
commit bd04316a89
43 changed files with 2250 additions and 2220 deletions

2
angularFiles.js vendored
View file

@ -70,7 +70,6 @@ angularFiles = {
'src/jstd-scenario-adapter/*.js',
'src/scenario/*.js',
'src/angular-mocks.js',
'test/mocks.js',
'test/scenario/*.js',
'test/scenario/output/*.js',
'test/jstd-scenario-adapter/*.js',
@ -133,7 +132,6 @@ angularFiles = {
'src/jstd-scenario-adapter/*.js',
'src/scenario/*.js',
'src/angular-mocks.js',
'test/mocks.js',
'test/scenario/*.js',
'test/scenario/output/*.js',
'test/jstd-scenario-adapter/*.js',

View file

@ -148,9 +148,12 @@ function TutorialInstructionsCtrl($cookieStore) {
};
}
angular.service('$locationConfig', function() {
return {
window.angular = window.angular || {};
angular.module = angular.module || {};
angular.module.ngdocs = function($provide) {
$provide.value('$locationConfig', {
html5Mode: true,
hashPrefix: '!'
};
});
});
};

View file

@ -2,7 +2,7 @@ describe('example.personalLog.LogCtrl', function() {
var logCtrl;
function createNotesCtrl() {
var injector = angular.injector('NG');
var injector = angular.injector('NG', 'NG_MOCK');
var scope = injector('$rootScope');
scope.$cookies = injector('$cookies');
return scope.$new(example.personalLog.LogCtrl);

View file

@ -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);
}

View file

@ -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()

View file

@ -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);
}];
}

View file

@ -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);

127
src/angular-mocks.js vendored
View file

@ -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);
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)); }
};
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); }
$log.reset = function (){
$log.log.logs = [];
$log.warn.logs = [];
$log.info.logs = [];
$log.error.logs = [];
};
$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;
};
$log.log.logs = [];
$log.warn.logs = [];
$log.info.logs = [];
$log.error.logs = [];
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;

View file

@ -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);
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;
}
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
}
},
addLinkFn:function(linkingFn) {
if (linkingFn) {
//TODO(misko): temporary hack.
if (isFunction(linkingFn) && !linkingFn.$inject) {
linkingFn.$inject = ['$element'];
}
this.linkFns.push(linkingFn);
}
},
addChild: function(index, template) {
if (template) {
this.paths.push(index);
this.children.push(template);
}
},
empty: function() {
return this.linkFns.length === 0 && this.paths.length === 0;
}
};
///////////////////////////////////
//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
var rootScope2 = angular.compile('<div ng:click="clicked = true">click me</div>')();
// 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;
}
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;
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
}
},
addLinkFn:function(linkingFn) {
if (linkingFn) {
//TODO(misko): temporary hack.
if (isFunction(linkingFn) && !linkingFn.$inject) {
linkingFn.$inject = ['$element'];
}
this.linkFns.push(linkingFn);
}
},
addChild: function(index, template) {
if (template) {
this.paths.push(index);
this.children.push(template);
}
},
empty: function() {
return this.linkFns.length === 0 && this.paths.length === 0;
}
}
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
//////////////////////////////////
/**
* @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
var rootScope2 = angular.compile('<div ng:click="clicked = true">click me</div>')();
// 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;
}
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;
};
},
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);
}
}
});
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 (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));
}
});
}
}
}
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));
});
}
});
}
// Process non text child nodes
if (descend) {
eachNode(element, function(child, i){
template.addChild(i, self.templatize(child, i));
});
}
return template.empty() ? null : template;
}
};
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){

View file

@ -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']);
}];
}

View file

@ -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();
}
})();
runEval = true;
//at the end of each eval, push cookies
//TODO: this should happen before the "delayed" watches fire, because if some cookies are not
// strings or browser refuses to store some cookies, we update the model in the push fn.
$rootScope.$watch(push);
return cookies;
/**
* Pushes all the cookies from the service to the browser and verifies if all cookies were stored.
*/
function push() {
var name,
value,
browserCookies,
updated;
//delete any cookies deleted in $cookies
for (name in lastCookies) {
if (isUndefined(cookies[name])) {
$browser.cookies(name, undefined);
//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();
}
}
})();
//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];
runEval = true;
//at the end of each eval, push cookies
//TODO: this should happen before the "delayed" watches fire, because if some cookies are not
// strings or browser refuses to store some cookies, we update the model in the push fn.
$rootScope.$watch(push);
return cookies;
/**
* Pushes all the cookies from the service to the browser and verifies if all cookies were stored.
*/
function push() {
var name,
value,
browserCookies,
updated;
//delete any cookies deleted in $cookies
for (name in lastCookies) {
if (isUndefined(cookies[name])) {
$browser.cookies(name, undefined);
}
} else if (value !== lastCookies[name]) {
$browser.cookies(name, value);
updated = true;
}
}
//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];
//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 {
cookies[name] = browserCookies[name];
delete cookies[name];
}
} else if (value !== lastCookies[name]) {
$browser.cookies(name, value);
updated = true;
}
}
//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];
}
updated = true;
}
}
}
}
}
}, ['$rootScope', '$browser']);
}];
}

View file

@ -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;
}];
}

View file

@ -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);
}];
}

View file

@ -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);
};
}];
}

View file

@ -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 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);
}
/**
* @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, {})));
}
}, ['$rootScope']);
/**
* @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 ]`.
*/
function propertiesUpdate(widget) {
widget.$valid = !(widget.$invalid =
!(widget.$readonly || widget.$disabled || equals(widget.$error, {})));
}
/**
* @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 $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 $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 $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 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 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#$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#$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#$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#$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#$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#$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.
*/
function FormController() {
var form = this,
$error = form.$error = {};
/**
* @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`.
*/
form.$on('$destroy', function(event){
var widget = event.targetScope;
if (widget.$widgetId) {
delete form[widget.$widgetId];
}
forEach($error, removeWidget, widget);
});
function FormController() {
var form = this,
$error = form.$error = {};
form.$on('$valid', function(event, error){
var widget = event.targetScope;
delete widget.$error[error];
propertiesUpdate(widget);
removeWidget($error[error], error, widget);
});
form.$on('$destroy', function(event){
var widget = event.targetScope;
if (widget.$widgetId) {
delete form[widget.$widgetId];
}
forEach($error, removeWidget, 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);
}
}
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 = [];
}
queue.push(widget);
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;
}
/**
* @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);
// assign the widgetModel to the form
if (alias && !form.hasOwnProperty(alias)) {
form[alias] = widget;
widget.$widgetId = alias;
} else {
$error[errorKey] = queue = [];
alias = null;
}
queue.push(widget);
propertiesUpdate(form);
}
return widget;
};
}
/**
* @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);
// assign the widgetModel to the form
if (alias && !form.hasOwnProperty(alias)) {
form[alias] = widget;
widget.$widgetId = alias;
} else {
alias = null;
}
return widget;
};

View file

@ -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
NUMBER_FORMATS: {
DECIMAL_SEP: '.',
GROUP_SEP: ',',
PATTERNS: [
{ // Decimal Pattern
minInt: 1,
minFrac: 0,
maxFrac: 3,
posPre: '',
posSuf: '',
negPre: '-',
negSuf: '',
gSize: 3,
lgSize: 3
},{ //Currency Pattern
minInt: 1,
minFrac: 2,
maxFrac: 2,
posPre: '\u00A4',
posSuf: '',
negPre: '(\u00A4',
negSuf: ')',
gSize: 3,
lgSize: 3
}
],
CURRENCY_SYM: '$'
},
DATETIME_FORMATS: {
MONTH: 'January,February,March,April,May,June,July,August,September,October,November,December'
.split(','),
SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','),
DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','),
SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','),
AMPMS: ['AM','PM'],
medium: 'MMM d, y h:mm:ss a',
short: 'M/d/yy h:mm a',
fullDate: 'EEEE, MMMM d, y',
longDate: 'MMMM d, y',
mediumDate: 'MMM d, y',
shortDate: 'M/d/yy',
mediumTime: 'h:mm:ss a',
shortTime: 'h:mm a'
},
pluralCat: function(num) {
if (num === 1) {
return 'one';
}
],
CURRENCY_SYM: '$'
},
DATETIME_FORMATS: {
MONTH: 'January,February,March,April,May,June,July,August,September,October,November,December'
.split(','),
SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','),
DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','),
SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','),
AMPMS: ['AM','PM'],
medium: 'MMM d, y h:mm:ss a',
short: 'M/d/yy h:mm a',
fullDate: 'EEEE, MMMM d, y',
longDate: 'MMMM d, y',
mediumDate: 'MMM d, y',
shortDate: 'M/d/yy',
mediumTime: 'h:mm:ss a',
shortTime: 'h:mm a'
},
pluralCat: function(num) {
if (num === 1) {
return 'one';
return 'other';
}
return 'other';
}
};
};
});
}

View file

@ -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();
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();
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;
$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 (event.ctrlKey || event.metaKey || event.which == 2) return;
var elm = jqLite(event.target);
// traverse the DOM up to find first A tag
while (elm.length && lowercase(elm[0].nodeName) !== 'a') {
elm = elm.parent();
if ($locationConfig.html5Mode) {
if ($sniffer.history) {
currentUrl = new LocationUrl(convertToHtml5Url(initUrl, basePath, hashPrefix), pathPrefix);
} else {
currentUrl = new LocationHashbangUrl(convertToHashbangUrl(initUrl, basePath, hashPrefix),
hashPrefix);
}
var href = elm.attr('href');
if (!href || isDefined(elm.attr('ng:ext-link')) || elm.attr('target')) return;
// link rewriting
var u = currentUrl,
absUrlPrefix = composeProtocolHostPort(u.protocol(), u.host(), u.port()) + pathPrefix;
// remove same domain from full url links (IE7 always returns full hrefs)
href = href.replace(absUrlPrefix, '');
$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
// link to different domain (or base path)
if (href.substr(0, 4) == 'http') return;
if (event.ctrlKey || event.metaKey || event.which == 2) return;
// remove pathPrefix from absolute links
href = href.indexOf(pathPrefix) === 0 ? href.substr(pathPrefix.length) : href;
var elm = jqLite(event.target);
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);
}
// traverse the DOM up to find first A tag
while (elm.length && lowercase(elm[0].nodeName) !== 'a') {
elm = elm.parent();
}
// rewrite hashbang url <> html5 url
if (currentUrl.absUrl() != initUrl) {
$browser.url(currentUrl.absUrl(), true);
}
var href = elm.attr('href');
if (!href || isDefined(elm.attr('ng:ext-link')) || elm.attr('target')) return;
// update $location when $browser url changes
$browser.onUrlChange(function(newUrl) {
if (currentUrl.absUrl() != newUrl) {
currentUrl.$$parse(newUrl);
$rootScope.$apply();
}
});
// remove same domain from full url links (IE7 always returns full hrefs)
href = href.replace(absUrlPrefix, '');
// 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;
// link to different domain (or base path)
if (href.substr(0, 4) == 'http') return;
// remove pathPrefix from absolute links
href = href.indexOf(pathPrefix) === 0 ? href.substr(pathPrefix.length) : href;
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: ''
};
};
});
}

View file

@ -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;
}
}
}, ['$window']);
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;
}
}
}];
}

View file

@ -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);
}];
}

View file

@ -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;
}
};
$rootScope.$watch(function() { return dirty + $location.url(); }, updateRoute);
return $route;
/////////////////////////////////////////////////////
function switchRouteMatcher(on, when) {
// TODO(i): this code is convoluted and inefficient, we should construct the route matching
// regex only once and then reuse it
var regex = '^' + when.replace(/([\.\\\(\)\^\$])/g, "\\$1") + '$',
params = [],
dst = {};
forEach(when.split(/\W/), function(param) {
if (param) {
var paramRegExp = new RegExp(":" + param + "([\\W])");
if (regex.match(paramRegExp)) {
regex = regex.replace(paramRegExp, "([^\\/]*)$1");
params.push(param);
}
}
});
var match = on.match(new RegExp(regex));
if (match) {
forEach(params, function(name, index) {
dst[name] = match[index + 1];
});
}
return match ? dst : null;
}
function updateRoute() {
var next = parseRoute(),
last = $route.current,
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();
} else {
$location.url(next.redirectTo(next.pathParams, $location.path(), $location.search()))
.replace();
/**
* @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);
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);
}
} else {
copy(next.params, $routeParams);
(Controller = next.controller) && inferInjectionArgs(Controller);
next.scope = parentScope.$new(Controller);
}
});
var match = on.match(new RegExp(regex));
if (match) {
forEach(params, function(name, index) {
dst[name] = match[index + 1];
});
}
$rootScope.$broadcast('$afterRouteChange', next, last);
return match ? dst : null;
}
}
function updateRoute() {
var next = parseRoute(),
last = $route.current,
Controller;
/**
* @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);
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 {
var segmentMatch = segment.match(/(\w+)(.*)/);
var key = segmentMatch[1];
result.push(params[key]);
result.push(segmentMatch[2] || '');
delete params[key];
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 {
copy(next.params, $routeParams);
(Controller = next.controller) && inferInjectionArgs(Controller);
next.scope = parentScope.$new(Controller);
}
}
$rootScope.$broadcast('$afterRouteChange', next, last);
}
});
return result.join('');
}
}
}, ['$rootScope', '$location', '$routeParams']);
/**
* @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('');
}
}];
}

View file

@ -25,6 +25,6 @@
* $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
* </pre>
*/
angularService('$routeParams', function() {
return {};
});
function $RouteParamsProvider() {
this.$get = valueFn({});
}

File diff suppressed because it is too large Load diff

View file

@ -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)
};
}];
}

View file

@ -23,4 +23,6 @@
</doc:scenario>
</doc:example>
*/
angularServiceInject("$window", bind(window, identity, window));
function $WindowProvider(){
this.$get = valueFn(window);
}

View file

@ -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;
}
});
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);
}
} 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);
}
} catch(e) {
$log.error(e);
}
});
noop();
});
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);
}
});
};
$rootScope.$watch(function() { bulkXHR.flush(); });
return bulkXHR;
}, ['$rootScope', '$xhr', '$xhr.error', '$log']);
}
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);
}
});
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);
}
});
noop();
});
}
});
};
$rootScope.$watch(function() { bulkXHR.flush(); });
return bulkXHR;
}];
}

View file

@ -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];
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});
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);
}
} catch(e) {
$log.error(e);
}
});
});
}
});
},
function(status, response) {
var errors = inflight[url].errors,
successes = inflight[url].successes;
delete inflight[url];
} else {
cache.data = {};
cache.delegate(method, url, post, success, error);
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);
}
});
});
}
} 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;
}];
}

View file

@ -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);
};
}];
}

View file

@ -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);
}
$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);
function xhr(method, url, post, success, error) {
if (isFunction(post)) {
error = success;
success = post;
post = null;
}
if (post && isObject(post)) {
post = toJson(post);
}
}, extend({'X-XSRF-TOKEN': $browser.cookies()['XSRF-TOKEN']},
xhrHeaderDefaults.common,
xhrHeaderDefaults[lowercase(method)]));
}
xhr.defaults = {headers: xhrHeaderDefaults};
$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);
}
}, extend({'X-XSRF-TOKEN': $browser.cookies()['XSRF-TOKEN']},
xhrHeaderDefaults.common,
xhrHeaderDefaults[lowercase(method)]));
}
return xhr;
}, ['$rootScope', '$browser', '$xhr.error', '$log']);
xhr.defaults = {headers: xhrHeaderDefaults};
return xhr;
}];
}

View file

@ -382,25 +382,6 @@ describe('angular', function() {
expect(fake).toEqual('new');
}));
it('should not preserve properties on override', function() {
angular.service('fake', {$one: true}, {$two: true}, {three: true});
var result = angular.service('fake', {$four: true});
expect(result.$one).toBeUndefined();
expect(result.$two).toBeUndefined();
expect(result.three).toBeUndefined();
expect(result.$four).toBe(true);
});
it('should not preserve non-angular properties on override', function() {
angular.service('fake', {one: true}, {two: true});
var result = angular.service('fake', {third: true});
expect(result.one).not.toBeDefined();
expect(result.two).not.toBeDefined();
expect(result.third).toBeTruthy();
});
it('should inject dependencies specified by $inject and ignore function argument name', function() {
expect(angular.injector(function($provide){
$provide.factory('svc1', function() { return 'svc1'; });

View file

@ -222,8 +222,8 @@ describe('Binder', function() {
}));
it('IfTextBindingThrowsErrorDecorateTheSpan', inject(
function($provide){
$provide.factory('$exceptionHandler', $exceptionHandlerMockFactory);
function($exceptionHandlerProvider){
$exceptionHandlerProvider.mode('log');
},
function($rootScope, $exceptionHandler, $compile) {
$compile('<div>{{error.throw()}}</div>', null, true)($rootScope);
@ -245,8 +245,8 @@ describe('Binder', function() {
})
);
it('IfAttrBindingThrowsErrorDecorateTheAttribute', inject(function($provide){
$provide.factory('$exceptionHandler', $exceptionHandlerMockFactory);
it('IfAttrBindingThrowsErrorDecorateTheAttribute', inject(function($exceptionHandlerProvider){
$exceptionHandlerProvider.mode('log');
}, function($rootScope, $exceptionHandler, $compile) {
$compile('<div attr="before {{error.throw()}} after"></div>', null, true)($rootScope);
var errorLogs = $exceptionHandler.errors;
@ -387,8 +387,8 @@ describe('Binder', function() {
}));
it('ActionOnAHrefThrowsError', inject(
function($provide){
$provide.factory('$exceptionHandler', $exceptionHandlerMockFactory);
function($exceptionHandlerProvider){
$exceptionHandlerProvider.mode('log');
},
function($rootScope, $exceptionHandler, $compile) {
var input = $compile('<a ng:click="action()">Add Phone</a>')($rootScope);
@ -471,8 +471,8 @@ describe('Binder', function() {
}));
it('ItShouldDisplayErrorWhenActionIsSyntacticlyIncorrect', inject(
function($provide){
$provide.factory('$exceptionHandler', $exceptionHandlerMockFactory);
function($exceptionHandlerProvider){
$exceptionHandlerProvider.mode('log');
},
function($rootScope, $exceptionHandler, $log, $compile) {
var element = $compile(

View file

@ -200,10 +200,10 @@ describe('filter', function() {
describe('date', function() {
var morning = new TzDate(+5, '2010-09-03T12:05:08.000Z'); //7am
var noon = new TzDate(+5, '2010-09-03T17:05:08.000Z'); //12pm
var midnight = new TzDate(+5, '2010-09-03T05:05:08.000Z'); //12am
var earlyDate = new TzDate(+5, '0001-09-03T05:05:08.000Z');
var morning = new angular.mock.TzDate(+5, '2010-09-03T12:05:08.000Z'); //7am
var noon = new angular.mock.TzDate(+5, '2010-09-03T17:05:08.000Z'); //12pm
var midnight = new angular.mock.TzDate(+5, '2010-09-03T05:05:08.000Z'); //12am
var earlyDate = new angular.mock.TzDate(+5, '0001-09-03T05:05:08.000Z');
var context, date;

View file

@ -239,6 +239,12 @@ describe('injector', function() {
expect($injector('a')).toEqual('abc');
});
it('should error on invalid madule name', function(){
expect(function(){
createInjector(['IDontExist'], {});
}).toThrow("Module 'IDontExist' is not defined!");
});
describe('$provide', function() {
describe('value', function(){

View file

@ -8,90 +8,90 @@ describe('mocks', function() {
}
it('should take millis as constructor argument', function() {
expect(new TzDate(0, 0).getTime()).toBe(0);
expect(new TzDate(0, 1283555108000).getTime()).toBe(1283555108000);
expect(new angular.mock.TzDate(0, 0).getTime()).toBe(0);
expect(new angular.mock.TzDate(0, 1283555108000).getTime()).toBe(1283555108000);
});
it('should take dateString as constructor argument', function() {
expect(new TzDate(0, '1970-01-01T00:00:00.000Z').getTime()).toBe(0);
expect(new TzDate(0, '2010-09-03T23:05:08.023Z').getTime()).toBe(1283555108023);
expect(new angular.mock.TzDate(0, '1970-01-01T00:00:00.000Z').getTime()).toBe(0);
expect(new angular.mock.TzDate(0, '2010-09-03T23:05:08.023Z').getTime()).toBe(1283555108023);
});
it('should fake getLocalDateString method', function() {
//0 in -3h
var t0 = new TzDate(-3, 0);
var t0 = new angular.mock.TzDate(-3, 0);
expect(t0.toLocaleDateString()).toMatch('1970');
//0 in +0h
var t1 = new TzDate(0, 0);
var t1 = new angular.mock.TzDate(0, 0);
expect(t1.toLocaleDateString()).toMatch('1970');
//0 in +3h
var t2 = new TzDate(3, 0);
var t2 = new angular.mock.TzDate(3, 0);
expect(t2.toLocaleDateString()).toMatch('1969');
});
it('should fake getHours method', function() {
//0 in -3h
var t0 = new TzDate(-3, 0);
var t0 = new angular.mock.TzDate(-3, 0);
expect(t0.getHours()).toBe(3);
//0 in +0h
var t1 = new TzDate(0, 0);
var t1 = new angular.mock.TzDate(0, 0);
expect(t1.getHours()).toBe(0);
//0 in +3h
var t2 = new TzDate(3, 0);
var t2 = new angular.mock.TzDate(3, 0);
expect(t2.getHours()).toMatch(21);
});
it('should fake getMinutes method', function() {
//0:15 in -3h
var t0 = new TzDate(-3, minutes(15));
var t0 = new angular.mock.TzDate(-3, minutes(15));
expect(t0.getMinutes()).toBe(15);
//0:15 in -3.25h
var t0a = new TzDate(-3.25, minutes(15));
var t0a = new angular.mock.TzDate(-3.25, minutes(15));
expect(t0a.getMinutes()).toBe(30);
//0 in +0h
var t1 = new TzDate(0, minutes(0));
var t1 = new angular.mock.TzDate(0, minutes(0));
expect(t1.getMinutes()).toBe(0);
//0:15 in +0h
var t1a = new TzDate(0, minutes(15));
var t1a = new angular.mock.TzDate(0, minutes(15));
expect(t1a.getMinutes()).toBe(15);
//0:15 in +3h
var t2 = new TzDate(3, minutes(15));
var t2 = new angular.mock.TzDate(3, minutes(15));
expect(t2.getMinutes()).toMatch(15);
//0:15 in +3.25h
var t2a = new TzDate(3.25, minutes(15));
var t2a = new angular.mock.TzDate(3.25, minutes(15));
expect(t2a.getMinutes()).toMatch(0);
});
it('should fake getSeconds method', function() {
//0 in -3h
var t0 = new TzDate(-3, 0);
var t0 = new angular.mock.TzDate(-3, 0);
expect(t0.getSeconds()).toBe(0);
//0 in +0h
var t1 = new TzDate(0, 0);
var t1 = new angular.mock.TzDate(0, 0);
expect(t1.getSeconds()).toBe(0);
//0 in +3h
var t2 = new TzDate(3, 0);
var t2 = new angular.mock.TzDate(3, 0);
expect(t2.getSeconds()).toMatch(0);
});
it('should create a date representing new year in Bratislava', function() {
var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00.000Z');
var newYearInBratislava = new angular.mock.TzDate(-1, '2009-12-31T23:00:00.000Z');
expect(newYearInBratislava.getTimezoneOffset()).toBe(-60);
expect(newYearInBratislava.getFullYear()).toBe(2010);
expect(newYearInBratislava.getMonth()).toBe(0);
@ -103,7 +103,7 @@ describe('mocks', function() {
it('should delegate all the UTC methods to the original UTC Date object', function() {
//from when created from string
var date1 = new TzDate(-1, '2009-12-31T23:00:00.000Z');
var date1 = new angular.mock.TzDate(-1, '2009-12-31T23:00:00.000Z');
expect(date1.getUTCFullYear()).toBe(2009);
expect(date1.getUTCMonth()).toBe(11);
expect(date1.getUTCDate()).toBe(31);
@ -113,7 +113,7 @@ describe('mocks', function() {
//from when created from millis
var date2 = new TzDate(-1, angular.String.toDate('2009-12-31T23:00:00.000Z').getTime());
var date2 = new angular.mock.TzDate(-1, angular.String.toDate('2009-12-31T23:00:00.000Z').getTime());
expect(date2.getUTCFullYear()).toBe(2009);
expect(date2.getUTCMonth()).toBe(11);
expect(date2.getUTCDate()).toBe(31);
@ -124,16 +124,20 @@ describe('mocks', function() {
it('should throw error when no third param but toString called', function() {
expect(function() { new TzDate(0,0).toString(); }).
expect(function() { new angular.mock.TzDate(0,0).toString(); }).
toThrow('Method \'toString\' is not implemented in the TzDate mock');
});
});
describe('$log mock', function() {
describe('$log', function() {
var $log;
beforeEach(function() {
$log = MockLogFactory();
});
beforeEach(inject(['$log', function(log) {
$log = log;
}]));
afterEach(inject(function($log){
$log.reset();
}));
it('should provide log method', function() {
expect(function() { $log.log(''); }).not.toThrow();
@ -170,14 +174,48 @@ describe('mocks', function() {
$log.error('fake log');
expect($log.error.logs).toContain(['fake log']);
});
it('should assertEmpty', function(){
try {
$log.error(Error('MyError'));
$log.warn(Error('MyWarn'));
$log.info(Error('MyInfo'));
$log.log(Error('MyLog'));
$log.assertEmpty();
} catch (error) {
error = error.message || error;
expect(error).toMatch(/Error: MyError/m);
expect(error).toMatch(/Error: MyWarn/m);
expect(error).toMatch(/Error: MyInfo/m);
expect(error).toMatch(/Error: MyLog/m);
} finally {
$log.reset();
}
});
it('should reset state', function(){
$log.error(Error('MyError'));
$log.warn(Error('MyWarn'));
$log.info(Error('MyInfo'));
$log.log(Error('MyLog'));
$log.reset();
var passed = false;
try {
$log.assertEmpty(); // should not throw error!
passed = true;
} catch (e) {
passed = e;
}
expect(passed).toBe(true);
});
});
describe('defer', function() {
var browser, log;
beforeEach(function() {
browser = new MockBrowser();
beforeEach(inject(function($browser) {
browser = $browser;
log = '';
});
}));
function logFn(text){ return function() {
log += text +';';
@ -229,5 +267,20 @@ describe('mocks', function() {
it('should rethrow exceptions', inject(function($exceptionHandler) {
expect(function() { $exceptionHandler('myException'); }).toThrow('myException');
}));
it('should log exceptions', inject(function($exceptionHandlerProvider){
$exceptionHandlerProvider.mode('log');
var $exceptionHandler = $exceptionHandlerProvider.$get();
$exceptionHandler('MyError');
expect($exceptionHandler.errors).toEqual(['MyError']);
}));
it('should thorw on wrong argument', inject(function($exceptionHandlerProvider) {
expect(function() {
$exceptionHandlerProvider.mode('XXX');
}).toThrow("Unknown mode 'XXX', only 'log'/'rethrow' modes are allowed!");
}));
});
});

View file

@ -1,69 +0,0 @@
'use strict';
/**
* Mock implementation of {@link angular.service.$log} that gathers all logged messages in arrays
* (one array per logging level). These arrays are exposed as `logs` property of each of the
* level-specific log function, e.g. for level `error` the array is exposed as
* `$logMock.error.logs`
*
* Please note that this is not a factory function, but rather the actual mock instance. This is
* important because it allows `beforeEach` and `afterEach` test hooks to clean up or check the
* state of `logs` arrays in between tests.
*
* Exposing the instance in this way makes this mock a singleton, which means that the instance
* becomes global state for tests. To mitigate the issue, each time the `$log` mock is registered
* with the injector, a check is performed to ensure that there are no pending logs in `logs`
* arrays. This means that if a message is logged via $log during a test, the `logs` array must be
* emptied before the test is finished. `Array#shift` method can be used for this purpose as
* follows:
*
* <pre>
* it('should do some good', function() {
* var scope = angular.scope(),
* $log = scope.$service('$log');
*
* //do something that triggers a message to be logged
* expect($log.error.logs.shift()).toEqual(['message', 'arg1', 'arg2']);
* });
* </pre>
*
* See {@link angular.mock} for more info on angular mocks.
*/
var $logMock = {
log: function() { $logMock.log.logs.push(concat([], arguments, 0)); },
warn: function() { $logMock.warn.logs.push(concat([], arguments, 0)); },
info: function() { $logMock.info.logs.push(concat([], arguments, 0)); },
error: function() { $logMock.error.logs.push(concat([], arguments, 0)); }
};
$logMock.log.logs = [];
$logMock.warn.logs = [];
$logMock.info.logs = [];
$logMock.error.logs = [];
angular.service('$log', function() {
return $logMock;
});
/**
* Factory that returns mock implementation of {@link angular.service.$exceptionHandler} that
* gathers all errors in an array. This array is exposed as `errors` property of the mock and can be
* accessed as `$exceptionHandler.errors`.
*
* Note that this factory is not registered with angular's injector by default (as opposed to
* `$logMock`). It is your responsibility to register this factory when you need it. Typically like
* this:
*
* <pre>
* var scope = angular.scope(null, {'$exceptionHandler': $exceptionHandlerMockFactory()});
* </pre>
*
*/
function $exceptionHandlerMockFactory() {
var mockHandler = function(e) {
mockHandler.errors.push(e);
};
mockHandler.errors = [];
return mockHandler;
}

View file

@ -3,7 +3,7 @@
describe('$cookies', function() {
beforeEach(inject(function($provide) {
$provide.factory('$browser', function(){
return angular.extend(new MockBrowser(), {cookieHash: {preexisting:'oldCookie'}});
return angular.extend(new angular.mock.$Browser(), {cookieHash: {preexisting:'oldCookie'}});
});
}));

View file

@ -5,11 +5,9 @@ describe('$exceptionHandler', function() {
it('should log errors', inject(
function($provide){
$provide.factory('$exceptionHandler', $exceptionHandlerFactory);
$provide.value('$log', $logMock);
$provide.service('$exceptionHandler', $ExceptionHandlerProvider);
},
function($log, $exceptionHandler) {
$log.error.rethrow = false;
$exceptionHandler('myError');
expect($log.error.logs.shift()).toEqual(['myError']);
}

View file

@ -2,7 +2,7 @@
describe('$locale', function() {
var $locale = angular.service('$locale')();
var $locale = new $LocaleProvider().$get();
it('should have locale id set to en-us', function() {
expect($locale.id).toBe('en-us');

View file

@ -12,7 +12,7 @@ describe('$log', function() {
beforeEach(inject(function($provide){
$window = {};
logger = '';
$provide.factory('$log', $logFactory);
$provide.service('$log', $LogProvider);
$provide.value('$exceptionHandler', rethrow);
$provide.value('$window', $window);
}));
@ -68,7 +68,7 @@ describe('$log', function() {
e.line = undefined;
e.stack = undefined;
$log = $logFactory({console:{error:function() {
$log = new $LogProvider().$get[1]({console:{error:function() {
errorArgs = arguments;
}}});
});

View file

@ -2,8 +2,8 @@
describe('Scope', function() {
beforeEach(inject(function($provide) {
$provide.factory('$exceptionHandler', $exceptionHandlerMockFactory);
beforeEach(inject(function($exceptionHandlerProvider) {
$exceptionHandlerProvider.mode('log');
}));
@ -121,12 +121,12 @@ describe('Scope', function() {
expect(spy).wasCalled();
}));
it('should delegate exceptions', inject(function($rootScope, $exceptionHandler) {
it('should delegate exceptions', inject(function($rootScope, $exceptionHandler, $log) {
$rootScope.$watch('a', function() {throw new Error('abc');});
$rootScope.a = 1;
$rootScope.$digest();
expect($exceptionHandler.errors[0].message).toEqual('abc');
$logMock.error.logs.length = 0;
$log.assertEmpty();
}));
@ -422,7 +422,7 @@ describe('Scope', function() {
}));
it('should catch exceptions', inject(function($rootScope, $exceptionHandler) {
it('should catch exceptions', inject(function($rootScope, $exceptionHandler, $log) {
var log = '';
var child = $rootScope.$new();
$rootScope.$watch('a', function(scope, a){ log += '1'; });
@ -430,7 +430,7 @@ describe('Scope', function() {
child.$apply(function() { throw new Error('MyError'); });
expect(log).toEqual('1');
expect($exceptionHandler.errors[0].message).toEqual('MyError');
$logMock.error.logs.shift();
$log.error.logs.shift();
}));

View file

@ -3,7 +3,7 @@
describe('$sniffer', function() {
function sniffer($window) {
return angular.service('$sniffer')($window);
return new $SnifferProvider().$get[1]($window);
}
describe('history', function() {

View file

@ -77,18 +77,13 @@ beforeEach(function() {
}
});
$logMock.log.logs = [];
$logMock.warn.logs = [];
$logMock.info.logs = [];
$logMock.error.logs = [];
});
function inject(){
var blockFns = sliceArgs(arguments);
return function(){
var spec = this;
spec.$injector = spec.$injector || angular.injector('NG');
spec.$injector = spec.$injector || angular.injector('NG', 'NG_MOCK');
angular.forEach(blockFns, function(fn){
spec.$injector.invoke(spec, fn);
});
@ -96,30 +91,12 @@ function inject(){
}
afterEach(inject(function($rootScope) {
afterEach(inject(function($rootScope, $log) {
// release the injector
dealoc($rootScope);
// check $log mock
forEach(['error', 'warn', 'info', 'log'], function(logLevel) {
if ($logMock[logLevel].logs.length) {
forEach($logMock[logLevel].logs, function(log) {
forEach(log, function deleteStack(logItem) {
if (logItem instanceof Error) {
dump(logItem.stack);
delete logItem.stack;
delete logItem.arguments;
} else {
dump(logItem);
}
});
});
throw new Error("Exprected $log." + logLevel + ".logs array to be empty. " +
"Either a message was logged unexpectedly, or an expected log message was not checked " +
"and removed. Array contents: " + toJson($logMock[logLevel].logs));
}
});
$log.assertEmpty && $log.assertEmpty();
clearJqCache();
}));

View file

@ -448,12 +448,12 @@ describe('widget: input', function() {
});
it('should report error on assignment error', function() {
it('should report error on assignment error', inject(function($log) {
expect(function() {
compile('<input type="text" ng:model="throw \'\'">');
}).toThrow("Syntax Error: Token '''' is an unexpected token at column 7 of the expression [throw ''] starting at [''].");
$logMock.error.logs.shift();
});
$log.error.logs.shift();
}));
});

View file

@ -223,12 +223,12 @@ describe("widget", function() {
expect(element.text()).toEqual('name:value;');
}));
it('should error on wrong parsing of ng:repeat', inject(function($rootScope, $compile) {
it('should error on wrong parsing of ng:repeat', inject(function($rootScope, $compile, $log) {
expect(function() {
var element = $compile('<ul><li ng:repeat="i dont parse"></li></ul>')($rootScope);
}).toThrow("Expected ng:repeat in form of '_item_ in _collection_' but got 'i dont parse'.");
$logMock.error.logs.shift();
$log.error.logs.shift();
}));
it('should expose iterator offset as $index when iterating over arrays', inject(function($rootScope, $compile) {
@ -487,9 +487,9 @@ describe("widget", function() {
}));
it('should be possible to nest ng:view in ng:include', inject(function() {
var injector = angular.injector('NG');
var injector = angular.injector('NG', 'NG_MOCK');
var myApp = injector('$rootScope');
var $browser = myApp.$service('$browser');
var $browser = injector('$browser');
$browser.xhr.expectGET('includePartial.html').respond('view: <ng:view></ng:view>');
injector('$location').path('/foo');