veliovgroup / flow-router

🚦 Carefully extended flow-router for Meteor
https://packosphere.com/ostrio/flow-router-extra
BSD 3-Clause "New" or "Revised" License
201 stars 30 forks source link

Question> The way to run a function after onRendered for all templates #86

Closed kakadais closed 3 years ago

kakadais commented 3 years ago

What I want

Run a function always after all templates onRendered with FlowRouter way. (not the way of jQuery or direct dom control)

Ex> In the case of dynamic templete with blaze-layout, we could have a lot of nested templates.

<template name="layout">
    {{> header}}
    {{> Template.dynamic template=name}} # or {{> yield}}
    {{> footer}}
</template>

And I need to run some code foo() once to control Dom after rendering all, always. I could accomplish this run the foo() in all of the dynamic templates, but seems like a bad idea.

Additional Example to explain

Suppose that I have five 5 templates and each template has the route by different url path, so if a user clicks the link and changes the path, the dynamic template just rendering on its position in the layout.

In this case, I have to add the foo()to the 5 templates this.render() in their callback to run the foo() every single time after rendering.

If I add the foo()to 'layout' template's this.render() in callback, it is working at the first rendering of 'layout' template, but the foo() is not running on the next path changing by user's click action. (dynamic template is changing but the layout's this.render() is not working cause it's already been rendered before)

So what I want to do is, run the foo() every single time when the dynamic path changing, without adding the foo() to all callback in the dynamic template's this.render().

Declare once and run everytime when the path chaning after rendering to avoid code mess and repeats

Codes Example

FlowRouter.route '/notice',
  name: 'notice'
  action: ->
    @render 'layout', 'notice', ->
      foo()

FlowRouter.route '/news',
  name: 'news'
  action: ->
    @render 'layout', 'news', ->
      foo()      

I just want to do the same thing without foo() repeats.

FlowRouter.route '/layout',
  name: 'layout'
  action: ->
    @render 'layout', ->
      foo()

This is not working on dynamic template changing.

watchPathChange way

https://github.com/VeliovGroup/flow-router/blob/master/docs/api/watchPathChange.md

This was the best approach in a document that I found. but I think there would be a better way to do this with the flow-router way.

Template.layout.onRendered ->
  console.log 'layout'

Template.something.onRendered ->
  console.log 'something'

FlowRouter.triggers.enter [-> console.log 'triggers']

Tracker.autorun ->
  console.log 'autorun'
  FlowRouter.watchPathChange()
  foo()

Result of on first load or full reload the page

autorun triggers autorun something layout

The foo() in 'autorun' fired faster than 'something', and 'triggers' is even faster.

Result of on changing path

triggers autorun publication

The foo() is faster still. But, in this case, the dom control that I want is worked. (Actually this part is more tricky for me.) Anyway, I think that this is not the best idea.

So What I want Again

is run the foo() after the last template rendered.

coagmano commented 3 years ago

if you're using Blaze, you could combine watchPathChange or triggersEnter with Tracker.afterFlush to run after Blaze has rendered all pending templates?

kakadais commented 3 years ago

In both cases, it runs before all the templates onRendered run in my test. So Dom control is missing sometimes.

I think this is a kind of old school question, and I still don't get the best solution. Probably I could miss something important.

Hopefully, @dr-dimitru has the answer~ ;)

dr-dimitru commented 3 years ago

Hello @kakadais,

How do you render templates?

In case if you're using this.render() method you can pass a callback as a last argument.

Providing more details on what you're actually trying to accomplish would help to find a solution.

As you said get a callback after all templates are rendered, -- top parent blaze template should fire onRendered callback only after all nested templates are rendered as well, isn't it 🤔?

kakadais commented 3 years ago

@dr-dimitru Sorry for the lack of information.

I've added details on the question, and I think I could say this is basically about lazy-loading for dynamic blaze things, am I right?

dr-dimitru commented 3 years ago

@kakadais

  1. Switch to using this.render() and {{> yield}} for templating
  2. Use this.render() callback to catch the moment when template fully rendered (including nested templates)

FlowRouter.watchPathChange() as you can guess from its name made to catch an event of URL change, and not related to rendering templates

kakadais commented 3 years ago

@dr-dimitru If I want to call the callback for every template, do I need to add the callback to all template's this.render() function? I just want to keep my code simple, so I am trying to find an event for all.

Could I use the layout template's this.render() or something like that? I understand that nested templates included on parent template's this.render(), but still I have multiple templates as a parent and url path. So I don't want to add the same callback for all parent templates.

Ex> to be a clear explanation with poor english ;) If I have template A and B, the url would be mysite/A and mysite/B. And each template uses it's own FlowRouter.route() setting and this.render(). I don't want to add the callback for A/B... multiple times. Am I missing something?

dr-dimitru commented 3 years ago

@kakadais sorry, I don't get it. Could you paste your FlowRouter.route() definitions?

You use this.render() only once per route inside action hook. The reat should be done in Template#onRendered callback. Callback passed into this.render() would be the one which runs after template (including all nested templates) rendered, — I assume this is what you were looking for in your original post (original question)

kakadais commented 3 years ago

Sorry for my poor explanation, or could be my misunderstanding. I added an additional explanation on my original question like below.

Additional Example to explain

Suppose that I have five 5 dynamic templates in the layout and each template has the own route and path by different url path, so if a user clicks the link and changes the path, the dynamic template just rendering on its position in the layout.

In this case, I have to add the foo()to all 5 templates this.render() in their callback to run the foo() every single time after rendering.

If I add the foo()to 'layout' template's this.render() in callback, it is working at the first rendering of 'layout' template, but the foo() is not running on the next path changing by user's click action. (dynamic template is changing but the layout's this.render() is not working cause it's already been rendered before)

So what I want to do is, run the foo() every single time when the dynamic path changing, without adding the foo() to all callback in the dynamic template's this.render().

Declare once and run everytime when the path chaning after rendering to avoid code mess and repeats.

dr-dimitru commented 3 years ago

@kakadais Seems like you're looking for { conf: { forceReRender: true } } option. Let me know if it helps

dr-dimitru commented 3 years ago

@kakadais if forceReRender isn't what you're looking for, please speak code and post your route definition.

kakadais commented 3 years ago

@dr-dimitru I believe this question could have a simple answer ;) I've added Code example. force-rerender wouldn't be my option, because I don't want to rerender every time.

Codes Example

FlowRouter.route '/notice',
  name: 'notice'
  action: ->
    @render 'layout', 'notice', ->
      foo()

FlowRouter.route '/news',
  name: 'news'
  action: ->
    @render 'layout', 'news', ->
      foo()      

I just want to do the same thing without foo() repeats, because there will be many templates and will be added.

FlowRouter.route '/layout',
  name: 'layout'
  action: ->
    @render 'layout', ->
      foo()

This is not working on dynamic template changing.

Thanks a lot always.

dr-dimitru commented 3 years ago

I just want to do the same thing without foo() repeats, because there will be many templates and will be added.

How you planning switch dynamic template in this FlowRouter.route '/layout', case? Still didn't get what are you trying to accomplish

dr-dimitru commented 3 years ago

@kakadais Id do something like this:

mycallback = -> 

FlowRouter.route '/notice',
  name: 'notice'
  action: ->
    @render 'layout', 'notice', mycallback

FlowRouter.route '/news',
  name: 'news'
  action: ->
    @render 'layout', 'news', mycallback  

Looks like correct usage to me. You can even create an array and iterate over it to register similar routes

dr-dimitru commented 3 years ago

Hey @kakadais,

Any progress on this one? Here's a recent example on the similar use-case

kakadais commented 3 years ago

I've run a function everytime when its needed such as your proposal. I still have some curious there would be a better way to apply this, but couldn't make it. The case was so specific for my case, so this would be better to be closed. Thanks-