peerlibrary / meteor-blaze-components

Reusable components for Blaze
http://components.meteorapp.com/
BSD 3-Clause "New" or "Revised" License
354 stars 26 forks source link

Provide equivalents to Blaze.render, Blaze.renderWithData and Blaze.remove #36

Open mitar opened 9 years ago

mitar commented 9 years ago

Provide equivalents to Blaze.render, Blaze.renderWithData and Blaze.remove.

For now you can use for Blaze.render:

renderedView = Blaze.render MyComponent.renderComponent parentComponent

or

renderedView = Blaze.render BlazeComponent.get('MyComponent').renderComponent parentComponent

For renderWithData:

renderedView = Blaze.renderWithData MyComponent.renderComponent(parentComponent), data

And Blaze.remove:

Blaze.remove renderedView
bensmeets commented 9 years ago

In the meantime, can you help me out with this example:

ViewStackComponent = BlazeComponent.extendComponent({
  add: function (name) {
    var comp = BlazeComponent.getComponent(name).renderComponent(this);
    var view = Blaze.renderWithData(comp, data, this.firstNode());
    // Insert question here :)
  }
});

In that code, I want to animate IN the panel that is being created. I know about the insertDomElement, but I don't want to use that route, since the code I need to animate is inside the component class of the panel. So getting to the question, how can I get a reference to the component instance of the "comp" I just created?

If I try it after BlazeComponent.getComponent(name); I get a Class back (not the instance). If I try it after renderComponent() I get a Blaze.Template back. If I try it after renderWithData() I get a Blaze.View back. Is what I'm trying even possible? Views...Templates...Instances...Components...oh my :)

Tnx.

mitar commented 9 years ago

You can do:

ViewStackComponent = BlazeComponent.extendComponent({
  add: function (name) {
    var component = new (BlazeComponent.getComponent(name))();
    var renderedComponent = component.renderComponent(this);
    var view = Blaze.renderWithData(comp, data, this.firstNode());
    // Insert question here :)
  }
});
bensmeets commented 9 years ago

Got it, tnx!

Does components change anything in the lifecycle of that view? I need to wait for that view to be rendered on screen before talking to it. In my old version of mentioned stack, i used "view.onViewReady()" for that. If I use that in this example, it's not called. Or am I missing something.

ViewStackComponent = BlazeComponent.extendComponent({
  add: function (name) {
    var comp = new (BlazeComponent.getComponent(name))();
    var renderedComponent = comp.renderComponent(this);
    var view = Blaze.renderWithData(comp, data, this.firstNode());

    view.onViewReady(function() {
      console.log('I am never called');
      comp.callFirstWith(null, 'doAnything');
    });
  }
});

P.S. I tried listening to the comp instead of to the view. But there is no equal method for components right? I can't call say

comp.onRendered(function() {
   console.log('Do this as well when rendered');
});

Can I?

mitar commented 9 years ago

I do not get you? You have onRendered method in your component you can use.

class MyComponent extends BlazeComponent
  @register 'MyComponent'

  template: ->
    'MyComponent'

  onRendered: ->
    console.log 'I am never called'
    @callFirstWith null, 'doAnything'
ViewStackComponent = BlazeComponent.extendComponent({
  add: function (name) {
    var comp = new (BlazeComponent.getComponent(name))();
    var renderedComponent = comp.renderComponent(this);
    var view = Blaze.renderWithData(comp, data, this.firstNode());
  }
});
mitar commented 9 years ago

Alternatively, you can also do:

ViewStackComponent = BlazeComponent.extendComponent({
  add: function (name) {
    var comp = new (BlazeComponent.getComponent(name))();
    var renderedComponent = comp.renderComponent(this);
    var view = Blaze.renderWithData(comp, data, this.firstNode());
    this.autorun(function (computation) {
      return unless comp.isRendered();
      computation.stop()
      console.log('Do this as well when rendered');
    });
  }
});
bensmeets commented 9 years ago

That last one did the trick. Thanks!

Food for thought, maybe match the Blaze api for this regarding "extending" onRendered callbacks like:

comp.onRendered(function() {
});

But maybe that's just a nice to have :)

mitar commented 9 years ago

No. This is not reusable nor composable. You should put hooks into the class method, so that you can reuse it in your children implementations. If you attach hooks like Blaze does, then you have issues when you want to reuse those hooks. You have to copy them. And you have to manually decide how to override/extend them.

Reactive isRendered on the other hand allows you nice composition. Combining onRendered with onClick is pretty hard. But if you have return unless @isRendered() && @isClicked() in the autorun, it is pretty easy to combine.

In your case, I would suggest you just use the first approach. Class method onRendered. Why you prefer the second?

bensmeets commented 9 years ago

Well, it's just a matter of seperation of concerns (kind of). My parent lays out it's children (simply saying). The parent is the only one that should know of all it's children and I want it to be able to tell each child where to go.

When a child is added, I want the parent to be able to tell it "you need to go to this position". But telling it to that child can only be done once it's rendered.

So I was stuck :) But the workaround works, it just feels a bit verbose. I'm not sure if I understand how that would make it less composable either. But will try to read it again later, just in case ;)

mitar commented 9 years ago

But the workaround works, it just feels a bit verbose.

Which workaround? My snippets above are not workaround, but normal code. :-)

I'm not sure if I understand how that would make it less composable either.

Hooks would make it less composable. This is why we do not provide them. :-)

The parent is the only one that should know of all it's children and I want it to be able to tell each child where to go.

But why don't you create an explicit API for this?

class ChildComponent
  ...
  onRendered: ->
    @componentParent().tellMeWhereToGo()

You could also do something like this in the parent:

listOfRenderedChildren = []
@autorun =>
  newListOfRenderedChildren = (child for child in @componentChildren() when child.isRendered())
  for child in newListOfRenderedChildren when child not in listOfRenderedChildren
    child.iAmTellingYouWhereToGo()
  for child in listOfRenderedChildren when child not in newListOfRenderedChildren
    @myChildHasBeenRemoved child
  listOfRenderedChildren = newListOfRenderedChildren
bensmeets commented 9 years ago

Which workaround? My snippets above are not workaround, but normal code. :-)

You're right, not a workaround. It's just different related to Blaze, which is fine. But for me as a noob, it's intimidating :) Might be for others as well.

But why don't you create an explicit API for this?

Because that's a hard link between the child and the parent. It must be my old school thinking, but to me that seemed inappropriate since it's possible that the child is used without the wrapping parent. Then it call's a function that won't exist?

I'm using the autorun example now, works great.

mitar commented 9 years ago

Because that's a hard link between the child and the parent. It must be my old school thinking, but to me that seemed inappropriate since it's possible that the child is used without the wrapping parent. Then it call's a function that won't exist?

But when it is used with the wrapping parent, how does the wrapping parent interact with the component? Make that into an explicit API and this is it. Or calling it from the child, or calling it from the parent.

bensmeets commented 9 years ago

True, exactly what I was trying to do. But had a hard part with waiting for the child component to be rendered (because the explicit API I'm calling from the parent to the child, needs that child's DOM).

bensmeets commented 9 years ago

Quick note, currently when you use Blaze.remove and directy after that call something that (e.g.) loops through this.componentChildren(), I'm getting weird errors about DOM not being available. This might be related to the package dispatch:kernel I use though.

mitar commented 9 years ago

Make a reproduction please.

bensmeets commented 9 years ago

TL;DR I think this works.

It must be something else, hidden within one of the packages or animations I use. Will dig deeper to find out the exact cause of the DOM exception, but in this http://meteorpad.com/pad/fiBufg8EQiyPJX8r9/Remove%20then%20loop example it works.

mitar commented 9 years ago

Instead of Blaze.remove analog, instance.removeComponent() has been implemented in 21d9160e58b3380cb114a8ef47a489d501956ed8.