Ractive teardown/re-render with partials doesn't re-render partials - ractivejs

Here's the fiddle (sorry about the alerts) http://jsfiddle.net/PCcqJ/92/
var ractive = new Ractive({
template: '#templateOne',
partials: {
aPartial: '<div>Oh look, the partial is rendered!</div>'
}
});
function cb() {
alert('but now we unrender');
ractive.once('complete', function() {
alert('we rendered again, and now you can\'t see the partial content');
});
ractive.render('container');
}
ractive.render('container');
ractive.once('complete', function() {
alert('so we render the first time, and you can see the partial');
ractive.unrender().then(cb);
});
The partial here doesn't get re-rendered. Why is this? The partials are still in the partials object, and they have been unrendered, so what would prevent them from getting rendered again?
This fiddle renders, unrenders, and then re-renders, giving you an alert each time one of those happens.

Related

rendering alternate components under the same flow:router URL

How can I render two different views in a one page app without changing URLs. I'm using meteor with the default blaze as well as the flow:router package. Right now I have it set up like this:
routes.js..
FlowRouter.route("/", {
name: "App.home",
action() {
BlazeLayout.render("App_body", {
main: "App_home",
mainContent: "calendar"
});
}
});
FlowRouter.route("/list", {
name: "App.list",
action() {
BlazeLayout.render("App_body", { main: "App_home", mainContent: "list" });
}
});
but this way I'm using the url /list and i dont want that. I would like to simply render an alternate component template in the same url. I'm very new to coding so forgive me if this is obvious. Essentially I just want two different view styles: a list and a calendar. So I would like a way to set it up so that a spacebars template can be rendered if a certain button is clicked, and a different one can be rendered instead if another button is clicked.
Thanks so much for any help, i've been at this for a couple of days :)
Create another template, which renders particular view conditionally. Something like this:
FlowRouter.route("/", {
name: "App.home",
action() {
BlazeLayout.render("App_body", {
main: "App_home",
mainContent: "listOrCal"
});
}
});
<template name="listOrCal">
{{#if showList}}
{{> list}}
{{else}}
{{> calendar}}
{{/if}}
<button id="switchView">Switch view</button>
</template>
Template.listOrCal.onCreated(function listOrCalOnCreated() {
this.showList = new ReactiveVar(true);
})
Template.listOrCal.helpers({
showList() {
return Template.instance().showList.get();
}
})
Template.listOrCal.events({
'click #switchView' {
let showList = Template.instance().showList.get();
Template.instance().showList.set(!showList);
}
})
You can handle this within a single template like so:
FlowRouter.route('/', {
name: 'App.home',
action() {
BlazeLayout.render('App_body', { main: 'App_home', mainContent: 'ListOrCalendar' });
}
And then the ListOrCalendar template would look like this:
{{#if displayList}}
{{> List}}
{{else}}
{{> Calendar}}
{{/if}}
<button>Switch</button>
You would set up a ReactiveVar in the ListOrCalendar template:
Template.ListOrCalendar.onCreated(function() {
const instance = this;
instance.displayList = new ReactiveVar(true);
});
See ReactiveVar explanation here (ignore Session)
Then you would have a helper which returns the value of your ReactiveVar:
Template.ListOrCalendar.helpers({
displayList() {
const instance = Template.instance();
return instance.displayList.get();
}
});
Finally, you would hook up an event to change the value of displayList to switch between templates:
Template.ListOrCalendar.events({
"click button"(event, instance) {
const displayListCurrent = instance.displayList.get();
const displayListNew = !displayListCurrent;
instance.displayList.set(displayListNew);
// or, more concisely, instance.displayList.set(!instance.displayList.get());
}
});
So, in summary:
When the template is created, your ReactiveVar is true
so your displayList returns true
so the #if displayList condition in the template is satisfied
and so the List template is displayed
When the button is clicked
The ReactiveVar is set to false
so the displayList helper returns false
so the #if displayList condition in the template is not satisfied and it goes to the else statement
and so, finally, the Calendar template is displayed
When the button is clicked again, the ReactiveVar is toggled back to true, and on we go as above
This might seem daunting or over-complicated, but there's nothing fancy going on here at all. You'll get used to it pretty quickly

Can Ractive events work in markup rendered with the triple-stash syntax?

If I have the following in my Ractive template:
<span on-click='handleClick'>click me</span>
Then I can listen for the click with this:
app.on({
handleClick:function() {
alert("clicked!") ;
}
})
But lets say I have that same markup stored in a string variable called clicklyspan:
app.set("clicklyspan", "<span on-click='handleClick'>click me</span>")
and I render it in the template using the triple-stash syntax:
{{{clicklyspan}}}
The handleClick listener no longer gets fired. Is there anything I can do to force some kind of update to the rendered template so that the listener works? Say, after I do that app.set() call?
Here's a fiddle demonstrating the problem.
Thanks,
Dave
I have never used Ractive, but I did some research and it seems you have to use partials, like this:
var app = new Ractive({
el: 'container',
template: '#template',
data: {
myFunction: function() {
var template = '<a on-click="handleClick">I can now be clicked as well!</a>';
if (!this.partials.myFunction) {
this.partials.myFunction = template;
}
else {
this.resetPartial('myFunction', template);
}
return 'myFunction';
}
}
});
You will also need to use this instead of the triple mustache:
{{> myFunction() }}
Here's the corresponding jsfiddle.
Of course, replace myFunction with whatever name you like.
Related question I found useful:
RactiveJS events on tripple mustache

Polymer two way binding

Banging my head on how to use polymer two way binding.
I have a home made Polymer element that defines a boolean property through
Polymer({
is: "test-element",
ready: function() {},
properties: {
propEnabled: {
type: Boolean,
notify: true,
value: false,
observer: "propEnabledChanged"
}
},
// Called when aoEnabled is changed
propEnabledChanged: function() { console.log("propEnabled value switched to " + this.propEnabled); },
});
Now I'm using this in an HTML page
<body>
<template id="t" is="dom-bind">
<test-element id="testElement"></test-element>
<paper-toggle-button checked="{{Model.propEnabled}}">prop. enabled</paper-toggle-button>
<button id="switchInternalState">switch state</button>
</template>
</body>
<script>
var t = document.querySelector('#t');
document.addEventListener('WebComponentsReady', function() {
console.log('WebComponentsReady');
// We have to bind the template with the model
var t = document.querySelector('#t');
t.Model = document.getElementById("testElement");
// chaging the property directly does not reflect in the GUI... :-(
var button = document.getElementById("switchInternalState");
button.addEventListener("click", function() {
t.Model.set("propEnabled", !t.Model.propEnabled);
});
});
</script>
But when clicking on the switch state button...
I get the log propEnabled value switched to true
But the toogle button on the page does not change...
If I add a simple
<label>{{Model.propEnabled}}</label>
The label does not change either...
To me it looks a bit like one way binding where it should be 2 way as
toggling the button fire the log and properly change the component propEnabled value. So it really looks like one way binding to me.
So... How can we actually benefit from two way binding with Polymer templates ????
You need to assign the propEnabled property from dom-bind to the test-element through html.
<test-element id="testElement" prop-enabled={{propEnabled}}></test-element>
<paper-toggle-button checked="{{propEnabled}}">prop. enabled</paper-toggle-button>
Also you don't need the variable t.Model in your script. You can remove it. The event listener should be like below
button.addEventListener("click", function() {
t.propEnabled = !t.propEnabled;
});
The following plunker has the working code: http://embed.plnkr.co/13QJ7QFETIBg4bEiCMS7/preview

Meteor Iron Router not working on certain links

I have a route like this:
Router.route('/box', function () {
this.render('boxCanvasTpl');
},{
name: 'box',
layoutTemplate: 'appWrapperLoggedInTpl',
waitOn: function() {
console.log("Box route ran ok.");
return [
Meteor.subscribe('item_ownership_pub', function() {
console.log("subscription 'item_ownership_pub' is ready.");
}),
Meteor.subscribe('my_items', function() {
console.log("subscription 'my_items' is ready.");
})
];
}
});
... and I am clicking a link in a Template like this:
My Link
I receive the 'Box route ran ok.' message, but some reason the page does not navigate to the given URL. I have added console.log code in the funciton that is run when the 'boxCanvasTpl' is rendered, but these aren't showing in the browser console. It seems that something inbetween is stopping the templkate from re-rendering, but can't put my finger on it - any ideas?
There are some properties of Iron Router that you need to be aware of.
Say that the user is currently already on /boxes and there is a box template that renders for that path. If you:
click on a link Click Me
or
click on a link Click Me
Iron Router will NOT re-render the template because it already exists on the page. It will also NOT re-render the template if the box template happens to be a partial template that is already rendered on the page that you're on and also exists on the page that you want to navigate to.
Since it doesn't re-render, any code you have inside Template.box.onRendered will also not run again.
This behavior is most common in your layout, header, and footer templates. For many users, these templates are used for all of a website's pages, regardless of path. Because the layout, header, and footer template is rendered on a person's first visit to the site, they won't be re-rendered ever again if the user decides to navigate to other parts of the site using the same templates, so the code inside Template.layout/header/footer.onRendered won't fire.
Also note - even if a reactive Spacebars helper changes the physical look of the layout / header / footer, it doesn't qualify as an actual render, so reactive updates to the template do not trigger the onRendered callback.
The lack of re-rendering is what gives Meteor that "snappy" feel.
EDIT
Try to code in a reactive, event-driven style. Try not to think too much in a render / re-render sense.
You go to /box
You click on a link for /box?box=2342
Get your params or query in Iron Router
https://github.com/iron-meteor/iron-router/blob/devel/Guide.md#route-parameters
In Iron Router use the data from the params or query to set the data context for the template.
Grab stuff from the data context as needed inside of the template's .onRendered, .events, and .helpers callbacks.
Set Session vars as necessary and use them in helpers to give reactive changes to the page without having to re-render a template. Also use events to trigger updates to the session vars to, again, trigger reactive changes to the page.
Try this:
afterwards, go to /test?BUNNIES=lalalala
check out the console logs
test.html
<template name="test">
{{myData}}
</template>
test.js
Template.test.helpers({
myData: function() {
console.log("data context accessed from test.helpers: ", this);
console.log("this.BUNNIES accessed from test.helpers: ", this.BUNNIES);
return this.BUNNIES;
}
});
Template.test.onRendered(function() {
console.log("data context accessed from test.onRendered: ", this.data);
});
Template.test.events({
'click': function(){
console.log("data accessed from test.events: ", this);
}
});
router.js
Router.route('/test', function() {
console.log("routed!");
this.render('test');
}, {
name: 'test',
data: function(){
//here I am setting the data context
// for /test?BUNNIES=1234
var query = this.params.query;
console.log("query: ", query);
return query;
},
waitOn: function() {
console.log("waitOn is running (should see this message once for each subscription)");
return [
Meteor.subscribe('item_ownership_pub'),
Meteor.subscribe('my_items')
];
}
});
way cleaner way of writing router
Router.route('/test', {
waitOn: function() {
console.log("waitOn is running (should see this message once for each subscription");
return [
Meteor.subscribe('item_ownership_pub'),
Meteor.subscribe('my_items')
];
},
data: function(){
var query = this.params.query;
console.log("query: ", query);
return query;
},
action: function(){
console.log("this will re-render if url params changed");
this.render();
}
})

Bootboxjs: how to render a Meteor template as dialog body

I have the following template:
<template name="modalTest">
{{session "modalTestNumber"}} <button id="modalTestIncrement">Increment</button>
</template>
That session helper simply is a go-between with the Session object. I have that modalTestNumber initialized to 0.
I want this template to be rendered, with all of it's reactivity, into a bootbox modal dialog. I have the following event handler declared for this template:
Template.modalTest.events({
'click #modalTestIncrement': function(e, t) {
console.log('click');
Session.set('modalTestNumber', Session.get('modalTestNumber') + 1);
}
});
Here are all of the things I have tried, and what they result in:
bootbox.dialog({
message: Template.modalTest()
});
This renders the template, which appears more or less like 0 Increment (in a button). However, when I change the Session variable from the console, it doesn't change, and the event handler isn't called when I click the button (the console.log doesn't even happen).
message: Meteor.render(Template.modalTest())
message: Meteor.render(function() { return Template.modalTest(); })
These both do exactly the same thing as the Template call by itself.
message: new Handlebars.SafeString(Template.modalTest())
This just renders the modal body as empty. The modal still pops up though.
message: Meteor.render(new Handlebars.SafeString(Template.modalTest()))
Exactly the same as the Template and pure Meteor.render calls; the template is there, but it has no reactivity or event response.
Is it maybe that I'm using this less packaging of bootstrap rather than a standard package?
How can I get this to render in appropriately reactive Meteor style?
Hacking into Bootbox?
I just tried hacked into the bootbox.js file itself to see if I could take over. I changed things so that at the bootbox.dialog({}) layer I would simply pass the name of the Template I wanted rendered:
// in bootbox.js::exports.dialog
console.log(options.message); // I'm passing the template name now, so this yields 'modalTest'
body.find(".bootbox-body").html(Meteor.render(Template[options.message]));
body.find(".bootbox-body").html(Meteor.render(function() { return Template[options.message](); }));
These two different versions (don't worry they're two different attempts, not at the same time) these both render the template non-reactively, just like they did before.
Will hacking into bootbox make any difference?
Thanks in advance!
I am giving an answer working with the current 0.9.3.1 version of Meteor.
If you want to render a template and keep reactivity, you have to :
Render template in a parent node
Have the parent already in the DOM
So this very short function is the answer to do that :
renderTmp = function (template, data) {
var node = document.createElement("div");
document.body.appendChild(node);
UI.renderWithData(template, data, node);
return node;
};
In your case, you would do :
bootbox.dialog({
message: renderTmp(Template.modalTest)
});
Answer for Meteor 1.0+:
Use Blaze.render or Blaze.renderWithData to render the template into the bootbox dialog after the bootbox dialog has been created.
function openMyDialog(fs){ // this can be tied to an event handler in another template
<! do some stuff here, like setting the data context !>
bootbox.dialog({
title: 'This will populate with content from the "myDialog" template',
message: "<div id='dialogNode'></div>",
buttons: {
do: {
label: "ok",
className: "btn btn-primary",
callback: function() {
<! take some actions !>
}
}
}
});
Blaze.render(Template.myDialog,$("#dialogNode")[0]);
};
This assumes you have a template defined:
<template name="myDialog">
Content for my dialog box
</template>
Template.myDialog is created for every template you're using.
$("#dialogNode")[0] selects the DOM node you setup in
message: "<div id='dialogNode'></div>"
Alternatively you can leave message blank and use $(".bootbox-body") to select the parent node.
As you can imagine, this also allows you to change the message section of a bootbox dialog dynamically.
Using the latest version of Meteor, here is a simple way to render a doc into a bootbox
let box = bootbox.dialog({title:'',message:''});
box.find('.bootbox-body').remove();
Blaze.renderWithData(template,MyCollection.findOne({_id}),box.find(".modal-body")[0]);
If you want the dialog to be reactive use
let box = bootbox.dialog({title:'',message:''});
box.find('.bootbox-body').remove();
Blaze.renderWithData(template,function() {return MyCollection.findOne({_id})},box.find(".modal-body")[0]);
In order to render Meteor templates programmatically while retaining their reactivity you'll want to use Meteor.render(). They address this issue in their docs under templates.
So for your handlers, etc. to work you'd use:
bootbox.dialog({
message: Meteor.render(function() { return Template.modalTest(); })
});
This was a major gotcha for me too!
I see that you were really close with the Meteor.render()'s. Let me know if it still doesn't work.
This works for Meteor 1.1.0.2
Assuming we have a template called changePassword that has two fields named oldPassword and newPassword, here's some code to pop up a dialog box using the template and then get the results.
bootbox.dialog({
title: 'Change Password',
message: '<span/>', // Message can't be empty, but we're going to replace the contents
buttons: {
success: {
label: 'Change',
className: 'btn-primary',
callback: function(event) {
var oldPassword = this.find('input[name=oldPassword]').val();
var newPassword = this.find('input[name=newPassword]').val();
console.log("Change password from " + oldPassword + " to " + newPassword);
return false; // Close the dialog
}
},
'Cancel': {
className: 'btn-default'
}
}
});
// .bootbox-body is the parent of the span, so we can replace the contents
// with our template
// Using UI.renderWithData means we can pass data in to the template too.
UI.insert(UI.renderWithData(Template.changePassword, {
name: "Harry"
}), $('.bootbox-body')[0]);

Resources