X-Git-Url: http://git.rot13.org/?a=blobdiff_plain;f=app%2Flib%2Fangular%2Fangular.js;h=9e4047110d92669a14d9d7079f03baea7735cb1d;hb=4b5b4681e43a95d95e26af714f2992909892a3ff;hp=0e4138b1d1b942edfa6ffde7115613e5a09ba4df;hpb=5c742c31fe9d1408923d5f138a6b56cc5284fba4;p=angular-drzb diff --git a/app/lib/angular/angular.js b/app/lib/angular/angular.js index 0e4138b..9e40471 100644 --- a/app/lib/angular/angular.js +++ b/app/lib/angular/angular.js @@ -1,5 +1,5 @@ /** - * @license AngularJS v1.0.2 + * @license AngularJS v1.0.3 * (c) 2010-2012 Google, Inc. http://angularjs.org * License: MIT */ @@ -794,7 +794,7 @@ function toKeyValue(obj) { /** - * We need our custom mehtod because encodeURIComponent is too agressive and doesn't follow + * We need our custom method because encodeURIComponent is too agressive and doesn't follow * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path * segments: * segment = *pchar @@ -838,7 +838,7 @@ function encodeUriQuery(val, pctEncodeSpaces) { * @name ng.directive:ngApp * * @element ANY - * @param {angular.Module} ngApp on optional application + * @param {angular.Module} ngApp an optional application * {@link angular.module module} name to load. * * @description @@ -1015,7 +1015,7 @@ function setupModuleLoader(window) { * * # Module * - * A module is a collocation of services, directives, filters, and configure information. Module + * A module is a collocation of services, directives, filters, and configuration information. Module * is used to configure the {@link AUTO.$injector $injector}. * *
@@ -1045,7 +1045,7 @@ function setupModuleLoader(window) { * @param {!string} name The name of the module to create or retrieve. * @param {Array.* * At first it might not be obvious why this extra complexity is worth the trouble. The payoff @@ -6848,7 +6875,7 @@ function $RouteProvider(){ * Object properties: * * - `controller` â `{(string|function()=}` â Controller fn that should be associated with newly - * created scope or the name of a {@link angular.Module#controller registered controller} + * created scope or the name of a {@link angular.Module#controller registered controller} * if passed as a string. * - `template` â `{string=}` â html template as a string that should be used by * {@link ng.directive:ngView ngView} or @@ -6859,7 +6886,7 @@ function $RouteProvider(){ * - `resolve` - `{Object.=} requires If specified then new module is being created. If unspecified then the * the module is being retrieved for further configuration. - * @param {Function} configFn Option configuration function for the module. Same as + * @param {Function} configFn Optional configuration function for the module. Same as * {@link angular.Module#config Module#config()}. * @returns {module} new module with the {@link angular.Module} api. */ @@ -1200,8 +1200,8 @@ function setupModuleLoader(window) { * @param {Function} initializationFn Execute this function after injector creation. * Useful for application initialization. * @description - * Use this method to register work which needs to be performed when the injector with - * with the current module is finished loading. + * Use this method to register work which should be performed when the injector is done + * loading all modules. */ run: function(block) { runBlocks.push(block); @@ -1247,11 +1247,11 @@ function setupModuleLoader(window) { * - `codeName` â `{string}` â Code name of the release, such as "jiggling-armfat". */ var version = { - full: '1.0.2', // all of these placeholder strings will be replaced by rake's + full: '1.0.3', // all of these placeholder strings will be replaced by rake's major: 1, // compile task minor: 0, - dot: 2, - codeName: 'debilitating-awesomeness' + dot: 3, + codeName: 'bouncy-thunder' }; @@ -1418,6 +1418,7 @@ function publishExternalAPI(angular){ * - [replaceWith()](http://api.jquery.com/replaceWith/) * - [text()](http://api.jquery.com/text/) * - [toggleClass()](http://api.jquery.com/toggleClass/) + * - [triggerHandler()](http://api.jquery.com/triggerHandler/) - Doesn't pass native event objects to handlers. * - [unbind()](http://api.jquery.com/unbind/) * - [val()](http://api.jquery.com/val/) * - [wrap()](http://api.jquery.com/wrap/) @@ -1493,12 +1494,7 @@ function JQLitePatchJQueryRemove(name, dispatchThis) { for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) { element = jqLite(set[setIndex]); if (fireEvent) { - events = element.data('events'); - if ( (fns = events && events.$destroy) ) { - forEach(fns, function(fn){ - fn.handler(); - }); - } + element.triggerHandler('$destroy'); } else { fireEvent = !fireEvent; } @@ -1630,9 +1626,9 @@ function JQLiteHasClass(element, selector) { indexOf( " " + selector + " " ) > -1); } -function JQLiteRemoveClass(element, selector) { - if (selector) { - forEach(selector.split(' '), function(cssClass) { +function JQLiteRemoveClass(element, cssClasses) { + if (cssClasses) { + forEach(cssClasses.split(' '), function(cssClass) { element.className = trim( (" " + element.className + " ") .replace(/[\n\t]/g, " ") @@ -1642,9 +1638,9 @@ function JQLiteRemoveClass(element, selector) { } } -function JQLiteAddClass(element, selector) { - if (selector) { - forEach(selector.split(' '), function(cssClass) { +function JQLiteAddClass(element, cssClasses) { + if (cssClasses) { + forEach(cssClasses.split(' '), function(cssClass) { if (!JQLiteHasClass(element, cssClass)) { element.className = trim(element.className + ' ' + trim(cssClass)); } @@ -2092,7 +2088,15 @@ forEach({ return element.getElementsByTagName(selector); }, - clone: JQLiteClone + clone: JQLiteClone, + + triggerHandler: function(element, eventName) { + var eventFns = (JQLiteExpandoStore(element, 'events') || {})[eventName]; + + forEach(eventFns, function(fn) { + fn.call(element, null); + }); + } }, function(fn, name){ /** * chaining functions @@ -2210,6 +2214,16 @@ HashQueueMap.prototype = { return array.shift(); } } + }, + + /** + * return the first item without deleting it + */ + peek: function(key) { + var array = this[hashKey(key)]; + if (array) { + return array[0]; + } } }; @@ -2233,7 +2247,7 @@ HashQueueMap.prototype = { * // create an injector * var $injector = angular.injector(['ng']); * - * // use the injector to kick of your application + * // use the injector to kick off your application * // use the type inference to auto inject arguments, or use implicit injection * $injector.invoke(function($rootScope, $compile, $document){ * $compile($document)($rootScope); @@ -2253,7 +2267,7 @@ HashQueueMap.prototype = { var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; var FN_ARG_SPLIT = /,/; -var FN_ARG = /^\s*(_?)(.+?)\1\s*$/; +var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; function annotate(fn) { var $inject, @@ -2872,9 +2886,10 @@ function $AnchorScrollProvider() { // does not scroll when user clicks on anchor link that is currently on // (no url change, no $locaiton.hash() change), browser native does scroll if (autoScrollingEnabled) { - $rootScope.$watch(function() {return $location.hash();}, function() { - $rootScope.$evalAsync(scroll); - }); + $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, + function autoScrollWatchAction() { + $rootScope.$evalAsync(scroll); + }); } return scroll; @@ -3258,10 +3273,10 @@ function $BrowserProvider(){ * * - `{object}` `info()` â Returns id, size, and options of cache. * - `{void}` `put({string} key, {*} value)` â Puts a new key-value pair into the cache. - * - `{{*}} `get({string} key) â Returns cached value for `key` or undefined for cache miss. - * - `{void}` `remove({string} key) â Removes a key-value pair from the cache. - * - `{void}` `removeAll() â Removes all cached values. - * - `{void}` `destroy() â Removes references to this cache from $cacheFactory. + * - `{{*}}` `get({string} key)` â Returns cached value for `key` or undefined for cache miss. + * - `{void}` `remove({string} key)` â Removes a key-value pair from the cache. + * - `{void}` `removeAll()` â Removes all cached values. + * - `{void}` `destroy()` â Removes references to this cache from $cacheFactory. * */ function $CacheFactoryProvider() { @@ -3313,6 +3328,8 @@ function $CacheFactoryProvider() { remove: function(key) { var lruEntry = lruHash[key]; + if (!lruEntry) return; + if (lruEntry == freshEnd) freshEnd = lruEntry.p; if (lruEntry == staleEnd) staleEnd = lruEntry.n; link(lruEntry.n,lruEntry.p); @@ -3720,26 +3737,26 @@ function $CompileProvider($provide) { //================================ - function compile($compileNode, transcludeFn, maxPriority) { - if (!($compileNode instanceof jqLite)) { + function compile($compileNodes, transcludeFn, maxPriority) { + if (!($compileNodes instanceof jqLite)) { // jquery always rewraps, where as we need to preserve the original selector so that we can modify it. - $compileNode = jqLite($compileNode); + $compileNodes = jqLite($compileNodes); } // We can not compile top level text elements since text nodes can be merged and we will // not be able to attach scope data to them, so we will wrap them in - forEach($compileNode, function(node, index){ + forEach($compileNodes, function(node, index){ if (node.nodeType == 3 /* text node */) { - $compileNode[index] = jqLite(node).wrap('').parent()[0]; + $compileNodes[index] = jqLite(node).wrap('').parent()[0]; } }); - var compositeLinkFn = compileNodes($compileNode, transcludeFn, $compileNode, maxPriority); - return function(scope, cloneConnectFn){ + var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority); + return function publicLinkFn(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 $linkNode = cloneConnectFn - ? JQLitePrototype.clone.call($compileNode) // IMPORTANT!!! - : $compileNode; + ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!! + : $compileNodes; $linkNode.data('$scope', scope); safeAddClass($linkNode, 'ng-scope'); if (cloneConnectFn) cloneConnectFn($linkNode, scope); @@ -3790,7 +3807,7 @@ function $CompileProvider($provide) { ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement) : null; - childLinkFn = (nodeLinkFn && nodeLinkFn.terminal) + childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || !nodeList[i].childNodes.length) ? null : compileNodes(nodeList[i].childNodes, nodeLinkFn ? nodeLinkFn.transclude : transcludeFn); @@ -3842,13 +3859,14 @@ function $CompileProvider($provide) { /** - * Looks for directives on the given node ands them to the directive collection which is sorted. + * Looks for directives on the given node and adds them to the directive collection which is + * sorted. * - * @param node node to search - * @param directives an array to which the directives are added to. This array is sorted before + * @param node Node to search. + * @param directives An array to which the directives are added to. This array is sorted before * the function returns. - * @param attrs the shared attrs object which is used to populate the normalized attributes. - * @param {number=} max directive priority + * @param attrs The shared attrs object which is used to populate the normalized attributes. + * @param {number=} maxPriority Max directive priority. */ function collectDirectives(node, directives, attrs, maxPriority) { var nodeType = node.nodeType, @@ -3883,7 +3901,7 @@ function $CompileProvider($provide) { // use class as directive className = node.className; - if (isString(className)) { + if (isString(className) && className !== '') { while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { nName = directiveNormalize(match[2]); if (addDirective(directives, nName, 'C', maxPriority)) { @@ -3937,7 +3955,7 @@ function $CompileProvider($provide) { preLinkFns = [], postLinkFns = [], newScopeDirective = null, - newIsolatedScopeDirective = null, + newIsolateScopeDirective = null, templateDirective = null, $compileNode = templateAttrs.$$element = jqLite(compileNode), directive, @@ -3959,10 +3977,10 @@ function $CompileProvider($provide) { } if (directiveValue = directive.scope) { - assertNoDuplicate('isolated scope', newIsolatedScopeDirective, directive, $compileNode); + assertNoDuplicate('isolated scope', newIsolateScopeDirective, directive, $compileNode); if (isObject(directiveValue)) { safeAddClass($compileNode, 'ng-isolate-scope'); - newIsolatedScopeDirective = directive; + newIsolateScopeDirective = directive; } safeAddClass($compileNode, 'ng-scope'); newScopeDirective = newScopeDirective || directive; @@ -4116,12 +4134,12 @@ function $CompileProvider($provide) { } $element = attrs.$$element; - if (newScopeDirective && isObject(newScopeDirective.scope)) { + if (newIsolateScopeDirective) { var LOCAL_REGEXP = /^\s*([@=&])\s*(\w*)\s*$/; var parentScope = scope.$parent || scope; - forEach(newScopeDirective.scope, function(definiton, scopeName) { + forEach(newIsolateScopeDirective.scope, function(definiton, scopeName) { var match = definiton.match(LOCAL_REGEXP) || [], attrName = match[2]|| scopeName, mode = match[1], // @, =, or & @@ -4144,10 +4162,10 @@ function $CompileProvider($provide) { // reset the change, or we will throw this exception on every $digest lastValue = scope[scopeName] = parentGet(parentScope); throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + attrs[attrName] + - ' (directive: ' + newScopeDirective.name + ')'); + ' (directive: ' + newIsolateScopeDirective.name + ')'); }; lastValue = scope[scopeName] = parentGet(parentScope); - scope.$watch(function() { + scope.$watch(function parentValueWatch() { var parentValue = parentGet(parentScope); if (parentValue !== scope[scopeName]) { @@ -4157,7 +4175,7 @@ function $CompileProvider($provide) { lastValue = scope[scopeName] = parentValue; } else { // if the parent can be assigned then do so - parentSet(parentScope, lastValue = scope[scopeName]); + parentSet(parentScope, parentValue = lastValue = scope[scopeName]); } } return parentValue; @@ -4175,7 +4193,7 @@ function $CompileProvider($provide) { default: { throw Error('Invalid isolate scope definition for directive ' + - newScopeDirective.name + ': ' + definiton); + newIsolateScopeDirective.name + ': ' + definiton); } } }); @@ -4309,7 +4327,7 @@ function $CompileProvider($provide) { origAsyncDirective = directives.shift(), // The fact that we have to copy and patch the directive seems wrong! derivedSyncDirective = extend({}, origAsyncDirective, { - controller: null, templateUrl: null, transclude: null + controller: null, templateUrl: null, transclude: null, scope: null }); $compileNode.html(''); @@ -4401,12 +4419,12 @@ function $CompileProvider($provide) { if (interpolateFn) { directives.push({ priority: 0, - compile: valueFn(function(scope, node) { + compile: valueFn(function textInterpolateLinkFn(scope, node) { var parent = node.parent(), bindings = parent.data('$binding') || []; bindings.push(interpolateFn); safeAddClass(parent.data('$binding', bindings), 'ng-binding'); - scope.$watch(interpolateFn, function(value) { + scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { node[0].nodeValue = value; }); }) @@ -4424,7 +4442,7 @@ function $CompileProvider($provide) { directives.push({ priority: 100, - compile: valueFn(function(scope, element, attr) { + compile: valueFn(function attrInterpolateLinkFn(scope, element, attr) { var $$observers = (attr.$$observers || (attr.$$observers = {})); if (name === 'class') { @@ -4436,7 +4454,7 @@ function $CompileProvider($provide) { attr[name] = undefined; ($$observers[name] || ($$observers[name] = [])).$$inter = true; (attr.$$observers && attr.$$observers[name].$$scope || scope). - $watch(interpolateFn, function(value) { + $watch(interpolateFn, function interpolateFnWatchAction(value) { attr.$set(name, value); }); }) @@ -5434,6 +5452,7 @@ function $LocationProvider(){ var changeCounter = 0; $rootScope.$watch(function $locationWatch() { var oldUrl = $browser.url(); + var currentReplace = $location.$$replace; if (!changeCounter || oldUrl != $location.absUrl()) { changeCounter++; @@ -5442,12 +5461,12 @@ function $LocationProvider(){ defaultPrevented) { $location.$$parse(oldUrl); } else { - $browser.url($location.absUrl(), $location.$$replace); - $location.$$replace = false; + $browser.url($location.absUrl(), currentReplace); afterLocationChange(oldUrl); } }); } + $location.$$replace = false; return changeCounter; }); @@ -5578,7 +5597,15 @@ var OPERATORS = { 'true':function(){return true;}, 'false':function(){return false;}, undefined:noop, - '+':function(self, locals, a,b){a=a(self, locals); b=b(self, locals); return (isDefined(a)?a:0)+(isDefined(b)?b:0);}, + '+':function(self, locals, a,b){ + a=a(self, locals); b=b(self, locals); + if (isDefined(a)) { + if (isDefined(b)) { + return a + b; + } + return a; + } + return isDefined(b)?b:undefined;}, '-':function(self, locals, a,b){a=a(self, locals); b=b(self, locals); return (isDefined(a)?a:0)-(isDefined(b)?b:0);}, '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);}, '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, @@ -6473,7 +6500,7 @@ function $ParseProvider() { * alert('Success: ' + greeting); * }, function(reason) { * alert('Failed: ' + reason); - * ); + * }); *
@@ -7906,7 +7938,7 @@ function $RootScopeProvider(){ * @function * * @description - * Listen on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for discussion of + * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for discussion of * event life cycle. * * @param {string} name Event name to listen on. @@ -7916,13 +7948,13 @@ function $RootScopeProvider(){ * The event listener function format is: `function(event, args...)`. The `event` object * passed into the listener has the following attributes: * - * - `targetScope` - {Scope}: the scope on which the event was `$emit`-ed or `$broadcast`-ed. - * - `currentScope` - {Scope}: the current scope which is handling the event. - * - `name` - {string}: Name of the event. - * - `stopPropagation` - {function=}: calling `stopPropagation` function will cancel further event propagation - * (available only for events that were `$emit`-ed). - * - `preventDefault` - {function}: calling `preventDefault` sets `defaultPrevented` flag to true. - * - `defaultPrevented` - {boolean}: true if `preventDefault` was called. + * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or `$broadcast`-ed. + * - `currentScope` - `{Scope}`: the current scope which is handling the event. + * - `name` - `{string}`: Name of the event. + * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel further event + * propagation (available only for events that were `$emit`-ed). + * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag to true. + * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. */ $on: function(name, listener) { var namedListeners = this.$$listeners[name]; @@ -7932,7 +7964,7 @@ function $RootScopeProvider(){ namedListeners.push(listener); return function() { - arrayRemove(namedListeners, listener); + namedListeners[indexOf(namedListeners, listener)] = null; }; }, @@ -7980,6 +8012,14 @@ function $RootScopeProvider(){ namedListeners = scope.$$listeners[name] || empty; event.currentScope = scope; for (i=0, length=namedListeners.length; ifractionSize + 1) { + numStr = '0'; + } else { + formatedText = numStr; + hasExponent = true; + } + } + + if (!hasExponent) { var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length; // determine fractionSize if it is not specified @@ -9791,7 +9850,7 @@ dateFilter.$inject = ['$locale']; function dateFilter($locale) { - var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; + var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; function jsonStringToDate(string){ var match; if (match = string.match(R_ISO8601_STR)) { @@ -10170,6 +10229,7 @@ var htmlAnchorDirective = valueFn({ // if we have no href url, then don't navigate anywhere. if (!element.attr('href')) { event.preventDefault(); + return false; // Needed for opera } }); } @@ -10460,7 +10520,7 @@ forEach(BOOLEAN_ATTR, function(propName, attrName) { priority: 100, compile: function() { return function(scope, element, attr) { - scope.$watch(attr[normalized], function(value) { + scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { attr.$set(attrName, !!value); }); }; @@ -10478,6 +10538,9 @@ forEach(['src', 'href'], function(attrName) { priority: 99, // it needs to run after the attributes are interpolated link: function(scope, element, attr) { attr.$observe(normalized, function(value) { + if (!value) + return; + attr.$set(attrName, value); // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist @@ -10606,6 +10669,7 @@ function FormController(element, attrs) { element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS); form.$dirty = true; form.$pristine = false; + parentForm.$setDirty(); }; } @@ -10791,7 +10855,10 @@ var inputType = { * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. - * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} required Adds `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than @@ -10861,6 +10928,9 @@ var inputType = { * @param {string=} min Sets the `min` validation error key if the value entered is less then `min`. * @param {string=} max Sets the `max` validation error key if the value entered is greater then `min`. * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than @@ -10927,6 +10997,9 @@ var inputType = { * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than @@ -10992,6 +11065,9 @@ var inputType = { * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than @@ -11414,6 +11490,9 @@ function checkboxInputType(scope, element, attr, ctrl) { * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than @@ -11438,6 +11517,7 @@ function checkboxInputType(scope, element, attr, ctrl) { * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {boolean=} ngRequired Sets `required` attribute if set to true * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than @@ -11771,22 +11851,25 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ // model -> value var ctrl = this; - $scope.$watch(ngModelGet, function(value) { - // ignore change from view - if (ctrl.$modelValue === value) return; + $scope.$watch(function ngModelWatch() { + var value = ngModelGet($scope); - var formatters = ctrl.$formatters, - idx = formatters.length; + // if scope model value and ngModel value are out of sync + if (ctrl.$modelValue !== value) { - ctrl.$modelValue = value; - while(idx--) { - value = formatters[idx](value); - } + var formatters = ctrl.$formatters, + idx = formatters.length; - if (ctrl.$viewValue !== value) { - ctrl.$viewValue = value; - ctrl.$render(); + ctrl.$modelValue = value; + while(idx--) { + value = formatters[idx](value); + } + + if (ctrl.$viewValue !== value) { + ctrl.$viewValue = value; + ctrl.$render(); + } } }); }]; @@ -11935,7 +12018,7 @@ var requiredDirective = function() { * @name ng.directive:ngList * * @description - * Text input that converts between comma-seperated string into an array of strings. + * Text input that converts between comma-separated string into an array of strings. * * @element input * @param {string=} ngList optional delimiter that should be used to split the value. If @@ -12018,7 +12101,7 @@ var ngValueDirective = function() { }; } else { return function(scope, elm, attr) { - scope.$watch(attr.ngValue, function(value) { + scope.$watch(attr.ngValue, function valueWatchAction(value) { attr.$set('value', value, false); }); }; @@ -12076,7 +12159,7 @@ var ngValueDirective = function() { */ var ngBindDirective = ngDirective(function(scope, element, attr) { element.addClass('ng-binding').data('$binding', attr.ngBind); - scope.$watch(attr.ngBind, function(value) { + scope.$watch(attr.ngBind, function ngBindWatchAction(value) { element.text(value == undefined ? '' : value); }); }); @@ -12159,7 +12242,7 @@ var ngBindTemplateDirective = ['$interpolate', function($interpolate) { var ngBindHtmlUnsafeDirective = [function() { return function(scope, element, attr) { element.addClass('ng-binding').data('$binding', attr.ngBindHtmlUnsafe); - scope.$watch(attr.ngBindHtmlUnsafe, function(value) { + scope.$watch(attr.ngBindHtmlUnsafe, function ngBindHtmlUnsafeWatchAction(value) { element.html(value || ''); }); }; @@ -12168,17 +12251,55 @@ var ngBindHtmlUnsafeDirective = [function() { function classDirective(name, selector) { name = 'ngClass' + name; return ngDirective(function(scope, element, attr) { - scope.$watch(attr[name], function(newVal, oldVal) { + + scope.$watch(attr[name], ngClassWatchAction, true); + + attr.$observe('class', function(value) { + var ngClass = scope.$eval(attr[name]); + ngClassWatchAction(ngClass, ngClass); + }); + + + if (name !== 'ngClass') { + scope.$watch('$index', function($index, old$index) { + var mod = $index % 2; + if (mod !== old$index % 2) { + if (mod == selector) { + addClass(scope.$eval(attr[name])); + } else { + removeClass(scope.$eval(attr[name])); + } + } + }); + } + + + function ngClassWatchAction(newVal, oldVal) { if (selector === true || scope.$index % 2 === selector) { if (oldVal && (newVal !== oldVal)) { - if (isObject(oldVal) && !isArray(oldVal)) - oldVal = map(oldVal, function(v, k) { if (v) return k }); - element.removeClass(isArray(oldVal) ? oldVal.join(' ') : oldVal); - } - if (isObject(newVal) && !isArray(newVal)) - newVal = map(newVal, function(v, k) { if (v) return k }); - if (newVal) element.addClass(isArray(newVal) ? newVal.join(' ') : newVal); } - }, true); + removeClass(oldVal); + } + addClass(newVal); + } + } + + + function removeClass(classVal) { + if (isObject(classVal) && !isArray(classVal)) { + classVal = map(classVal, function(v, k) { if (v) return k }); + } + element.removeClass(isArray(classVal) ? classVal.join(' ') : classVal); + } + + + function addClass(classVal) { + if (isObject(classVal) && !isArray(classVal)) { + classVal = map(classVal, function(v, k) { if (v) return k }); + } + if (classVal) { + element.addClass(isArray(classVal) ? classVal.join(' ') : classVal); + } + } }); } @@ -12837,7 +12958,7 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile' element.html(''); }; - scope.$watch(srcExp, function(src) { + scope.$watch(srcExp, function ngIncludeWatchAction(src) { var thisChangeId = ++changeCounter; if (src) { @@ -13122,7 +13243,7 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp offset + endSymbol)); }); - scope.$watch(function() { + scope.$watch(function ngPluralizeWatch() { var value = parseFloat(scope.$eval(numberExp)); if (!isNaN(value)) { @@ -13133,7 +13254,7 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp } else { return ''; } - }, function(newVal) { + }, function ngPluralizeWatchAction(newVal) { element.text(newVal); }); } @@ -13228,7 +13349,8 @@ var ngRepeatDirective = ngDirective({ // We need an array of these objects since the same object can be returned from the iterator. // We expect this to be a rare case. var lastOrder = new HashQueueMap(); - scope.$watch(function(scope){ + + scope.$watch(function ngRepeatWatch(scope){ var index, length, collection = scope.$eval(rhs), collectionLength = size(collection, true), @@ -13257,7 +13379,9 @@ var ngRepeatDirective = ngDirective({ for (index = 0, length = array.length; index < length; index++) { key = (collection === array) ? index : array[index]; value = collection[key]; + last = lastOrder.shift(value); + if (last) { // if we have already seen this object, then we need to reuse the // associated scope/element @@ -13354,7 +13478,7 @@ var ngRepeatDirective = ngDirective({ */ //TODO(misko): refactor to remove element from the DOM var ngShowDirective = ngDirective(function(scope, element, attr){ - scope.$watch(attr.ngShow, function(value){ + scope.$watch(attr.ngShow, function ngShowWatchAction(value){ element.css('display', toBoolean(value) ? '' : 'none'); }); }); @@ -13365,11 +13489,11 @@ var ngShowDirective = ngDirective(function(scope, element, attr){ * @name ng.directive:ngHide * * @description - * The `ngHide` and `ngShow` directives hide or show a portion - * of the HTML conditionally. + * The `ngHide` and `ngShow` directives hide or show a portion of the DOM tree (HTML) + * conditionally. * * @element ANY - * @param {expression} ngHide If the {@link guide/expression expression} truthy then + * @param {expression} ngHide If the {@link guide/expression expression} is truthy then * the element is shown or hidden respectively. * * @example @@ -13394,7 +13518,7 @@ var ngShowDirective = ngDirective(function(scope, element, attr){ */ //TODO(misko): refactor to remove element from the DOM var ngHideDirective = ngDirective(function(scope, element, attr){ - scope.$watch(attr.ngHide, function(value){ + scope.$watch(attr.ngHide, function ngHideWatchAction(value){ element.css('display', toBoolean(value) ? 'none' : ''); }); }); @@ -13437,7 +13561,7 @@ var ngHideDirective = ngDirective(function(scope, element, attr){ */ var ngStyleDirective = ngDirective(function(scope, element, attr) { - scope.$watch(attr.ngStyle, function(newStyles, oldStyles) { + scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { if (oldStyles && (newStyles !== oldStyles)) { forEach(oldStyles, function(val, style) { element.css(style, '');}); } @@ -13517,7 +13641,7 @@ var ngSwitchDirective = valueFn({ selectedElement, selectedScope; - scope.$watch(watchExpr, function(value) { + scope.$watch(watchExpr, function ngSwitchWatchAction(value) { if (selectedElement) { selectedScope.$destroy(); selectedElement.remove(); @@ -13851,6 +13975,9 @@ var scriptDirective = ['$templateCache', function($templateCache) { * * @param {string} name assignable expression to data-bind to. * @param {string=} required The control is considered valid only if value is entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. * @param {comprehension_expression=} ngOptions in one of the following forms: * * * for array data sources: @@ -14091,7 +14218,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) { // we have to do it on each watch since ngModel watches reference, but // we need to work of an array, so we need to see if anything was inserted/removed - scope.$watch(function() { + scope.$watch(function selectMultipleWatch() { if (!equals(lastView, ctrl.$viewValue)) { lastView = copy(ctrl.$viewValue); ctrl.$render(); @@ -14208,7 +14335,8 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) { selected, selectedSet = false, // nothing is selected yet lastElement, - element; + element, + label; if (multiple) { selectedSet = new HashMap(modelValue); @@ -14232,9 +14360,11 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) { selected = modelValue === valueFn(scope, locals); selectedSet = selectedSet || selected; // see if at least one item is selected } + label = displayFn(scope, locals); // what will be seen by the user + label = label === undefined ? '' : label; // doing displayFn(scope, locals) || '' overwrites zero values optionGroup.push({ id: keyName ? keys[index] : index, // either the index into array or key from object - label: displayFn(scope, locals) || '', // what will be seen by the user + label: label, selected: selected // determine if we should be selected }); } @@ -14366,7 +14496,7 @@ var optionDirective = ['$interpolate', function($interpolate) { } if (interpolateFn) { - scope.$watch(interpolateFn, function(newVal, oldVal) { + scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) { attr.$set('value', newVal); if (newVal !== oldVal) selectCtrl.removeOption(oldVal); selectCtrl.addOption(newVal);