As the title states, how do I reactively update a view rendered by renderWithData when its relying data changes ?
Thanks ~
You can do this using Tracker.autorun:
Tracker.autorun(function () {
// get view and parentNode
// fetch the data from a reactive source:
var data = ...
Blaze.renderWithData(view, data, parentNode);
});
When thee data changes, your autorun will be re-evaluated with the new data.
Related
I currently havea widget which grabs hundreds of documents from the DB via subscription and then keep listening for new documents, so it can update a stock chart.
There is a problem tough, which is every time the data is updated the chart is updated, which causes a redraw.
This is a problem cause it's calling redraw hundreds of time at the beginning even tough it just need to "fetch all data then draw and wait for updates", the updates will then happen not very often, so then it would be ok to redraw.
my current code:
Template.nwidget.onRendered(function() {
return this.autorun(function() {
var data;
data = {};
data = Data.find({
type: 'my_type'
});
data = data.fetch();
return update(data);
});
});
For doing some after data subscription you can do like this:
Meteor.subscribe( 'collection', {
onStop: function( error /* optional */ ) {
// when the sub terminates for any reason,
// with an error argument if an error triggered the stop
},
onReady: function() {
// when ready
}
});
If you want to render page after the data subcribe then you can add waitOn in your router.
There is one more way to check where subscription is ready or not. If subscription is not ready you can show something else like a loading screen.
var handle = Meteor.subscribe( 'collection');
Tracker.autorun(function() {
if (handle.ready())
//write whatever you want to do here.
});
For the auto update in your view you can store the date in a reactive thing its may reactive var, Session or collection.
Then you can return there values from helper to view. And that will auto update your view.
I have built a list using Meteor. I do not want the entire list to be reactive, i.e. auto-update when the data changes. However, I do want the title of a list item to update if the data relating to that list item changes. I am inserting the list items using Blaze.renderWithData, so how can I achieve this?
Use two helpers, a non-reactive one which returns the cursor and a reactive one which returns the title. To make something non-reactive, use Tracker.nonreactive, (related question).
In the following contrived example I'm returning a reactive title that includes the cursor count if non-zero while the cursor returned from the helper is non-reactive.
subHandle = Meteor.subscribe('mySubscription');
Template.foo.helpers({
cursor: function(){
if ( subHandle.ready() ) return Tracker.nonreactive(function() {
return myCollection.find(query,options);
});
}),
title: function(){
var nDocs = myCollection.find(query,options).count();
if ( nDocs ) return "Title (" + nDocs + ")";
else return "Title";
}
});
Update:
Modified to deal with the subscription handle being ready so that the nonreactive function is called for the first time with a ready subscription.
Here's a Meteorpad with a working example. You can see that the total points updates as you add points to players but the player scores and sort never change.
I'm in the process of learning meteor. I followed the tutorial to create microscope. If some one submits a post meteor will re render the template for all users. This could be very annoying if there are hundreds of posts then the user will come back to the top of the page and loose track of where he was. I want to implement something similar to what facebook has. When a new post is submitted template isn't rendered rather, a button or link will appear. Clicking it will cause the template to re-render and show the new posts.
I was thinking of using observeChanges on the collection to detect any changes and it does stop the page from showing new posts but only way to show them is to reload the page.
Meteor.publish('posts', function(options) {
var self = this, postHandle = null;
var initializing = true;
postHandle = Posts.find({}, options).observeChanges({
added: function(id, post) {
if (initializing){
self.added('posts', id, post);
}
},
changed: function(id, fields) {
self.changed('posts', id, fields);
}
});
self.ready();
initializing = false;
self.onStop(function() { postHandle.stop(); });
});
Is this the right path to take? If yes, how do I alert the user of new posts? Else, what would be a better way to implement this?
Thank you
This is a tricky question but also valuable as it pertains to a design pattern that is applicable in many instances. One of the key aspects is wanting to know that there is new data but not wanting to show it (yet) to the user. We can also assume that when the user does want to see the data, they probably don't want to wait for it to be loaded into the client (just like Facebook). This means that the client still needs to cache the data as it arrives, just not display it immediately.
Therefore, you probably don't want to restrict the data displayed in the publication - because this won't send the data to the client. Rather, you want to send all the (relevant) data to the client and cache it there until it is ready.
The easiest way involves having a timestamp in your data to work from. You can then couple this with a Reactive Variable to only add new documents to your displayed set when that Reactive Variable changes. Something like this (code will probably be in different files):
// Within the template where you want to show your data
Template.myTemplate.onCreated(function() {
var self = this;
var options = null; // Define non-time options
// Subscribe to the data so everything is loaded into the client
// Include relevant options to limit data but exclude timestamps
self.subscribe("posts", options);
// Create and initialise a reactive variable with the current date
self.loadedTime = new ReactiveVar(new Date());
// Create a reactive variable to see when new data is available
// Create an autorun for whenever the subscription changes ready() state
// Ignore the first run as ready() should be false
// Subsequent false values indicate new data is arriving
self.newData = new ReactiveVar(false);
self.autorun(function(computation) {
if(!computation.firstRun) {
if(!self.subscriptionsReady()) {
self.newData.set(true);
}
}
});
});
// Fetch the relevant data from that subscribed (cached) within the client
// Assume this will be within the template helper
// Use the value (get()) of the Reactive Variable
Template.myTemplate.helpers({
displayedPosts = function() {
return Posts.find({timestamp: {$lt: Template.instance().loadedTime.get()}});
},
// Second helper to determine whether or not new data is available
// Can be used in the template to notify the user
newData = function() {
return Template.instance().newData.get();
});
// Update the Reactive Variable to the current time
// Assume this takes place within the template helper
// Assume you have button (or similar) with a "reload" class
Template.myTemplate.events({
'click .reLoad' = function(event, template) {
template.loadedTime.set(new Date());
}
});
I think this is the simplest pattern to cover all of the points you raise. It gets more complicated if you don't have a timestamp, you have multiple subscriptions (then need to use the subscription handles) etc. Hope this helps!
As Duncan said in his answer, ReactiveVar is the way to go. I've actually implemented a simple facebook feed page with meteor where I display the public posts from a certain page. I use infinite scroll to keep adding posts to the bottom of the page and store them in a ReactiveVar. Check the sources on github here and the live demo here. Hope it helps!
When creating a Meteor event handler, what's the difference between...
'click .something': function(e,t){
var data = t.data
}
vs
'click .something': function(e,t){
var data = Template.instance().data
}
They both seem to bring up the same data. Is there a reason why I should one or the other?
Similar question here:
Difference between Template.instance() and this
The thing to realize is that:
In the template life-cycle functions (onCreated, onRendered...) this is equal to Template.instance() so this.data is the same as Template.instance().data AT THAT TIME!
In a helper or event, this is the current data context.
So, note an important thing here: the Data context can change over time if your data changes upstream:
If you pass data to a template, the template will be re-rendered with the new data. New data = new data context.
So if you do something like:
Template.example.onCreated(function() {
this.data.myKey = "my example data set on template creation"; //WRONG!
// or equivalently:
Template.instance().data.myOtherKey = "another key"; //WRONG!
})
well, this data may be under this (i.e. the data context) in your helper (this.myKey) but only as long as the upstream data does not change.
As soon as the upstream data changes, this will be the new data context, and will NOT contain your added data.
So, in summary:
If you need to add context to your template in onCreated or onRendered, make sure you do NOT bind it to the current data context, but to the Template.instance()
you should do:
Template.example.onCreated(function() {
this.myKey = "my example data set on template creation";
// or equivalently:
Template.instance().myOtherKey = "another key";
})
and you can access this data in helper and events via
Template.instance().myKey
It's actually Template.instance() (with a lower i), and as this function returns the current template instance in scope (the one where the event originated), there's no difference with the second parameter of an event handler, which also holds the current template instance, this is why you can access the template data indifferently using Template.instance().data or t.data in an event handler.
There is however a simpler way to access the current data context inside an event handler : the this object is bound to the data context where the event was triggered.
I have just started to use ember.js. I have two models in my application. One that holds data and one that holds this data edited by user. I bind them using one-way binding.
App.ViewModel = Ember.Object.create({
title:'title',
text:'text',
)};
App.EditModel = Ember.Object.create({
titleBinding: Ember.Binding.oneWay('App.ViewModel.title'),
textBinding: Ember.Binding.oneWay('App.ViewModel.text'),
)};
I let a user edit the data in EditModel model. But if the user discard the changes I want to be able to set the values back to the state before editing, ie. to the values in ViewModel.
Is there a way to rebind those properties? Or to manualy rise change event on properties in ViewModel so EditModel gets updated? Or any other approach to my problem?
You could create a custom Mixin which handles the reset for a model, see http://jsfiddle.net/pangratz666/CjB4S/
App.Editable = Ember.Mixin.create({
startEditing: function() {
var propertyNames = this.get('propertyNames');
var props = this.getProperties.apply(this, propertyNames);
this.set('origProps', props);
},
reset: function() {
var props = this.get('origProps');
Ember.setProperties(this, props);
}
});
App.myModel = Ember.Object.create(App.Editable, {
propertyNames: ['title', 'text'],
title: 'le title',
text: 'le text'
});
And later in the views you just invoke the startEditing when you want to take a snapshot of the current values and reset when you want to reset to the previous snapshot of the values.