-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FEATURE ember-contextual-components] Implement contextual component
This feature moves previous component keyword implementation to `element-component` and adds a `closure-component` that creates a cell that closes over the component's path, positional parameters and hash parameters. These cells can be nested, merging the previous parameters with the previous ones.
- Loading branch information
Showing
11 changed files
with
677 additions
and
88 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/** | ||
@module ember | ||
@submodule ember-templates | ||
*/ | ||
|
||
/** | ||
Use the `{{hash}}` helper to create a hash to pass as an option to your | ||
components. This is specially useful for contextual components where you can | ||
just yield a hash: | ||
```handlebars | ||
{{yield (hash | ||
name='Sarah' | ||
title=office | ||
)}} | ||
``` | ||
Would result in an object such as: | ||
```js | ||
{ name: 'Sarah', title: this.get('office') } | ||
``` | ||
Where the `title` is bound to updates of the `office` property. | ||
@method hash | ||
@for Ember.Templates.helpers | ||
@param {Object} options | ||
@return {Object} Hash | ||
@public | ||
*/ | ||
|
||
export default function hashHelper(params, hash, options) { | ||
return hash; | ||
} |
109 changes: 109 additions & 0 deletions
109
packages/ember-htmlbars/lib/keywords/closure-component.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
/** | ||
@module ember | ||
@submodule ember-templates | ||
*/ | ||
|
||
import { symbol } from 'ember-metal/utils'; | ||
import BasicStream from 'ember-metal/streams/stream'; | ||
import { read } from 'ember-metal/streams/utils'; | ||
import { labelForSubexpr } from 'ember-htmlbars/hooks/subexpr'; | ||
import assign from 'ember-metal/assign'; | ||
import { processPositionalParams } from 'ember-htmlbars/utils/extract-positional-params'; | ||
import lookupComponent from 'ember-htmlbars/utils/lookup-component'; | ||
|
||
export const COMPONENT_REFERENCE = symbol('COMPONENT_REFERENCE'); | ||
export const COMPONENT_CELL = symbol('COMPONENT_CELL'); | ||
export const COMPONENT_PATH = symbol('COMPONENT_PATH'); | ||
export const COMPONENT_POSITIONAL_PARAMS = symbol('COMPONENT_POSITIONAL_PARAMS'); | ||
export const COMPONENT_HASH = symbol('COMPONENT_HASH'); | ||
|
||
let ClosureComponentStream = BasicStream.extend({ | ||
init(env, path, params, hash) { | ||
this._env = env; | ||
this._path = path; | ||
this._params = params; | ||
this._hash = hash; | ||
this.label = labelForSubexpr([path, ...params], hash, 'component'); | ||
this[COMPONENT_REFERENCE] = true; | ||
}, | ||
compute() { | ||
return createClosureComponentCell(this._env, this._path, this._params, this._hash); | ||
} | ||
}); | ||
|
||
export default function closureComponent(env, [path, ...params], hash) { | ||
let s = new ClosureComponentStream(env, path, params, hash); | ||
|
||
s.addDependency(path); | ||
|
||
// FIXME: If the stream invalidates on every params or hash change, then | ||
// the {{component helper will be forces to rerender the whole component | ||
// each time. Instead, these dependencies should not be required and the | ||
// element component keyword should add the params and hash as dependencies | ||
params.forEach(item => s.addDependency(item)); | ||
Object.keys(hash).forEach(key => s.addDependency(hash[key])); | ||
|
||
return s; | ||
} | ||
|
||
function createClosureComponentCell(env, originalComponentPath, params, hash) { | ||
let componentPath = read(originalComponentPath); | ||
|
||
if (isComponentCell(componentPath)) { | ||
return createNestedClosureComponentCell(componentPath, params, hash); | ||
} else { | ||
return createNewClosureComponentCell(env, componentPath, params, hash); | ||
} | ||
} | ||
|
||
export function isComponentCell(component) { | ||
return component && component[COMPONENT_CELL]; | ||
} | ||
|
||
function createNestedClosureComponentCell(componentCell, params, hash) { | ||
let positionalParams = componentCell[COMPONENT_POSITIONAL_PARAMS]; | ||
|
||
// This needs to be done in each nesting level to avoid raising assertions | ||
processPositionalParams(null, positionalParams, params, hash); | ||
|
||
return { | ||
[COMPONENT_PATH]: componentCell[COMPONENT_PATH], | ||
[COMPONENT_HASH]: mergeHash(componentCell[COMPONENT_HASH], hash), | ||
[COMPONENT_POSITIONAL_PARAMS]: positionalParams, | ||
[COMPONENT_CELL]: true | ||
}; | ||
} | ||
|
||
function createNewClosureComponentCell(env, componentPath, params, hash) { | ||
let positionalParams = getPositionalParams(env.container, componentPath); | ||
|
||
// This needs to be done in each nesting level to avoid raising assertions | ||
processPositionalParams(null, positionalParams, params, hash); | ||
|
||
return { | ||
[COMPONENT_PATH]: componentPath, | ||
[COMPONENT_HASH]: hash, | ||
[COMPONENT_POSITIONAL_PARAMS]: positionalParams, | ||
[COMPONENT_CELL]: true | ||
}; | ||
} | ||
|
||
/* | ||
Returns the positional parameters for component `componentPath`. | ||
If it has no positional parameters, it returns the empty array. | ||
*/ | ||
function getPositionalParams(container, componentPath) { | ||
if (!componentPath) { return []; } | ||
let result = lookupComponent(container, componentPath); | ||
let component = result.component; | ||
|
||
if (component && component.positionalParams) { | ||
return component.positionalParams; | ||
} else { | ||
return []; | ||
} | ||
} | ||
|
||
export function mergeHash(original, updates) { | ||
return assign(original, updates); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import assign from 'ember-metal/assign'; | ||
import { | ||
COMPONENT_PATH, | ||
COMPONENT_POSITIONAL_PARAMS, | ||
COMPONENT_HASH, | ||
isComponentCell, | ||
mergeHash, | ||
} from './closure-component'; | ||
import { processPositionalParams } from 'ember-htmlbars/utils/extract-positional-params'; | ||
|
||
export default { | ||
setupState(lastState, env, scope, params, hash) { | ||
let componentPath = getComponentPath(params[0], env); | ||
return assign({}, lastState, { | ||
componentPath, | ||
isComponentHelper: true | ||
}); | ||
}, | ||
|
||
render(morph, ...rest) { | ||
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 (`<foo-bar>`) cannot change at runtime, | ||
// but the `{{component}}` helper can. | ||
state.manager = null; | ||
|
||
render(morph, ...rest); | ||
}, | ||
|
||
rerender: render | ||
}; | ||
|
||
function getComponentPath(param, env) { | ||
let path = env.hooks.getValue(param); | ||
if (isComponentCell(path)) { | ||
path = path[COMPONENT_PATH]; | ||
} | ||
return path; | ||
} | ||
|
||
function render(morph, env, scope, [path, ...params], hash, template, inverse, visitor) { | ||
let { | ||
componentPath | ||
} = morph.getState(); | ||
|
||
// If the value passed to the {{component}} helper is undefined or null, | ||
// don't create a new ComponentNode. | ||
if (componentPath === undefined || componentPath === null) { | ||
return; | ||
} | ||
|
||
path = env.hooks.getValue(path); | ||
|
||
if (isComponentCell(path)) { | ||
let closureComponent = env.hooks.getValue(path); | ||
let positionalParams = closureComponent[COMPONENT_POSITIONAL_PARAMS]; | ||
|
||
// This needs to be done in each nesting level to avoid raising assertions | ||
processPositionalParams(null, positionalParams, params, hash); | ||
params = []; | ||
hash = mergeHash(closureComponent[COMPONENT_HASH], hash); | ||
} | ||
|
||
let templates = { default: template, inverse }; | ||
env.hooks.component( | ||
morph, env, scope, componentPath, | ||
params, hash, templates, visitor | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.