-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
[RFC #338] Implement new template helpers #17177
Changes from 20 commits
16d6724
95925ab
4fecacb
98e99e1
207ea8c
22a8d33
7142979
58f1426
552f776
d024e91
1d791ad
77fc37a
544da6d
b6f5f7f
c720549
4ccf90a
8fbdda2
9f8e8a5
f3e4192
81e61d7
1b7e613
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { Arguments, CapturedArguments, VM } from '@glimmer/runtime'; | ||
import { InternalHelperReference } from '../utils/references'; | ||
|
||
/** | ||
@module ember | ||
*/ | ||
|
||
/** | ||
Evaluates the given arguments with && in short-circuit | ||
|
||
Example: | ||
|
||
```handlebars | ||
{{and isAdmin isIdle validData}} | ||
|
||
{{! be validData if `isAdmin` and `isIdle` are both truthy}} | ||
``` | ||
|
||
@public | ||
@method and | ||
@for Ember.Templates.helpers | ||
@since 3.7.0 | ||
*/ | ||
function and({ positional: { references } }: CapturedArguments) { | ||
let last: any = true; | ||
for (let i = 0; i < references.length; i++) { | ||
last = references[i].value(); | ||
if (!last) { | ||
return last; | ||
} | ||
} | ||
return last; | ||
} | ||
|
||
export default function(_vm: VM, args: Arguments) { | ||
return new InternalHelperReference(and, args.capture()); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { Arguments, CapturedArguments, VM } from '@glimmer/runtime'; | ||
import { InternalHelperReference } from '../utils/references'; | ||
|
||
/** | ||
@module ember | ||
*/ | ||
|
||
/** | ||
Compares the two given values with === | ||
|
||
Example: | ||
|
||
```handlebars | ||
{{eq type "button"}} | ||
|
||
{{! be true if `type` is "button"}} | ||
``` | ||
|
||
@public | ||
cibernox marked this conversation as resolved.
Show resolved
Hide resolved
|
||
@method eq | ||
@for Ember.Templates.helpers | ||
@since 3.7.0 | ||
*/ | ||
function eq({ positional: { references } }: CapturedArguments) { | ||
return references[0].value() === references[1].value(); | ||
} | ||
|
||
export default function(_vm: VM, args: Arguments) { | ||
return new InternalHelperReference(eq, args.capture()); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { Arguments, CapturedArguments, VM } from '@glimmer/runtime'; | ||
import { InternalHelperReference } from '../utils/references'; | ||
|
||
/** | ||
@module ember | ||
*/ | ||
|
||
/** | ||
Compares the two given values with > | ||
|
||
Example: | ||
|
||
```handlebars | ||
{{gt age 17}} | ||
|
||
{{! be true if `age` is > 17}} | ||
``` | ||
|
||
@public | ||
@method gt | ||
@for Ember.Templates.helpers | ||
@since 3.7.0 | ||
*/ | ||
function gt({ positional: { references } }: CapturedArguments) { | ||
let left = references[0].value(); | ||
if (left === undefined || left === null) { | ||
return false; | ||
} | ||
let right = references[1].value(); | ||
if (right === undefined) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this satisfy a specific edge case? I would have imagined that the following would have been enough: return left > right; There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried, but typescript doesn't like comparing things with null or undefined. |
||
return false; | ||
cibernox marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
if (right === null) { | ||
return left > 0; | ||
} | ||
return left > right; | ||
} | ||
|
||
export default function(_vm: VM, args: Arguments) { | ||
return new InternalHelperReference(gt, args.capture()); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { Arguments, CapturedArguments, VM } from '@glimmer/runtime'; | ||
import { InternalHelperReference } from '../utils/references'; | ||
|
||
/** | ||
@module ember | ||
*/ | ||
|
||
/** | ||
Compares the two given values with >= | ||
|
||
Example: | ||
|
||
```handlebars | ||
{{gt age 17}} | ||
|
||
{{! be true if `age` is > 17}} | ||
``` | ||
|
||
@public | ||
@method gte | ||
@for Ember.Templates.helpers | ||
@since 3.7.0 | ||
*/ | ||
function gte({ positional: { references } }: CapturedArguments) { | ||
let left = references[0].value(); | ||
if (left === undefined || left === null) { | ||
return false; | ||
} | ||
let right = references[1].value(); | ||
if (right === undefined) { | ||
return false; | ||
} | ||
if (right === null) { | ||
return left >= 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems odd to me, why do we need to extra logic (vs just using the final There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same reason. Typescript doesn't ket me compare things with undefined or null. How can we silence that? |
||
} | ||
return left >= right; | ||
} | ||
|
||
export default function(_vm: VM, args: Arguments) { | ||
return new InternalHelperReference(gte, args.capture()); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { Arguments, CapturedArguments, VM } from '@glimmer/runtime'; | ||
import { InternalHelperReference } from '../utils/references'; | ||
|
||
/** | ||
@module ember | ||
*/ | ||
|
||
/** | ||
Compares the two given values with < | ||
|
||
Example: | ||
|
||
```handlebars | ||
{{lt age 17}} | ||
|
||
{{! be true if `age` is < 17}} | ||
``` | ||
|
||
@public | ||
@method lt | ||
@for Ember.Templates.helpers | ||
@since 2.7.0 | ||
*/ | ||
function lt({ positional: { references } }: CapturedArguments) { | ||
let left = references[0].value(); | ||
if (left === undefined || left === null) { | ||
return false; | ||
} | ||
let right = references[1].value(); | ||
if (right === undefined) { | ||
return false; | ||
} | ||
if (right === null) { | ||
return left < 0; | ||
} | ||
return left < right; | ||
} | ||
|
||
export default function(_vm: VM, args: Arguments) { | ||
return new InternalHelperReference(lt, args.capture()); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { Arguments, CapturedArguments, VM } from '@glimmer/runtime'; | ||
import { InternalHelperReference } from '../utils/references'; | ||
|
||
/** | ||
@module ember | ||
*/ | ||
|
||
/** | ||
Compares the two given values with <= | ||
|
||
Example: | ||
|
||
```handlebars | ||
{{lte age 17}} | ||
|
||
{{! be true if `age` is <= 17}} | ||
``` | ||
|
||
@public | ||
@method lte | ||
@for Ember.Templates.helpers | ||
@since 2.7.0 | ||
*/ | ||
function lte({ positional: { references } }: CapturedArguments) { | ||
let left = references[0].value(); | ||
if (left === undefined || left === null) { | ||
return false; | ||
} | ||
let right = references[1].value(); | ||
if (right === undefined) { | ||
return false; | ||
} | ||
if (right === null) { | ||
return left <= 0; | ||
} | ||
return left <= right; | ||
} | ||
|
||
export default function(_vm: VM, args: Arguments) { | ||
return new InternalHelperReference(lte, args.capture()); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { Arguments, CapturedArguments, VM } from '@glimmer/runtime'; | ||
import { InternalHelperReference } from '../utils/references'; | ||
|
||
/** | ||
@module ember | ||
*/ | ||
|
||
/** | ||
Compares the two given values with !== | ||
|
||
Example: | ||
|
||
```handlebars | ||
{{not-eq type "button"}} | ||
|
||
{{! be true if `type` !== "button"}} | ||
``` | ||
|
||
@public | ||
@method not-eq | ||
@for Ember.Templates.helpers | ||
@since 3.7.0 | ||
*/ | ||
function notEq({ positional: { references } }: CapturedArguments) { | ||
return references[0].value() !== references[1].value(); | ||
} | ||
|
||
export default function(_vm: VM, args: Arguments) { | ||
return new InternalHelperReference(notEq, args.capture()); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { Arguments, CapturedArguments, VM } from '@glimmer/runtime'; | ||
import { InternalHelperReference } from '../utils/references'; | ||
|
||
/** | ||
@module ember | ||
*/ | ||
|
||
/** | ||
Compares the two given values with === | ||
|
||
Example: | ||
|
||
```handlebars | ||
{{not disabled}} | ||
|
||
{{! be true if `disabled` is false}} | ||
``` | ||
|
||
@public | ||
@method not | ||
@for Ember.Templates.helpers | ||
@since 3.7.0 | ||
*/ | ||
function not({ positional: { references } }: CapturedArguments) { | ||
return !references[0].value(); | ||
} | ||
|
||
export default function(_vm: VM, args: Arguments) { | ||
return new InternalHelperReference(not, args.capture()); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { Arguments, CapturedArguments, VM } from '@glimmer/runtime'; | ||
import { InternalHelperReference } from '../utils/references'; | ||
|
||
/** | ||
@module ember | ||
*/ | ||
|
||
/** | ||
Evaluates the given arguments with || in short-circuit | ||
|
||
Example: | ||
|
||
```handlebars | ||
{{or isAdmin isAuthor}} | ||
|
||
{{! be validData either `isAdmin` or `isAuthor` are truthy}} | ||
``` | ||
|
||
@public | ||
@method or | ||
@for Ember.Templates.helpers | ||
@since 3.7.0 | ||
*/ | ||
function or({ positional: { references } }: CapturedArguments) { | ||
let last: any = false; | ||
for (let i = 0; i < references.length; i++) { | ||
last = references[i].value(); | ||
if (last) { | ||
return last; | ||
} | ||
} | ||
return last; | ||
cibernox marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
export default function(_vm: VM, args: Arguments) { | ||
return new InternalHelperReference(or, args.capture()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@chancancode - Should we consider
[]
as falsey here? In general, in template land I think we do right?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, I think we do.
ember.js/packages/@ember/-internals/glimmer/lib/utils/to-bool.ts
Lines 4 to 10 in 1b7e613
I bigger question is should we support proxies, I completely forgot about that. I think that needed to be discussed and clarified in the RFC 🤧
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is surprising. I was expecting and/or helpers to behave just like && / II. Why this deviation from standard JS behaviour?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{{#each}}
is designed to display lists, and seems normal that it has special behavior for empty lists, butand
andor
are not for lists, so I don't see why the behavior of{{each}}
should influence them and make them behave different than&&
and||
On top of that, this is not how ember-truth-helpers behave. I'm not saying that we can't deviate from what e-t-h if there is a good reason, but it would be nice not to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well it really isn't about
each
,{{#if someEmptyArray}}{{else}}{{/if}}
will render the falsey branch. I think it is very confusing to haveif
behave differently thaneq
/not
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It works the same way for...
...and
with
,let
etc. I think the bottom line is it would be pretty surprising if{{else}}
doesn't work consistently in the control flow things in the "language".So now what if you refactor from the first example into...
...what should this do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is probably important for the "language" to have one boolean semantics, and for better or for worse empty arrays are falsey everywhere else.