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

Helpers #53

Merged
merged 1 commit into from
Jun 7, 2015
Merged

Helpers #53

merged 1 commit into from
Jun 7, 2015

Conversation

mixonic
Copy link
Sponsor Member

@mixonic mixonic commented May 18, 2015

View as HTML

Pardon any spelling snafus etc, written on a 🚌 :-p

@mixonic mixonic mentioned this pull request May 18, 2015
|has positional params|has template|has lifecycle, instance|can control rerender
---|---|---|---|---
components|Yes|Yes|Yes|Yes
function helpers|Yes|No|No|No
Copy link
Member

Choose a reason for hiding this comment

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

Function helpers can yield to templates. if for example is a function helper today...

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

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

helpers can yield but do not have a template. Do you think it would be more accurate to say they do not have a layout?

Copy link
Member

Choose a reason for hiding this comment

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

I thought you were saying they cannot "accept a block" (aka the template) which is not correct. The terminology of layout and template are annoying, and I am sorry that you have to thread the needle here...

@jamesarosen
Copy link

@mixonic you've made my day 😀

My two favorite points are making registerHelper public and the recompute method.

I'm definitely for init and willDestroy hooks, but its far from a deal-breaker to have to override destroy.


* The helper instance is created.
* The `compute` method is called, and its return value is treated like the
return value of a function-based helper.
Copy link
Member

Choose a reason for hiding this comment

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

It is not obvious to me what exactly is done with the return value of a function-based helper. Can you potentially list a few in context examples for:

  • sub-expr: {{#each (get model recordType) as |record|}} {{/each}}
  • element space: <div {{get model recordType}}></div> (I don't like this and it should be discouraged for sure)
  • attribute assignment: <div data-name="{{get model recordType}}"></div>
  • "normal" (aka none of the above): <p>{{get model recordType}}</p>

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

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

I've added a section on return values, and ran some tests. I discovered that function-based helpers don't work in some of the places I describe they should. I'd like to make them work in those cases.

@mmun
Copy link
Member

mmun commented May 18, 2015

This is going to be very popular with addon authors :) I don't like recomputeObserves. Maybe observes, dependencies, dependentProperties, observedProperties, etc.

I like plain observes. Kinda like needs.


Porting this usage to a stateful helper only changes the boilerplate:

```
Copy link
Member

Choose a reason for hiding this comment

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

js block

@rwjblue
Copy link
Member

rwjblue commented May 18, 2015

Looks wonderful. A couple points:

  • Can you add verbiage to indicate the lifespan of a stateful helper? Are they singleton? Are they created/destroyed along the same lines as components?
  • Does the stateful helper have knowledge of its surroundings? For example: is it aware of its containing component/view?
  • What internal helpers can be restructured to extend from Ember.Helper (at least the new get helper that is being worked on)?

@mmun
Copy link
Member

mmun commented May 18, 2015

The get helper is not amenable to this kind of refactoring because it needs to munge the params to create a custom stream. As I understand it, the only additional dependencies you have should come from global singletons, so basically services.

@cibernox
Copy link
Contributor

I do like this proposal better than the subexpresions one.

This kind of helpers remains as close as possible to the existing helpers, that are the simplest abstraction you can have for getting an output from some input, just adding the minimum amount of functionality to let them watch other subsystems.

I'd like the to be one way and not having access to the context. That would limit side efects changes to things you're injecting, and ideally you should keep that to a minimum.


They fill a gap in Ember's current template APIs:

| | has positional params | has layout | can yield template | has lifecycle, instance | can control rerender |
Copy link
Member

Choose a reason for hiding this comment

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

I don't necessarily feel like this table is adding a lot to understanding the problem, and it makes it look like each of the columns is a legitimate "primitive knob" that you might want to tune separately. I also think the comparison with Component makes it seem even more like there are knobs being tuned.

The way I'd talk about it is:

  • Components are stateful and manage an area of DOM
  • Helpers produce a single value that can be passed into other helpers, but don't directly control an area of DOM
    • Ember.Helpers have a full lifecycle and access to services, and have the ability to trigger a recomputation
      when something external changes.
    • There is a shorthand for Ember.Helper that you can use if you have no dependencies on external services.

@tomdale
Copy link
Member

tomdale commented May 22, 2015

Awesome start, @mixonic.

I'd prefer to descope using stateful helpers in block form and focus solely on the subexpression case. Once we've seen how that shakes out, we can circle back around and better understand the use cases for the block form.

Personally, I'd rather push people in the direction of components if they need block semantics, especially now that @ef4 has implemented positional params. The biggest downsides of Components today is that they cannot be used in subexpressions, which IMO should be the major focus of this RFC.

Lastly, the block form tempts people to make a comparison of stateful helpers to Components. By eliminating the block form for now, you can dramatically simplify and clarify the narrative: Stateful helpers are just regular helpers that have access to services, and here's the syntax for building them.

I agree with the above comments that recomputeObserves isn't the ideal name. We should also consider a syntactic sugar for recomputing in response to events on services and not just property changes, although perhaps that's outside the scope of this RFC. (Supporting a .on('service.eventName') syntax, perhaps?).

# Motivation

Helpers in Ember are pure functions. This make them easy to reason about, but
also overly simplistic. It is difficult to write a helper that recomputes due to
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 I would say this a little bit differently.

Historically, helpers in Ember have had access to a lot of ambient information in the scope. This made them powerful, but also error-prone. In practice, they were used by addon authors to implement powerful functionality.

Glimmer does not want to expose the guts of the scoping system to user code, so our initial stab at helpers was to make them pure functions: data in, data out. That works well for a lot of cases, and is very ergonomic, but is a regression for addons that need the value they emit to be based on external state. This proposal is an attempt to recoup enough expressiveness so that these addons can retool for Glimmer without exposing the guts of the scoping system to addons again.

Relatedly, we want to be able to expose an API that we can continue to maintain over the 2.x series, unlike the private "guts" APIs that have had to change multiple times in the 1.x series.

@jamesarosen
Copy link

@wycats said,

I assume that the term "stateful helper" is meant to be used in this RFC, but that in documentation we would just talk about "helpers", and talk about the function form as a shorthand.

That suggests that this RFC probably needs to consider the public API for that shorthand.

ember-cli/ember-cli#3710 may be of interest. I opened that because the guides and ember-cli differ about how to build pure-functional helpers, and both recommend using APIs that are currently private (though widely assumed to be public).

@mixonic
Copy link
Sponsor Member Author

mixonic commented May 23, 2015

I've made a significant set of changes this morning:

  • Re-introduce the dash requirement WAIT DONT KILL ME. @rwjblue and I have a more complete solution for this we still plan to include in 1.13, as it is required for us to have feature parity with 1.12 for addon authors. I am very aware this is a requirement, its omission here is just to let us focus on solving one problem at a time.
  • Remove the ability for helpers to yield and otherwise interact with blocks. They now only return a value.
  • Change some terms:
    • "Stateful Helper" -> "Helper"
    • function-based helpers are called "shorthand helpers"
  • Remove the recomputeObserves sugar.

@mixonic
Copy link
Sponsor Member Author

mixonic commented May 23, 2015

@rlivsey "What's the difference between a stateful block helper and a tagless component?"

This is exactly Tom's thought, and is why I've removed the block stuff.

@mixonic
Copy link
Sponsor Member Author

mixonic commented May 24, 2015

See #58 for a detailed proposal about dashless helpers.

@tim-evans
Copy link

How would you deal with helpers that take in promises?

@jamesarosen
Copy link

@tim-evans

How would you deal with helpers that take in promises?

With a (full) Helper, this is straightforward: just call this.recompute() when the promise is fulfilled. For a shorthand helper, this wouldn't work unless you could somehow convert promise._state to a Stream.

@mixonic the current guides use Ember.Handlebars.registerHelper, which allows for dependencies:

Ember.Handlebars.helper('fullName', function(person) {
  return person.get('firstName') + ' ' + person.get('lastName');
}, 'firstName', 'lastName');

I see two options for this in the new world:

  1. shorthand helpers can only recompute on properties directly passed to them; everything else requires a full Helper
  2. the manual registration mechanism in Helper listing (dashless helpers) #58 could allow for adding dependencies to shorthand helpers

@rwjblue
Copy link
Member

rwjblue commented Jun 7, 2015

@mixonic - Can you double check the final details on this (as compared to the implementation PR), rename the file (increment the number) so we can merge?

mixonic added a commit that referenced this pull request Jun 7, 2015
@mixonic mixonic merged commit b52780c into master Jun 7, 2015
@mixonic mixonic deleted the improved-helpers branch June 7, 2015 20:15
@mixonic
Copy link
Sponsor Member Author

mixonic commented Jun 7, 2015

@jamesarosen for now, I'd like to say you need to use the full helper syntax for additional bindings. We can improve this, just need to debate the sugar separate from shipping in 1.13 :-)

mmun added a commit that referenced this pull request Jun 7, 2015
session: Ember.inject.service(),
onCurrentUserChange: Ember.observes('session.currentUser', function() {
this.recompute();
}),

Choose a reason for hiding this comment

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

If this is a common occurrence, could it be achieved in a more declarative manner?

Maybe something like:

export default Ember.Helper.extend({
  recomputeWhenTheseThingsChange: ['session.currentUser', 'session.isLoggedIn'],
});

EDIT: (Sorry, late to the party on this one. Please ignore if this has already been discussed)

Copy link
Member

Choose a reason for hiding this comment

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

I would like to see what cases are common before adding sugar, especially since ES7 decorators will shortly make many of these things much nicer to express.

@Eptis
Copy link

Eptis commented Jun 8, 2015

Will this be in Ember 1.13 in the beta cycle or will it be in the Ember 2.0 (beta) cycle?

@cibernox
Copy link
Contributor

cibernox commented Jun 8, 2015

@Eptis This is already in 1.13 (although probably a 1.13.beta3 should be released)

@rwjblue
Copy link
Member

rwjblue commented Jun 8, 2015

@Eptis - It will be in 1.13

mmun added a commit that referenced this pull request Jun 13, 2015
nickiaconis pushed a commit to nickiaconis/rfcs that referenced this pull request Mar 16, 2016
nickiaconis pushed a commit to nickiaconis/rfcs that referenced this pull request Mar 16, 2016
chancancode pushed a commit that referenced this pull request Oct 27, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.