Meteor JS Template rendered function called before template is rendered? - meteor

I'm trying to use the Buttonset widget in JQuery UI. I've got the package loaded and my template renders the radio buttons fine. I have a "rendered" function to call the JQ UI routine to setup the buttonset:
Template.teamList.rendered = function () {
$("#buttonsetID").buttonset();
}
But it looks like the rendered function is being called before the template is rendered! I stick a console.log in the function and it prints out to the console before there's anything on the screen. So none of the radio buttons are set up, therefore the .buttonset() call does nothing. If I make that call in the console after the page is rendered, JQuery UI does the right thing and my button set appears.
Isn't the .rendered function supposed to be called after everything's set up? Am I doing something wrong?
Thanks!
edit:
As an example, the same thing is seen in the leaderboard example.
If you add:
Template.leaderboard.rendered = function() {
alert($('.player').length);
}
When the page is displayed, it will show 0. This makes it difficult to access the DOM items if you need to add some JQuery events or, in this case, a JQuery UI element.

rendered only works for elements which will appear in the DOM the very first time the template is added to the page. Assume that subscription data takes infinitely long to arrive, then look at the template and see which elements would appear in the default state.
Using the leaderboard example, we can't assume that players are available when the leaderboard template renders (it depends on a subscription). To target a player, you should use the rendered callback on the player template.
It's hard to generalize a strategy for when to apply jQuery plugins, but here are some ideas:
Use the rendered callback if the element will always be added in the default state (e.g. the element is hard-coded and doesn't depend on a conditional).
Use the rendered callback of the most specific child template to target that child (e.g. the player template from above).
Consider using an event handler callback to target the element if it's appearance depends on an event (e.g. a button click).
Consider using a template autorun callback to target the element if it's appearance depends on reactive state. See this question for an example.

Related

Viewing Ractive events in browser console

I've been reading the RactiveJS docs on event management at https://ractive.js.org/get-started/tutorials/events/, and had a question around rendering of events:
Instead, the on- directive will bind a shared callback directly to the element using addEventListener when it is rendered.
Is it possible to see what the rendered event would look like? I know it won't appear within console, but it would be nice to see if there is a way of viewing an example of how a rendered event would appear.
What that portion of the tutorials meant to say is that the on-* directive doesn't render inline JS nor render on the DOM at all. It's a directive that simply signals Ractive what to do. Even if the template looks like this:
<button on-click="#global.alert( 'Activating!' )">Activate!</button>
What ends up in the DOM is a button with an event handler.
<button>Activate!</button>
Under the hood, the on-* is processed from the parsed template and is converted to something roughly similar to the following:
buttonReference.addEventListener('click', function(){
theGlobalDependingOnTheEnv.alert( 'Activating!' );
});

Component initialization with Iron Router, Jquery, Materialize

Struggling here with what would otherwise be a simple $( document ).ready().
Not sure what I'm doing wrong.
Materialize needs jquery components to be initialized on DOM ready. Finding a way to initialize components on all views is surprisingly tricky.
Here is the online DEMO
From reading the docs: this should initialize everything the sub-templates require:
Template.layout.rendered = function(){
$('ul.tabs').tabs()
}
}
However, this only works on a hard page refresh, and not with links routing the views.
So instead you would have to initialize on each template that element will be used
Template.x.rendered ...
Template.y.rendered ...
Here is the github code
BTW We've tried iron-router events:
onRun
onBeforeAction
onAfterAction
All of these seem to happen before the route's template content is present. I noticed that onBeforeAction required a call to this.next() to go on, I even tried looking for the DOM content after the next call.
I also tried rewriting our routes like this:
Router.route('someRoute', function() {
this.render('someRoute');
// look for DOM content, still not found
});
Just to be clear, the reason this is happening is because your layout is only firing the rendered hook once. When you switch routes the layout template will not be rerendered, only the templates in the yield region will be. The previous template in that region gets destroyed and the next one rerendered. This means you have to run $('ul.tabs').tabs() again for that Template as the DOM elements it contains are rerendered.
Putting that code in the rendered function of the template that uses it works because that rendered hook gets run every time that particular template gets rendered again.
A way you could get around this could be to create a Template specifically for your tabs, like a control in a way, that calls $('ul.tabs').tabs() in its own rendered function. You could then put this control on a template that needed it and pass the required arguments, like number of tabs and content for each tab etc. It's a bit of work though, and I'd only consider it if I had a really large number of templates that used the tab control.

How can I simulate a click event on an anchor tag without actually calling click or refreshing?

I am using meteor with iron router and meteors standard blaze tempting.
I have an ul set up so that when one of my li items is clicked its containing anchor tag is clicked on using the function click. The only problem is that when that a is clicked with the function it counts as another click on my li and my menu isn't toggling right.
I am wondering if there is a way to call the pathFor without actually calling a click function.
Thanks for any help!
Update, here is the code its working along with a pretty standard drop down nav. I am not sure if I can catch that event the same as a jquery event since I think it is the standard browser click event working on the [0], but I think I should be able to make a function that matches href to route and call Router.go
Template.nav.rendered = () ->
# set initial page view and take care of refeshes
currentPageHtlm = getCurrentPage(window.location.pathname)
$('#currentPage').find('span').html(currentPageHtlm)
$("li").on "click", () ->
#simulate anchor click and set currentPage session
$(this).find("a")[0].click()
Session.set 'pageName', getCurrentPage($(this).find("a").attr("href"))
currentPageHtlm = Session.get 'pageName'
$('#currentPage').find('span').html(currentPageHtlm)
# toggle nav and arrow if mobile view
if $('.smOnly').css('display') != 'none'
$('#nav').slideToggle('slow')
$('#dropArrow').toggleClass('fa-caret-square-o-down fa-caret-square-o-up')
It sounds like you're looking for a way to programatically redirect from JS rather than via UI interaction, in which case you need to use Router.go('ROUTE_NAME'), as per the docs (you can use the route name as in pathFor as an alternative to supplying the actual path).

Unable to trigger modal-dialog show in Meteor template

I have a modal dialog in my template. This dialog needs to be triggered from the code programatically. So I need to show the modal through javascript, as I cannot have a data-toggle button to launch the modal-dialog.
The modal was working with bootstrap but with bootstrap-3 its not showing up, even though I can show it from the console directly. the problem here is how can I execute javascript post the template render, to launch the modal-dialog.
There is a Template.rendered/created function which is called, and inside this this.autorun(runFunc) is supposed to run the code to update the DOM element. This is called correctly, but I still cannot trigger the modal to show-up.
Template.createDialog.created = function() {
console.log("teamplate created");
this.autorun(function(){
$('#myModal').modal('show');
});
};
Update:
This works:
Template.createDialog.rendered = function() {
console.log("teamplate created");
this.autorun(function(){
$('#myModal').modal('show');
});
};
Using the rendered function, I am able to trigger the modal to show up. But the problem is that rendered and created both are only called once. And I need a way to trigger the modal dialog consistently if a condition is reached.
This bootstrap modal dialog with meteor is turning out to be painful and hacky. Is it not possible to show/hide modal using some class parameters?
Modals can be tricky to get right in Meteor for exactly the reasons you've discovered. I don't use Bootstrap, but the basic principle is that you need to trigger the modal programatically so that you can run the relevant framework code once you know the html has been rendered but still retain reactivity (this is certainly the case with Foundation and Semantic-UI modals) .
In your use case (which appears to be a single modal), this shouldn't be too much of a problem. Set a reactive variable modalVisible (a Session variable or similar), and use that to show or hide the modal as required.
this.autorun(function(c) {
if (Session.get('modalVisible')) {
$('#myModal').modal('show');
} else {
$('#myModal').modal('hide');
}
});
If you put all of that in the rendered callback then it will only try to show the modal once it's been added to the DOM (without which you'll get an error and the computation will stop running, breaking reactivity). Note that you shouldn't make rendering of the template dependent on a reactive variable - it should always be rendered but only visible based on the value of the modalVisible Session variable.
Apologies if this is too simple for your use case - if so I would recommend investigating the several packages on Atmosphere for Bootstrap modals as others will almost certainly have faced the same problem.

How to do page reveal animations with IronRouter?

I'm using meteor & IronRouter, and trying to do some javascript-driven animations when the template is ready/rendered.
However, the IR hook events don't seem to fire when they should.
onBeforeAction and onAfterAction only seem to fire before the new page is rendered.
So there is no updated DOM to animate.
I also tried overriding the action, calling render myself, and then animating.
however, the DOM is still not updated... even using Meteor.defer
action: ->
this.render() # works ok
Meteor.defer ->
Template.SceneView.animateScene()
This was still getting called while the old template was present. I guess since Meteor is still updating async-ly, and so defer() didn't really defer...
So a hack around this is to call the animate function from the template itself, and then also use Meteor.defer to call the animation, so the DOM is updated.
The Blaze rendered() hook only fires once on template creation.
Separately there are some new ui_hooks in Blaze, but these seem to be before insert or deletes happen. I guess I could use this and take over the whole DOM manipulation but that seems like overkill for just playing some animations when a page is ready...
https://github.com/percolatestudio/transition-helper/blob/master/transition-helper.js
UPDATE. I found out about the afterFlush event
So it seems that having reactive templates, somehow makes defer timing not work.
Instead if I use the following, the DOM is all updated in time for the animation trigger.
action: ->
this.render()
Deps.afterFlush ->
Template.SceneView.animateScene()
You could also use plain css, if you have a package like animate.css in your app (http://daneden.github.io/animate.css/)
If you have this class animated bounceIn on the div or element thats at the top of your template it will also display an animation when the template comes into view.

Resources