diff --git a/ember-cli-build.js b/ember-cli-build.js index d0fe2e98b7d..c4cb0b45039 100644 --- a/ember-cli-build.js +++ b/ember-cli-build.js @@ -35,7 +35,7 @@ function babelConfigFor(environment) { if (isProduction) { plugins.push(filterImports({ - 'ember-metal/debug': ['assert', 'debug', 'deprecate', 'info', 'runInDebug', 'warn'] + 'ember-metal/debug': ['assert', 'debug', 'deprecate', 'info', 'runInDebug', 'warn', 'debugSeal'] })); } diff --git a/package.json b/package.json index 1794808882d..8fc7479c376 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "finalhandler": "^0.4.0", "github": "^0.2.3", "glob": "~4.3.2", - "htmlbars": "0.14.4", + "htmlbars": "0.14.5", "qunit-extras": "^1.3.0", "qunitjs": "^1.16.0", "route-recognizer": "0.1.5", diff --git a/packages/ember-debug/lib/main.js b/packages/ember-debug/lib/main.js index fb1f008fbb3..71136575ee3 100644 --- a/packages/ember-debug/lib/main.js +++ b/packages/ember-debug/lib/main.js @@ -149,6 +149,10 @@ setDebugFunction('runInDebug', function runInDebug(func) { func(); }); +setDebugFunction('debugSeal', function debugSeal(obj) { + Object.seal(obj); +}); + setDebugFunction('deprecate', _deprecate); setDebugFunction('warn', _warn); /** diff --git a/packages/ember-htmlbars/lib/env.js b/packages/ember-htmlbars/lib/env.js index ed19e76a481..95533a0de55 100644 --- a/packages/ember-htmlbars/lib/env.js +++ b/packages/ember-htmlbars/lib/env.js @@ -7,14 +7,16 @@ import merge from 'ember-metal/merge'; import subexpr from 'ember-htmlbars/hooks/subexpr'; import concat from 'ember-htmlbars/hooks/concat'; import linkRenderNode from 'ember-htmlbars/hooks/link-render-node'; -import createFreshScope from 'ember-htmlbars/hooks/create-fresh-scope'; +import createFreshScope, { createChildScope } from 'ember-htmlbars/hooks/create-fresh-scope'; import bindShadowScope from 'ember-htmlbars/hooks/bind-shadow-scope'; import bindSelf from 'ember-htmlbars/hooks/bind-self'; import bindScope from 'ember-htmlbars/hooks/bind-scope'; import bindLocal from 'ember-htmlbars/hooks/bind-local'; +import bindBlock from 'ember-htmlbars/hooks/bind-block'; import updateSelf from 'ember-htmlbars/hooks/update-self'; import getRoot from 'ember-htmlbars/hooks/get-root'; import getChild from 'ember-htmlbars/hooks/get-child'; +import getBlock from 'ember-htmlbars/hooks/get-block'; import getValue from 'ember-htmlbars/hooks/get-value'; import getCellOrValue from 'ember-htmlbars/hooks/get-cell-or-value'; import cleanupRenderNode from 'ember-htmlbars/hooks/cleanup-render-node'; @@ -40,11 +42,14 @@ emberHooks.keywords = keywords; merge(emberHooks, { linkRenderNode, createFreshScope, + createChildScope, bindShadowScope, bindSelf, bindScope, bindLocal, + bindBlock, updateSelf, + getBlock, getRoot, getChild, getValue, @@ -74,6 +79,7 @@ import partial from 'ember-htmlbars/keywords/partial'; import input from 'ember-htmlbars/keywords/input'; import textarea from 'ember-htmlbars/keywords/textarea'; import collection from 'ember-htmlbars/keywords/collection'; +import yieldKeyword from 'ember-htmlbars/keywords/yield'; import legacyYield from 'ember-htmlbars/keywords/legacy-yield'; import mut, { privateMut } from 'ember-htmlbars/keywords/mut'; import each from 'ember-htmlbars/keywords/each'; @@ -88,6 +94,7 @@ registerKeyword('component', componentKeyword); registerKeyword('partial', partial); registerKeyword('input', input); registerKeyword('textarea', textarea); +registerKeyword('yield', yieldKeyword); registerKeyword('legacy-yield', legacyYield); registerKeyword('mut', mut); registerKeyword('@mut', privateMut); diff --git a/packages/ember-htmlbars/lib/hooks/bind-block.js b/packages/ember-htmlbars/lib/hooks/bind-block.js new file mode 100644 index 00000000000..353bb950fbc --- /dev/null +++ b/packages/ember-htmlbars/lib/hooks/bind-block.js @@ -0,0 +1,3 @@ +export default function bindBlock(env, scope, block, name='default') { + scope.bindBlock(name, block); +} diff --git a/packages/ember-htmlbars/lib/hooks/bind-local.js b/packages/ember-htmlbars/lib/hooks/bind-local.js index a3b36117ab8..43454af1bb8 100644 --- a/packages/ember-htmlbars/lib/hooks/bind-local.js +++ b/packages/ember-htmlbars/lib/hooks/bind-local.js @@ -3,19 +3,18 @@ @submodule ember-htmlbars */ -import Stream from 'ember-metal/streams/stream'; +import { wrap } from 'ember-metal/streams/stream'; import ProxyStream from 'ember-metal/streams/proxy-stream'; export default function bindLocal(env, scope, key, value) { - var isExisting = scope.locals.hasOwnProperty(key); - if (isExisting) { - var existing = scope.locals[key]; - + // TODO: What is the cause of these cases? + if (scope.hasOwnLocal(key)) { + let existing = scope.getLocal(key); if (existing !== value) { existing.setSource(value); } } else { - var newValue = Stream.wrap(value, ProxyStream, key); - scope.locals[key] = newValue; + let newValue = wrap(value, ProxyStream, key); + scope.bindLocal(key, newValue); } } diff --git a/packages/ember-htmlbars/lib/hooks/bind-self.js b/packages/ember-htmlbars/lib/hooks/bind-self.js index 6e38f015cbf..1656d4674d8 100644 --- a/packages/ember-htmlbars/lib/hooks/bind-self.js +++ b/packages/ember-htmlbars/lib/hooks/bind-self.js @@ -3,7 +3,8 @@ @submodule ember-htmlbars */ -import newStream from 'ember-htmlbars/utils/new-stream'; +import _Ember from 'ember-metal'; +import ProxyStream from 'ember-metal/streams/proxy-stream'; export default function bindSelf(env, scope, _self) { let self = _self; @@ -12,25 +13,41 @@ export default function bindSelf(env, scope, _self) { let { controller } = self; self = self.self; - newStream(scope.locals, 'controller', controller || self); + if (!!_Ember.ENV._ENABLE_LEGACY_CONTROLLER_SUPPORT) { + scope.bindLocal('controller', newStream(controller || self)); + } } if (self && self.isView) { - newStream(scope.locals, 'view', self, null); - newStream(scope.locals, 'controller', scope.locals.view.getKey('controller')); + if (!!_Ember.ENV._ENABLE_LEGACY_VIEW_SUPPORT) { + scope.bindLocal('view', newStream(self, 'view')); + } + + if (!!_Ember.ENV._ENABLE_LEGACY_CONTROLLER_SUPPORT) { + scope.bindLocal('controller', newStream(self, '').getKey('controller')); + } + + let selfStream = newStream(self, ''); if (self.isGlimmerComponent) { - newStream(scope, 'self', self, null, true); + scope.bindSelf(selfStream); } else { - newStream(scope, 'self', scope.locals.view.getKey('context'), null, true); + scope.bindSelf(newStream(selfStream.getKey('context'), '')); } return; } - newStream(scope, 'self', self, null, true); + let selfStream = newStream(self, ''); + scope.bindSelf(selfStream); - if (!scope.locals.controller) { - scope.locals.controller = scope.self; + if (!!_Ember.ENV._ENABLE_LEGACY_CONTROLLER_SUPPORT) { + if (!scope.hasLocal('controller')) { + scope.bindLocal('controller', selfStream); + } } } + +function newStream(newValue, key) { + return new ProxyStream(newValue, key); +} diff --git a/packages/ember-htmlbars/lib/hooks/bind-shadow-scope.js b/packages/ember-htmlbars/lib/hooks/bind-shadow-scope.js index 68a8d606aab..b37c1109b01 100644 --- a/packages/ember-htmlbars/lib/hooks/bind-shadow-scope.js +++ b/packages/ember-htmlbars/lib/hooks/bind-shadow-scope.js @@ -3,7 +3,7 @@ @submodule ember-htmlbars */ -import newStream from 'ember-htmlbars/utils/new-stream'; +import ProxyStream from 'ember-metal/streams/proxy-stream'; export default function bindShadowScope(env, parentScope, shadowScope, options) { if (!options) { return; } @@ -12,31 +12,35 @@ export default function bindShadowScope(env, parentScope, shadowScope, options) if (parentScope && parentScope.overrideController) { didOverrideController = true; - shadowScope.locals.controller = parentScope.locals.controller; + shadowScope.bindLocal('controller', parentScope.getLocal('controller')); } var view = options.view; if (view && !view.isComponent) { - newStream(shadowScope.locals, 'view', view, null); + shadowScope.bindLocal('view', newStream(view, 'view')); if (!didOverrideController) { - newStream(shadowScope.locals, 'controller', shadowScope.locals.view.getKey('controller')); + shadowScope.bindLocal('controller', newStream(shadowScope.getLocal('view').getKey('controller'))); } if (view.isView) { - newStream(shadowScope, 'self', shadowScope.locals.view.getKey('context'), null, true); + shadowScope.bindSelf(newStream(shadowScope.getLocal('view').getKey('context'), '')); } } - shadowScope.view = view; + shadowScope.bindView(view); if (view && options.attrs) { - shadowScope.component = view; + shadowScope.bindComponent(view); } if ('attrs' in options) { - shadowScope.attrs = options.attrs; + shadowScope.bindAttrs(options.attrs); } return shadowScope; } + +function newStream(newValue, key) { + return new ProxyStream(newValue, key); +} diff --git a/packages/ember-htmlbars/lib/hooks/component.js b/packages/ember-htmlbars/lib/hooks/component.js index 2d445e0d7a4..d651feb0748 100644 --- a/packages/ember-htmlbars/lib/hooks/component.js +++ b/packages/ember-htmlbars/lib/hooks/component.js @@ -4,7 +4,7 @@ import buildComponentTemplate, { buildHTMLTemplate } from 'ember-views/system/bu import lookupComponent from 'ember-htmlbars/utils/lookup-component'; export default function componentHook(renderNode, env, scope, _tagName, params, attrs, templates, visitor) { - var state = renderNode.state; + var state = renderNode.getState(); // Determine if this is an initial render or a re-render if (state.manager) { @@ -77,7 +77,7 @@ export default function componentHook(renderNode, env, scope, _tagName, params, tagName, isAngleBracket: true, isComponentElement: true, - outerAttrs: scope.attrs, + outerAttrs: scope.getAttrs(), parentScope: scope }; diff --git a/packages/ember-htmlbars/lib/hooks/create-fresh-scope.js b/packages/ember-htmlbars/lib/hooks/create-fresh-scope.js index 65df151905c..d00ba5a3006 100644 --- a/packages/ember-htmlbars/lib/hooks/create-fresh-scope.js +++ b/packages/ember-htmlbars/lib/hooks/create-fresh-scope.js @@ -1,3 +1,6 @@ +import ProxyStream from 'ember-metal/streams/proxy-stream'; +import EmptyObject from 'ember-metal/empty_object'; + /* Ember's implementation of HTMLBars creates an enriched scope. @@ -46,13 +49,133 @@ the current view's `controller`. */ +function Scope(parent) { + this._self = undefined; + this._blocks = undefined; + this._component = undefined; + this._view = undefined; + this._attrs = undefined; + this._locals = undefined; + this._localPresent = undefined; + this.overrideController = undefined; + this.parent = parent; +} + +let proto = Scope.prototype; + +proto.getSelf = function() { + return this._self || this.parent.getSelf(); +}; + +proto.bindSelf = function(self) { + this._self = self; +}; + +proto.updateSelf = function(self, key) { + let existing = this._self; + + if (existing) { + existing.setSource(self); + } else { + this._self = new ProxyStream(self, key); + } +}; + +proto.getBlock = function(name) { + if (!this._blocks) { return this.parent.getBlock(name); } + return this._blocks[name] || this.parent.getBlock(name); +}; + +proto.hasBlock = function(name) { + if (!this._blocks) { return this.parent.hasBlock(name); } + return !!(this._blocks[name] || this.parent.hasBlock(name)); +}; + +proto.bindBlock = function(name, block) { + if (!this._blocks) { this._blocks = new EmptyObject(); } + this._blocks[name] = block; +}; + +proto.getComponent = function() { + return this._component || this.parent.getComponent(); +}; + +proto.bindComponent = function(component) { + this._component = component; +}; + +proto.getView = function() { + return this._view || this.parent.getView(); +}; + +proto.bindView = function(view) { + this._view = view; +}; + +proto.getAttrs = function() { + return this._attrs || this.parent.getAttrs(); +}; + +proto.bindAttrs = function(attrs) { + this._attrs = attrs; +}; + +proto.hasLocal = function(name) { + if (!this._localPresent) { return this.parent.hasLocal(name); } + return this._localPresent[name] || this.parent.hasLocal(name); +}; + +proto.hasOwnLocal = function(name) { + return this._localPresent && this._localPresent[name]; +}; + +proto.getLocal = function(name) { + if (!this._localPresent) { return this.parent.getLocal(name); } + return this._localPresent[name] ? this._locals[name] : this.parent.getLocal(name); +}; + +proto.bindLocal = function(name, value) { + if (!this._localPresent) { + this._localPresent = new EmptyObject(); + this._locals = new EmptyObject(); + } + + this._localPresent[name] = true; + this._locals[name] = value; +}; + +const EMPTY = { + _self: undefined, + _blocks: undefined, + _component: undefined, + _view: undefined, + _attrs: undefined, + _locals: undefined, + _localPresent: undefined, + overrideController: undefined, + + getSelf() { return null; }, + bindSelf(self) { return null; }, + updateSelf(self, key) { return null; }, + getBlock(name) { return null; }, + bindBlock(name, block) { return null; }, + hasBlock(name) { return false; }, + getComponent() { return null; }, + bindComponent() { return null; }, + getView() { return null; }, + bindView(view) { return null; }, + getAttrs() { return null; }, + bindAttrs(attrs) { return null; }, + hasLocal(name) { return false; }, + hasOwnLocal(name) { return false; }, + getLocal(name) { return null; }, + bindLocal(name, value) { return null; } +}; + export default function createFreshScope() { - return { - self: null, - blocks: {}, - component: null, - attrs: null, - locals: {}, - localPresent: {} - }; + return new Scope(EMPTY); +} + +export function createChildScope(parent) { + return new Scope(parent); } diff --git a/packages/ember-htmlbars/lib/hooks/did-render-node.js b/packages/ember-htmlbars/lib/hooks/did-render-node.js index 524e212c521..9fe36f77617 100644 --- a/packages/ember-htmlbars/lib/hooks/did-render-node.js +++ b/packages/ember-htmlbars/lib/hooks/did-render-node.js @@ -1,3 +1,3 @@ export default function didRenderNode(morph, env) { - env.renderedNodes[morph.guid] = true; + env.renderedNodes.add(morph); } diff --git a/packages/ember-htmlbars/lib/hooks/element.js b/packages/ember-htmlbars/lib/hooks/element.js index 281bd7033c3..a944939ea9a 100644 --- a/packages/ember-htmlbars/lib/hooks/element.js +++ b/packages/ember-htmlbars/lib/hooks/element.js @@ -13,7 +13,7 @@ export default function emberElement(morph, env, scope, path, params, hash, visi } var result; - var helper = findHelper(path, scope.self, env); + var helper = findHelper(path, scope.getSelf(), env); if (helper) { var helperStream = buildHelperStream(helper, params, hash, { element: morph.element }, env, scope, path); result = helperStream.value(); diff --git a/packages/ember-htmlbars/lib/hooks/get-block.js b/packages/ember-htmlbars/lib/hooks/get-block.js new file mode 100644 index 00000000000..6d67d19955c --- /dev/null +++ b/packages/ember-htmlbars/lib/hooks/get-block.js @@ -0,0 +1,3 @@ +export default function getBlock(scope, key) { + return scope.getBlock(key); +} diff --git a/packages/ember-htmlbars/lib/hooks/get-root.js b/packages/ember-htmlbars/lib/hooks/get-root.js index 196c60e5489..45fd883da77 100644 --- a/packages/ember-htmlbars/lib/hooks/get-root.js +++ b/packages/ember-htmlbars/lib/hooks/get-root.js @@ -5,30 +5,35 @@ export default function getRoot(scope, key) { if (key === 'this') { - return [scope.self]; + return [scope.getSelf()]; } else if (key === 'hasBlock') { - return [!!scope.blocks.default]; + return [!!scope.hasBlock('default')]; } else if (key === 'hasBlockParams') { - return [!!(scope.blocks.default && scope.blocks.default.arity)]; - } else if (key in scope.locals) { - return [scope.locals[key]]; + let block = scope.getBlock('default'); + return [!!block && block.arity]; + } else if (scope.hasLocal(key)) { + return [scope.getLocal(key)]; } else { return [getKey(scope, key)]; } } function getKey(scope, key) { - if (key === 'attrs' && scope.attrs) { - return scope.attrs; + if (key === 'attrs') { + let attrs = scope.getAttrs(); + if (attrs) { return attrs; } } - var self = scope.self || scope.locals.view; + var self = scope.getSelf() || scope.getLocal('view'); if (self) { return self.getKey(key); - } else if (scope.attrs && key in scope.attrs) { + } + + let attrs = scope.getAttrs(); + if (key in attrs) { // TODO: attrs // deprecate("You accessed the `" + key + "` attribute directly. Please use `attrs." + key + "` instead."); - return scope.attrs[key]; + return attrs[key]; } } diff --git a/packages/ember-htmlbars/lib/hooks/link-render-node.js b/packages/ember-htmlbars/lib/hooks/link-render-node.js index 98baa6d7a94..767161e1785 100644 --- a/packages/ember-htmlbars/lib/hooks/link-render-node.js +++ b/packages/ember-htmlbars/lib/hooks/link-render-node.js @@ -14,7 +14,7 @@ export default function linkRenderNode(renderNode, env, scope, path, params, has var keyword = env.hooks.keywords[path]; if (keyword && keyword.link) { - keyword.link(renderNode.state, params, hash); + keyword.link(renderNode.getState(), params, hash); } else { switch (path) { case 'unbound': return true; diff --git a/packages/ember-htmlbars/lib/hooks/lookup-helper.js b/packages/ember-htmlbars/lib/hooks/lookup-helper.js index 0b4c6c86816..bb159b47fbd 100644 --- a/packages/ember-htmlbars/lib/hooks/lookup-helper.js +++ b/packages/ember-htmlbars/lib/hooks/lookup-helper.js @@ -1,5 +1,5 @@ import lookupHelper from 'ember-htmlbars/system/lookup-helper'; export default function lookupHelperHook(env, scope, helperName) { - return lookupHelper(helperName, scope.self, env); + return lookupHelper(helperName, scope.getSelf(), env); } diff --git a/packages/ember-htmlbars/lib/hooks/subexpr.js b/packages/ember-htmlbars/lib/hooks/subexpr.js index 4f02582e8a4..08b61cb1397 100644 --- a/packages/ember-htmlbars/lib/hooks/subexpr.js +++ b/packages/ember-htmlbars/lib/hooks/subexpr.js @@ -19,7 +19,7 @@ export default function subexpr(env, scope, helperName, params, hash) { } var label = labelForSubexpr(params, hash, helperName); - var helper = lookupHelper(helperName, scope.self, env); + var helper = lookupHelper(helperName, scope.getSelf(), env); var helperStream = buildHelperStream(helper, params, hash, null, env, scope, label); diff --git a/packages/ember-htmlbars/lib/hooks/update-self.js b/packages/ember-htmlbars/lib/hooks/update-self.js index 02242d79406..44015e28e36 100644 --- a/packages/ember-htmlbars/lib/hooks/update-self.js +++ b/packages/ember-htmlbars/lib/hooks/update-self.js @@ -5,7 +5,6 @@ import { assert } from 'ember-metal/debug'; import { get } from 'ember-metal/property_get'; -import updateScope from 'ember-htmlbars/utils/update-scope'; export default function updateSelf(env, scope, _self) { let self = _self; @@ -14,16 +13,16 @@ export default function updateSelf(env, scope, _self) { let { controller } = self; self = self.self; - updateScope(scope.locals, 'controller', controller || self); + scope.updateLocal('controller', controller || self); } assert('BUG: scope.attrs and self.isView should not both be true', !(scope.attrs && self.isView)); if (self && self.isView) { - updateScope(scope.locals, 'view', self, null); - updateScope(scope, 'self', get(self, 'context'), null, true); + scope.updateLocal('view', self); + scope.updateSelf(get(self, 'context'), ''); return; } - updateScope(scope, 'self', self, null); + scope.updateSelf(self); } diff --git a/packages/ember-htmlbars/lib/keywords/collection.js b/packages/ember-htmlbars/lib/keywords/collection.js index 8dbd747cff9..a1f8a65719e 100644 --- a/packages/ember-htmlbars/lib/keywords/collection.js +++ b/packages/ember-htmlbars/lib/keywords/collection.js @@ -147,15 +147,15 @@ export default { // of a mutable param and used it in its layout, because there are // no params at all. if (Object.keys(hash).length) { - return morph.state.manager.rerender(env, hash, visitor, true); + return morph.getState().manager.rerender(env, hash, visitor, true); } }, render(node, env, scope, params, hash, template, inverse, visitor) { - var state = node.state; + var state = node.getState(); var parentView = state.parentView; - var options = { component: node.state.viewClassOrInstance, layout: null }; + var options = { component: state.viewClassOrInstance, layout: null }; if (template) { options.createOptions = { _itemViewTemplate: template && { raw: template }, diff --git a/packages/ember-htmlbars/lib/keywords/component.js b/packages/ember-htmlbars/lib/keywords/component.js index 0281209e70a..675e93a4adf 100644 --- a/packages/ember-htmlbars/lib/keywords/component.js +++ b/packages/ember-htmlbars/lib/keywords/component.js @@ -59,14 +59,16 @@ export default { }, render(morph, ...rest) { - if (morph.state.manager) { - morph.state.manager.destroy(); + let state = morph.getState(); + + if (state.manager) { + state.manager.destroy(); } // Force the component hook to treat this as a first-time render, // because normal components (``) cannot change at runtime, // but the `{{component}}` helper can. - morph.state.manager = null; + state.manager = null; render(morph, ...rest); }, @@ -75,7 +77,7 @@ export default { }; function render(morph, env, scope, params, hash, template, inverse, visitor) { - let componentPath = morph.state.componentPath; + let componentPath = morph.getState().componentPath; // If the value passed to the {{component}} helper is undefined or null, // don't create a new ComponentNode. diff --git a/packages/ember-htmlbars/lib/keywords/debugger.js b/packages/ember-htmlbars/lib/keywords/debugger.js index 6e0079deb2a..831fa9789a6 100644 --- a/packages/ember-htmlbars/lib/keywords/debugger.js +++ b/packages/ember-htmlbars/lib/keywords/debugger.js @@ -52,8 +52,8 @@ import { info } from 'ember-metal/debug'; export default function debuggerKeyword(morph, env, scope) { /* jshint unused: false, debug: true */ - var view = env.hooks.getValue(scope.locals.view); - var context = env.hooks.getValue(scope.self); + var view = env.hooks.getValue(scope.getLocal('view')); + var context = env.hooks.getValue(scope.getSelf()); function get(path) { return env.hooks.getValue(env.hooks.get(env, scope, path)); diff --git a/packages/ember-htmlbars/lib/keywords/get.js b/packages/ember-htmlbars/lib/keywords/get.js index 59af43b2a9a..da5fde73c28 100644 --- a/packages/ember-htmlbars/lib/keywords/get.js +++ b/packages/ember-htmlbars/lib/keywords/get.js @@ -4,10 +4,9 @@ */ import { assert } from 'ember-metal/debug'; -import Stream from 'ember-metal/streams/stream'; +import BasicStream from 'ember-metal/streams/stream'; import KeyStream from 'ember-metal/streams/key-stream'; import { isStream } from 'ember-metal/streams/utils'; -import merge from 'ember-metal/merge'; import subscribe from 'ember-htmlbars/utils/subscribe'; import { get } from 'ember-metal/property_get'; import { set } from 'ember-metal/property_set'; @@ -22,17 +21,90 @@ function labelFor(source, key) { return `(get ${sourceLabel} ${keyLabel})`; } +let DynamicKeyStream = BasicStream.extend({ + init(source, keySource) { + assert('DynamicKeyStream error: source must be a stream', isStream(source)); // TODO: This isn't necessary. + + // used to get the original path for debugging and legacy purposes + var label = labelFor(source, keySource); + + this.label = label; + this.path = label; + this.sourceDep = this.addMutableDependency(source); + this.keyDep = this.addMutableDependency(keySource); + this.observedObject = null; + this.observedKey = null; + }, + + key() { + const key = this.keyDep.getValue(); + if (typeof key === 'string') { + assert('DynamicKeyStream error: key must not have a \'.\'', key.indexOf('.') === -1); + return key; + } + }, + + compute() { + var object = this.sourceDep.getValue(); + var key = this.key(); + if (object && key) { + return get(object, key); + } + }, + + setValue(value) { + var object = this.sourceDep.getValue(); + var key = this.key(); + if (object) { + set(object, key, value); + } + }, + + _super$revalidate: BasicStream.prototype.revalidate, + + revalidate(value) { + this._super$revalidate(value); + + var object = this.sourceDep.getValue(); + var key = this.key(); + if (object !== this.observedObject || key !== this.observedKey) { + this._clearObservedObject(); + + if (object && typeof object === 'object' && key) { + addObserver(object, key, this, this.notify); + this.observedObject = object; + this.observedKey = key; + } + } + }, + + _clearObservedObject() { + if (this.observedObject) { + removeObserver(this.observedObject, this.observedKey, this, this.notify); + this.observedObject = null; + this.observedKey = null; + } + } +}); + const buildStream = function buildStream(params) { const [objRef, pathRef] = params; assert('The first argument to {{get}} must be a stream', isStream(objRef)); assert('{{get}} requires at least two arguments', params.length > 1); - const stream = new DynamicKeyStream(objRef, pathRef); + const stream = buildDynamicKeyStream(objRef, pathRef); return stream; }; +function buildDynamicKeyStream(source, keySource) { + if (!isStream(keySource)) { + return new KeyStream(source, keySource); + } else { + return new DynamicKeyStream(source, keySource); + } +} /** Dynamically look up a property on an object. The second argument to `{{get}}` @@ -97,75 +169,4 @@ var getKeyword = function getKeyword(morph, env, scope, params, hash, template, return true; }; -var DynamicKeyStream = function DynamicKeyStream(source, keySource) { - if (!isStream(keySource)) { - return new KeyStream(source, keySource); - } - assert('DynamicKeyStream error: source must be a stream', isStream(source)); // TODO: This isn't necessary. - - // used to get the original path for debugging and legacy purposes - var label = labelFor(source, keySource); - - this.init(label); - this.path = label; - this.sourceDep = this.addMutableDependency(source); - this.keyDep = this.addMutableDependency(keySource); - this.observedObject = null; - this.observedKey = null; -}; - -DynamicKeyStream.prototype = Object.create(KeyStream.prototype); - -merge(DynamicKeyStream.prototype, { - key() { - const key = this.keyDep.getValue(); - if (typeof key === 'string') { - assert('DynamicKeyStream error: key must not have a \'.\'', key.indexOf('.') === -1); - return key; - } - }, - - compute() { - var object = this.sourceDep.getValue(); - var key = this.key(); - if (object && key) { - return get(object, key); - } - }, - - setValue(value) { - var object = this.sourceDep.getValue(); - var key = this.key(); - if (object) { - set(object, key, value); - } - }, - - _super$revalidate: Stream.prototype.revalidate, - - revalidate(value) { - this._super$revalidate(value); - - var object = this.sourceDep.getValue(); - var key = this.key(); - if (object !== this.observedObject || key !== this.observedKey) { - this._clearObservedObject(); - - if (object && typeof object === 'object' && key) { - addObserver(object, key, this, this.notify); - this.observedObject = object; - this.observedKey = key; - } - } - }, - - _clearObservedObject() { - if (this.observedObject) { - removeObserver(this.observedObject, this.observedKey, this, this.notify); - this.observedObject = null; - this.observedKey = null; - } - } -}); - export default getKeyword; diff --git a/packages/ember-htmlbars/lib/keywords/input.js b/packages/ember-htmlbars/lib/keywords/input.js index 9ddf24499d3..33b74a252e3 100644 --- a/packages/ember-htmlbars/lib/keywords/input.js +++ b/packages/ember-htmlbars/lib/keywords/input.js @@ -166,7 +166,7 @@ export default { }, render(morph, env, scope, params, hash, template, inverse, visitor) { - env.hooks.component(morph, env, scope, morph.state.componentName, params, hash, { default: template, inverse }, visitor); + env.hooks.component(morph, env, scope, morph.getState().componentName, params, hash, { default: template, inverse }, visitor); }, rerender(...args) { diff --git a/packages/ember-htmlbars/lib/keywords/legacy-yield.js b/packages/ember-htmlbars/lib/keywords/legacy-yield.js index 32a9c2a7571..e83216485fe 100644 --- a/packages/ember-htmlbars/lib/keywords/legacy-yield.js +++ b/packages/ember-htmlbars/lib/keywords/legacy-yield.js @@ -2,8 +2,9 @@ import ProxyStream from 'ember-metal/streams/proxy-stream'; export default function legacyYield(morph, env, _scope, params, hash, template, inverse, visitor) { let scope = _scope; + let block = scope.getBlock('default'); - if (scope.blocks.default.arity === 0) { + if (block.arity === 0) { // Typically, the `controller` local is persists through lexical scope. // However, in this case, the `{{legacy-yield}}` in the legacy each view // needs to override the controller local for the template it is yielding. @@ -11,12 +12,12 @@ export default function legacyYield(morph, env, _scope, params, hash, template, // prevents the downstream scope from attempting to bind the `controller` local. if (hash.controller) { scope = env.hooks.createChildScope(scope); - scope.locals.controller = new ProxyStream(hash.controller, 'controller'); + scope.bindLocal('controller', new ProxyStream(hash.controller, 'controller')); scope.overrideController = true; } - scope.blocks.default.invoke(env, [], params[0], morph, scope, visitor); + block.invoke(env, [], params[0], morph, scope, visitor); } else { - scope.blocks.default.invoke(env, params, undefined, morph, scope, visitor); + block.invoke(env, params, undefined, morph, scope, visitor); } return true; diff --git a/packages/ember-htmlbars/lib/keywords/mut.js b/packages/ember-htmlbars/lib/keywords/mut.js index 54f71e7e1cc..23399011db7 100644 --- a/packages/ember-htmlbars/lib/keywords/mut.js +++ b/packages/ember-htmlbars/lib/keywords/mut.js @@ -4,16 +4,46 @@ */ import { assert } from 'ember-metal/debug'; -import merge from 'ember-metal/merge'; import { symbol } from 'ember-metal/utils'; import ProxyStream from 'ember-metal/streams/proxy-stream'; +import BasicStream from 'ember-metal/streams/stream'; import { isStream } from 'ember-metal/streams/utils'; -import Stream from 'ember-metal/streams/stream'; import { MUTABLE_CELL } from 'ember-views/compat/attrs-proxy'; import { INVOKE, ACTION } from 'ember-routing-htmlbars/keywords/closure-action'; export let MUTABLE_REFERENCE = symbol('MUTABLE_REFERENCE'); +let MutStream = ProxyStream.extend({ + init(stream) { + this.label = `(mut ${stream.label})`; + this.path = stream.path; + this.sourceDep = this.addMutableDependency(stream); + this[MUTABLE_REFERENCE] = true; + }, + + cell() { + let source = this; + let value = source.value(); + + if (value && value[ACTION]) { + return value; + } + + let val = { + value, + update(val) { + source.setValue(val); + } + }; + + val[MUTABLE_CELL] = true; + return val; + }, + [INVOKE](val) { + this.setValue(val); + } +}); + /** The `mut` helper lets you __clearly specify__ that a child `Component` can update the (mutable) value passed to it, which will __change the value of the parent component__. @@ -67,15 +97,27 @@ export function privateMut(morph, env, scope, originalParams, hash, template, in return true; } +let LiteralStream = BasicStream.extend({ + init(literal) { + this.literal = literal; + this.label = `(literal ${literal})`; + }, + + compute() { + return this.literal; + }, + + setValue(val) { + this.literal = val; + this.notify(); + } +}); + function mutParam(read, stream, internal) { if (internal) { if (!isStream(stream)) { let literal = stream; - stream = new Stream(function() { return literal; }, `(literal ${literal})`); - stream.setValue = function(newValue) { - literal = newValue; - stream.notify(); - }; + stream = new LiteralStream(literal); } } else { assert('You can only pass a path to mut', isStream(stream)); @@ -87,36 +129,3 @@ function mutParam(read, stream, internal) { return new MutStream(stream); } - -function MutStream(stream) { - this.init(`(mut ${stream.label})`); - this.path = stream.path; - this.sourceDep = this.addMutableDependency(stream); - this[MUTABLE_REFERENCE] = true; -} - -MutStream.prototype = Object.create(ProxyStream.prototype); - -merge(MutStream.prototype, { - cell() { - let source = this; - let value = source.value(); - - if (value && value[ACTION]) { - return value; - } - - let val = { - value, - update(val) { - source.setValue(val); - } - }; - - val[MUTABLE_CELL] = true; - return val; - }, - [INVOKE](val) { - this.setValue(val); - } -}); diff --git a/packages/ember-htmlbars/lib/keywords/outlet.js b/packages/ember-htmlbars/lib/keywords/outlet.js index e9683a7e581..7c78b1692a5 100644 --- a/packages/ember-htmlbars/lib/keywords/outlet.js +++ b/packages/ember-htmlbars/lib/keywords/outlet.js @@ -103,7 +103,7 @@ export default { }, render(renderNode, env, scope, params, hash, template, inverse, visitor) { - var state = renderNode.state; + var state = renderNode.getState(); var parentView = env.view; var outletState = state.outletState; var toRender = outletState.render; diff --git a/packages/ember-htmlbars/lib/keywords/partial.js b/packages/ember-htmlbars/lib/keywords/partial.js index d776ca68b1e..4308e11639f 100644 --- a/packages/ember-htmlbars/lib/keywords/partial.js +++ b/packages/ember-htmlbars/lib/keywords/partial.js @@ -53,7 +53,7 @@ export default { }, render(renderNode, env, scope, params, hash, template, inverse, visitor) { - var state = renderNode.state; + var state = renderNode.getState(); if (!state.partialName) { return true; } var found = lookupPartial(env, state.partialName); if (!found) { return true; } diff --git a/packages/ember-htmlbars/lib/keywords/unbound.js b/packages/ember-htmlbars/lib/keywords/unbound.js index e6c51f76cc9..550e3c88e6c 100644 --- a/packages/ember-htmlbars/lib/keywords/unbound.js +++ b/packages/ember-htmlbars/lib/keywords/unbound.js @@ -4,6 +4,8 @@ */ import { assert } from 'ember-metal/debug'; +import BasicStream from 'ember-metal/streams/stream'; +import { read } from 'ember-metal/streams/utils'; /** The `{{unbound}}` helper disconnects the one-way binding of a property, @@ -33,6 +35,20 @@ import { assert } from 'ember-metal/debug'; @public */ +let VolatileStream = BasicStream.extend({ + init(source) { + this.label = `(volatile ${source.label})`; + this.source = source; + this.addDependency(source); + }, + + value() { + return read(this.source); + }, + + notify() {} +}); + export default function unbound(morph, env, scope, params, hash, template, inverse, visitor) { assert( 'unbound helper cannot be called with multiple params or hash params', @@ -58,23 +74,3 @@ export default function unbound(morph, env, scope, params, hash, template, inver return true; } -import merge from 'ember-metal/merge'; -import Stream from 'ember-metal/streams/stream'; -import { read } from 'ember-metal/streams/utils'; - -function VolatileStream(source) { - this.init(`(volatile ${source.label})`); - this.source = source; - - this.addDependency(source); -} - -VolatileStream.prototype = Object.create(Stream.prototype); - -merge(VolatileStream.prototype, { - value() { - return read(this.source); - }, - - notify() {} -}); diff --git a/packages/ember-htmlbars/lib/keywords/view.js b/packages/ember-htmlbars/lib/keywords/view.js index efda98204cf..1aed411156a 100644 --- a/packages/ember-htmlbars/lib/keywords/view.js +++ b/packages/ember-htmlbars/lib/keywords/view.js @@ -188,7 +188,7 @@ import ViewNodeManager from 'ember-htmlbars/node-managers/view-node-manager'; export default { setupState(state, env, scope, params, hash) { var read = env.hooks.getValue; - var targetObject = read(scope.self); + var targetObject = read(scope.getSelf()); var viewClassOrInstance = state.viewClassOrInstance; if (!viewClassOrInstance) { viewClassOrInstance = getView(read(params[0]), env.container); @@ -196,7 +196,7 @@ export default { // if parentView exists, use its controller (the default // behavior), otherwise use `scope.self` as the controller - var controller = scope.locals.view ? null : read(scope.self); + var controller = scope.hasLocal('view') ? null : read(scope.getSelf()); return { manager: state.manager, @@ -212,7 +212,7 @@ export default { // of a mutable param and used it in its layout, because there are // no params at all. if (Object.keys(hash).length) { - return morph.state.manager.rerender(env, hash, visitor, true); + return morph.getState().manager.rerender(env, hash, visitor, true); } }, @@ -225,25 +225,25 @@ export default { hash.classNameBindings = hash.classNameBindings.split(' '); } - var state = node.state; + var state = node.getState(); var parentView = state.parentView; var options = { - component: node.state.viewClassOrInstance, + component: state.viewClassOrInstance, layout: null }; options.createOptions = {}; - if (node.state.controller) { + if (state.controller) { // Use `_controller` to avoid stomping on a CP // that exists in the target view/component - options.createOptions._controller = node.state.controller; + options.createOptions._controller = state.controller; } - if (node.state.targetObject) { + if (state.targetObject) { // Use `_targetObject` to avoid stomping on a CP // that exists in the target view/component - options.createOptions._targetObject = node.state.targetObject; + options.createOptions._targetObject = state.targetObject; } if (state.manager) { diff --git a/packages/ember-htmlbars/lib/keywords/yield.js b/packages/ember-htmlbars/lib/keywords/yield.js new file mode 100644 index 00000000000..73f53cbdec1 --- /dev/null +++ b/packages/ember-htmlbars/lib/keywords/yield.js @@ -0,0 +1,10 @@ +export default function yieldKeyword(morph, env, scope, params, hash, template, inverse, visitor) { + let to = env.hooks.getValue(hash.to) || 'default'; + let block = scope.getBlock(to); + + if (block) { + block.invoke(env, params, hash.self, morph, scope, visitor); + } + + return true; +} diff --git a/packages/ember-htmlbars/lib/morphs/attr-morph.js b/packages/ember-htmlbars/lib/morphs/attr-morph.js index 0b5f898fe59..ee73b3ab723 100644 --- a/packages/ember-htmlbars/lib/morphs/attr-morph.js +++ b/packages/ember-htmlbars/lib/morphs/attr-morph.js @@ -9,16 +9,13 @@ export var styleWarning = '' + 'including how to disable this warning, see ' + 'http://emberjs.com/deprecations/v1.x/#toc_binding-style-attributes.'; -function EmberAttrMorph(element, attrName, domHelper, namespace) { - HTMLBarsAttrMorph.call(this, element, attrName, domHelper, namespace); +let proto = HTMLBarsAttrMorph.prototype; +proto.didInit = function() { this.streamUnsubscribers = null; -} - -var proto = EmberAttrMorph.prototype = Object.create(HTMLBarsAttrMorph.prototype); -proto.HTMLBarsAttrMorph$setContent = HTMLBarsAttrMorph.prototype.setContent; +}; -proto._deprecateEscapedStyle = function EmberAttrMorph_deprecateEscapedStyle(value) { +function deprecateEscapedStyle(morph, value) { warn( styleWarning, (function(name, value, escaped) { @@ -32,14 +29,13 @@ proto._deprecateEscapedStyle = function EmberAttrMorph_deprecateEscapedStyle(val } return !escaped; - }(this.attrName, value, this.escaped)), + }(morph.attrName, value, morph.escaped)), { id: 'ember-htmlbars.style-xss-warning' } ); -}; +} -proto.setContent = function EmberAttrMorph_setContent(value) { - this._deprecateEscapedStyle(value); - this.HTMLBarsAttrMorph$setContent(value); +proto.willSetContent = function (value) { + deprecateEscapedStyle(this, value); }; -export default EmberAttrMorph; +export default HTMLBarsAttrMorph; diff --git a/packages/ember-htmlbars/lib/morphs/morph.js b/packages/ember-htmlbars/lib/morphs/morph.js index 9aeca8b199e..a887383ec27 100644 --- a/packages/ember-htmlbars/lib/morphs/morph.js +++ b/packages/ember-htmlbars/lib/morphs/morph.js @@ -53,7 +53,7 @@ proto.cleanup = function() { }; proto.didRender = function(env, scope) { - env.renderedNodes[this.guid] = true; + env.renderedNodes.add(this); }; export default EmberMorph; diff --git a/packages/ember-htmlbars/lib/node-managers/component-node-manager.js b/packages/ember-htmlbars/lib/node-managers/component-node-manager.js index f2557e99bfc..741d5c57091 100644 --- a/packages/ember-htmlbars/lib/node-managers/component-node-manager.js +++ b/packages/ember-htmlbars/lib/node-managers/component-node-manager.js @@ -8,7 +8,7 @@ import { MUTABLE_CELL } from 'ember-views/compat/attrs-proxy'; import { instrument } from 'ember-htmlbars/system/instrumentation-support'; import LegacyEmberComponent from 'ember-views/components/component'; import GlimmerComponent from 'ember-htmlbars/glimmer-component'; -import Stream from 'ember-metal/streams/stream'; +import { Stream } from 'ember-metal/streams/stream'; import { readArray } from 'ember-metal/streams/utils'; import { symbol } from 'ember-metal/utils'; @@ -62,8 +62,8 @@ ComponentNodeManager.create = function(renderNode, env, options) { // If there is a controller on the scope, pluck it off and save it on the // component. This allows the component to target actions sent via // `sendAction` correctly. - if (parentScope.locals.controller) { - createOptions._controller = getValue(parentScope.locals.controller); + if (parentScope.hasLocal('controller')) { + createOptions._controller = getValue(parentScope.getLocal('controller')); } extractPositionalParams(renderNode, component, params, attrs); @@ -121,7 +121,7 @@ function processPositionalParams(renderNode, positionalParams, params, attrs) { // if the component is rendered via {{component}} helper, the first // element of `params` is the name of the component, so we need to // skip that when the positional parameters are constructed - const paramsStartIndex = renderNode.state.isComponentHelper ? 1 : 0; + const paramsStartIndex = renderNode.getState().isComponentHelper ? 1 : 0; const isNamed = typeof positionalParams === 'string'; let paramsStream; @@ -221,6 +221,10 @@ ComponentNodeManager.prototype.rerender = function(_env, attrs, visitor) { var snapshot = takeSnapshot(attrs); if (component._renderNode.shouldReceiveAttrs) { + if (component._propagateAttrsToThis) { + component._propagateAttrsToThis(takeLegacySnapshot(attrs)); + } + env.renderer.componentUpdateAttrs(component, snapshot); component._renderNode.shouldReceiveAttrs = false; } @@ -258,11 +262,9 @@ export function createComponent(_component, isAngleBracket, _props, renderNode, props.attrs = snapshot; if (!isAngleBracket) { - let proto = _component.proto(); - assert('controller= is no longer supported', !('controller' in attrs)); - mergeBindings(props, shadowedAttrs(proto, snapshot)); + mergeBindings(props, snapshot); } else { props._isAngleBracket = true; } @@ -289,28 +291,21 @@ export function createComponent(_component, isAngleBracket, _props, renderNode, return component; } -function shadowedAttrs(target, attrs) { - let shadowed = {}; - - // For backwards compatibility, set the component property - // if it has an attr with that name. Undefined attributes - // are handled on demand via the `unknownProperty` hook. - for (var attr in attrs) { - if (attr in target) { - // TODO: Should we issue a deprecation here? - // deprecate(deprecation(attr)); - shadowed[attr] = attrs[attr]; - } +function takeSnapshot(attrs) { + let hash = {}; + + for (var prop in attrs) { + hash[prop] = getCellOrValue(attrs[prop]); } - return shadowed; + return hash; } -function takeSnapshot(attrs) { +export function takeLegacySnapshot(attrs) { let hash = {}; for (var prop in attrs) { - hash[prop] = getCellOrValue(attrs[prop]); + hash[prop] = getValue(attrs[prop]); } return hash; diff --git a/packages/ember-htmlbars/lib/node-managers/view-node-manager.js b/packages/ember-htmlbars/lib/node-managers/view-node-manager.js index b7dc37198d1..7993c3aaef2 100644 --- a/packages/ember-htmlbars/lib/node-managers/view-node-manager.js +++ b/packages/ember-htmlbars/lib/node-managers/view-node-manager.js @@ -8,6 +8,7 @@ import View from 'ember-views/views/view'; import { MUTABLE_CELL } from 'ember-views/compat/attrs-proxy'; import getCellOrValue from 'ember-htmlbars/hooks/get-cell-or-value'; import { instrument } from 'ember-htmlbars/system/instrumentation-support'; +import { takeLegacySnapshot } from 'ember-htmlbars/node-managers/component-node-manager'; // In theory this should come through the env, but it should // be safe to import this until we make the hook system public @@ -45,8 +46,11 @@ ViewNodeManager.create = function(renderNode, env, attrs, found, parentView, pat if (attrs && attrs._defaultTagName) { options._defaultTagName = getValue(attrs._defaultTagName); } if (attrs && attrs.viewName) { options.viewName = getValue(attrs.viewName); } - if (found.component.create && contentScope && contentScope.self) { - options._context = getValue(contentScope.self); + if (found.component.create && contentScope) { + let _self = contentScope.getSelf(); + if (_self) { + options._context = getValue(contentScope.getSelf()); + } } if (found.self) { @@ -122,6 +126,10 @@ ViewNodeManager.prototype.rerender = function(env, attrs, visitor) { env.renderer.willUpdate(component, snapshot); if (component._renderNode.shouldReceiveAttrs) { + if (component._propagateAttrsToThis) { + component._propagateAttrsToThis(takeLegacySnapshot(attrs)); + } + env.renderer.componentUpdateAttrs(component, snapshot); component._renderNode.shouldReceiveAttrs = false; } @@ -171,7 +179,7 @@ export function createOrUpdateComponent(component, options, createOptions, rende merge(props, createOptions); } - mergeBindings(props, shadowedAttrs(proto, snapshot)); + mergeBindings(props, snapshot); props.container = options.parentView ? options.parentView.container : env.container; props.renderer = options.parentView ? options.parentView.renderer : props.container && props.container.lookup('renderer:-dom'); props._viewRegistry = options.parentView ? options.parentView._viewRegistry : props.container && props.container.lookup('-view-registry:main'); @@ -184,6 +192,10 @@ export function createOrUpdateComponent(component, options, createOptions, rende } else { env.renderer.componentUpdateAttrs(component, snapshot); setProperties(component, props); + + if (component._propagateAttrsToThis) { + component._propagateAttrsToThis(takeLegacySnapshot(attrs)); + } } if (options.parentView) { @@ -200,23 +212,6 @@ export function createOrUpdateComponent(component, options, createOptions, rende return component; } -function shadowedAttrs(target, attrs) { - let shadowed = {}; - - // For backwards compatibility, set the component property - // if it has an attr with that name. Undefined attributes - // are handled on demand via the `unknownProperty` hook. - for (var attr in attrs) { - if (attr in target) { - // TODO: Should we issue a deprecation here? - // deprecate(deprecation(attr)); - shadowed[attr] = attrs[attr]; - } - } - - return shadowed; -} - function takeSnapshot(attrs) { let hash = {}; diff --git a/packages/ember-htmlbars/lib/streams/built-in-helper.js b/packages/ember-htmlbars/lib/streams/built-in-helper.js index b4d0938aaa1..04ab9a02c7b 100644 --- a/packages/ember-htmlbars/lib/streams/built-in-helper.js +++ b/packages/ember-htmlbars/lib/streams/built-in-helper.js @@ -1,24 +1,23 @@ -import Stream from 'ember-metal/streams/stream'; -import merge from 'ember-metal/merge'; +import BasicStream from 'ember-metal/streams/stream'; import { getArrayValues, getHashValues } from 'ember-htmlbars/streams/utils'; -export default function BuiltInHelperStream(helper, params, hash, templates, env, scope, label) { - this.init(label); - this.helper = helper; - this.params = params; - this.templates = templates; - this.env = env; - this.scope = scope; - this.hash = hash; -} +let BuiltInHelperStream = BasicStream.extend({ + init(helper, params, hash, templates, env, scope, label) { + this.helper = helper; + this.params = params; + this.templates = templates; + this.env = env; + this.scope = scope; + this.hash = hash; + this.label = label; + }, -BuiltInHelperStream.prototype = Object.create(Stream.prototype); - -merge(BuiltInHelperStream.prototype, { compute() { return this.helper(getArrayValues(this.params), getHashValues(this.hash), this.templates, this.env, this.scope); } }); + +export default BuiltInHelperStream; diff --git a/packages/ember-htmlbars/lib/streams/helper-factory.js b/packages/ember-htmlbars/lib/streams/helper-factory.js index bfcab2d3466..24726173bf7 100644 --- a/packages/ember-htmlbars/lib/streams/helper-factory.js +++ b/packages/ember-htmlbars/lib/streams/helper-factory.js @@ -1,28 +1,26 @@ -import Stream from 'ember-metal/streams/stream'; -import merge from 'ember-metal/merge'; +import BasicStream from 'ember-metal/streams/stream'; import { getArrayValues, getHashValues } from 'ember-htmlbars/streams/utils'; -export default function HelperFactoryStream(helperFactory, params, hash, label) { - this.init(label); - this.helperFactory = helperFactory; - this.params = params; - this.hash = hash; - this.linkable = true; - this.helper = null; -} - -HelperFactoryStream.prototype = Object.create(Stream.prototype); +let HelperFactoryStream = BasicStream.extend({ + init(helperFactory, params, hash, label) { + this.helperFactory = helperFactory; + this.params = params; + this.hash = hash; + this.linkable = true; + this.helper = null; + this.label = label; + }, -merge(HelperFactoryStream.prototype, { compute() { if (!this.helper) { this.helper = this.helperFactory.create({ _stream: this }); } return this.helper.compute(getArrayValues(this.params), getHashValues(this.hash)); }, + deactivate() { this.super$deactivate(); if (this.helper) { @@ -30,5 +28,7 @@ merge(HelperFactoryStream.prototype, { this.helper = null; } }, - super$deactivate: HelperFactoryStream.prototype.deactivate + super$deactivate: BasicStream.prototype.deactivate }); + +export default HelperFactoryStream; diff --git a/packages/ember-htmlbars/lib/streams/helper-instance.js b/packages/ember-htmlbars/lib/streams/helper-instance.js index e5852709d51..20487935193 100644 --- a/packages/ember-htmlbars/lib/streams/helper-instance.js +++ b/packages/ember-htmlbars/lib/streams/helper-instance.js @@ -1,22 +1,21 @@ -import Stream from 'ember-metal/streams/stream'; -import merge from 'ember-metal/merge'; +import BasicStream from 'ember-metal/streams/stream'; import { getArrayValues, getHashValues } from 'ember-htmlbars/streams/utils'; -export default function HelperInstanceStream(helper, params, hash, label) { - this.init(label); - this.helper = helper; - this.params = params; - this.hash = hash; - this.linkable = true; -} +let HelperInstanceStream = BasicStream.extend({ + init(helper, params, hash, label) { + this.helper = helper; + this.params = params; + this.hash = hash; + this.linkable = true; + this.label = label; + }, -HelperInstanceStream.prototype = Object.create(Stream.prototype); - -merge(HelperInstanceStream.prototype, { compute() { return this.helper.compute(getArrayValues(this.params), getHashValues(this.hash)); } }); + +export default HelperInstanceStream; diff --git a/packages/ember-htmlbars/lib/system/render-env.js b/packages/ember-htmlbars/lib/system/render-env.js index 86b939e1440..5e1d664cc87 100644 --- a/packages/ember-htmlbars/lib/system/render-env.js +++ b/packages/ember-htmlbars/lib/system/render-env.js @@ -1,9 +1,10 @@ import defaultEnv from 'ember-htmlbars/env'; +import { MorphSet } from 'ember-metal-views/renderer'; export default function RenderEnv(options) { this.lifecycleHooks = options.lifecycleHooks || []; this.renderedViews = options.renderedViews || []; - this.renderedNodes = options.renderedNodes || {}; + this.renderedNodes = options.renderedNodes || new MorphSet(); this.hasParentOutlet = options.hasParentOutlet || false; this.view = options.view; diff --git a/packages/ember-htmlbars/lib/utils/subscribe.js b/packages/ember-htmlbars/lib/utils/subscribe.js index 498bd96d619..70983d42372 100644 --- a/packages/ember-htmlbars/lib/utils/subscribe.js +++ b/packages/ember-htmlbars/lib/utils/subscribe.js @@ -2,7 +2,7 @@ import { isStream, labelFor } from 'ember-metal/streams/utils'; export default function subscribe(node, env, scope, stream) { if (!isStream(stream)) { return; } - var component = scope.component; + var component = scope.getComponent(); var unsubscribers = node.streamUnsubscribers = node.streamUnsubscribers || []; unsubscribers.push(stream.subscribe(function() { @@ -17,7 +17,7 @@ export default function subscribe(node, env, scope, stream) { component._renderNode.isDirty = true; } - if (node.state.manager) { + if (node.getState().manager) { node.shouldReceiveAttrs = true; } diff --git a/packages/ember-metal-views/lib/renderer.js b/packages/ember-metal-views/lib/renderer.js index 55e282a9e67..95c8041ec59 100755 --- a/packages/ember-metal-views/lib/renderer.js +++ b/packages/ember-metal-views/lib/renderer.js @@ -98,9 +98,31 @@ Renderer.prototype.ensureViewNotRendering = } }; +export function MorphSet() { + this.morphs = []; +} + +MorphSet.prototype.add = function(morph) { + this.morphs.push(morph); + morph.seen = true; +}; + +MorphSet.prototype.has = function(morph) { + return morph.seen; +}; + +MorphSet.prototype.clear = function() { + let morphs = this.morphs; + for (let i = 0, l = morphs.length; i < l; i++) { + morphs[i].seen = false; + } + + this.morphs = []; +}; + Renderer.prototype.clearRenderedViews = function Renderer_clearRenderedViews(env) { - env.renderedNodes = {}; + env.renderedNodes.clear(); env.renderedViews.length = 0; }; @@ -147,8 +169,6 @@ Renderer.prototype.setAttrs = function (view, attrs) { }; // set attrs the first time Renderer.prototype.componentInitAttrs = function (component, attrs) { - // for attrs-proxy support - component.trigger('_internalDidReceiveAttrs'); component.trigger('didInitAttrs', { attrs }); component.trigger('didReceiveAttrs', { newAttrs: attrs }); }; // set attrs the first time @@ -183,8 +203,6 @@ Renderer.prototype.componentUpdateAttrs = function (component, newAttrs) { set(component, 'attrs', newAttrs); } - // for attrs-proxy support - component.trigger('_internalDidReceiveAttrs'); component.trigger('didUpdateAttrs', { oldAttrs, newAttrs }); component.trigger('didReceiveAttrs', { oldAttrs, newAttrs }); }; diff --git a/packages/ember-metal/lib/debug.js b/packages/ember-metal/lib/debug.js index 010cdfaa4a7..06bcdab98c5 100644 --- a/packages/ember-metal/lib/debug.js +++ b/packages/ember-metal/lib/debug.js @@ -5,7 +5,8 @@ export let debugFunctions = { debug() {}, deprecate() {}, deprecateFunc(...args) { return args[args.length - 1]; }, - runInDebug() {} + runInDebug() {}, + debugSeal() {} }; export function getDebugFunction(name) { @@ -43,3 +44,7 @@ export function deprecateFunc() { export function runInDebug() { return debugFunctions.runInDebug.apply(undefined, arguments); } + +export function debugSeal() { + return debugFunctions.debugSeal.apply(undefined, arguments); +} diff --git a/packages/ember-metal/lib/streams/key-stream.js b/packages/ember-metal/lib/streams/key-stream.js index 98b001cd3ed..c7c702fe327 100644 --- a/packages/ember-metal/lib/streams/key-stream.js +++ b/packages/ember-metal/lib/streams/key-stream.js @@ -1,36 +1,28 @@ import { assert } from 'ember-metal/debug'; -import merge from 'ember-metal/merge'; import { get } from 'ember-metal/property_get'; import { set } from 'ember-metal/property_set'; import { addObserver, removeObserver } from 'ember-metal/observer'; -import Stream from 'ember-metal/streams/stream'; +import BasicStream from 'ember-metal/streams/stream'; import { isStream } from 'ember-metal/streams/utils'; -function KeyStream(source, key) { - assert('KeyStream error: source must be a stream', isStream(source)); // TODO: This isn't necessary. - assert('KeyStream error: key must be a non-empty string', typeof key === 'string' && key.length > 0); - assert('KeyStream error: key must not have a \'.\'', key.indexOf('.') === -1); +let KeyStream = BasicStream.extend({ + init(source, key) { + assert('KeyStream error: source must be a stream', isStream(source)); // TODO: This isn't necessary. + assert('KeyStream error: key must be a non-empty string', typeof key === 'string' && key.length > 0); + assert('KeyStream error: key must not have a \'.\'', key.indexOf('.') === -1); - // used to get the original path for debugging and legacy purposes - var label = labelFor(source, key); + var label = labelFor(source, key); - this.init(label); - this.path = label; - this.sourceDep = this.addMutableDependency(source); - this.observedObject = null; - this.key = key; -} - -function labelFor(source, key) { - return source.label ? source.label + '.' + key : key; -} - -KeyStream.prototype = Object.create(Stream.prototype); + this.path = label; + this.observedObject = null; + this.key = key; + this.sourceDep = this.addMutableDependency(source); + this.label = label; + }, -merge(KeyStream.prototype, { compute() { var object = this.sourceDep.getValue(); if (object) { @@ -50,7 +42,7 @@ merge(KeyStream.prototype, { this.notify(); }, - _super$revalidate: Stream.prototype.revalidate, + _super$revalidate: BasicStream.prototype.revalidate, revalidate(value) { this._super$revalidate(value); @@ -66,7 +58,7 @@ merge(KeyStream.prototype, { } }, - _super$deactivate: Stream.prototype.deactivate, + _super$deactivate: BasicStream.prototype.deactivate, _clearObservedObject() { if (this.observedObject) { @@ -81,4 +73,8 @@ merge(KeyStream.prototype, { } }); +function labelFor(source, key) { + return source.label ? source.label + '.' + key : key; +} + export default KeyStream; diff --git a/packages/ember-metal/lib/streams/proxy-stream.js b/packages/ember-metal/lib/streams/proxy-stream.js index 9f8ffefdbb1..2ae732a9c33 100644 --- a/packages/ember-metal/lib/streams/proxy-stream.js +++ b/packages/ember-metal/lib/streams/proxy-stream.js @@ -1,15 +1,12 @@ -import merge from 'ember-metal/merge'; -import Stream from 'ember-metal/streams/stream'; import EmberObject from 'ember-runtime/system/object'; +import BasicStream from 'ember-metal/streams/stream'; -function ProxyStream(source, label) { - this.init(label); - this.sourceDep = this.addMutableDependency(source); -} - -ProxyStream.prototype = Object.create(Stream.prototype); +let ProxyStream = BasicStream.extend({ + init(source, label) { + this.label = label; + this.sourceDep = this.addMutableDependency(source); + }, -merge(ProxyStream.prototype, { compute() { return this.sourceDep.getValue(); }, @@ -29,4 +26,6 @@ merge(ProxyStream.prototype, { } }); +ProxyStream.extend = BasicStream.extend; + export default ProxyStream; diff --git a/packages/ember-metal/lib/streams/stream.js b/packages/ember-metal/lib/streams/stream.js index b9c590c14c0..79515280792 100644 --- a/packages/ember-metal/lib/streams/stream.js +++ b/packages/ember-metal/lib/streams/stream.js @@ -1,5 +1,6 @@ import Ember from 'ember-metal/core'; -import { assert } from 'ember-metal/debug'; +import merge from 'ember-metal/merge'; +import { debugSeal, assert } from 'ember-metal/debug'; import { getFirstKey, getTailPath } from 'ember-metal/path_cache'; import { addObserver, removeObserver } from 'ember-metal/observer'; import { isStream } from 'ember-metal/streams/utils'; @@ -17,18 +18,17 @@ import Dependency from 'ember-metal/streams/dependency'; @namespace Ember.stream @constructor */ -function Stream(fn, label) { - this.init(label); - this.compute = fn; +function BasicStream(label) { + this._init(label); } var KeyStream; var ProxyMixin; -Stream.prototype = { +BasicStream.prototype = { isStream: true, - init(label) { + _init(label) { this.label = makeLabel(label); this.isActive = false; this.isDirty = true; @@ -298,19 +298,44 @@ Stream.prototype = { } } - this.dependencies = null; return true; } } }; -Stream.wrap = function(value, Kind, param) { +BasicStream.extend = function(object) { + let Child = function(...args) { + this._init(); + this.init(...args); + + debugSeal(this); + }; + + Child.prototype = Object.create(this.prototype); + + merge(Child.prototype, object); + Child.extend = BasicStream.extend; + return Child; +}; + +var Stream = BasicStream.extend({ + init(fn, label) { + this._compute = fn; + this.label = label; + }, + + compute() { + return this._compute(); + } +}); + +export function wrap(value, Kind, param) { if (isStream(value)) { return value; } else { return new Kind(value, param); } -}; +} function makeLabel(label) { if (label === undefined) { @@ -320,5 +345,5 @@ function makeLabel(label) { } } - -export default Stream; +export default BasicStream; +export { Stream }; diff --git a/packages/ember-metal/lib/streams/utils.js b/packages/ember-metal/lib/streams/utils.js index 2b86faedefd..1ac1f39b5ce 100644 --- a/packages/ember-metal/lib/streams/utils.js +++ b/packages/ember-metal/lib/streams/utils.js @@ -1,5 +1,5 @@ import { assert } from 'ember-metal/debug'; -import Stream from './stream'; +import BasicStream, { Stream } from './stream'; /* Check whether an object is a stream or not @@ -156,6 +156,26 @@ export function scanHash(hash) { return containsStream; } +let ConcatStream = BasicStream.extend({ + init(array, separator) { + this.array = array; + this.separator = separator; + + // used by angle bracket components to detect an attribute was provided + // as a string literal + this.isConcat = true; + }, + + label() { + let labels = labelsFor(this.array); + return `concat([${labels.join(', ')}]; separator=${inspect(this.separator)})`; + }, + + compute() { + return concat(readArray(this.array), this.separator); + } +}); + /* Join an array, with any streams replaced by their current values @@ -174,21 +194,12 @@ export function concat(array, separator) { // subscribing to streams until the value() is called. var hasStream = scanArray(array); if (hasStream) { - var i, l; - var stream = new Stream(function() { - return concat(readArray(array), separator); - }, function() { - var labels = labelsFor(array); - return `concat([${labels.join(', ')}]; separator=${inspect(separator)})`; - }); - - for (i = 0, l = array.length; i < l; i++) { - stream.addDependency(array[i]); + let stream = new ConcatStream(array, separator); + + for (let i = 0, l = array.length; i < l; i++) { + addDependency(stream, array[i]); } - // used by angle bracket components to detect an attribute was provided - // as a string literal - stream.isConcat = true; return stream; } else { return array.join(separator); diff --git a/packages/ember-metal/tests/streams/concat_test.js b/packages/ember-metal/tests/streams/concat_test.js index c9ff20d4e6b..4aa985a2f27 100644 --- a/packages/ember-metal/tests/streams/concat_test.js +++ b/packages/ember-metal/tests/streams/concat_test.js @@ -1,4 +1,4 @@ -import Stream from 'ember-metal/streams/stream'; +import { Stream } from 'ember-metal/streams/stream'; import { concat, read diff --git a/packages/ember-metal/tests/streams/key-stream-test.js b/packages/ember-metal/tests/streams/key-stream-test.js index 54a8588d42c..dc6bc68b644 100644 --- a/packages/ember-metal/tests/streams/key-stream-test.js +++ b/packages/ember-metal/tests/streams/key-stream-test.js @@ -1,5 +1,5 @@ import { isWatching } from 'ember-metal/watching'; -import Stream from 'ember-metal/streams/stream'; +import { Stream } from 'ember-metal/streams/stream'; import KeyStream from 'ember-metal/streams/key-stream'; import { set } from 'ember-metal/property_set'; diff --git a/packages/ember-metal/tests/streams/proxy-stream-test.js b/packages/ember-metal/tests/streams/proxy-stream-test.js index 7cda630e593..3fb113f2d87 100644 --- a/packages/ember-metal/tests/streams/proxy-stream-test.js +++ b/packages/ember-metal/tests/streams/proxy-stream-test.js @@ -1,23 +1,28 @@ -import Stream from 'ember-metal/streams/stream'; +import BasicStream from 'ember-metal/streams/stream'; import ProxyStream from 'ember-metal/streams/proxy-stream'; -var source, value; +var source; QUnit.module('ProxyStream', { setup() { - value = 'zlurp'; - - source = new Stream(function() { - return value; + let Source = BasicStream.extend({ + init(val) { + this.val = val; + }, + + compute() { + return this.val; + }, + + setValue(value) { + this.val = value; + this.notify(); + } }); - source.setValue = function(_value) { - value = _value; - this.notify(); - }; + source = new Source('zlurp'); }, teardown() { - value = undefined; source = undefined; } }); @@ -31,7 +36,7 @@ QUnit.test('supports a stream argument', function() { }); QUnit.test('supports a non-stream argument', function() { - var stream = new ProxyStream(value); + var stream = new ProxyStream('zlurp'); equal(stream.value(), 'zlurp'); stream.setValue('blorg'); diff --git a/packages/ember-metal/tests/streams/stream-test.js b/packages/ember-metal/tests/streams/stream-test.js index cdd316c9b64..e7fb8be8cd8 100644 --- a/packages/ember-metal/tests/streams/stream-test.js +++ b/packages/ember-metal/tests/streams/stream-test.js @@ -1,4 +1,4 @@ -import Stream from 'ember-metal/streams/stream'; +import { Stream } from 'ember-metal/streams/stream'; import ObjectProxy from 'ember-runtime/system/object_proxy'; import { get } from 'ember-metal/property_get'; diff --git a/packages/ember-routing-htmlbars/lib/keywords/closure-action.js b/packages/ember-routing-htmlbars/lib/keywords/closure-action.js index 14fbdc0b6e4..f77311ad07c 100644 --- a/packages/ember-routing-htmlbars/lib/keywords/closure-action.js +++ b/packages/ember-routing-htmlbars/lib/keywords/closure-action.js @@ -1,4 +1,4 @@ -import Stream from 'ember-metal/streams/stream'; +import { Stream } from 'ember-metal/streams/stream'; import { read, readArray @@ -25,7 +25,7 @@ export default function closureAction(morph, env, scope, params, hash, template, // on-change={{action setName}} // element-space actions look to "controller" then target. Here we only // look to "target". - target = read(scope.self); + target = read(scope.getSelf()); action = read(rawAction); let actionType = typeof action; diff --git a/packages/ember-routing-htmlbars/lib/keywords/element-action.js b/packages/ember-routing-htmlbars/lib/keywords/element-action.js index 35efd35808a..28d96237d80 100644 --- a/packages/ember-routing-htmlbars/lib/keywords/element-action.js +++ b/packages/ember-routing-htmlbars/lib/keywords/element-action.js @@ -32,7 +32,7 @@ export default { target = read(hash.target); } } else { - target = read(scope.locals.controller) || read(scope.self); + target = read(scope.getLocal('controller')) || read(scope.getSelf()); } return { actionName, actionArgs, target }; @@ -91,7 +91,7 @@ ActionHelper.registerAction = function({ actionId, node, eventName, preventDefau event.stopPropagation(); } - let { target, actionName, actionArgs } = node.state; + let { target, actionName, actionArgs } = node.getState(); run(function runRegisteredAction() { if (typeof actionName === 'function') { diff --git a/packages/ember-routing-htmlbars/lib/keywords/render.js b/packages/ember-routing-htmlbars/lib/keywords/render.js index 245ebb4b072..60677825a8d 100644 --- a/packages/ember-routing-htmlbars/lib/keywords/render.js +++ b/packages/ember-routing-htmlbars/lib/keywords/render.js @@ -117,7 +117,7 @@ export default { }, render(node, env, scope, params, hash, template, inverse, visitor) { - var state = node.state; + var state = node.getState(); var name = params[0]; var context = params[1]; @@ -185,7 +185,7 @@ export default { controllerFullName = 'controller:' + controllerName; } - var parentController = read(scope.locals.controller); + var parentController = read(scope.getLocal('controller')); var controller; // choose name @@ -217,8 +217,6 @@ export default { hash.viewName = camelize(name); - // var state = node.state; - // var parentView = scope.view; if (template && template.raw) { template = template.raw; } @@ -244,7 +242,7 @@ export default { rerender(node, env, scope, params, hash, template, inverse, visitor) { var model = read(params[1]); - node.state.controller.set('model', model); + node.getState().controller.set('model', model); } }; diff --git a/packages/ember-routing-htmlbars/tests/helpers/element_action_test.js b/packages/ember-routing-htmlbars/tests/helpers/element_action_test.js index be14fb1e806..61f3ecbc331 100644 --- a/packages/ember-routing-htmlbars/tests/helpers/element_action_test.js +++ b/packages/ember-routing-htmlbars/tests/helpers/element_action_test.js @@ -88,7 +88,7 @@ QUnit.test('should by default target the view\'s controller', function() { var controller = {}; ActionHelper.registerAction = function({ node }) { - registeredTarget = node.state.target; + registeredTarget = node.getState().target; }; view = EmberView.create({ @@ -136,7 +136,7 @@ QUnit.test('should allow a target to be specified', function() { var registeredTarget; ActionHelper.registerAction = function({ node }) { - registeredTarget = node.state.target; + registeredTarget = node.getState().target; }; var anotherTarget = EmberView.create(); diff --git a/packages/ember-views/lib/compat/attrs-proxy.js b/packages/ember-views/lib/compat/attrs-proxy.js index 56de3c9ff21..4706acd500a 100644 --- a/packages/ember-views/lib/compat/attrs-proxy.js +++ b/packages/ember-views/lib/compat/attrs-proxy.js @@ -1,8 +1,6 @@ import { Mixin } from 'ember-metal/mixin'; import { symbol } from 'ember-metal/utils'; import { PROPERTY_DID_CHANGE } from 'ember-metal/property_events'; -import { on } from 'ember-metal/events'; -import EmptyObject from 'ember-metal/empty_object'; export function deprecation(key) { return `You tried to look up an attribute directly on the component. This is deprecated. Use attrs.${key} instead.`; @@ -14,37 +12,9 @@ function isCell(val) { return val && val[MUTABLE_CELL]; } -function setupAvoidPropagating(instance) { - // This caches the list of properties to avoid setting onto the component instance - // inside `_propagateAttrsToThis`. We cache them so that every instantiated component - // does not have to pay the calculation penalty. - let constructor = instance.constructor; - if (!constructor.__avoidPropagating) { - constructor.__avoidPropagating = new EmptyObject(); - let i, l; - for (i = 0, l = instance.concatenatedProperties.length; i < l; i++) { - let prop = instance.concatenatedProperties[i]; - - constructor.__avoidPropagating[prop] = true; - } - - for (i = 0, l = instance.mergedProperties.length; i < l; i++) { - let prop = instance.mergedProperties[i]; - - constructor.__avoidPropagating[prop] = true; - } - } -} - let AttrsProxyMixin = { attrs: null, - init() { - this._super(...arguments); - - setupAvoidPropagating(this); - }, - getAttr(key) { let attrs = this.attrs; if (!attrs) { return; } @@ -67,50 +37,11 @@ let AttrsProxyMixin = { val.update(value); }, - _propagateAttrsToThis() { - let attrs = this.attrs; - - for (let prop in attrs) { - if (prop !== 'attrs' && !this.constructor.__avoidPropagating[prop]) { - this.set(prop, this.getAttr(prop)); - } - } - }, - - initializeShape: on('init', function() { - this._isDispatchingAttrs = false; - }), - - _internalDidReceiveAttrs() { - this._super(); + _propagateAttrsToThis(attrs) { this._isDispatchingAttrs = true; - this._propagateAttrsToThis(); + this.setProperties(attrs); this._isDispatchingAttrs = false; - }, - - - unknownProperty(key) { - if (this._isAngleBracket) { return; } - - var attrs = this.attrs; - - if (attrs && key in attrs) { - // do not deprecate accessing `this[key]` at this time. - // add this back when we have a proper migration path - // deprecate(deprecation(key), { id: 'ember-views.', until: '3.0.0' }); - let possibleCell = attrs[key]; - - if (possibleCell && possibleCell[MUTABLE_CELL]) { - return possibleCell.value; - } - - return possibleCell; - } } - - //setUnknownProperty(key) { - - //} }; AttrsProxyMixin[PROPERTY_DID_CHANGE] = function(key) { diff --git a/packages/ember-views/lib/mixins/view_support.js b/packages/ember-views/lib/mixins/view_support.js index ae8f869bc2d..b713160200b 100644 --- a/packages/ember-views/lib/mixins/view_support.js +++ b/packages/ember-views/lib/mixins/view_support.js @@ -627,7 +627,7 @@ export default Mixin.create({ }, scheduleRevalidate(node, label, manualRerender) { - if (node && !this._dispatching && node.guid in this.env.renderedNodes) { + if (node && !this._dispatching && this.env.renderedNodes.has(node)) { if (manualRerender) { deprecate( `You manually rerendered ${label} (a parent component) from a child component during the rendering process. This rarely worked in Ember 1.x and will be removed in Ember 2.0`, diff --git a/packages/ember-views/lib/streams/should_display.js b/packages/ember-views/lib/streams/should_display.js index 5b3d56f683c..6015a483e69 100644 --- a/packages/ember-views/lib/streams/should_display.js +++ b/packages/ember-views/lib/streams/should_display.js @@ -1,50 +1,24 @@ import { assert } from 'ember-metal/debug'; -import merge from 'ember-metal/merge'; import { get } from 'ember-metal/property_get'; import { isArray } from 'ember-runtime/utils'; -import Stream from 'ember-metal/streams/stream'; +import BasicStream from 'ember-metal/streams/stream'; import { read, isStream } from 'ember-metal/streams/utils'; -export default function shouldDisplay(predicate) { - if (isStream(predicate)) { - return new ShouldDisplayStream(predicate); - } +let ShouldDisplayStream = BasicStream.extend({ + init(predicate) { + assert('ShouldDisplayStream error: predicate must be a stream', isStream(predicate)); - var type = typeof predicate; - - if (type === 'boolean') { return predicate; } + var isTruthy = predicate.get('isTruthy'); - if (type && type === 'object' && predicate !== null) { - var isTruthy = get(predicate, 'isTruthy'); - if (typeof isTruthy === 'boolean') { - return isTruthy; - } - } - - if (isArray(predicate)) { - return get(predicate, 'length') !== 0; - } else { - return !!predicate; - } -} - -function ShouldDisplayStream(predicate) { - assert('ShouldDisplayStream error: predicate must be a stream', isStream(predicate)); - - var isTruthy = predicate.get('isTruthy'); - - this.init(); - this.predicate = predicate; - this.isTruthy = isTruthy; - this.lengthDep = null; - - this.addDependency(predicate); - this.addDependency(isTruthy); -} + this.init(); + this.predicate = predicate; + this.isTruthy = isTruthy; + this.lengthDep = null; -ShouldDisplayStream.prototype = Object.create(Stream.prototype); + this.addDependency(predicate); + this.addDependency(isTruthy); + }, -merge(ShouldDisplayStream.prototype, { compute() { var truthy = read(this.isTruthy); @@ -72,3 +46,27 @@ merge(ShouldDisplayStream.prototype, { } } }); + +export default function shouldDisplay(predicate) { + if (isStream(predicate)) { + return new ShouldDisplayStream(predicate); + } + + var type = typeof predicate; + + if (type === 'boolean') { return predicate; } + + if (type && type === 'object' && predicate !== null) { + var isTruthy = get(predicate, 'isTruthy'); + if (typeof isTruthy === 'boolean') { + return isTruthy; + } + } + + if (isArray(predicate)) { + return get(predicate, 'length') !== 0; + } else { + return !!predicate; + } +} + diff --git a/packages/ember-views/lib/views/states/has_element.js b/packages/ember-views/lib/views/states/has_element.js index ac1bc40f927..24fce6f8bc9 100644 --- a/packages/ember-views/lib/views/states/has_element.js +++ b/packages/ember-views/lib/views/states/has_element.js @@ -34,7 +34,7 @@ merge(hasElement, { renderNode.isDirty = true; internal.visitChildren(renderNode.childNodes, function(node) { - if (node.state && node.state.manager) { + if (node.getState().manager) { node.shouldReceiveAttrs = true; } node.isDirty = true; diff --git a/tests/index.html b/tests/index.html index fd3936145a6..f668a69310f 100644 --- a/tests/index.html +++ b/tests/index.html @@ -36,7 +36,8 @@ testing: true }; window.ENV = window.ENV || { - _ENABLE_LEGACY_VIEW_SUPPORT: true + _ENABLE_LEGACY_VIEW_SUPPORT: true, + _ENABLE_LEGACY_CONTROLLER_SUPPORT: true }; window.printTestCounts = function() {