/**
- * @license AngularJS v1.0.2
+ * @license AngularJS v1.0.3
* (c) 2010-2012 Google, Inc. http://angularjs.org
* License: MIT
*/
/**
- * 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
* @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
*
* # 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}.
*
* <pre>
* @param {!string} name The name of the module to create or retrieve.
* @param {Array.<string>=} 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.
*/
* @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);
* - `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'
};
* - [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/)
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;
}
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, " ")
}
}
-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));
}
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
return array.shift();
}
}
+ },
+
+ /**
+ * return the first item without deleting it
+ */
+ peek: function(key) {
+ var array = this[hashKey(key)];
+ if (array) {
+ return array[0];
+ }
}
};
* // 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);
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,
// 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;
*
* - `{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() {
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);
//================================
- 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 <span>
- forEach($compileNode, function(node, index){
+ forEach($compileNodes, function(node, index){
if (node.nodeType == 3 /* text node */) {
- $compileNode[index] = jqLite(node).wrap('<span></span>').parent()[0];
+ $compileNodes[index] = jqLite(node).wrap('<span></span>').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);
? 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);
/**
- * 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,
// 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)) {
preLinkFns = [],
postLinkFns = [],
newScopeDirective = null,
- newIsolatedScopeDirective = null,
+ newIsolateScopeDirective = null,
templateDirective = null,
$compileNode = templateAttrs.$$element = jqLite(compileNode),
directive,
}
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;
}
$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 &
// 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]) {
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;
default: {
throw Error('Invalid isolate scope definition for directive ' +
- newScopeDirective.name + ': ' + definiton);
+ newIsolateScopeDirective.name + ': ' + definiton);
}
}
});
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('');
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;
});
})
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') {
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);
});
})
var changeCounter = 0;
$rootScope.$watch(function $locationWatch() {
var oldUrl = $browser.url();
+ var currentReplace = $location.$$replace;
if (!changeCounter || oldUrl != $location.absUrl()) {
changeCounter++;
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;
});
'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);},
* alert('Success: ' + greeting);
* }, function(reason) {
* alert('Failed: ' + reason);
- * );
+ * });
* </pre>
*
* At first it might not be obvious why this extra complexity is worth the trouble. The payoff
* 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
* - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should
* be injected into the controller. If any of these dependencies are promises, they will be
* resolved and converted to a value before the controller is instantiated and the
- * `$afterRouteChange` event is fired. The map object is:
+ * `$routeChangeSuccess` event is fired. The map object is:
*
* - `key` – `{string}`: a name of a dependency to be injected into the controller.
* - `factory` - `{string|function}`: If `string` then it is an alias for a service.
forEach(next.resolve || {}, function(value, key) {
keys.push(key);
- values.push(isFunction(value) ? $injector.invoke(value) : $injector.get(value));
+ values.push(isString(value) ? $injector.get(value) : $injector.invoke(value));
});
if (isDefined(template = next.template)) {
} else if (isDefined(template = next.templateUrl)) {
* the scope and its child scopes to be permanently detached from the parent and thus stop
* participating in model change detection and listener notification by invoking.
*
- * @param {boolean} isolate if true then the scoped does not prototypically inherit from the
- * parent scope. The scope is isolated, as it can not se parent scope properties.
- * When creating widgets it is useful for the widget to not accidently read parent
+ * @param {boolean} isolate if true then the scope does not prototypically inherit from the
+ * parent scope. The scope is isolated, as it can not see parent scope properties.
+ * When creating widgets it is useful for the widget to not accidentally read parent
* state.
*
* @returns {Object} The newly created child scope.
* reruns when it detects changes the `watchExpression` can execute multiple times per
* {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.)
* - The `listener` is called only when the value from the current `watchExpression` and the
- * previous call to `watchExpression' are not equal (with the exception of the initial run
+ * previous call to `watchExpression` are not equal (with the exception of the initial run,
* see below). The inequality is determined according to
- * {@link angular.equals} function. To save the value of the object for later comparison
+ * {@link angular.equals} function. To save the value of the object for later comparison, the
* {@link angular.copy} function is used. It also means that watching complex options will
* have adverse memory and performance implications.
* - The watch `listener` may change the model, which may trigger other `listener`s to fire. This
* is achieved by rerunning the watchers until no changes are detected. The rerun iteration
- * limit is 100 to prevent infinity loop deadlock.
+ * limit is 10 to prevent an infinite loop deadlock.
*
*
* If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
- * you can register an `watchExpression` function with no `listener`. (Since `watchExpression`,
+ * you can register a `watchExpression` function with no `listener`. (Since `watchExpression`
* can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a change is
* detected, be prepared for multiple calls to your listener.)
*
* - `string`: Evaluated as {@link guide/expression expression}
* - `function(newValue, oldValue, scope)`: called with current and previous values as parameters.
*
- * @param {boolean=} objectEquality Compare object for equality rather then for refference.
+ * @param {boolean=} objectEquality Compare object for equality rather than for reference.
* @returns {function()} Returns a deregistration function for this listener.
*/
$watch: function(watchExp, listener, objectEquality) {
* @function
*
* @description
- * Remove the current scope (and all of its children) from the parent scope. Removal implies
+ * Removes the current scope (and all of its children) from the parent scope. Removal implies
* that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer
* propagate to the current scope and its children. Removal also implies that the current
* scope is eligible for garbage collection.
if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
+
+ // This is bogus code that works around Chrome's GC leak
+ // see: https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
+ this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead =
+ this.$$childTail = null;
},
/**
*
* @description
* Executes the `expression` on the current scope returning the result. Any exceptions in the
- * expression are propagated (uncaught). This is useful when evaluating engular expressions.
+ * expression are propagated (uncaught). This is useful when evaluating Angular expressions.
*
* # Example
* <pre>
* @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.
* 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];
namedListeners.push(listener);
return function() {
- arrayRemove(namedListeners, listener);
+ namedListeners[indexOf(namedListeners, listener)] = null;
};
},
namedListeners = scope.$$listeners[name] || empty;
event.currentScope = scope;
for (i=0, length=namedListeners.length; i<length; i++) {
+
+ // if listeners were deregistered, defragment the array
+ if (!namedListeners[i]) {
+ namedListeners.splice(i, 1);
+ i--;
+ length--;
+ continue;
+ }
try {
namedListeners[i].apply(null, listenerArgs);
if (stopPropagation) return event;
},
defaultPrevented: false
},
- listenerArgs = concat([event], arguments, 1);
+ listenerArgs = concat([event], arguments, 1),
+ listeners, i, length;
//down while you can, then up and next sibling or up and next sibling until back at root
do {
current = next;
event.currentScope = current;
- forEach(current.$$listeners[name], function(listener) {
+ listeners = current.$$listeners[name] || [];
+ for (i=0, length = listeners.length; i<length; i++) {
+ // if listeners were deregistered, defragment the array
+ if (!listeners[i]) {
+ listeners.splice(i, 1);
+ i--;
+ length--;
+ continue;
+ }
+
try {
- listener.apply(null, listenerArgs);
+ listeners[i].apply(null, listenerArgs);
} catch(e) {
$exceptionHandler(e);
}
- });
+ }
// Insanity Warning: scope depth-first traversal
// yes, this code is a bit crazy, but it works and we have tests to prove it!
* @param {number=} [delay=0] Delay in milliseconds.
* @param {boolean=} [invokeApply=true] If set to false skips model dirty checking, otherwise
* will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
- * @returns {*} Promise that will be resolved when the timeout is reached. The value this
+ * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this
* promise will be resolved with is the return value of the `fn` function.
*/
function timeout(fn, delay, invokeApply) {
formatedText = '',
parts = [];
+ var hasExponent = false;
if (numStr.indexOf('e') !== -1) {
- formatedText = numStr;
- } else {
+ var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
+ if (match && match[2] == '-' && match[3] > fractionSize + 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
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)) {
// if we have no href url, then don't navigate anywhere.
if (!element.attr('href')) {
event.preventDefault();
+ return false; // Needed for opera
}
});
}
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);
});
};
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
element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS);
form.$dirty = true;
form.$pristine = false;
+ parentForm.$setDirty();
};
}
*
* @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
* @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
* @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
* @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
* @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
* @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
// 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();
+ }
}
});
}];
* @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
};
} else {
return function(scope, elm, attr) {
- scope.$watch(attr.ngValue, function(value) {
+ scope.$watch(attr.ngValue, function valueWatchAction(value) {
attr.$set('value', value, false);
});
};
*/
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);
});
});
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 || '');
});
};
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);
+ }
+ }
});
}
element.html('');
};
- scope.$watch(srcExp, function(src) {
+ scope.$watch(srcExp, function ngIncludeWatchAction(src) {
var thisChangeId = ++changeCounter;
if (src) {
offset + endSymbol));
});
- scope.$watch(function() {
+ scope.$watch(function ngPluralizeWatch() {
var value = parseFloat(scope.$eval(numberExp));
if (!isNaN(value)) {
} else {
return '';
}
- }, function(newVal) {
+ }, function ngPluralizeWatchAction(newVal) {
element.text(newVal);
});
}
// 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),
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
*/
//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');
});
});
* @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
*/
//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' : '');
});
});
</example>
*/
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, '');});
}
selectedElement,
selectedScope;
- scope.$watch(watchExpr, function(value) {
+ scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
if (selectedElement) {
selectedScope.$destroy();
selectedElement.remove();
*
* @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:
// 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();
selected,
selectedSet = false, // nothing is selected yet
lastElement,
- element;
+ element,
+ label;
if (multiple) {
selectedSet = new HashMap(modelValue);
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
});
}
}
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);