meteor-blaze-components
meteor-blaze-components copied to clipboard
Ideas for new features of Blaze Components
I am not yet completely sure how I would implement this, but I am thinking of doing some backwards incompatible changes to Blaze Components. In short, it is getting rid of a data context, instead of replacing it with scope.
- including
{{> Component}}would not pass data context anymore, scope of the included component would be empty - you can include only with explicit keyword arguments like
{{> Component header=value}}.headerbecomes a reactive value (populated fromvalue) inside the scope of theComponent. - components have a known list of arguments they can get, with additional metadata:
- is argument required
- documentation of the argument
- equality function of those arguments
- we will not check the type/value of the argument, because I do not want to force evaluation if not necessary (so if
valueis a function, and you pass it on, it would be great that it is passed on as a function) - arguments can be marked as constructor-level argument, or render-level argument (are they passed when component instance is created, or are they available once it is rendered)
- other arguments than those specified passed make rendering fail
- each argument is exposed as a method on the component
- components can then have public methods which can be called from outside, so you can also pass a component itself to child component, like
{{> Component parent=this}}, and then code inside could run something likethis.parent().fooBar()iffooBaris a method of the parent component thisinside a template would be now the component instance itselfparentComponentand things like that would not be available anymore, component should get parent component explicitly as an argument, if it depends on it- not sure about children components?
- we will have to have some way to mark public methods, because I do not want to have many private methods which I would then have to use in templates like
{{_privateHelper}}, or should we do this? - to me there are two levels of private methods: some which are private to component logic and should not be called from a template (I use
_prefix on them), and some which are private to templates, and should not be called from outside
- for helpers/methods you can still mix args and kwargs
- kwargs object at the end will be passed only if it there are really kwargs provided
- there will be no
hashthere, it will just be an instance of known class - functions passed to helpers will not be automatically called, but if you use it in a template it will be (so
{{value}}will callvalue()for you ifvalueis a function, but{{fooBar value}}will get you a functionvalueas the first argument, ifvalueis a function)
What my plan is that I bump this package to 1.0 and release it. And then the above changes will be 2.0 of this package.
So the API of a component are arguments you can pass, and public methods you can call on the component. Events handlers are internal to the component.
cc @stubailo, because I think he will like some ideas here
getting rid of a data context, instead of replacing it with scope
Yes please!!
arguments can be marked as constructor-level argument, or render-level argument
Not sure what this means? Also, there are so many ways to specify types of arguments... React PropTypes, Meteor Match, etc. which one do we use?
each argument is exposed as a method on the component
Perhaps this.args.x? Or we just expect people to avoid collisions? Maybe we can just warn on collisions.
Events handlers are internal to the component.
So should people use the React-style callback passing to set event handlers on children?
arguments can be marked as constructor-level argument, or render-level argument Not sure what this means?
In Blaze Components you have two types of data which can come to a component. One is what is passed to the component's constructor, another is which is currently a data context. The difference is that if the first arguments change, we recreate/reinitialize the component, if a second argument changes, only the state of the component changes, and things re-render reactivelly.
Currently, you could pass or another, but not both. And now I am proposing that this is simply metadata of the argument, how it should be provided to the component (and which time).
Also, there are so many ways to specify types of arguments... React PropTypes, Meteor Match, etc. which one do we use?
None, as specified above, so that we do not force evaluation of a reactive function passed.
Or we just expect people to avoid collisions? Maybe we can just warn on collisions.
To me arguments is something you would add to your component inside a constructor. You should make sure you do not clobber any prototype method you have there anyway. And yes, we could check if a component has a prototype method defined the same as you defined that potentially arguments could be.
So should people use the React-style callback passing to set event handlers on children?
So, arguments can be functions. And so yea, you could do onClick={{argumentPassedFromParent}} in a child template, probably. Or your child template could do onClick={{parent.childOnClick}} if parent was passed as an argument to child component, and has childOnClick as a public method. So in the latter case there is a constraint on what types of parent values are allowed, one with childOnClick public methods.
It would be great to have something like reacts componentDidUpdate, sometimes you have to use jQuery libs and stuff like this saves time (in the situations when you receive new data, but the template already rendered, but you need jQuery to do something with new data);
It's quite hacky and not the intended way of properly doing things, but well, every time I hit this kind of situation I need to reinvent the wheel :) .
@mitar everything looks awesome here, and I'm very excited to see public methods as one of the priorities. I'm not going to comment everything because I agree with all the changes, but I can suggest implementations (as a new but already heavy user view).
One thing that we already created is a sharedState variable to be our central reactive source between components. I identify all components at startup and create a reactive data source for each one that can be used and accessed by all components. Is that something to consider? If yes, I have some ideas.
I identify all components at startup and create a reactive data source for each one that can be used and accessed by all components. Is that something to consider? If yes, I have some ideas.
Isn't this the same as Session?
@stubailo Session can be used here, but I'm thinking in something more scoped to the component it self. Using Session would require me and my team to be careful about variable names and everything. What we are doing is something like this:
commonState.componentName.VariableName()
This way we know that this is a variable that has componentName as parent. It's a simple (mostly because of ReactiveVar 😄 ) implementation but that makes sense for us.
We are thinking about changing the method to something like this this.shareState('foo', bar);. And in another component I can use this.getState('component', 'foo');.
At least for us this makes a lot of sense. I can already think about some implications of this implementations. For example, what if I have multiple components rendered on the page? That means that all of them will share the same state? Maybe yes, maybe no.
Does it makes sense for you now, @stubailo ? If no, I would love to hear maybe a simpler implementation that we can use.
I guess that seems just as global, since it's just like saying Session.get('componentName/variableName') but if it works for you that's great!
It would be great to have something like reacts componentDidUpdate, sometimes you have to use jQuery libs and stuff like this saves time (in the situations when you receive new data, but the template already rendered, but you need jQuery to do something with new data);
When should this run? On every change? Of any attribute of any dom element?
One thing that we already created is a sharedState variable to be our central reactive source between components.
Why don't you just use Minimongo/local collections on the client? It has nice things like easy limiting of reactivity, complex queries against data, and so on? You could have one document per component. You could in onCreated create such document, if you for example to have independent state per each component.
But anyway, this is for me orthogonal question to view layer. I do not think this should be part of the view layer, but it should just allow its use. Personally, I do not see the benefit of what you are proposing. But everyone has its own preferences here.
Yeah, looks like I did it the wrong thing all the way, though I used the client-side collections. Thank you :)