Re-initialize template with new id on selectbox change - meteor

I have a selectbox in template A which selects an item "name and id".
I also have an "item" template that needs an ID as parameter to load its data from its database. I am using a session variable for the id and I pass the id to the "item" template using return Session.get . This only works for on load. When the session variable change the "item" template is not updated. How do I get the "item" template to re-initialize
Some code:
Template.selectBox.helpers({
selectList: function () {
return Templates.find({}, {fields: {'_id': 1, 'name': 1}});
},
selectedId: function() {
return Session.get("selectedId");
}
});
Template.selectBox.events({
'change #item-chooser': function (event) {
var selectedId = $(event.currentTarget).find(":selected").val();
if (typeof(selectedId) === 'undefined'
|| selectedId === "new") {
Session.set("selectedId", "new");
}
else {
Session.set("selectedId", selectedId);
}
}
});
The items template is called using
{{> item selectedId}}
Template.item.onCreated(function() {
var selectedId = this.data.selectedId;
this.selectedItem = new ReactiveVar;
if (typeof(selectedId) === 'undefined'
|| selectedId === "new") {
this.selectedItem.set(emptyItem);
}
else {
var selectedItemData = Templates.findOne({_id: selectedId});
this.selectedItem.set(selectedItemData );
}
});

It's important to note that the Template.onCreated method is not reactive so if you have reactive variables, this method does not automatically re-run when a reactive data source changes unlike Template.helpers
The easiest way to fix your problem would be to use autorun
Template.item.onCreated(function(){
var self = this;
self.autorun(function(){
// some code that has a reactive data source (e.g. Session Var, Collection, or Reactive Var
// NOTE: you can access template instance data using self.data
});
});
However, based on your description, I think there could be a better way to handle your problem using Template.helpers
Template.item.helpers({
selectedItem: function(){
return Templates.findOne({_id: Session.get("selectedId")});
}
});

Related

Reactive cursor without updating UI for added record

I am trying to make a newsfeed similar to twitter, where new records are not added to the UI (a button appears with new records count), but updates, change reactively the UI.
I have a collection called NewsItems and I a use a basic reactive cursor (NewsItems.find({})) for my feed. UI is a Blaze template with a each loop.
Subscription is done on a route level (iron router).
Any idea how to implement this kind of behavior using meteor reactivity ?
Thanks,
The trick is to have one more attribute on the NewsItem Collection Say show which is a boolean. NewsItem should have default value of show as false
The Each Loop Should display only Feeds with show == true and button should show the count of all the items with show == false
On Button click update all the elements in the Collection with show == false to show = true
this will make sure that all your feeds are shown .
As and when a new feed comes the Button count will also increase reactively .
Hope this Helps
The idea is to update the local collection (yourCollectionArticles._collection): all articles are {show: false} by default except the first data list (in order not to have a white page).
You detect first collection load using :
Meteor.subscribe("articles", {
onReady: function () {
articlesReady = true;
}
});
Then you observe new added data using
newsItems = NewsItems.find({})
newsItems.observeChanges({
addedBefore: (id, article, before)=> {
if (articlesReady) {
article.show = false;
NewsItems._collection.update({_id: id}, article);
}
else {
article.show = true;
NewsItems._collection.update({_id: id}, article);
}
}
});
Here is a working example: https://gist.github.com/mounibec/9bc90953eb9f3e04a2b3.
Finally I managed it using a session variable for the current date /time:
Template.newsFeed.onCreated(function () {
var tpl = this;
tpl.loaded = new ReactiveVar(0);
tpl.limit = new ReactiveVar(30);
Session.set('newsfeedTime', new Date());
tpl.autorun(function () {
var limit = tpl.limit.get();
var time = Session.get('newsfeedTime');
var subscription = tpl.subscribe('lazyload-newsfeed', time, limit);
var subscriptionCount = tpl.subscribe('news-count', time);
if (subscription.ready()) {
tpl.loaded.set(limit);
}
});
tpl.news = function() {
return NewsItems.find({creationTime: {$lt: Session.get('newsfeedTime')}},
{sort: {relevancy: -1 }},
{limit: tpl.loaded.get()});
},
tpl.countRecent = function() {
return Counts.get('recentCount');
},
tpl.displayCount = function() {
return Counts.get('displayCount');
}
});
Template.newsFeed.events({
'click .load-new': function (evt, tpl) {
evt.preventDefault();
var time = new Date();
var limit = tpl.limit.get();
var countNewsToAdd = tpl.countRecent();
limit += countNewsToAdd;
tpl.limit.set(limit);
Session.set('newsfeedTime', new Date());
}
});

How to make a Meteor template helper reactive without changing collection data

I'm writing a small application that shows live hits on a website. I'm displaying the hits as a table and passing each one to a template helper to determine the row's class class. The idea is that over time hits will change colour to indicate their age.
Everything renders correctly but I need to refresh the page in order to see the helper's returned class change over time. How can I make the helper work reactively?
I suspect that because the collection object's data isn't changing that this is why and I think I need to use a Session object.
Router:
Router.route('/tracked-data', {
name: 'tracked.data'
});
Controller:
TrackedDataController = RouteController.extend({
data: function () {
return {
hits: Hits.find({}, {sort: {createdAt: -1}})
};
}
});
Template:
{{#each hits}}
<tr class="{{ getClass this }}">{{> hit}}</tr>
{{/each}}
Helper:
Template.trackedData.helpers({
getClass: function(hit) {
var oneMinuteAgo = Date.now() - 1*60*1000;
if (hit.createdAt.getTime() > oneMinuteAgo) {
return 'success';
} else {
return 'error';
}
}
});
I've managed to get this working though I'm not sure it's the 'right' way to do it. I created a function that is called every second to update Session key containing the current time. Then, in my helper, I can create a new Session key for each of the objects that I want to add a class to. This session key is based upon the value in Session.get('currentTime') and thus updates every second. Session is reactive and so the template updates once the time comparison condition changes value.
var updateTime = function () {
var time = Date.now();
Session.set('currentTime', time);
setTimeout(updateTime, 1 * 1000); // 1 second
};
updateTime();
Template.trackedData.helpers({
getClass: function(hit) {
var tenMinutesAgo = Session.get('currentTime') - 10*1000,
sessionName = "class_" + hit._id,
className;
className = (hit.createdAt.getTime() > tenMinutesAgo) ? 'success' : 'error';
Session.set(sessionName, className);
return Session.get(sessionName);
}
});
Update
Thanks for the comments. The solution I ended up with was this:
client/utils.js
// Note that `Session` is only available on the client so this is a client only utility.
Utils = (function(exports) {
return {
updateTime: function () {
// Date.getTime() returns milliseconds
Session.set('currentTime', Date.now());
setTimeout(Utils.updateTime, 1 * 1000); // 1 second
},
secondsAgo: function(seconds) {
var milliseconds = seconds * 1000; // ms => s
return Session.get('currentTime') - milliseconds;
},
};
})(this);
Utils.updateTime();
client/templates/hits/hit_list.js
Template.trackedData.helpers({
getClass: function() {
if (this.createdAt.getTime() > Utils.secondsAgo(2)) {
return 'success';
} else if (this.createdAt.getTime() > Utils.secondsAgo(4)) {
return 'warning';
} else {
return 'error';
}
}
});

How to assign value to a variable on click event in meteor

In my meteor app I need to change the value of an array based on the item clicked.
This is how I fetch items from db.
Template.templatename.vname = function(){
return Db.find();
}
On clicking a button I need to change the items in the array vname.
Can I do something like
'click #item1' : function()
{
Template.templatename.vname = function(){
return Db.find({id : this._id});
}
}
You could use a Session variable
Template.templatename.vname = function(){
var searchId = Session.get("searchId");
if(searchId) {
return Db.find();
}
else {
return Db.find({_id: searchId});
}
}
Then
'click #item1' : function()
{
Session.set("searchId", this._id);
}
A couple of notes
The search helper returns an array, but you're fetching a single item (_id is unique)
You would need to clear the session variable to null to show all the results again.

How to translate templates in Meteor?

UPDATED
NOW I try to do this in my app (thx to Akshat)
//common
LANG = 'ru';
Dictionary = new Meteor.Collection("dictionary");
//if server
Meteor.startup(function () {
if (Dictionary.find().count() === 0) {
// code to fill the Dictionary
}
});
Meteor.publish('dictionary', function () {
return Dictionary.find();
});
//endif
//client
t = function(text) {
if (typeof Dictionary === 'undefined') return text;
var res = Dictionary.find({o:text}).fetch()[0];
return res && res.t;
}
Meteor.subscribe('dictionary', function(){
document.title = t('Let the game starts!');
});
Template.help.text = t('How to play');
//html
<body>
{{> help}}
</body>
<template name="help">
{{text}}
</template>
Still doesn't work as we wanted: when templates are rendered Dictionary was undefined. Butt('How to play') in console works perfectly )
Javascript variables aren't shared between the client and server reactively. You have to use a Meteor Collection to store your data e.g
if (Meteor.isServer) {
var Dictionary = new Meteor.Collection("dictionary");
if(Dictionary.find().count() == 0) {
//If the 'dictionary collection is empty (count ==0) then add stuff in
_.each(Assets.getText(LANG+".txt").split(/\r?\n/), function (line) {
// Skip comment lines
if (line.indexOf("//") !== 0) {
var split = line.split(/ = /);
DICTIONARY.insert({o: split[0], t:split[1]});
}
});
}
}
if (Meteor.isClient) {
var Dictionary = new Meteor.Collection("dictionary");
Template.help.text = function() {
return Dictionary.find({o:'Let the game starts!'});
}
}
In the above i'm assuming you have the autopublish package in (its in by default when you create a package so this shouldn't really bother you, but just in case you removed)
With your document title you would have to use a slightly different implementation because remember the data wouldn't be downloaded at the time Meteor.startup is run, since the html and javascript are downloaded first & the data is empty, then the data comes in slowly (and then reactively fills the data up)

How to implement editable text in Meteor and DRY?

I have come up with a methodology for making editable text in my Meteor app. However, it does not follow the DRY paradigm and I'd like to change that but I am not too good with Javascript yet...
Suppose I have a table cell with some text and I'd like to double click it to edit it. I created a template variable to handle this:
<td class="itemName">
{{#unless editItemName}}
{{name}}
{{else}}
<input class="editItemName" type="text" value="{{name}}" style="width:100px;">
{{/unless}}
</td>
I then create an event to execute this transition on a double-click:
Template.inventoryItemDetail.events = {
'dblclick td.itemName': function (evt) {
Session.set("editItemName",true);
},
'blur input.editItemName': function () {
Session.set("editItemName",null);
},};
I also reused the ok_cancel code from the ToDo's example app (but that's sort of irrelevant):
// Returns an event_map key for attaching "ok/cancel" events to
// a text input (given by selector)
var okcancel_events = function (selector) {
return 'keyup '+selector+', keydown '+selector+', focusout '+selector;
};
// Creates an event handler for interpreting "escape", "return", and "blur"
// on a text field and calling "ok" or "cancel" callbacks.
var make_okcancel_handler = function (options) {
var ok = options.ok || function () {};
var cancel = options.cancel || function () {};
return function (evt) {
if (evt.type === "keydown" && evt.which === 27) {
// escape = cancel
cancel.call(this, evt);
evt.currentTarget.blur();
} else if (evt.type === "keyup" && evt.which === 13) {
// blur/return/enter = ok/submit if non-empty
var value = String(evt.target.value || "");
if (value) {
ok.call(this, value, evt);
evt.currentTarget.blur();
}
else {
cancel.call(this, evt);
evt.currentTarget.blur();
}
}
};
};
Template.inventoryItemDetail.events[ okcancel_events('input.editItemName') ] = make_okcancel_handler({
ok: function (value) {
Items.update(this._id, {$set: {name: value}});
}
});
Finally, I have to tie this Session variable to the template variable:
Template.inventoryItemDetail.editItemName = function () {
return Session.get("editItemName");
};
So right now, I have repeated all of this again and again for each editable text field and it all works, but it seems like terribly programming practice. I have found various editable text utilities on Github but I don't entirely understand them and none of them are for Meteor!
I'd really like to expand my knowledge of Meteor and Javascript by creating a tool that allows me to have editable text without repeating myself this ridiculous amount for each editable text field.
Thanks,
Chet
https://github.com/nate-strauser/meteor-x-editable-bootstrap for the package.
http://vitalets.github.io/x-editable/docs.html for the docs.
I just implemented this in my project and I won't ever go back to contenteditable.

Resources