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

Adding Logical Operators to Templates #562

Merged
merged 6 commits into from
Jan 15, 2021
Merged
Changes from 3 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
95 changes: 95 additions & 0 deletions text/0000-add-logical-operators.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
- Start Date: 2019-12-08
- Relevant Team(s): (fill this in with the [team(s)](README.md#relevant-teams) to which this RFC applies)
- RFC PR: https://github.com/emberjs/rfcs/pull/562
- Tracking: (leave this empty)

# Adding Logical Operators to Templates

## Summary

Add new built-in template `{{and}}`, `{{or}}` and `{{not}}` helpers to perform basic logical operations in templates, similar to those included in `ember-truth-helpers`.

This RFC is a subset of the changes proposed in #388.

## Motivation

It is a very common need in any sufficiently complex Ember app to perform some logical comparison operations and often the most convenient place to do it is right in the templates.
Because of that, [ember-truth-helpers](https://github.com/jmurphyau/ember-truth-helpers) is one of the most installed addons out there, either directly by apps or indirectly by
other addons that those apps consume.

The fact that `ember-truth-helpers` is so popular is a good signal that this it is filling a perceived gap in Ember's functionality.

A second reason is that it might help make Ember more approachable to newcomers that have some experience in other frameworks.
Most if not all web frameworks have some way of performing logical operations in the templates and it's surprising that Ember requires an third party package to perform
even the most basic operations.

A third reason, this time technical, is that by implementing those helpers at a lower-level, we can make them more performant by making them short-circuit.
Right now helpers implemented using public APIs like those in `ember-truth-helpers` eagerly consume their arguments. In the case of logical operations like `{{and a b}}`,
once the first argument (`a`) is evalued to _falsey_, the second argument (`b`) is irrelevant. Sometimes arguments can be expensive properties to calculate,
and by short-circuiting we can avoid computing them at all sometimes.


## Detailed design

Add `{{and}}`, `{{or}}` and `{{not}}` helpers.

#### `{{and}}`
Takes at least two positional arguments. Raises an error if invoked with less than two arguments.
It evaluates arguments left to right, returning the first one that is not _truthy_ (**by handlebar's definition of truthiness**)
or the right-most arguments if **all** evaluate to _truthy_.
This is *NOT* equivalent to the `{{and}}` helper from `ember-truth-helpers` because unlike this proposed helper, the one in `ember-truth-helpers`
uses Javascript's definition of truthiness.

#### `{{or}}`
Takes at least two positional arguments. Raises an error if invoked with less than two arguments.
It evaluates arguments left to right, returning the first one that is _truthy_ (**by handlebar's definition of truthiness**) or the
right-most argument if all evaluate to _falsy_.
This is *NOT* equivalent to the `{{or}}` helper from `ember-truth-helpers` because unlike this proposed helper, the one in `ember-truth-helpers`
uses Javascript's definition of truthiness.

#### `{{not}}`
Unary operator. Raises an error if invoked with more than one positional argument. If the given value evaluates to a _truthy_ value (**by handlebar's definition of truthiness**),
the `false` is returned. If the given value evaluates to a _falsy_ value (**by handlebar's definition of truthiness**) then it returns `true`.
This is *NOT* equivalent to the `{{not}}` helper from `ember-truth-helpers` because unlike this proposed helper, the one in `ember-truth-helpers`
uses Javascript's definition of truthiness.

#### Handlebar's definition of truthiness
This is the most important detail of this proposal because it's where it deviates from `ember-truth-helpers`.
Handlebars has its own definition of _truthyness_, which is similar to Javascripts except that empty arrays are
considered **falsy**, while in JS are considered **truthy**.


This RFC intentionally leaves the implementation details unspecified, but one can think of those helpers as macros that
expand to combinations of `if`s.

##### `{{and}}`
- `{{and a b}}` is equivalent to `{{if a b a}}`
- `{{and a b c}}` is equivalent to `{{if a (if b c b) a}}`
- and so on

##### `{{or}}`
- `{{or a b}}` is equivalent to `{{if a a b}}`
- `{{and a b c}}` is equivalent to `{{if a a (if b b c)}}`
cibernox marked this conversation as resolved.
Show resolved Hide resolved
- and so on

##### `{{not}}`
- `{{not a}}` is equivalent to `{{if a false true}}`

## How we teach this

The introduction of these helpers does not impact the current mental model for Ember applications.

In addition to API and Guides documentation with illustrative examples of usage of the various helpers, stressing out
where the definition of truthiness of handlebars is different from the one in Javascript.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to beef this section a bit more. Specifically, I do think this changes some of the mental models. For example, commonly we punt folks into JS-land instead of using ember-truth-helpers in the guides/tutorial (e.g. we might suggest using computed.not). This would definitely change if we introduce these helpers.


## Drawbacks

Adding new helpers increases the surface area of the framework and the code the core team commits to support long term.

## Alternatives

One alternative path is to not take any action and let users continue to define their own helpers (or install `ember-truth-helpers`).

## Unresolved questions

- Consider following Javascript's definition of truthiness. This would also help with the transition from `ember-truth-helpers`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably belongs under “Alternatives” instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd argue that it belongs here. The way I understand RFCs, alternatives is where you list alternative options to implementing the RFC, not so much about doubts regarding what the best implementation is.

If anyone can confirm that I got it wrong, I'm happy to change it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think an exact implementation of ember-truth-helpers logical operators (except maybe not) is not what anyone wants, given the desire for short circuit logic. But alternatives sections are not always fully specced out, so I think with just a little more detail, you could write an alternative.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Being someone who believes that the lack of logical operators is the main benefit that handlebars provides, and one who has never had a need to install this addon in the many ember apps i have built, simply assuming that this is something that is required to be installed does not sit well with me. I would like somewhere in this process to highlight the benefits that not having these be a part of the framework provides.

Copy link
Contributor Author

@cibernox cibernox Dec 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@webark You can certainly live without those helpers, but I'll add some examples of things that are much nicer with helpers like those:

Default Values in template-only components

Imagine a template only component that wants to have a default value for say, @name

<img class="avatar" src={{or @user.avatar "/imgs/default-avatar.png">

Perform an operation on each iteration of a loop without extracting another component

{{#each @users as |user|}}
  Name: {{user.name}} 
  {{#if (and session.hasAdminPrivileges user.inactive)}}
    <button {{on "click" this.activate}}>Delete</button>
  {{/if}}
{{/each}}

In both situation you can find a way to do the same things without using any of those helpers, but it involve either create a JS file for a component only to put a computed property on it, or to extract a new component to call it on each iteration of a loop, just to perform a very simple logical operation.

It's a matter of convenience and as any tool it can be abused, but the popularity of ember-truth-helpers proves that devs really find it convenient.

I think that Ember providing just the most basic and used ones (and, or, not, eq are the main 4 for me, but this RFC focuses on the first 3) and optimized at the VM level we are alleviating a pain.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

call me a curmudgeon (and i’ll postpone my “at this point we might as well be using jsx” rant for another day) I just feel it should be mentioned why this was never in core to begin with. Your examples, to me, are the base examples of why. Just a brief “handlebars was originally meant to be “logic-less” but it has grown, and the community has pushed it, past that for sometime now” blurb would be helpful, at least for a historical sense.