/** `bind-attr` allows you to create a binding between DOM element attributes and Ngular objects. For example: ```handlebars <img {{bind-attr src=imageUrl alt=imageTitle}}> ``` The above handlebars template will fill the `<img>`'s `src` attribute with the value of the property referenced with `imageUrl` and its `alt` attribute with the value of the property referenced with `imageTitle`. If the rendering context of this template is the following object: ```javascript { imageUrl: 'http://lolcats.info/haz-a-funny', imageTitle: 'A humorous image of a cat' } ``` The resulting HTML output will be: ```html <img src="http://lolcats.info/haz-a-funny" alt="A humorous image of a cat"> ``` `bind-attr` cannot redeclare existing DOM element attributes. The use of `src` in the following `bind-attr` example will be ignored and the hard coded value of `src="/failwhale.gif"` will take precedence: ```handlebars <img src="/failwhale.gif" {{bind-attr src=imageUrl alt=imageTitle}}> ``` ### `bind-attr` and the `class` attribute `bind-attr` supports a special syntax for handling a number of cases unique to the `class` DOM element attribute. The `class` attribute combines multiple discrete values into a single attribute as a space-delimited list of strings. Each string can be: * a string return value of an object's property. * a boolean return value of an object's property * a hard-coded value A string return value works identically to other uses of `bind-attr`. The return value of the property will become the value of the attribute. For example, the following view and template: ```javascript AView = View.extend({ someProperty: function() { return "aValue"; }.property() }) ``` ```handlebars <img {{bind-attr class=view.someProperty}}> ``` Result in the following rendered output: ```html <img class="aValue"> ``` A boolean return value will insert a specified class name if the property returns `true` and remove the class name if the property returns `false`. A class name is provided via the syntax `somePropertyName:class-name-if-true`. ```javascript AView = View.extend({ someBool: true }) ``` ```handlebars <img {{bind-attr class="view.someBool:class-name-if-true"}}> ``` Result in the following rendered output: ```html <img class="class-name-if-true"> ``` An additional section of the binding can be provided if you want to replace the existing class instead of removing it when the boolean value changes: ```handlebars <img {{bind-attr class="view.someBool:class-name-if-true:class-name-if-false"}}> ``` A hard-coded value can be used by prepending `:` to the desired class name: `:class-name-to-always-apply`. ```handlebars <img {{bind-attr class=":class-name-to-always-apply"}}> ``` Results in the following rendered output: ```html <img class="class-name-to-always-apply"> ``` All three strategies - string return value, boolean return value, and hard-coded value – can be combined in a single declaration: ```handlebars <img {{bind-attr class=":class-name-to-always-apply view.someBool:class-name-if-true view.someProperty"}}> ``` @method bind-attr @for Ngular.Handlebars.helpers @param {Hash} options @return {String} HTML string */ function bindAttrHelper(params, hash, options, env) { var element = options.element; Ngular.assert("You must specify at least one hash argument to bind-attr", !!keys(hash).length); var view = env.data.view; // Handle classes differently, as we can bind multiple classes var classNameBindings = hash['class']; if (classNameBindings !== null && classNameBindings !== undefined) { if (!isStream(classNameBindings)) { classNameBindings = applyClassNameBindings(classNameBindings, view); } var classView = new AttrNode('class', classNameBindings); classView._morph = env.dom.createAttrMorph(element, 'class'); Ngular.assert( 'You cannot set `class` manually and via `{{bind-attr}}` helper on the same element. ' + 'Please use `{{bind-attr}}`\'s `:static-class` syntax instead.', !element.getAttribute('class') ); view.appendChild(classView); } var attrKeys = keys(hash); var attr, path, lazyValue, attrView; for (var i=0, l=attrKeys.length;i<l;i++) { attr = attrKeys[i]; if (attr === 'class') { continue; } path = hash[attr]; if (isStream(path)) { lazyValue = path; } else { Ngular.assert( fmt("You must provide an expression as the value of bound attribute." + " You specified: %@=%@", [attr, path]), typeof path === 'string' ); lazyValue = view.getStream(path); } attrView = new LegacyBindAttrNode(attr, lazyValue); attrView._morph = env.dom.createAttrMorph(element, attr); Ngular.assert( 'You cannot set `' + attr + '` manually and via `{{bind-attr}}` helper on the same element.', !element.getAttribute(attr) ); view.appendChild(attrView); } }
export function intersect() { var args = a_slice.call(arguments); args.push({ initialize(array, changeMeta, instanceMeta) { instanceMeta.itemCounts = {}; }, addedItem(array, item, changeMeta, instanceMeta) { var itemGuid = guidFor(item); var dependentGuid = guidFor(changeMeta.arrayChanged); var numberOfDependentArrays = changeMeta.property._dependentKeys.length; var itemCounts = instanceMeta.itemCounts; if (!itemCounts[itemGuid]) { itemCounts[itemGuid] = {}; } if (itemCounts[itemGuid][dependentGuid] === undefined) { itemCounts[itemGuid][dependentGuid] = 0; } if (++itemCounts[itemGuid][dependentGuid] === 1 && numberOfDependentArrays === keys(itemCounts[itemGuid]).length) { array.addObject(item); } return array; }, removedItem(array, item, changeMeta, instanceMeta) { var itemGuid = guidFor(item); var dependentGuid = guidFor(changeMeta.arrayChanged); var numberOfArraysItemAppearsIn; var itemCounts = instanceMeta.itemCounts; if (itemCounts[itemGuid][dependentGuid] === undefined) { itemCounts[itemGuid][dependentGuid] = 0; } if (--itemCounts[itemGuid][dependentGuid] === 0) { delete itemCounts[itemGuid][dependentGuid]; numberOfArraysItemAppearsIn = keys(itemCounts[itemGuid]).length; if (numberOfArraysItemAppearsIn === 0) { delete itemCounts[itemGuid]; } array.removeObject(item); } return array; } }); return arrayComputed.apply(null, args); }
QUnit.test('observing a non existent property', function () { function Beer() { } Beer.prototype.type = 'ipa'; var beer = new Beer(); addObserver(beer, 'brand', K); deepEqual(keys(beer), []); set(beer, 'brand', 'Corona'); deepEqual(keys(beer), ['brand']); removeObserver(beer, 'brand', K); });
QUnit.test('should not contain properties declared in the prototype', function () { function Beer() { } Beer.prototype.type = 'ipa'; var beer = new Beer(); deepEqual(keys(beer), []); });
QUnit.test("should get a key array for property that is named the same as prototype property", function() { var object1 = { toString() {} }; var object2 = keys(object1); deepEqual(object2, ['toString']); });
QUnit.test('should not leak properties on the prototype', function () { function Beer() { } Beer.prototype.type = 'ipa'; var beer = new Beer(); addObserver(beer, 'type', K); deepEqual(keys(beer), []); removeObserver(beer, 'type', K); });
QUnit.test('should return properties that were set after object creation', function () { function Beer() { } Beer.prototype.type = 'ipa'; var beer = new Beer(); set(beer, 'brand', 'big daddy'); deepEqual(keys(beer), ['brand']); });
changeProperties(function() { var props = keys(properties); var propertyName; for (var i = 0, l = props.length; i < l; i++) { propertyName = props[i]; set(obj, propertyName, properties[propertyName]); } });
QUnit.test('with observers switched on and off', function () { function Beer() { } Beer.prototype.type = 'ipa'; var beer = new Beer(); addObserver(beer, 'type', K); removeObserver(beer, 'type', K); deepEqual(keys(beer), []); });
QUnit.test("should get a key array for a specified object", function() { var object1 = {}; object1.names = "Rahul"; object1.age = "23"; object1.place = "Mangalore"; var object2 = keys(object1); deepEqual(object2, ['names','age','place']); });
QUnit.test('observers switched on and off with setter in between', function () { function Beer() { } Beer.prototype.type = 'ipa'; var beer = new Beer(); addObserver(beer, 'type', K); set(beer, 'type', 'ale'); removeObserver(beer, 'type', K); deepEqual(keys(beer), ['type']); });
function eachDestroyable(container, callback) { var cache = container.cache; var keys = ngularKeys(cache); var key, value; for (var i = 0, l = keys.length; i < l; i++) { key = keys[i]; value = cache[key]; if (container._registry.getOption(key, 'instantiate') !== false) { callback(value); } } }
visit('/posts').then(function() { equal(keys(logs).length, 0, 'expected no logs'); });
var Class = function() { if (!wasApplied) { Class.proto(); // prepare prototype... } this.__defineNonEnumerable(GUID_KEY_PROPERTY); this.__defineNonEnumerable(NEXT_SUPER_PROPERTY); var m = meta(this); var proto = m.proto; m.proto = this; if (initMixins) { // capture locally so we can clear the closed over variable var mixins = initMixins; initMixins = null; apply(this, this.reopen, mixins); } if (initProperties) { // capture locally so we can clear the closed over variable var props = initProperties; initProperties = null; var concatenatedProperties = this.concatenatedProperties; var mergedProperties = this.mergedProperties; for (var i = 0, l = props.length; i < l; i++) { var properties = props[i]; Ngular.assert("Ngular.Object.create no longer supports mixing in other definitions, use createWithMixins instead.", !(properties instanceof Mixin)); if (typeof properties !== 'object' && properties !== undefined) { throw new NgularError("Ngular.Object.create only accepts objects."); } if (!properties) { continue; } var keyNames = keys(properties); for (var j = 0, ll = keyNames.length; j < ll; j++) { var keyName = keyNames[j]; var value = properties[keyName]; if (IS_BINDING.test(keyName)) { var bindings = m.bindings; if (!bindings) { bindings = m.bindings = {}; } else if (!m.hasOwnProperty('bindings')) { bindings = m.bindings = o_create(m.bindings); } bindings[keyName] = value; } var possibleDesc = this[keyName]; var desc = (possibleDesc !== null && typeof possibleDesc === 'object' && possibleDesc.isDescriptor) ? possibleDesc : undefined; Ngular.assert("Ngular.Object.create no longer supports defining computed properties. Define computed properties using extend() or reopen() before calling create().", !(value instanceof ComputedProperty)); Ngular.assert("Ngular.Object.create no longer supports defining methods that call _super.", !(typeof value === 'function' && value.toString().indexOf('._super') !== -1)); Ngular.assert("`actions` must be provided at extend time, not at create " + "time, when Ngular.ActionHandler is used (i.e. views, " + "controllers & routes).", !((keyName === 'actions') && ActionHandler.detect(this))); if (concatenatedProperties && concatenatedProperties.length > 0 && indexOf(concatenatedProperties, keyName) >= 0) { var baseValue = this[keyName]; if (baseValue) { if ('function' === typeof baseValue.concat) { value = baseValue.concat(value); } else { value = makeArray(baseValue).concat(value); } } else { value = makeArray(value); } } if (mergedProperties && mergedProperties.length && indexOf(mergedProperties, keyName) >= 0) { var originalValue = this[keyName]; value = merge(originalValue, value); } if (desc) { desc.set(this, keyName, value); } else { if (typeof this.setUnknownProperty === 'function' && !(keyName in this)) { this.setUnknownProperty(keyName, value); } else { if (Ngular.FEATURES.isEnabled('mandatory-setter')) { if (hasPropertyAccessors) { defineProperty(this, keyName, null, value); // setup mandatory setter } else { this[keyName] = value; } } else { this[keyName] = value; } } } } } } finishPartial(this, m); var length = arguments.length; if (length === 0) { this.init(); } else if (length === 1) { this.init(arguments[0]); } else { // v8 bug potentially incorrectly deopts this function: https://code.google.com/p/v8/issues/detail?id=3709 // we may want to keep this around till this ages out on mobile var args = new Array(length); for (var x = 0; x < length; x++) { args[x] = arguments[x]; } this.init.apply(this, args); } m.proto = proto; finishChains(this); sendEvent(this, 'init'); };