Skip to content

Commit 2bb7890

Browse files
authored
Merge pull request #13492 from Joelkang/each-in
[Glimmer2] Add each-in support
2 parents f30b50f + a11b9c2 commit 2bb7890

File tree

8 files changed

+280
-15
lines changed

8 files changed

+280
-15
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
@module ember
3+
@submodule ember-glimmer
4+
*/
5+
6+
/**
7+
A Glimmer2 AST transformation that replaces all instances of
8+
9+
```handlebars
10+
{{#each-in iterableThing as |key value|}}
11+
```
12+
13+
with
14+
15+
```handlebars
16+
{{#each (-each-in iterableThing) as |key value|}}
17+
```
18+
19+
@private
20+
@class TransformHasBlockSyntax
21+
*/
22+
23+
export default function TransformEachInIntoEach() {
24+
// set later within Glimmer2 to the syntax package
25+
this.syntax = null;
26+
}
27+
28+
/**
29+
@private
30+
@method transform
31+
@param {AST} ast The AST to be transformed.
32+
*/
33+
TransformEachInIntoEach.prototype.transform = function TransformEachInIntoEach_transform(ast) {
34+
let { traverse, builders: b } = this.syntax;
35+
36+
traverse(ast, {
37+
BlockStatement(node) {
38+
if (node.path.original === 'each-in') {
39+
node.params[0] = b.sexpr(b.path('-each-in'), [node.params[0]]);
40+
return b.block(b.path('each'), node.params, node.hash, node.program, node.inverse, node.loc);
41+
}
42+
}
43+
});
44+
45+
return ast;
46+
};

packages/ember-glimmer-template-compiler/lib/system/compile-options.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import defaultPlugins from 'ember-template-compiler/plugins';
22
import TransformHasBlockSyntax from '../plugins/transform-has-block-syntax';
33
import TransformActionSyntax from '../plugins/transform-action-syntax';
4+
import TransformEachInIntoEach from '../plugins/transform-each-in-into-each';
45
import assign from 'ember-metal/assign';
56

67
export const PLUGINS = [
78
...defaultPlugins,
89
// the following are ember-glimmer specific
910
TransformHasBlockSyntax,
10-
TransformActionSyntax
11+
TransformActionSyntax,
12+
TransformEachInIntoEach
1113
];
1214

1315
let USER_PLUGINS = [];

packages/ember-glimmer/lib/environment.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { default as readonly } from './helpers/readonly';
3232
import { default as unbound } from './helpers/unbound';
3333
import { default as classHelper } from './helpers/-class';
3434
import { default as queryParams } from './helpers/query-param';
35+
import { default as eachIn } from './helpers/each-in';
3536
import { OWNER } from 'container/owner';
3637

3738
const builtInComponents = {
@@ -50,7 +51,8 @@ const builtInHelpers = {
5051
readonly,
5152
unbound,
5253
'query-params': queryParams,
53-
'-class': classHelper
54+
'-class': classHelper,
55+
'-each-in': eachIn
5456
};
5557

5658
import { default as ActionModifierManager } from './modifiers/action';
@@ -206,7 +208,6 @@ export default class Environment extends GlimmerEnvironment {
206208

207209
lookupHelper(name) {
208210
let helper = builtInHelpers[name[0]] || this.owner.lookup(`helper:${name}`);
209-
210211
// TODO: try to unify this into a consistent protocol to avoid wasteful closure allocations
211212
if (helper.isInternalHelper) {
212213
return (args) => helper.toReference(args);
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
@module ember
3+
@submodule ember-templates
4+
*/
5+
6+
import symbol from 'ember-metal/symbol';
7+
8+
/**
9+
The `{{each-in}}` helper loops over properties on an object. It is unbound,
10+
in that new (or removed) properties added to the target object will not be
11+
rendered.
12+
13+
For example, given a `user` object that looks like:
14+
15+
```javascript
16+
{
17+
"name": "Shelly Sails",
18+
"age": 42
19+
}
20+
```
21+
22+
This template would display all properties on the `user`
23+
object in a list:
24+
25+
```handlebars
26+
<ul>
27+
{{#each-in user as |key value|}}
28+
<li>{{key}}: {{value}}</li>
29+
{{/each-in}}
30+
</ul>
31+
```
32+
33+
Outputting their name and age.
34+
35+
@method each-in
36+
@for Ember.Templates.helpers
37+
@public
38+
@since 2.1.0
39+
*/
40+
const EACH_IN_REFERENCE = symbol('EACH_IN');
41+
42+
export function isEachIn(ref) {
43+
return ref && ref[EACH_IN_REFERENCE];
44+
}
45+
46+
export default {
47+
isInternalHelper: true,
48+
toReference(args) {
49+
let ref = Object.create(args.positional.at(0));
50+
ref[EACH_IN_REFERENCE] = true;
51+
return ref;
52+
}
53+
};

packages/ember-glimmer/lib/utils/iterable.js

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { get } from 'ember-metal/property_get';
22
import { guidFor } from 'ember-metal/utils';
33
import { objectAt, isEmberArray } from 'ember-runtime/mixins/array';
4-
import { UpdatableReference } from './references';
4+
import { UpdatableReference, UpdatablePrimitiveReference } from './references';
5+
import { isEachIn } from '../helpers/each-in';
56

67
export default function iterableFor(ref, keyPath) {
78
return new Iterable(ref, keyFor(keyPath));
@@ -88,6 +89,33 @@ class EmberArrayIterator {
8889
}
8990
}
9091

92+
class ObjectKeysIterator {
93+
constructor(keys, values, keyFor) {
94+
this.keys = keys;
95+
this.values = values;
96+
this.keyFor = keyFor;
97+
this.position = 0;
98+
}
99+
100+
isEmpty() {
101+
return this.keys.length === 0;
102+
}
103+
104+
next() {
105+
let { keys, values, keyFor, position } = this;
106+
107+
if (position >= keys.length) { return null; }
108+
109+
let value = values[position];
110+
let memo = keys[position];
111+
let key = keyFor(value, memo);
112+
113+
this.position++;
114+
115+
return { key, value: memo, memo: value };
116+
}
117+
}
118+
91119
class EmptyIterator {
92120
isEmpty() {
93121
return true;
@@ -117,6 +145,10 @@ class Iterable {
117145
return iterable.length > 0 ? new ArrayIterator(iterable, keyFor) : EMPTY_ITERATOR;
118146
} else if (isEmberArray(iterable)) {
119147
return new EmberArrayIterator(iterable, keyFor);
148+
} else if (isEachIn(ref)) {
149+
let keys = Object.keys(iterable);
150+
let values = keys.map(key => iterable[key]);
151+
return keys.length > 0 ? new ObjectKeysIterator(keys, values, keyFor) : EMPTY_ITERATOR;
120152
} else {
121153
throw new Error(`Don't know how to {{#each ${iterable}}}`);
122154
}
@@ -131,7 +163,7 @@ class Iterable {
131163
}
132164

133165
memoReferenceFor(item) {
134-
return new UpdatableReference(item.memo);
166+
return new UpdatablePrimitiveReference(item.memo);
135167
}
136168

137169
updateMemoReference(reference, item) {

packages/ember-glimmer/lib/utils/references.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,12 @@ export class UpdatableReference extends EmberPathReference {
135135
}
136136
}
137137

138+
export class UpdatablePrimitiveReference extends UpdatableReference {
139+
get() {
140+
return UNDEFINED_REFERENCE;
141+
}
142+
}
143+
138144
export class ConditionalReference extends GlimmerConditionalReference {
139145
static create(reference) {
140146
if (isConst(reference)) {

0 commit comments

Comments
 (0)