Skip to content

Commit

Permalink
[FEATURE ember-htmlbars-dashless-helpers] RFC#58.
Browse files Browse the repository at this point in the history
  • Loading branch information
rwjblue committed Jun 12, 2015
1 parent dfab8ec commit d8e2fe0
Show file tree
Hide file tree
Showing 15 changed files with 376 additions and 27 deletions.
7 changes: 6 additions & 1 deletion FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ for a detailed explanation.
displayedPropertyTitle: 'First Name',
displayedPropertyKey: 'firstName'
};
```
```

```hbs
<h2>{{displayedPropertyTitle}}</h2>
Expand All @@ -315,3 +315,8 @@ for a detailed explanation.

Implements RFC https://github.com/emberjs/rfcs/pull/53, a public helper
api.

* `ember-htmlbars-dashless-helpers`

Implements RFC https://github.com/emberjs/rfcs/pull/58, adding support for
dashless helpers.
3 changes: 2 additions & 1 deletion features.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"ember-libraries-isregistered": null,
"ember-routing-htmlbars-improved-actions": true,
"ember-htmlbars-get-helper": null,
"ember-htmlbars-helper": true
"ember-htmlbars-helper": true,
"ember-htmlbars-dashless-helpers": true
},
"debugStatements": [
"Ember.warn",
Expand Down
32 changes: 32 additions & 0 deletions packages/container/lib/registry.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Ember from 'ember-metal/core'; // Ember.assert
import isEnabled from "ember-metal/features";
import dictionary from 'ember-metal/dictionary';
import keys from 'ember-metal/keys';
import { assign } from 'ember-metal/merge';
import Container from './container';

var VALID_FULL_NAME_REGEXP = /^[^:]+.+:[^:]+$/;
Expand Down Expand Up @@ -691,6 +693,36 @@ Registry.prototype = {
});
},

/**
@method knownForType
@param {String} type the type to iterate over
@private
*/
knownForType(type) {
let fallbackKnown, resolverKnown;

let localKnown = dictionary(null);
let registeredNames = keys(this.registrations);
for (let index = 0, length = registeredNames.length; index < length; index++) {
let fullName = registeredNames[index];
let itemType = fullName.split(':')[0];

if (itemType === type) {
localKnown[fullName] = true;
}
}

if (this.fallback) {
fallbackKnown = this.fallback.knownForType(type);
}

if (this.resolver.knownForType) {
resolverKnown = this.resolver.knownForType(type);
}

return assign({}, fallbackKnown, localKnown, resolverKnown);
},

validateFullName(fullName) {
if (!VALID_FULL_NAME_REGEXP.test(fullName)) {
throw new TypeError('Invalid Fullname, expected: `type:name` got: ' + fullName);
Expand Down
55 changes: 55 additions & 0 deletions packages/container/tests/registry_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -345,3 +345,58 @@ QUnit.test("`getFactoryTypeInjections` includes factory type injections from a f

equal(registry.getFactoryTypeInjections('model').length, 1, "Factory type injections from the fallback registry are merged");
});

QUnit.test("`knownForType` contains keys for each item of a given type", function() {
let registry = new Registry();

registry.register('foo:bar-baz', 'baz');
registry.register('foo:qux-fez', 'fez');

let found = registry.knownForType('foo');

deepEqual(found, {
'foo:bar-baz': true,
'foo:qux-fez': true
});
});

QUnit.test("`knownForType` includes fallback registry results", function() {
var fallback = new Registry();
var registry = new Registry({ fallback: fallback });

registry.register('foo:bar-baz', 'baz');
registry.register('foo:qux-fez', 'fez');
fallback.register('foo:zurp-zorp', 'zorp');

let found = registry.knownForType('foo');

deepEqual(found, {
'foo:bar-baz': true,
'foo:qux-fez': true,
'foo:zurp-zorp': true
});
});

QUnit.test("`knownForType` is called on the resolver if present", function() {
expect(3);

function resolver() { }
resolver.knownForType = function(type) {
ok(true, 'knownForType called on the resolver');
equal(type, 'foo', 'the type was passed through');

return { 'foo:yorp': true };
};

var registry = new Registry({
resolver
});
registry.register('foo:bar-baz', 'baz');

let found = registry.knownForType('foo');

deepEqual(found, {
'foo:yorp': true,
'foo:bar-baz': true
});
});
6 changes: 6 additions & 0 deletions packages/ember-application/lib/system/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -1125,6 +1125,12 @@ function resolverFor(namespace) {
}
};

resolve.knownForType = function knownForType(type) {
if (resolver.knownForType) {
return resolver.knownForType(type);
}
};

resolve.moduleBasedResolver = resolver.moduleBasedResolver;

resolve.__resolver__ = resolver;
Expand Down
53 changes: 52 additions & 1 deletion packages/ember-application/lib/system/resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@
import Ember from 'ember-metal/core'; // Ember.TEMPLATES, Ember.assert
import { get } from 'ember-metal/property_get';
import Logger from 'ember-metal/logger';
import keys from 'ember-metal/keys';
import {
classify,
capitalize,
dasherize,
decamelize
} from 'ember-runtime/system/string';
import EmberObject from 'ember-runtime/system/object';
import Namespace from 'ember-runtime/system/namespace';
import helpers from 'ember-htmlbars/helpers';
import validateType from 'ember-application/utils/validate-type';
import dictionary from 'ember-metal/dictionary';

export var Resolver = EmberObject.extend({
/*
Expand Down Expand Up @@ -104,7 +107,6 @@ export var Resolver = EmberObject.extend({
@extends Ember.Object
@public
*/
import dictionary from 'ember-metal/dictionary';

export default EmberObject.extend({
/**
Expand Down Expand Up @@ -419,5 +421,54 @@ export default EmberObject.extend({
}

Logger.info(symbol, parsedName.fullName, padding, this.lookupDescription(parsedName.fullName));
},

/**
Used to iterate all items of a given type.
@method knownForType
@param {String} type the type to search for
@private
*/
knownForType(type) {
let namespace = get(this, 'namespace');
let suffix = classify(type);
let typeRegexp = new RegExp(`${suffix}$`);

let known = dictionary(null);
let knownKeys = keys(namespace);
for (let index = 0, length = knownKeys.length; index < length; index++) {
let name = knownKeys[index];

if (typeRegexp.test(name)) {
let containerName = this.translateToContainerFullname(type, name);

known[containerName] = true;
}
}

return known;
},

/**
Converts provided name from the backing namespace into a container lookup name.
Examples:
App.FooBarHelper -> helper:foo-bar
App.THelper -> helper:t
@method translateToContainerFullname
@param {String} type
@param {String} name
@private
*/

translateToContainerFullname(type, name) {
let suffix = classify(type);
let namePrefix = name.slice(0, suffix.length * -1);
let dasherizedName = dasherize(namePrefix);

return `${type}:${dasherizedName}`;
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,23 @@ QUnit.test("no deprecation warning for component factories that extend from Embe
application.FooView = Component.extend();
registry.resolve('component:foo');
});

QUnit.test('knownForType returns each item for a given type found', function() {
application.FooBarHelper = 'foo';
application.BazQuxHelper = 'bar';

let found = registry.resolver.knownForType('helper');

deepEqual(found, {
'helper:foo-bar': true,
'helper:baz-qux': true
});
});

QUnit.test('knownForType is not required to be present on the resolver', function() {
delete registry.resolver.__resolver__.knownForType;

registry.resolver.knownForType('helper', function() { });

ok(true, 'does not error');
});
2 changes: 1 addition & 1 deletion packages/ember-htmlbars/lib/hooks/has-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default function hasHelperHook(env, scope, helperName) {
}

var container = env.container;
if (validateLazyHelperName(helperName, container, env.hooks.keywords)) {
if (validateLazyHelperName(helperName, container, env.hooks.keywords, env.knownHelpers)) {
var containerName = 'helper:' + helperName;
if (container._registry.has(containerName)) {
return true;
Expand Down
26 changes: 26 additions & 0 deletions packages/ember-htmlbars/lib/system/discover-known-helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import isEnabled from "ember-metal/features";
import dictionary from 'ember-metal/dictionary';
import keys from 'ember-metal/keys';

export default function discoverKnownHelpers(container) {
let registry = container && container._registry;
let helpers = dictionary(null);

if (isEnabled('ember-htmlbars-dashless-helpers')) {
if (!registry) {
return helpers;
}

let known = registry.knownForType('helper');
let knownContainerKeys = keys(known);

for (let index = 0, length = knownContainerKeys.length; index < length; index++) {
let fullName = knownContainerKeys[index];
let name = fullName.slice(7); // remove `helper:` from fullName

helpers[name] = true;
}
}

return helpers;
}
12 changes: 9 additions & 3 deletions packages/ember-htmlbars/lib/system/lookup-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@ export var CONTAINS_DASH_CACHE = new Cache(1000, function(key) {
return key.indexOf('-') !== -1;
});

export function validateLazyHelperName(helperName, container, keywords) {
return container && CONTAINS_DASH_CACHE.get(helperName) && !(helperName in keywords);
export function validateLazyHelperName(helperName, container, keywords, knownHelpers) {
if (!container || (helperName in keywords)) {
return false;
}

if (knownHelpers[helperName] || CONTAINS_DASH_CACHE.get(helperName)) {
return true;
}
}

function isLegacyBareHelper(helper) {
Expand All @@ -38,7 +44,7 @@ export function findHelper(name, view, env) {

if (!helper) {
var container = env.container;
if (validateLazyHelperName(name, container, env.hooks.keywords)) {
if (validateLazyHelperName(name, container, env.hooks.keywords, env.knownHelpers)) {
var helperName = 'helper:' + name;
if (container._registry.has(helperName)) {
helper = container.lookupFactory(helperName);
Expand Down
8 changes: 6 additions & 2 deletions packages/ember-htmlbars/lib/system/render-env.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import defaultEnv from "ember-htmlbars/env";
import discoverKnownHelpers from "ember-htmlbars/system/discover-known-helpers";

export default function RenderEnv(options) {
this.lifecycleHooks = options.lifecycleHooks || [];
Expand All @@ -11,6 +12,7 @@ export default function RenderEnv(options) {
this.container = options.container;
this.renderer = options.renderer;
this.dom = options.dom;
this.knownHelpers = options.knownHelpers || discoverKnownHelpers(options.container);

this.hooks = defaultEnv.hooks;
this.helpers = defaultEnv.helpers;
Expand All @@ -37,7 +39,8 @@ RenderEnv.prototype.childWithView = function(view) {
lifecycleHooks: this.lifecycleHooks,
renderedViews: this.renderedViews,
renderedNodes: this.renderedNodes,
hasParentOutlet: this.hasParentOutlet
hasParentOutlet: this.hasParentOutlet,
knownHelpers: this.knownHelpers
});
};

Expand All @@ -51,6 +54,7 @@ RenderEnv.prototype.childWithOutletState = function(outletState, hasParentOutlet
lifecycleHooks: this.lifecycleHooks,
renderedViews: this.renderedViews,
renderedNodes: this.renderedNodes,
hasParentOutlet: hasParentOutlet
hasParentOutlet: hasParentOutlet,
knownHelpers: this.knownHelpers
});
};
46 changes: 46 additions & 0 deletions packages/ember-htmlbars/tests/integration/helper-lookup-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import isEnabled from "ember-metal/features";
import Registry from "container/registry";
import compile from "ember-template-compiler/system/compile";
import ComponentLookup from 'ember-views/component_lookup';
import Component from "ember-views/views/component";
import { helper } from "ember-htmlbars/helper";
import { runAppend, runDestroy } from "ember-runtime/tests/utils";

var registry, container, component;

QUnit.module('component - invocation', {
setup() {
registry = new Registry();
container = registry.container();
registry.optionsForType('component', { singleton: false });
registry.optionsForType('view', { singleton: false });
registry.optionsForType('template', { instantiate: false });
registry.optionsForType('helper', { instantiate: false });
registry.register('component-lookup:main', ComponentLookup);
},

teardown() {
runDestroy(container);
runDestroy(component);
registry = container = component = null;
}
});

if (isEnabled('ember-htmlbars-dashless-helpers')) {
QUnit.test('non-dashed helpers are found', function() {
expect(1);

registry.register('helper:fullname', helper(function( [first, last]) {
return `${first} ${last}`;
}));

component = Component.extend({
layout: compile('{{fullname "Robert" "Jackson"}}'),
container: container
}).create();

runAppend(component);

equal(component.$().text(), 'Robert Jackson');
});
}
Loading

0 comments on commit d8e2fe0

Please sign in to comment.