Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE ember-htmlbars-local-lookup] #12673

Merged
merged 1 commit into from
Dec 4, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,10 @@ for a detailed explanation.
```

Implements RFC [#64](https://github.com/emberjs/rfcs/blob/master/text/0064-contextual-component-lookup.md)

* `ember-htmlbars-local-lookup`

Provides the ability for component lookup to be relative to the source template.

When the proper API's are implemented by the resolver in use this feature allows `{{x-foo}}` in a
given routes template (say the `post` route) to lookup a component nested under `post`.
3 changes: 2 additions & 1 deletion features.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"ember-routing-routable-components": null,
"ember-metal-ember-assign": null,
"ember-contextual-components": true,
"ember-container-inject-owner": true
"ember-container-inject-owner": true,
"ember-htmlbars-local-lookup": null
}
}
39 changes: 32 additions & 7 deletions packages/container/lib/container.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ Container.prototype = {
@private
@method lookup
@param {String} fullName
@param {Object} options
@param {Object} [options]
@param {String} [options.source] The fullname of the request source (used for local lookup)
@return {any}
*/
lookup(fullName, options) {
Expand All @@ -122,11 +123,13 @@ Container.prototype = {
@private
@method lookupFactory
@param {String} fullName
@param {Object} [options]
@param {String} [options.source] The fullname of the request source (used for local lookup)
@return {any}
*/
lookupFactory(fullName) {
lookupFactory(fullName, options) {
assert('fullName must be a proper full name', this.registry.validateFullName(fullName));
return factoryFor(this, this.registry.normalize(fullName));
return factoryFor(this, this.registry.normalize(fullName), options);
},

/**
Expand Down Expand Up @@ -178,8 +181,18 @@ function isSingleton(container, fullName) {
return container.registry.getOption(fullName, 'singleton') !== false;
}

function lookup(container, fullName, options) {
options = options || {};
function lookup(container, _fullName, _options) {
let options = _options || {};
let fullName = _fullName;

if (isEnabled('ember-htmlbars-local-lookup')) {
if (options.source) {
fullName = container.registry.expandLocalLookup(fullName, options);

// if expandLocalLookup returns falsey, we do not support local lookup
if (!fullName) { return; }
}
}

if (container.cache[fullName] && options.singleton !== false) {
return container.cache[fullName];
Expand Down Expand Up @@ -232,12 +245,24 @@ function buildInjections(/* container, ...injections */) {
return hash;
}

function factoryFor(container, fullName) {
function factoryFor(container, _fullName, _options) {
let options = _options || {};
let registry = container.registry;
let fullName = _fullName;

if (isEnabled('ember-htmlbars-local-lookup')) {
if (options.source) {
fullName = registry.expandLocalLookup(fullName, options);

// if expandLocalLookup returns falsey, we do not support local lookup
if (!fullName) { return; }
}
}

var cache = container.factoryCache;
if (cache[fullName]) {
return cache[fullName];
}
var registry = container.registry;
var factory = registry.resolve(fullName);
if (factory === undefined) { return; }

Expand Down
102 changes: 91 additions & 11 deletions packages/container/lib/registry.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import isEnabled from 'ember-metal/features';
import { assert, deprecate } from 'ember-metal/debug';
import dictionary from 'ember-metal/dictionary';
import EmptyObject from 'ember-metal/empty_object';
import assign from 'ember-metal/assign';
import Container from './container';

Expand Down Expand Up @@ -36,6 +38,7 @@ function Registry(options) {
this._factoryTypeInjections = dictionary(null);
this._factoryInjections = dictionary(null);

this._localLookupCache = new EmptyObject();
this._normalizeCache = dictionary(null);
this._resolveCache = dictionary(null);
this._failCache = dictionary(null);
Expand Down Expand Up @@ -205,6 +208,8 @@ Registry.prototype = {

var normalizedName = this.normalize(fullName);

this._localLookupCache = new EmptyObject();

delete this.registrations[normalizedName];
delete this._resolveCache[normalizedName];
delete this._failCache[normalizedName];
Expand Down Expand Up @@ -242,13 +247,15 @@ Registry.prototype = {
@private
@method resolve
@param {String} fullName
@param {Object} [options]
@param {String} [options.source] the fullname of the request source (used for local lookups)
@return {Function} fullName's factory
*/
resolve(fullName) {
resolve(fullName, options) {
assert('fullName must be a proper full name', this.validateFullName(fullName));
var factory = resolve(this, this.normalize(fullName));
let factory = resolve(this, this.normalize(fullName), options);
if (factory === undefined && this.fallback) {
factory = this.fallback.resolve(fullName);
factory = this.fallback.resolve(...arguments);
}
return factory;
},
Expand Down Expand Up @@ -333,11 +340,19 @@ Registry.prototype = {
@private
@method has
@param {String} fullName
@param {Object} [options]
@param {String} [options.source] the fullname of the request source (used for local lookups)
@return {Boolean}
*/
has(fullName) {
has(fullName, options) {
assert('fullName must be a proper full name', this.validateFullName(fullName));
return has(this, this.normalize(fullName));

let source;
if (isEnabled('ember-htmlbars-local-lookup')) {
source = options && options.source && this.normalize(options.source);
}

return has(this, this.normalize(fullName), source);
},

/**
Expand Down Expand Up @@ -387,8 +402,8 @@ Registry.prototype = {
@param {String} fullName
@param {Object} options
*/
options(fullName, options) {
options = options || {};
options(fullName, _options) {
let options = _options || {};
var normalizedName = this.normalize(fullName);
this._options[normalizedName] = options;
},
Expand Down Expand Up @@ -758,8 +773,73 @@ function deprecateResolverFunction(registry) {
};
}

function resolve(registry, normalizedName) {
let cached = registry._resolveCache[normalizedName];
if (isEnabled('ember-htmlbars-local-lookup')) {
/**
Given a fullName and a source fullName returns the fully resolved
fullName. Used to allow for local lookup.

```javascript
var registry = new Registry();

// the twitter factory is added to the module system
registry.expandLocalLookup('component:post-title', { source: 'template:post' }) // => component:post/post-title
```

@private
@method expandLocalLookup
@param {String} fullName
@param {Object} [options]
@param {String} [options.source] the fullname of the request source (used for local lookups)
@return {String} fullName
*/
Registry.prototype.expandLocalLookup = function Registry_expandLocalLookup(fullName, options) {
if (this.resolver && this.resolver.expandLocalLookup) {
assert('fullName must be a proper full name', this.validateFullName(fullName));
assert('options.source must be provided to expandLocalLookup', options && options.source);
assert('options.source must be a proper full name', this.validateFullName(options.source));

let normalizedFullName = this.normalize(fullName);
let normalizedSource = this.normalize(options.source);

return expandLocalLookup(this, normalizedFullName, normalizedSource);
} else if (this.fallback) {
return this.fallback.expandLocalLookup(fullName, options);
} else {
return null;
}
};
}

function expandLocalLookup(registry, normalizedName, normalizedSource) {
let cache = registry._localLookupCache;
let normalizedNameCache = cache[normalizedName];

if (!normalizedNameCache) {
normalizedNameCache = cache[normalizedName] = new EmptyObject();
}

let cached = normalizedNameCache[normalizedSource];

if (cached !== undefined) { return cached; }

let expanded = registry.resolver.expandLocalLookup(normalizedName, normalizedSource);

return normalizedNameCache[normalizedSource] = expanded;
}

function resolve(registry, normalizedName, options) {
if (isEnabled('ember-htmlbars-local-lookup')) {
if (options && options.source) {
// when `source` is provided expand normalizedName
// and source into the full normalizedName
normalizedName = registry.expandLocalLookup(normalizedName, options);

// if expandLocalLookup returns falsey, we do not support local lookup
if (!normalizedName) { return; }
}
}

var cached = registry._resolveCache[normalizedName];
if (cached) { return cached; }
if (registry._failCache[normalizedName]) { return; }

Expand All @@ -780,8 +860,8 @@ function resolve(registry, normalizedName) {
return resolved;
}

function has(registry, fullName) {
return registry.resolve(fullName) !== undefined;
function has(registry, fullName, source) {
return registry.resolve(fullName, { source }) !== undefined;
}

export default Registry;
42 changes: 42 additions & 0 deletions packages/container/tests/container_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -610,3 +610,45 @@ if (isEnabled('ember-container-inject-owner')) {
strictEqual(postController.container, container, '');
});
}

if (isEnabled('ember-htmlbars-local-lookup')) {
QUnit.test('lookupFactory passes options through to expandlocallookup', function(assert) {
let registry = new Registry();
let container = registry.container();
let PostController = factory();

registry.register('controller:post', PostController);

registry.expandLocalLookup = function(fullName, options) {
assert.ok(true, 'expandLocalLookup was called');
assert.equal(fullName, 'foo:bar');
assert.deepEqual(options, { source: 'baz:qux' });

return 'controller:post';
};

let PostControllerFactory = container.lookupFactory('foo:bar', { source: 'baz:qux' });

assert.ok(PostControllerFactory.create() instanceof PostController, 'The return of factory.create is an instance of PostController');
});

QUnit.test('lookup passes options through to expandlocallookup', function(assert) {
let registry = new Registry();
let container = registry.container();
let PostController = factory();

registry.register('controller:post', PostController);

registry.expandLocalLookup = function(fullName, options) {
assert.ok(true, 'expandLocalLookup was called');
assert.equal(fullName, 'foo:bar');
assert.deepEqual(options, { source: 'baz:qux' });

return 'controller:post';
};

let PostControllerLookupResult = container.lookup('foo:bar', { source: 'baz:qux' });

assert.ok(PostControllerLookupResult instanceof PostController);
});
}
Loading