diff options
| -rw-r--r-- | src/Angular.js | 12 | ||||
| -rw-r--r-- | src/ng/compiler.js | 160 | ||||
| -rw-r--r-- | src/ng/directive/booleanAttrDirs.js | 4 | ||||
| -rw-r--r-- | src/ng/directive/input.js | 2 | ||||
| -rw-r--r-- | test/AngularSpec.js | 9 | ||||
| -rw-r--r-- | test/ng/compilerSpec.js | 6 |
6 files changed, 102 insertions, 91 deletions
diff --git a/src/Angular.js b/src/Angular.js index 0f66d99c..c0da2174 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -598,16 +598,16 @@ function copy(source, destination){ /** * Create a shallow copy of an object - * @param src */ -function shallowCopy(src) { - var dst = {}, - key; - for(key in src) { - if (src.hasOwnProperty(key)) { +function shallowCopy(src, dst) { + dst = dst || {}; + + for(var key in src) { + if (src.hasOwnProperty(key) && key.substr(0, 2) !== '$$') { dst[key] = src[key]; } } + return dst; } diff --git a/src/ng/compiler.js b/src/ng/compiler.js index 2e8de121..4a95eefb 100644 --- a/src/ng/compiler.js +++ b/src/ng/compiler.js @@ -214,6 +214,79 @@ function $CompileProvider($provide) { } }; + var Attributes = function(element, attr) { + this.$$element = element; + this.$$observers = {}; + this.$attr = attr || {}; + }; + + Attributes.prototype = { + $normalize: directiveNormalize, + + + /** + * Set a normalized attribute on the element in a way such that all directives + * can share the attribute. This function properly handles boolean attributes. + * @param {string} key Normalized key. (ie ngAttribute) + * @param {string|boolean} value The value to set. If `null` attribute will be deleted. + * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. + * Defaults to true. + * @param {string=} attrName Optional none normalized name. Defaults to key. + */ + $set: function(key, value, writeAttr, attrName) { + var booleanKey = isBooleanAttr(this.$$element[0], key.toLowerCase()); + + if (booleanKey) { + this.$$element.prop(key, value); + attrName = booleanKey; + } + + this[key] = value; + + // translate normalized key to actual key + if (attrName) { + this.$attr[key] = attrName; + } else { + attrName = this.$attr[key]; + if (!attrName) { + this.$attr[key] = attrName = snake_case(key, '-'); + } + } + + if (writeAttr !== false) { + if (value === null || value === undefined) { + this.$$element.removeAttr(attrName); + } else { + this.$$element.attr(attrName, value); + } + } + + // fire observers + forEach(this.$$observers[key], function(fn) { + try { + fn(value); + } catch (e) { + $exceptionHandler(e); + } + }); + }, + + + /** + * Observe an interpolated attribute. + * The observer will never be called, if given attribute is not interpolated. + * + * @param {string} key Normalized key. (ie ngAttribute) . + * @param {function(*)} fn Function that will be called whenever the attribute value changes. + */ + $observe: function(key, fn) { + // keep only observers for interpolated attrs + if (this.$$observers[key]) { + this.$$observers[key].push(fn); + } + } + }; + return compile; //================================ @@ -278,13 +351,8 @@ function $CompileProvider($provide) { directiveLinkingFn, childLinkingFn, directives, attrs, linkingFnFound; for(var i = 0, ii = nodeList.length; i < ii; i++) { - attrs = { - $attr: {}, - $normalize: directiveNormalize, - $set: attrSetter, - $observe: interpolatedAttrObserve, - $observers: {} - }; + attrs = new Attributes(); + // we must always refer to nodeList[i] since the nodes can be replaced underneath us. directives = collectDirectives(nodeList[i], [], attrs, maxPriority); @@ -441,7 +509,7 @@ function $CompileProvider($provide) { newIsolatedScopeDirective = null, templateDirective = null, delayedLinkingFn = null, - element = templateAttrs.$element = jqLite(templateNode), + element = templateAttrs.$$element = jqLite(templateNode), directive, directiveName, template, @@ -485,7 +553,7 @@ function $CompileProvider($provide) { terminalPriority = directive.priority; if (directiveValue == 'element') { template = jqLite(templateNode); - templateNode = (element = templateAttrs.$element = jqLite( + templateNode = (element = templateAttrs.$$element = jqLite( '<!-- ' + directiveName + ': ' + templateAttrs[directiveName] + ' -->'))[0]; replaceWith(rootElement, jqLite(template[0]), templateNode); childTranscludeFn = compile(template, transcludeFn, terminalPriority); @@ -609,11 +677,9 @@ function $CompileProvider($provide) { if (templateNode === linkNode) { attrs = templateAttrs; } else { - attrs = shallowCopy(templateAttrs); - attrs.$element = jqLite(linkNode); - attrs.$observers = {}; + attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr)); } - element = attrs.$element; + element = attrs.$$element; if (newScopeDirective && isObject(newScopeDirective.scope)) { forEach(newScopeDirective.scope, function(mode, name) { @@ -720,7 +786,7 @@ function $CompileProvider($provide) { function mergeTemplateAttributes(dst, src) { var srcAttr = src.$attr, dstAttr = dst.$attr, - element = dst.$element; + element = dst.$$element; // reapply the old attributes to the new element forEach(dst, function(value, key) { if (key.charAt(0) != '$') { @@ -873,7 +939,7 @@ function $CompileProvider($provide) { // we define observers array only for interpolated attrs // and ignore observers for non interpolated attrs to save some memory - attr.$observers[name] = []; + attr.$$observers[name] = []; attr[name] = undefined; scope.$watch(interpolateFn, function(value) { attr.$set(name, value); @@ -910,70 +976,6 @@ function $CompileProvider($provide) { } element[0] = newNode; } - - - /** - * Set a normalized attribute on the element in a way such that all directives - * can share the attribute. This function properly handles boolean attributes. - * @param {string} key Normalized key. (ie ngAttribute) - * @param {string|boolean} value The value to set. If `null` attribute will be deleted. - * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. - * Defaults to true. - * @param {string=} attrName Optional none normalized name. Defaults to key. - */ - function attrSetter(key, value, writeAttr, attrName) { - var booleanKey = isBooleanAttr(this.$element[0], key.toLowerCase()); - - if (booleanKey) { - this.$element.prop(key, value); - attrName = booleanKey; - } - - this[key] = value; - - // translate normalized key to actual key - if (attrName) { - this.$attr[key] = attrName; - } else { - attrName = this.$attr[key]; - if (!attrName) { - this.$attr[key] = attrName = snake_case(key, '-'); - } - } - - if (writeAttr !== false) { - if (value === null || value === undefined) { - this.$element.removeAttr(attrName); - } else { - this.$element.attr(attrName, value); - } - } - - - // fire observers - forEach(this.$observers[key], function(fn) { - try { - fn(value); - } catch (e) { - $exceptionHandler(e); - } - }); - } - - - /** - * Observe an interpolated attribute. - * The observer will never be called, if given attribute is not interpolated. - * - * @param {string} key Normalized key. (ie ngAttribute) . - * @param {function(*)} fn Function that will be called whenever the attribute value changes. - */ - function interpolatedAttrObserve(key, fn) { - // keep only observers for interpolated attrs - if (this.$observers[key]) { - this.$observers[key].push(fn); - } - } }]; } diff --git a/src/ng/directive/booleanAttrDirs.js b/src/ng/directive/booleanAttrDirs.js index f79c02ce..f0182610 100644 --- a/src/ng/directive/booleanAttrDirs.js +++ b/src/ng/directive/booleanAttrDirs.js @@ -286,7 +286,7 @@ forEach(BOOLEAN_ATTR, function(propName, attrName) { priority: 100, compile: function(tpl, attr) { return function(scope, element, attr) { - attr.$observers[attrName] = []; + attr.$$observers[attrName] = []; scope.$watch(attr[normalized], function(value) { attr.$set(attrName, value); }); @@ -305,7 +305,7 @@ forEach(['src', 'href'], function(attrName) { priority: 100, compile: function(tpl, attr) { return function(scope, element, attr) { - attr.$observers[attrName] = []; + attr.$$observers[attrName] = []; attr.$observe(normalized, function(value) { attr.$set(attrName, value); }); diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js index 7343c358..2f7a6a64 100644 --- a/src/ng/directive/input.js +++ b/src/ng/directive/input.js @@ -1181,7 +1181,7 @@ var ngValueDirective = [function() { attr.$set('value', scope.$eval(attr.ngValue)); }; } else { - attr.$observers.value = []; + attr.$$observers.value = []; return function(scope) { scope.$watch(attr.ngValue, function(value) { diff --git a/test/AngularSpec.js b/test/AngularSpec.js index da56449a..cf34305a 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -88,6 +88,15 @@ describe('angular', function() { expect(copy).toEqual(original); expect(copy.key).toBe(original.key); }); + + it('should not copy $$ properties nor prototype properties', function() { + var original = {$$some: true, $$: true}; + var clone = {}; + + expect(shallowCopy(original, clone)).toBe(clone); + expect(clone.$$some).toBeUndefined(); + expect(clone.$$).toBeUndefined(); + }); }); describe('elementHTML', function() { diff --git a/test/ng/compilerSpec.js b/test/ng/compilerSpec.js index fb5e99e6..55479396 100644 --- a/test/ng/compilerSpec.js +++ b/test/ng/compilerSpec.js @@ -166,7 +166,7 @@ describe('$compile', function() { compile: function(element, templateAttr) { expect(typeof templateAttr.$normalize).toBe('function'); expect(typeof templateAttr.$set).toBe('function'); - expect(isElement(templateAttr.$element)).toBeTruthy(); + expect(isElement(templateAttr.$$element)).toBeTruthy(); expect(element.text()).toEqual('unlinked'); expect(templateAttr.exp).toEqual('abc'); expect(templateAttr.aa).toEqual('A'); @@ -344,7 +344,7 @@ describe('$compile', function() { template: '<div class="log" style="width: 10px" high-log>Hello: <<CONTENT>></div>', compile: function(element, attr) { attr.$set('compiled', 'COMPILED'); - expect(element).toBe(attr.$element); + expect(element).toBe(attr.$$element); } })); $compileProvider.directive('append', valueFn({ @@ -352,7 +352,7 @@ describe('$compile', function() { template: '<div class="log" style="width: 10px" high-log>Hello: <<CONTENT>></div>', compile: function(element, attr) { attr.$set('compiled', 'COMPILED'); - expect(element).toBe(attr.$element); + expect(element).toBe(attr.$$element); } })); })); |
