-
-
Notifications
You must be signed in to change notification settings - Fork 408
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
Contextual Helpers and Modifiers (a.k.a. "first-class helpers/modifiers") #432
Conversation
Is it possible to render {{#let (helper "my-helper" 1 2 3) as |someCurriedHelper|}}
{{component someCurriedHelper}}
{{/let}}
|
If we went with this alternative, this problem would be amplified if template imports are introduced. In particular, transitioning from global access to component to an import could change the behavior. In my opinion, we should strive to avoid this kind of discontinuity when migrating from global access to imports (especially in templates that don't produce any deprecations). |
@lifeart it's syntactically valid, but line 2 will result in a runtime error (the value you passed in is not a component, similar to |
@chancancode any way get curried instance type helper/component without exact knowledge of it? for example: // extenral scope {{my-component
item=(random-left-or-right
(component 'foo-bar')
(helper 'zoo-buzz')
)
}} // my-component {{item}}
// <- how I can get Item's type? |
I just wanted to say that it would be great to have it. I worked on a code that could be simplified by passing helpers. Here's one example: travis-ci/travis-web@b0f9cb2#diff-94481a0922ee4e3d9abcdefa49b300c2R73. I ended up exporting a function from a component, but since I couldn't pass it further down I used a |
@lifeart If it's just between components and helpers, and only in the content position, you can just do |
@chancancode what if I need to decide should I use block statement for |
wow. Super exciting 👌 |
I take that this would replace #208 I opened long ago. For what I've read the helper part very similar except that this doesn't require an |
@cibernox - Yes, I agree that this supersedes your earlier RFC. @chancancode - Is there a way to call that out as "prior art" / thought in the space? |
@cibernox thanks for working on this, I added your RFC to the alternatives section. Can you confirm this addressed the use cases you were designing for? Do you spot any further problems with the ambiguity in this new design? |
Please update the link in the top post after renaming. 🙇 |
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.
Overall I'm very much 👍 on this, thank you for taking the time to put this together!
I'd love to see a bit more discussion on a few things:
- An overall transition guide. Do any of the propose changes have order of operations concerns?
- A more precise (read non-prosey 😝) list of the deprecations being proposed here. This likely would include a rough transition plan for each as well as a bit of explanation around each deprecation being independent or not of other changes.
- I think the "How do we teach" this section needs to include addition to the guides's template section (along with API docs).
text/0432-contextual-helpers.md
Outdated
produce an opaque, internal "helper definition" or "modifier definition" | ||
object that can be passed around and invoked elsewhere. | ||
|
||
* Any additional positional and/or named arguments will be stored ("curried") |
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.
FWIW, I know that we generally call these positional arguments
and named arguments
today (as of "Glimmer 2"), but I don't think those terms are as well understood across the community.
In my experience, most folks call these params
and hash
. It might be nice to have a glossary/terminology section or something to clarify...
In the mean time, globals can be referenced explicitly using the `component`, | ||
`helper`, and `modifier` helpers. | ||
|
||
Another difference is how global helpers can be invoked without arguments in |
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's important to call out that this isn't about named arguments generally it's about named arguments to angle bracket invocations.
Specifically, this does not invoke pi
(it passes the value):
{{my-component value=pi}}
text/0432-contextual-helpers.md
Outdated
positions (which includes argument positions for curly invocations), the | ||
parentheses are already mandatory (otherwise it invokes the property fallback). | ||
|
||
We propose to deprecate invoking global helpers in named argument positions and |
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.
Is is this deprecation part of this RFC? Does it need its own transition path (ala https://emberjs.github.io/rfcs/0308-deprecate-property-lookup-fallback.html#transition-path)?
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 is part of this RFC. It should say "deprecate auto-invoking global helpers with no arguments in named arguments positions" and the transition path is from @foo={{bar}}
to @foo={{(bar)}}
.
|
||
Some additional details: | ||
|
||
* When the first argument passed to the `helper` or `modifier` helper is |
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.
Could you elaborate why you prefer this option over raising an error?
I'm assuming you did this thinking on {{helper @boundValue}}
, but I'd say that in a non-bound form it should raise in build-time (p.e {{helper ""}}
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.
This matches the behavior of the component
helper. In general, Handlebars tries to be quite forgiving about these things, and errors during render is generally unexpected (and currently unrecoverable).
It also enables use cases like:
{{#if this.featureIsEnabled}}
{{yield (component "experimental")}}
{{else}}
{{yield null}}
{{/if}}
..which the other side can further curry or "invoke" it without caring for whether the value is falsy.
I suppose we can statically disallow the {{helper ""}}
but I don't quite see the point of it since 1) we have to support the runtime empty string anyway (see above) and 2) this involves using Magic™ that is otherwise not possible in user land (the compiler needs to know that the helper
helper is special and disallow the static empty string).
I don't think it is worth the trouble.
Co-Authored-By: chancancode <godfreykfc@gmail.com>
problem. Alternatively, we could exclude the angle bracket invocation position | ||
from being able to "see" implicit global identifiers. | ||
|
||
### Local helpers and modifiers |
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.
Based on the design, it seems like this is not limited to helpers and modifiers. Specifically if the design of this RFC is that these things are "just values" (which I think is roughly what is being said) then this section also applies to components.
text/0432-contextual-helpers.md
Outdated
avoid the conflict. With proper linting, this could be quite easily avoided | ||
altogether. | ||
|
||
With _implicit_ global bindings, this problem is might more difficult to spot |
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.
'is might' typo
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.
much 😆
We discussed this at today's Ember.js core team meeting, and we believe that this RFC is ready to move into final comment period. |
Is it really a good idea that this is not equivalent?
What will happen for
Should it invoke the helper or not? I honestly don't know the answer. We just added so much clarity to the templates, I feel like we're removing clarity now! Could we maybe rename I really want to have this functionality, but do we really have no better ideas for the syntax? |
@luxferresum in both cases you are “passing” the helper, it’s just what the position decide to do with the passed value that is different. In the named argument position, it passes the value to the component. In the attribute (and content) position, it sees that you are trying to “render” a helper value, and the only sensible thing to do at that point is to invoke it and then render the result. Alternatively, it could render [object Object] or maybe throw an error, but is that really helpful? |
I agree its not helpful, but maybe more expected. Don't get me wrong, I don't really have a better idea. But at least I would disallow using What feels like a better solution would be a sigil to invoke helpers. But this would break what we already have. Something like |
@luxferresum thanks for all the detailed feedback!
A few things. First of all, in all three cases, When you pass it as a named argument (as When you pass it as an attribute (as Passing it as content (as This distinction has nothing to do with angle bracket invocation vs. curly invocation. It has to do with the fact that Ember has decided to invoke the helper, while the user-space code (the component implementation) can choose between invoking the helper, stashing it off, or even adding more arguments before invoking it. Also of note: it's very unlikely that this distinction will come up when using the However, this forms is more likely: {{#let (helper foo-bar separator=",") as |foo-bar|}}
<div class={{foo-bar}}>hello world</div>
<Tab @helper={{foo-bar}} />
{{/let}} In this case, it's very important that This will become more relevant with --- js ---
import { title, upcase } from "./title-helpers";
import { Title } from './Title';
--- hbs ---
<div class={{title}}>hello world</div>
<Title @transform={{upcase}} @title="Hello world" /> With this definition of import Helper from '@ember/component/helper';
import { inject as service } from '@ember/service';
export const title = Helper.extend({
title: service('title'),
compute() {
return this.title;
}
});
export const upcase = helper(([string]) => string.toUpperCase()); And this implementation of <h1>{{@transform @title}}</h1> In this case, it's important that our component can pass the uninvoked The distinction here is that the Finally, note that in the previous example, I needed to use a class-based helper to illustrate a situation with For this reason, we expect this confusion to be less serious than it first seems. The confusion would come from noticing that you don't need First, zero-argument helpers are simply rare. The most likely reason to write Second, we would have a development-mode assertion when attempting to use a passed-in helper as a value (using the same proxy-wrapping strategy we use elsewhere in the framework). The solution (as the assertion will say) is to say |
@chancancode can you recommend a specific section of the Guides' existing content that should be used to teach this? Also a recommendation of what level of detail belongs in the guides, versus that appropriate to just have available in the API docs. Thanks! |
@jenweber They should definitely be available in the API docs first and foremost. For guides, my recommendation is the "How we teach this" section. In terms of the current guides, do we already teach the |
🎉 - Sorry for the delay here, I was supposed to land this on Friday.... |
So looking forward to this! What's the status on this? Edit: RFC Tracking Issue emberjs/rfc-tracking#6 |
For future readers: this feature is now done and shipped in Ember in 3.27 |
Rendered