I'm not sure that I understand how to keep my web application reactive with Meteor.
I have this very simple template
<body>
{{> simple}}
</body>
<template name="simple">
Counter: {{counter}} <br/>
<button>Increase</button>
</template>
And client side script
var counter = 0;
Template.simple.counter = function () {
return counter;
}
Template.simple.events({
'click button': function () {
counter++;
console.log("counter is ", counter);
Meteor.flush();
}
});
When clicking on button I can see in console that counter variable is increasing properly but nothing happens on UI. Why? I thought it's exactly what Meteor.flush() is intended to do.
The UI isn't reactive all by itself, you need to use one of Meteor's reactive items. In this case, you probably want to use Session. Try the following, instead of the second script you pasted:
Session.set('counter', 0); // Initialize a *reactive* variable
Template.simple.counter = function () {
return Session.get('counter'); // This will automatically update, because Session is reactive
}
Template.simple.events({
'click button': function () {
Session.set('counter', Session.get('counter') + 1);
}
});
You could also build your own reactive data source:
if (Meteor.isClient) {
Sort = {
sort: 1,
dep: new Deps.Dependency,
get: function () {
this.dep.depend();
return this.sort;
},
toggle: function () {
this.sort *= -1;
this.dep.changed();
}
};
Template.leaderboard.players = function () {
return Players.find({}, {sort: {score: Sort.get(), name: 1}});
};
Template.leaderboard.events({
'click input.sort': function () {
Sort.toggle();
}
});
}
Recent versions of Meteor provides the reactive-var package, see here : http://docs.meteor.com/#/full/reactivevar_pkg
To use ReactiveVar, add the reactive-var package to your project by running in your terminal:
meteor add reactive-var
Related
I'm building a custom wizard in knockout that dynamically loads knockout components during each "step" of the wizard. I've managed to get all of this working without much hassle, and it seems to work pretty well.
However, I'm wanting to implement some callbacks within the wizard when certain events happen. For example, before and after navigation.
Currently, one of my navigation functions looks like this:
this.goNext = function () {
if (!this.canGoNext()) return;
this.currentStep(this.pages()[this.currentIndex() + 1]);
this.loadPage();
}
I would like to build 2 callback functions called beforePageChange and onPageChange.
My general assumption is that beforePageChange would pass in a couple parameters, notably the current page and the next page. However, I also want it to be able to be observed from any other class utilization the wizard.
For example, on my parent page I would have something like:
this.wizard = Site.loadWizard(arguments);
this.wizard.beforePageChange(function(options) {
if (!options.currentPage.complete) return false;
// do stuff
return true;
});
In turn the wizard would execute its navigation commands and trigger the appropriate callbacks.
I feel like there's something I'm just fundamentally missing here.
Edit
My current version works as follows:
In the wizard:
this.canChangePage = ko.obserable(true);
this.beforePageChange = function (options) {
};
this.beforePageChangeHandler = function (options) {
this.beforePageChange(options);
// do stuff
return true;
};
this.onPageChange = function (options) {
};
this.onPageChangeHandler = function (options) {
this.onPageChange(options);
//do stuff
return true;
}
On the page implementing the wizard:
this.wizard = Site.loadComponent(params, function () {
this.wizard.beforePageChange = function (options) {
this.canChangePage(false);
};
}.bind(this));
I'm not sure if there's a better way to implement this, or if this is the best solution.
The solution Tomalak described in their comment (I think):
Since you already have access to the wizard instance, you can subscribe to its currentStep observable. To get notifications before it changes, you pass a third parameter: "beforeChange". (The second is the this context).
var Wizard = function() {
this.currentStep = ko.observable(0);
}
Wizard.prototype.next = function() {
this.currentStep(this.currentStep() + 1);
}
Wizard.prototype.prev = function() {
this.currentStep(this.currentStep() - 1);
}
var Page = function() {
this.wizard = new Wizard();
this.wizard.currentStep.subscribe(function(oldStep) {
console.log("Page work before step change from step", oldStep);
}, this, "beforeChange");
this.wizard.currentStep.subscribe(function(newStep) {
console.log("Page work after step change to", newStep);
});
}
ko.applyBindings(new Page());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="with:wizard">
<button data-bind="click: prev">back</button>
<span data-bind="text: currentStep"></span>
<button data-bind="click: next">next</button>
</div>
I am making two different app's with Meteor. In first app, witch you can see here, I am using ... template.текст.set( true ); ... and everything is working fine. Now in second app I got error
ReferenceError: template is not defined
So, what is the problem? I Checked, packages are same.
Here is the code of second app:
Template.body.onCreated(function bodyOnCreated() {
this.TrenutniKorisnik = new ReactiveVar(true);
});
Template.PrijavaKorisnika.events({
'submit .Prijava': function(event) {
event.preventDefault();
var korisnik = event.target.КорисничкоИме.value;
var šifra = event.target.Лозинка.value;
if (Korisnici.findOne({КорисничкоИме: korisnik, Шифра: šifra})) { template.TrenutniKorisnik.set( false )};
event.target.КорисничкоИме.value = "";
event.target.Лозинка.value = "";
}
});
Template.body.helpers({
TrenutniKorisnik: function() {
return Template.instance().TrenutniKorisnik.get();
},
});
The template instance is the second parameter in an event handler. Simply change this:
'submit .Prijava': function(event) {
to this:
'submit .Prijava': function(event, template) {
so template will be defined in the function body.
Once you solve that, however you'll find that TrenutniKorisnik isn't defined because it's on the body template and not the current template. One way to solve that is to use a file-scoped variable rather than a template one. Here's an example:
var TrenutniKorisnik = new ReactiveVar(true);
Template.PrijavaKorisnika.events({
'submit .Prijava': function (event) {
...
if (Korisnici.findOne({ КорисничкоИме: korisnik, Шифра: šifra })) {
TrenutniKorisnik.set(false);
}
...
},
});
Template.body.helpers({
TrenutniKorisnik: function () {
return TrenutniKorisnik.get();
},
});
I am trying to display different sets of data based on a variable, without changing the route in meteor.js.
it looks like this:
<template name="somename">
{{#if form_submitted }}
display some data
{{else}}
display other data
{{/if}}
</template>
I am trying to do this with a helper:
Template.somename.helpers({
form_submitted = false;
});
However, when I run this, I get an error.
Also, I would like the variable to change to true when the button is clicked (and then refresh to false after a certain amount of time)
Any ideas?
You'll want to use a combination of template helpers and reactive variables for this:
Template.somename.onCreated( function() {
Template.instance().isFormClicked = new ReactiveVar( false );
});
Template.somename.helpers({
form_submitted: function () {
return Template.instance().isFormClicked.get();
}
});
Template.somename.events({
'click button': function( event, template ) {
template.isFormClicked.set( true );
Meteor.setTimeout( function() {
template.isFormClicked.set( false );
}, 10000); // Reset after 10 seconds.
};
});
Helpers a functions that return a value.
In your specific case it would be:
Template.somename.helpers({
form_submitted: function () {
return true;
}
});
Perhaps you could check the Meteor documentation about helpers http://docs.meteor.com/#/full/template_helpers
I am building a chat application and on my "new chats" page I have a list of contacts, which you can select one by one by tapping them (upon which I apply a CSS selected class and push the user id into an array called 'newChatters'.
I want to make this array available to a helper method so I can display a reactive list of names, with all users who have been added to the chat.
The template that I want to display the reactive list in:
<template name="newChatDetails">
<div class="contactHeader">
<h2 class="newChatHeader">{{newChatters}}</h2>
</div>
</template>
The click contactItem event triggered whenever a contact is selected:
Template.contactsLayout.events({
'click #contactItem': function (e) {
e.preventDefault();
$(e.target).toggleClass('selected');
newChatters.push(this.username);
...
The newChatters array is getting updated correctly so up to this point all is working fine. Now I need to make {{newChatters}} update reactively. Here's what I've tried but it's not right and isn't working:
Template.newChatDetails.helpers({
newChatters: function() {
return newChatters;
}
});
How and where do I use Deps.autorun() to make this work? Do I even need it, as I thought that helper methods auto update on invalidation anyway?
1) Define Tracker.Dependency in the same place where you define your object:
var newChatters = [];
var newChattersDep = new Tracker.Dependency();
2) Use depend() before you read from the object:
Template.newChatDetails.newChatters = function() {
newChattersDep.depend();
return newChatters;
};
3) Use changed() after you write:
Template.contactsLayout.events({
'click #contactItem': function(e, t) {
...
newChatters.push(...);
newChattersDep.changed();
},
});
You should use the Session object for this.
Template.contactsLayout.events({
'click #contactItem': function (e) {
//...
newChatters.push(this.username);
Session.set('newChatters', newChatters);
}
});
and then
Template.newChatDetails.helpers({
newChatters: function() {
return Session.get('newChatters');
}
});
You could use a local Meteor.Collection cursor as a reactive data source:
var NewChatters = new Meteor.Collection("null");
Template:
<template name="newChatDetails">
<ul>
{{#each newChatters}}
<li>{{username}}</li>
{{/each}}
</ul>
</template>
Event:
Template.contactsLayout.events({
'click #contactItem': function (e) {
NewChatters.insert({username: this.username});
}
});
Helper:
Template.newChatDetails.helpers({
newChatters: function() { return NewChatters.find(); }
});
To mimick the behaviour of Session without polluting the Session, use a ReactiveVar:
Template.contactsLayout.created = function() {
this.data.newChatters = new ReactiveVar([]);
}
Template.contactsLayout.events({
'click #contactItem': function (event, template) {
...
template.data.newChatters.set(
template.data.newChatters.get().push(this.username)
);
...
Then, in the inner template, use the parent reactive data source:
Template.newChatDetails.helpers({
newChatters: function() {
return Template.parentData(1).newChatters.get();
}
});
for people who is looking for a workaround for this in the year 2015+ (since the post is of 2014).
I'm implementing a posts wizard pw_module where I need to update data reactively depending on the route parameters:
Router.route('/new-post/:pw_module', function(){
var pwModule = this.params.pw_module;
this.render('post_new', {
data: function(){
switch (true) {
case (pwModule == 'basic-info'):
return {
title: 'Basic info'
};
break;
case (pwModule == 'itinerary'):
return {
title: 'Itinerary'
};
break;
default:
}
}
});
}, {
name: 'post.new'
});
Later in the template just do a:
<h1>{{title}}</h1>
Changing routes
The navigation that updates the URL looks like this:
<nav>
Basic info
Itinerary
</nav>
Hope it still helps someone.
I have a computed function that notifies a button if it should be disabled or not.
I also have a subscribe to this computed function that updates an another observable. When this observable is updated, it runs a custom binding.
The problem is that the subscribe() is running before the button is disabled. I want the computed function to run first, then the button is enabled/disabled, and finally it runs the subscription..
Is this possible? setTimeout() is not a good option.
this.enableButton = ko.computed(function() { return true or false });
this.enableButton.subscribe(function() { myself.triggerBinding(true) });
html:
<button data-bind="enable: enableButton" />
If I understand you correctly I hope this helps:
http://jsfiddle.net/UHgTS/1
var viewModel = {
buttonIsEnabled : ko.observable(true)
};
viewModel.enableButton = ko.computed({
read : function () {
log("read enableButton");
return this.buttonIsEnabled();
},
write : function (isEnabled) {
log("write enableButton");
this.buttonIsEnabled(isEnabled);
},
owner : viewModel
});
viewModel.otherObservable = ko.computed({
read : function(){
log("read otherObservable");
return this.buttonIsEnabled();
},
write : function () {
log("write otherObservable");
},
owner : viewModel
});
viewModel.otherObservable.subscribe(function(){
log("run subscription");
log("----------------");
});
function log(message){
$("#log").append(message + "\n");
}
ko.applyBindings(viewModel);