In the latest version of Meteor, the transform functionality was added.
Example usage:
var myCollection = new Meteor.Collection("mycollection",
{
transform: function(doc){
doc["newProperty"] = "test"; return doc;
})
}
Is there any way to cause these transformations to be re-calculated?
I'm using a time humanize function (MomentJS humanize) in the DOM, and this is literally the only transform being done to the collection, so re-applying it once every 10 seconds (for about 15 entries) shouldn't be much of a performance hit.
One way could be to put your collection result in a Dependency
Client JS:
var times = [];
var timesDeps = new Deps.Dependency;
var getTimes = function () {
Deps.depend(timesDeps);
return myCollection.find(); //Your Query
};
Template.home.times = function() {
return getTimes();
}
Meteor.setInterval(function() {
timesDeps.changed();
}, 10000) //Recalculate ever 10000 ms
So what's being done is your collection is being called with getTimes(), and when you call timesDeps.changed() its reactive context is invalidated and it refreshes the data, thereby calling transform again.
Related
I update reactive var on autorun. The same reactive var is used in helper. But helper values doesn't refresh by autorun function. My below code will explain you clearly.
Template.home.onCreated(function () {
var self = this;
self.ITEMS_INCREMENT = 2;
self.itemsLimit = new ReactiveVar(2);
});
Template.home.onRendered(function () {
var self = this;
this.autorun(function(){
if( true ){
self.itemsLimit.set(self.itemsLimit.get()+self.ITEMS_INCREMENT);
console.log(self.itemsLimit.get()); // set values are fine
}
});
});
Template.home.helpers({
testHelper: function(){
console.log(Template.instance().itemsLimit.get()); // console returns 2 and 4 only. no more update :(
return true;
}
});
Anything wrong in handling of data or usage? How to make helpers are workable?
Well well well, I don't see where are you going to change the value of reactive variable. It gets changed only ones, when first computation runs in your tracker. So only ones you see the update. The idea of using ReactiveVar is that when you change it, it gets changed inside helper or inside autorun.
Briefly, what you can do to test it is:
Template.home.onRendered(function () {
var self = this;
Meteor.setInterval(function(){
self.itemsLimit.set(self.itemsLimit.get()+self.ITEMS_INCREMENT);
}, 500);
});
Is there a way (maybe using rules) to duplicate data on add/push to firebase?
What I want to archive is when I do an add to a firebase array I want to duplicate the data to another array.
So this is my firebase structure:
my-firebase: {
items: [ ... ],
queue: [ ... ]
}
And this is how I have my services defined:
.factory('Service1',['$firebaseArray', function($firebaseArray) {
var items = new Firebase('my-firebase.firebaseio.com/items');
return $firebaseArray(items);
}])
.factory('Service2',['$firebaseArray', function($firebaseArray) {
var queue = new Firebase('my-firebase.firebaseio.com/queue');
return $firebaseArray(queue);
}])
And here is how I use them:
.controller('controller', function($scope, Service1, Service2) {
$scope.save = function() {
Service1.$add({name: "test1"});
Service2.$add({name: "test1"});
}
};
And want I to have a single call not a duplicate call/code but having the result in both arrays (items and queue).
Thanks so much!
Always remember that AngularFire is a relatively thin wrapper around Firebase's JavaScript SDK that helps in binding data into your AngularJS views. If you're not trying to bind and something is not immediately obvious, you'll often find more/better information in the documentation of Firebase's JavaScript SDK.
The API documentation for $firebaseArray.$add() is helpful for this. From there:
var list = $firebaseArray(ref);
list.$add({ foo: "bar" }).then(function(ref) {
var id = ref.key();
console.log("added record with id " + id);
list.$indexFor(id); // returns location in the array
});
So $add() returns a promise that is fulfilled when the item has been added to Firebase. With that knowledge you can add a same-named child to the other list:
var queue = new Firebase('my-firebase.firebaseio.com/queue');
$scope.save = function() {
Service1.$add({name: "test1"}).then(function(ref) {
queue.child(ref.key().set({name: "test1"});
});
}
This last snippet uses a regular Firebase reference. Since AngularFire builds on top of the Firebase JavaScript SDK, they work perfectly together. In fact: unless you're binding these $firebaseArrays to the $scope, you're better off not using AngularFire for them:
var items = new Firebase('my-firebase.firebaseio.com/items');
var queue = new Firebase('my-firebase.firebaseio.com/queue');
$scope.save = function() {
var ref = queue.push();
ref.set({name: "test1"})
queue.child(ref.key().set({name: "test1"});
}
To my eyes this is much easier to read, because we're skipping a layer that wasn't being used. Even if somewhere else in your code, you're binding a $firebaseArray() or $firebaseObject() to the same data, they'll update in real-time there too.
Frank's answer is authoritative. One additional thought here is that AngularFire is extremely extensible.
If you want data pushed to two paths, you could simply override the $add method and apply the update to the second path at the same time:
app.factory('DoubleTap', function($firebaseArray, $q) {
var theOtherPath = new Firebase(...);
return $firebaseArray.$extend({
$add: function(recordOrItem) {
var self = this;
return $firebaseArray.prototype.$add.apply(this, arguments).then(function(ref) {
var rec = self.$getRecord(ref.key());
var otherData = ...do something with record here...;
return $q(function(resolve, reject) {
theOtherPath.push(rec.$id).set(otherData);
});
});
}
});
});
I want to get data from a number of queries on the same collection, and unfortunately this is not yet supported on meteor. That's why I tried to do something like this:
Common
Dep = new Deps.Dependency;
Server
Meteor.methods({
fetch: function(){
var results = Data.find(dataQuery).fetch();
var otherResults = Data.find(queryThatCannotBeCombinedWithPrevious).fetch();
return results.concat(otherResults);
},
save: function(data){
Data.insert(data);
Dep.changed();
}
update: function(data){
Data.update({_id: data._id}, data);
Dep.changed();
}
});
Client
Session.setDefault('combinedData', []);
Template.demo.data = function(){
Dep.depend();
Meteor.call('fetch',function(error, data){
Session.set('combinedData', data);
});
return Session.get('combinedData');
};
This doesn't work though, propably because the Dep variable on the client is different from the Dep on the server. Is there a way to make the method call reactive when the contents of the Data collection change?
Notes
I am currently using Meteor 0.8.1.1, which doesn't allow subscriptions that return multiple cursors of the same collection yet.
This requires a small hack and you're close. First, you need a client–only dependence, the server just passes a data returned from the method and doesn't share variables (also there's nothing on the server that requires deps). Second, you only want to fetch the actual variable once, otherwise you'll end up with an infinite loop.
Example implementation:
var value = null;
var valueInitialized = false;
var valueDep = new Deps.Dependency();
Template.demo.data = function() {
valueDep.depend();
if(!valueInitialized) {
valueInitialized = true;
Meteor.call('fetchData', function(err, result) {
value = result;
valueDep.changed();
});
}
return value;
};
Is there a way to create an observable array or in memory collection in Meteor?
The way I'm faking it is by creating a session variable containing the array, Session.setDefault('people', []); and then updating that value when the array changes, Session.set('people', modifiedArray).
You can create a local collection by calling Meteor.Collection constructor without supplying collection name in the parameter, i.e.:
LocalList = new Meteor.Collection();
See this in the Meteor documentation.
Notice also that you can observe anything you want thanks to Dependencies.
Example:
List = function() {
this.data = [];
this.dep = new Deps.Dependency();
};
_.extends(List.prototype, {
insert: function(element) {
this.data.push(element);
this.dep.changed();
},
});
var list = new List();
Template.observer.helper = function() {
list.dep.depend();
return list.data;
};
helper will be updated and observer template will rerender each time you call list.insert function.
I have an probably not so unique issue of having a complicated meteor app.
I have several actions which are causing parts of the page to refresh which really aren't needed. But I'm having trouble locating which find() (or multiple find()'s ) is the one being triggered. I know the Collection in question, just not which find().
I could use observeChanges on every find I use, but that would be a lot of extra code.
Is there an easy way to see what is being triggered and by what?
Thanks!
Here is a render logging function you might find useful. It logs the number of times each template is rendered to the console. You know if a template is re-rendered after initial page load, it's because a reactive data source that it relies on has changed. Either this reactive data source could have been accessed in a helper method, or the template is a list item (i.e. inside an {{#each ...}} block helper) and a list item was added/moved/removed/changed. Also keep in mind that child templates call their parent's rendered callback when the child is rendered or re-rendered. So, this might confuse you into thinking the parent has actually been taken off the DOM and put back, but that's not true.
So, you can call this function at the end of your client code to see the render counts:
function logRenders () {
_.each(Template, function (template, name) {
var oldRender = template.rendered;
var counter = 0;
template.rendered = function () {
console.log(name, "render count: ", ++counter);
oldRender && oldRender.apply(this, arguments);
};
});
}
EDIT: Here is a way to wrap the find cursor to log all changes to a cursor to the console. I just wrote a similar function to this for a new package I'm working on called reactive-vision. Hopefully released soon.
var wrappedFind = Meteor.Collection.prototype.find;
Meteor.Collection.prototype.find = function () {
var cursor = wrappedFind.apply(this, arguments);
var collectionName = this._name;
cursor.observeChanges({
added: function (id, fields) {
console.log(collectionName, 'added', id, fields);
},
changed: function (id, fields) {
console.log(collectionName, 'changed', id, fields);
},
movedBefore: function (id, before) {
console.log(collectionName, 'movedBefore', id, before);
},
removed: function (id) {
console.log(collectionName, 'removed', id);
}
});
return cursor;
};
Thank you #cmather for the idea.
Her is Meteor 1.3 adapted and more advanced version of logRenders
// Log all rendered templates
// If filter is set, only templates in filter will be logged
// #params filter - name or names of template to filter
logRenders = function logRenders (filter) {
for (name in Object(Template)){
if (filter && !Array.isArray(filter)) filter = [filter];
var template = Template[name];
if (!template) continue;
if (filter && filter.indexOf(name) == -1){
// Clear previous logRenders
if ('oldRender' in template) template.rendered = template.oldRender;
delete template.oldRender;
continue;
}
var t = function(name, template){
if (!('oldRender' in template)) template.oldRender = template.rendered;
var counter = 0;
template.rendered = function () {
console.log(name, ++counter, this);
this.oldRender && this.oldRender.apply(this, arguments);
};
}(name, template);
};
};