-
Notifications
You must be signed in to change notification settings - Fork 396
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
The many places for functions #2176
Comments
You are confusing event method calls and data functions. This looks up the function on the
This calls a method on the ractive instance to handle an event:
Right now that's the way it is, but I agree it's a bit confusing. There are some discussions going on about whether to deprecate the method call syntax in favor of event name syntax to unify this confusion since you aren't the only one confused. |
I believe you. I am indeed confused.
I'm sure there's a difference between those two statements, other than one gets passed an event object. But a "method" and a function are not really two separate things in JavaScript... So, to me, that's more just a way of talking about Ractive. So maybe it takes a greater conceptual knowledge of Ractive to get it. I mean, I get that they're looking at different parts of the ractive instance. But it's always seemed odd to me that functions for the template are on the data object, since data is just data, and the functions operate ON the data. So it's almost as if (in the mental model), you're calling a function that acts upon itself. If you can't |
The issue is that each of the functions do have distinct uses, and ractive expects completely different things from them. This means it would be very difficult/impossible to adequately combine them. As @JonDum said, using a function inside a keypath is related to data; the function is expected to return some kind of result to be rendered to the template. Thus it makes contextual sense for the functions to be accessible in the same way as other data, namely from the Meanwhile, event functions are most appropriate attached to the ractive instance itself, since that's the context ractive is accessing when it resolves those references. This allows you to call built-in ractive methods from your template, like so: <button on-click="toggle( 'foo.bar.baz' )">Toggle Button</button> A custom event function is basically an extension of the ractive prototype, and therefore it makes sense to place them on the ractive instance itself. These aren't expected to return a result to be rendered in the template. They're a fundamentally different type of function from data functions, and they have different expected inputs and outputs. Combining these functions in one spot would cause more confusion than they prevent. |
I'm not necessarily saying they need to be combined in one spot. Just maybe some way for it to make more intuitive sense. What you say about their context makes sense, but they're also both in not that dissimilar JavaScript contexts. If it were more like this, it might be more intuitive: <button on-click="this.toggle( 'foo.bar.baz' )">{{ getButtonTitle() }}</button> That would intuitively communicate that one is on the instance ( |
Here's my rule of thumb:
One thing that does help is to register your common helpers in the global Ractive data registry: Ractive.defaults.data.helper = function() { return "yep"; };
new Ractive({
el: 'main',
template: '{{helper()}}'
}); Outputs:
|
Also, for functions in data, it is a bit confusing until you realize that they're also just references that you're calling, and references have to live in data at this point because that's the only way to resolve them. It also gives you the ability to switch them at runtime. If there are any lurking LISPers, this is where they should jump out from behind a bush and say something about "the code is data and the data is code". |
That's what I was attempting to get at in my post, but that's a way more concise way of putting it. |
@evs-chris Thanks for the rules of thumb! That helps.
There's actually a spray you can get if you have too many LISPers in your bushes. |
You'd still have ambiguity of {{#each items}}
<li on-click='this.set("selected", this.foo)'>what is this?</li>
{{/each}} Which is why the controller method is implicitly on ractive. If we opened event methods up to chaining, it could allow you do this: {{#each items}}
<li on-click='get( "items" ).someMethod( this )'>what is this?</li>
{{/each}} In other words give a bridge back from event handlers to data. |
Okay, well I think you've all indulged my rant which had no specific request, but you all gave a lot of good explanations, thank you. @martypdx That would be a cool idea. I actually had this come up. I simplified my example, but I wanted to fire two methods for one click. I basically had to rewrite one method so it fired the other. A lot of this I run into because of the way I'm abstracting out Ractive creation. It's more of a generic loader, with both page data and template arbitrary for each page. That means that I end up putting more interaction logic into the template, rather than adding various bits to my ractiveloader file that may be needed for a particular page. Anyway, I'll close this since @JonDum said similar issues have already been raised and are being discussed. |
Referencing #1172 so I can find this issue again, should I ever need evidence that the proxy event syntax adds an unnecessary layer of confusion. |
One thing I don't know if I mentioned with this: I often find myself having to refactor my data structure because of the current approach for methods. That is, often my data has a natural "root" node that actual represents my data. But as soon as a want to add a function available to my templates, I end up moving the data down one level into a "root" or "data" or "top" node, or whatever I end up naming it, so that it's separate from functions. It's really weird that functions are inside data, especially when they're returned with get(). Not sure why you'd ever want to "get" a function, although I understand that just happens because they're stuffed within the data model. |
dude, you are the king of bringing old conversation a new wind... lol |
@heavyk YES. Wait... is that good? lol |
Just to document to this thread in case someone comes back to it later, but I just came across the syntax in Vue.js that basically does what I'm describing (methods are separate from data, which seems saner): |
Methods are separate from data in Ractive (or, rather, data is separate from methods) – the only difference is that a Ractive component's methods aren't inside a |
Methods in templates? Don't those still have to be within the |
No, all of a component's methods are available to event handlers, like so: <button on-click='set('foo', foo + 1)'>add 1 to foo ({{foo}})</button> That goes for built-in methods as well as ones you specify within Functions exist in data when they need to be accessible to expressions in templates, and Vue copies this behaviour: var ractive = new Ractive({
template: '{{double(foo)}}',
data: {
foo: 42,
double: x => x * 2
}
}); |
@Rich-Harris Oh... Vue.js has good documentation, except that it weirdly says almost nothing about what you can use in mustache expressions. At least nothing I could easily find, so I didn't see that. Regardless, the same thing I said before about having to insert functions into data to be available to expressions, and that often causing model refactoring still holds true. I'd still prefer that a data model only held data, and that adding a function wouldn't require fundamentally changing the data model. |
It really does. It does a lot of things right!
Have you tried this trick? It gives you that separation and lets you share helpers efficiently between components. var helpers = Ractive.prototype.data;
helpers.double = x => x * 2;
helpers.triple = x => x * 3;
var ractive = new Ractive({
template: `
<input type='number' value='{{foo}}'>
<p>twice {{foo}} is {{double(foo)}}</p>
<p>three times {{foo}} is {{triple(foo)}}</p>`,
data: {
foo: 1
}
}); Ultimately, functions do need to be data (if you want to get all Zen about it, functions are data in this context) because they can change. For example if you're doing dataviz you might have |
Whaaaa? That's a cool trick. Although I guess in that case you're modifying the base prototype? FYI - I've had some back-and-forth tweets with the Vue.js account on Twitter, and passed along your comment, and the response was, "No, Vue.js doesn't work that way." (Re: functions being on data.) They are indeed attached to the "methods" property, which seems very intuitive. So I'm not sure about the idea that a) functions do need to be data, or b) they should be data. Data is what I serialize and send to my database, and when I update my model, data is what I send from the backend and update the front end. None of that includes functions. Data consists of integers, objects, arrays, and strings. Databases do not house functions (typically) Even in how Ractive is using them, functions are not "data", since they "act" on data. Sure, they're required on the Stepping back, even if, academically, you could somehow convince me of the argument that template functions should somehow be considered "data", regardless of how "data" is defined anywhere else, that still doesn't solve the fact that it's still terribly inconvenient to have functions on the data object, even with the particular hack you showed, or any other workaround hacks I've used. So, either way, this architecture feels wrong, both intuitively in terms of what data is, and practically, in terms of setting up a Ractive object. It still seems like the wrong approach. |
As a library dev, I know the worst can be, "But x does this better; can't you do x?" So I'm trying to avoid saying that, and just approach the feature on Ractive's terms. Having used Ractive quite a bit by now, function/method declaration & usage still trips me up. |
One of the nice things about Ractive is how hackable it is. It's relatively trivial to extend ractive to implement this behavior yourself: https://jsfiddle.net/aeddLb44/ That said, I think that "you can do it yourself" isn't really an excuse. I certainly think there's merit in the idea of at least cosmetically separating methods from data. Internally, it could do a slightly fancier version of the snippet I made above, but no great shifts in functionality need take place. I think the question is more whether ractive should inherently include the option, or if we leave it as an optional snippet you can implement yourself if you so desire. |
Yes, though you can also add helpers to a specific component class, and each instance of that component gets access to the helpers without needing to faff around with anything non-serialisable: http://jsfiddle.net/1x4uLnmy/
My mistake – turns out properties of Both approaches have their merits. One advantage you do get from treating functions as data in Ractive is that they propagate, so non-isolated components can use helpers defined on parents – so this works, but this doesn't. (For simple expressions you can solve this with
It's cool, I know it comes from the right place! I am sympathetic to what you're saying – really. I'm just not sure how to address the aesthetic concerns without breaking other things (like being able to update functions dynamically), other than mixing data and instance methods together in the same place when it's time to retrieve references, which seems like fertile ground for confusion. Here's where I think we disagree: your view (correct me if I'm putting the wrong words in your mouth) is that the important distinction is between data (strings and arrays and objects) and things that act on data. One on side you have things that can be represented as JSON, and things that can't. It is, I agree, a very natural-looking distinction. But I think the distinction between things that determine what gets rendered (data including functions) and things that determine how the instance behaves (methods) is more important. If we were being all MVC about it, the M is data-including-functions, the V is the template, and the C is the methods. You call a method when you want something to happen. After all, the fact that some things are functions and some aren't is neither here nor there – you could have So to me, the question isn't 'what is this thing?' (function or not-function), it's 'what is this thing for?'. Side-note: I find the easiest way to keep data from the server separate from helpers is simply to have top-level properties, so I do Sorry, that turned into a very long response. Hope it at least sheds some light on my way of thinking about it, even if it doesn't totally convince you – would be interested to know what anyone else out there following this thread thinks.
Ha! I never thought of that. That's pretty cool. |
Just throwing this in, there were several discussions related to the stuff mentioned above (was to comment this morning but ehh... I thought it would be opened up by the others).
By the way, it also reminds me of other frameworks:
But before anything else, the issue above seems like the job of a computed property. |
Okay, this may be obvious to everyone else who uses Ractive, and not an issue, but I find remembering all the places where to put functions when making a new Ractive maddening.
If I want to have a type of evaluation function like
{{ somefunc(val) }}
, I have to do this:...and then I think, "Wait, never mind, I can't do anything with this; I need to put it inside my data." So then I have to do this:
(If you're asking, "Why not just expand data{} and put the function there" it's because I often have generic functions that pass in data to make a Ractive, and while the data changes, the functions do not, because the functions are a requirement of the template not the data, so the functions have to be attached after the data object is received.)
Later, I decide to call a custom function within an element like:
Inevitably, that's where I go wrong. I usually make the mistake of putting the function attached to the data, or if I defined the custom functions, and decide to add an evaluation function later, I usually make the mistake of putting those functions together. Instead I need to do:
Then, later I want to fire an event delegate, so it looks like:
So, in the end, I end up with this "spread out" effect.
Now, I'm sure for developers familiar with the internals of Ractive, the mental models for each of these differences makes their placement more obvious than it does to me. But, for me, it's 3 different ways of "registering" functions for Ractive, with 3 different locations for that registration, and with some (or one) type passing in an undeclared event, and the others not. (I sometimes mistakenly put the event var in data functions or custom functions.)
To me, this spread out and inconsistent model doesn't make sense. I'd rather tell Ractive: "HEY MY CUSTOM FUNCTIONS ARE HERE" in one place and then use them where I want and how I want in my template, to evaluate things as helper functions or when firing events, whether that's as a delegate or not. And I wish events were attached to this.event or some such mechanism so that I wouldn't have to remember which function type passes in an event.
It's also frustrating because Ractive is so generous about scope with keypaths, where it searches up the object for a matching key, but with functions, it punches me in the face repeatedly and tells me I look ugly if I put them in the wrong place.
I should say: I absolutely love Ractive probably over any templating or data binding system I've used. But this particular facet makes me crazy.
Or maybe I'm doing all of this wrong and there's something incredibly simple that I'm not seeing.
Help and/or counselling wanted. Thank you for your time.
The text was updated successfully, but these errors were encountered: