I'm using meteor and I have a question about the publish function (server side)
Meteor.publish('users', function () { .... }
I'm sending now documents to the browser which have id's of other collections. For example the Task document belongs to a project
{
title: '....',
projectId: 'KjbJHvJCHTCJGVY234',
...
}
What I want is to add a property to the this document projectTitle so I don't have to look up the project on the client. However, when I add this property in the publish function it is not send to the client. This is what I've tried:
Meteor.publish('tasks', function () {
var tasks = Tasks.find();
tasks.forEach(function (task) {
var project = Projects.findOne({_id: task.projectId});
task.projectTitle = project.title;
});
return tasks;
}
Any suggestions how to modify documents (not persistent) inside the publish function?
You could do this:
Meteor.publish("tasks", function() {
var transform = function(task) {
var project = Projects.findOne({_id: task.projectId});
task.projectTitle = project.title;
return task;
}
var self = this;
var tasks = Tasks.find().observe({
added: function (document) {
self.added('tasks', document._id, transform(document));
},
changed: function (newDocument, oldDocument) {
self.changed('tasks', document._id, transform(newDocument));
},
removed: function (oldDocument) {
self.removed('tasks', oldDocument._id);
}
});
self.ready();
self.onStop(function () {
tasks.stop();
});
});
There's a lot of custom logic there, but the 'transform' basically adds the attributes in.
Your code looks good but you're forgetting the .fetch() method on your task request. It should be var tasks = Tasks.find().fetch();
Related
I have two JS files, base_player.js and station.js.
I have some logic in base_player.js that needs access to a value that comes from station.js. While the var is 'global', it is wrapped in a namepace mm.Station. The value I need is a UUID of a selected station:
mm.Station = function($el) {
"use strict";
if (_.isUndefined($el)) {
return;
}
var self = mm.EventEmitter();
var $actions;
self.uuid = $el.attr("data-uuid");
.........
I feed any selected station with songs in batches at a certain point in a queue. This logic all happens in base_player.js.
I want to get the UUID from station.js to use in this GET in base_player.js:
mm.BasePlayer = function ($el) {
........
function feedSelectedStation(uuid) {
if(_.isUndefined(uuid)){
return false;
}
$.get('/feed-station/' + uuid)
.done(function (data) {
console.log('feedSelectedStation');
if(!_.has(data, "list")){ return; }
self.queue = self.queue.concat(data.list);
});
}
.........
I don't write much Javascript so I'm a bit unsure how to achieve this. Any pointers appreciated.
I am using my custom yeoman generator programmatical in one of my nodejs module. I have written an Adapter to replace default TerminalAdapter.
The issues are,
When I am triggering custom event using emit method, I am not able
to listen for that event in my module. It is not getting fired.
Even end event listener also not getting fired.
Please let me know what I am missing here,
Below is my module code,
'use strict';
var fs = require('fs');
var path = require('path');
var MyOwnAdapter = require('./MyOwnAdapter');
var adapt = new MyOwnAdapter();
var env = require('./generator-myframewrk/node_modules/yeoman-generator')(null, {}, adapt);
env.register(path.resolve(__dirname, './generator-myframewrk'), 'myframewrk');
exports.run = function (options, answers) {
var obj = {};
adapt.setAnswers(answers);
process.chdir(options.projdir);
env.on("end", function(){
this.log("Even this is not getting called !!!"); //not coming here
}.bind(this));
env.on("alldone", function(){
this.log("Everything is done (including Bower install)"); //not coming here
obj.cb();
}.bind(this));
env.run('myframewrk', function(){
this.log('ran yo myframewrk, But bower install might be pending'); //coming here
});
return {
done: function (cb) {
obj.cb = cb;
}
};
};
Below is my Generator code,
var MyframewrkGenerator = yeoman.generators.Base.extend({
init: function () {
this.pkg = require('../package.json');
this.on('end', function () {
if (!this.options['skip-install']) {
this._installBower();
}
});
},
_installBower: function () {
this.log("Running bower install...");
/*reads bower.json and installs*/
bower.commands.install([], {}, {directory : "./"}).on('error', function (error) {
this.log("BOWER error::");
this.log(JSON.stringify(error));
}.bind(this)).on('log', function (log) {
this.log("BOWER LOG::"); // coming here
}.bind(this)).on('end', function (installed) {
this.log("BOWER END::"); // coming here
this.emit("alldone"); // my custom event
}.bind(this));
},
askFor: function () { ...
I took _installBower method out of this.on('end', function () {}); and made it a separate async func. This works fine. I don't need custom event anymore!
Thx...
bowerInstallHelper: function(){
if (!this.options['skip-install']) {
var cb = this.async();
this._installBower(cb);
}
}
I'm building an offline HTML page using Angular and using ydn-db for offline storage.
I have a database service like so,
demoApp.SericeFactory.database = function database() {
var database = {
dataStore: null,
admins: [],
students: [],
errors: [],
getadmindata: function(username) {
self = null, that = this
database.dataStore.get('admins', username).done(function(record) {
that.self = record;
return record;
}).fail(function(e) {
console.log(e);
database.errors.push(e);
});
return self; //This does not change.
}
};
database.dataStore = new ydn.db.Storage('DemoApp');
angular.forEach(INITSTUDENTS, function(student) {
database.dataStore.put('students', student, student.matricno);
database.students.push(student);
});
angular.forEach(INITADMINS, function(admin) {
database.dataStore.put('admins', admin, admin.username);
database.admins.push(admin);
});
return database;
I also have a controller that attempts to use the database;
function AppCntl ($scope, database) {
var user = database.getadmindata('user'); //I get nothing here.
}
What I have tried,
I have tried making changing self to var self
I have tried splitting the function like so
rq = database.dataStore.get('admins', 'user');
rq.done(function(record), {
self = record;
alert(self.name) //Works.
});
alert(self) //Doesn't work.
I have gone through questions like this o StackOverflow but nothings seems to be working for me or maybe I have just been looking in the wrong place.
Database request are asynchronous and hence it executes later after end of execution of the codes.
So when the last alert execute, self is still undefined. Secound alert execute after db request completion and it is usual right design pattern.
EDIT:
I have success with following code:
// Database service
angular.module('myApp.services', [])
.factory('database', function() {
return new ydn.db.Storage('feature-matrix', schema);
}
});
// controller using database service
angular.module('myApp.controllers', [])
.controller('HomeCtrl', ['$scope', 'utils', 'database', function($scope, utils, db) {
var index_name = 'platform, browser';
var key_range = null;
var limit = 200;
var offset = 0;
var reverse = false;
var unique = true;
db.keys('ydn-db-meta', index_name, key_range, limit, offset, reverse, unique)
.then(function(keys) {
var req = db.values('ydn-db', keys);
req.then(function(json) {
$scope.results = utils.processResult(json);
$scope.$apply();
}, function(e) {
throw e;
}, this);
});
}])
Complete app is available at https://github.com/yathit/feature-matrix
Running demo app is here: http://dev.yathit.com/demo/feature-matrix/index.html
After the latest ember-data 1.0 release, I have had some problems creating records. I read in the release notes - https://github.com/emberjs/data/blob/master/TRANSITION.md that:
App.NewPostRoute = Ember.Route.extend({
model: function() {
return App.Post.createRecord();
}
});
is now replaced with:
App.NewPostRoute = Ember.Route.extend({
model: function() {
return this.store.createRecord('post');
}
});
However in my controller I cannot figure it out how to call the createRecord() method, as I have something like this:
addNewTrip:function() {
var tripDeparature = this.get("tripDeparature");
var tripArrival = this.get("tripArrival");
var trips = App.Trips.createRecord({
tripDeparature: tripDeparature,
tripArrival:tripArrival,
isCompleted:false
});
trip.save();
this.set("tripDeparture","");
this.set("tripArrival","");
}
And it throws an error: ...has no method 'createRecord' (which is expected after the new release), but I cannot figure it out how to call the createRecord correctly. Any help is greatly appreciated.
Instead of App.Trips.createRecord(parameters ...) use this.store.createRecord('trips', parameters ...).
Your code will become:
addNewTrip:function() {
var tripDeparature = this.get("tripDeparature");
var tripArrival = this.get("tripArrival");
var trip = this.store.createRecord('trips', {
tripDeparature: tripDeparature,
tripArrival:tripArrival,
isCompleted:false
});
trip.save();
this.set("tripDeparture","");
this.set("tripArrival","");
}
I’m trying to keep a Backbone.js Collection up-to-date with what’s happening on the server.
My code is similar to the following:
var Comment = Backbone.Model.extend({});
var CommentCollection = Backbone.Collection.extend({
model: Comment
});
var CommentView = Backbone.View.extend({ /* ... */ });
var CommentListView = Backbone.View.extend({
initialize: function () {
_.bindAll(this, 'addOne', 'addAll');
this.collection.bind('add', this.addOne);
this.collection.bind('refresh', this.addAll);
},
addOne: function (item) {
var view = new CommentView({model: item});
$(this.el).append(view.render().el);
},
addAll: function () {
this.collection.each(this.addOne);
}
});
var comments = new CommentCollection;
setInterval(function () {
comments.fetch();
}, 5000);
What happens is that when the comments are fetched, refresh is called, the same comments to the bottom of the CommentListView—which is what I’d expect from the code above.
What I’d like to know is what’s the best way to “refresh” the view, without loosing any “local state”.
Or just use the far simpler addition to backbone's fetch method:
this.fetch({ update: true });
When the model data returns from the server, the collection will be (efficiently) reset, unless you pass {update: true}, in which case it will use update to (intelligently) merge the fetched models. - Backbone Documentation
:-)
What you want to do is refresh the collection every few seconds and append the new comments. My suggestion is to deal with that problem on your backend. Send over the last timestamp from your last comment and ask the server for the delta from this date only.
To do so, in your collection:
CommentCollection = Backbone.Collection.extend({
url: function(){
return "/comments?from_time=" + this.last().get("created_at");
},
comparator: function(comment){
return comment.get("created_at");
}
});
In your backend, query your database based on the from_time parameter.Your client code does not change to refresh the view.
If you do not want to change your backend code for any reason add this line in the addAll function:
addAll: function(){
$(this.el).empty();
this.collection.each(this.addOne);
}
Backbone.Collection.merge([options])
Building on #Jeb's response above, I've encapsulated this behavior into a Backbone extension that you can copy and paste into a .js file and include in your page (after including the Backbone library itself).
It provides a method called merge for Backbone.Collection objects. Rather than fully resetting the existing collection (as fetch does), it compares the server response to the existing collection and merges their differences.
It adds models that are in the response, but not in the existing collection.
It removes models that are in the existing collection, but not in the response.
Finally, it updates the attributes of models found in the existing collection AND in the response.
All expected events are triggered for adding, removing, and updating models.
The options hash takes success and error callbacks which will be passed (collection, response) as arguments, and it provides a third callback option called complete that is executed regardless of success or error (mostly helpful for polling scenarios).
It triggers events called "merge:success" and "merge:error".
Here is the extension:
// Backbone Collection Extensions
// ---------------
// Extend the Collection type with a "merge" method to update a collection
// of models without doing a full reset.
Backbone.Collection.prototype.merge = function(callbacks) {
// Make a new collection of the type of the parameter
// collection.
var me = this;
var newCollection = new me.constructor(me.models, me.options);
this.success = function() { };
this.error = function() { };
this.complete = function() { };
// Set up any callbacks that were provided
if(callbacks != undefined) {
if(callbacks.success != undefined) {
me.success = callbacks.success;
}
if(callbacks.error != undefined) {
me.error = callbacks.error;
}
if(callbacks.complete != undefined) {
me.complete = callbacks.complete;
}
}
// Assign it the model and url of collection.
newCollection.url = me.url;
newCollection.model = me.model;
// Call fetch on the new collection.
return newCollection.fetch({
success: function(model, response) {
// Calc the deltas between the new and original collections.
var modelIds = me.getIdsOfModels(me.models);
var newModelIds = me.getIdsOfModels(newCollection.models);
// If an activity is found in the new collection that isn't in
// the existing one, then add it to the existing collection.
_(newCollection.models).each(function(activity) {
if (_.indexOf(modelIds, activity.id) == -1) {
me.add(activity);
}
}, me);
// If an activity in the existing collection isn't found in the
// new one, remove it from the existing collection.
var modelsToBeRemoved = new Array();
_(me.models).each(function(activity) {
if (_.indexOf(newModelIds, activity.id) == -1) {
modelsToBeRemoved.push(activity);
}
}, me);
if(modelsToBeRemoved.length > 0) {
for(var i in modelsToBeRemoved) {
me.remove(modelsToBeRemoved[i]);
}
}
// If an activity in the existing collection is found in the
// new one, update the existing collection.
_(me.models).each(function(activity) {
if (_.indexOf(newModelIds, activity.id) != -1) {
activity.set(newCollection.get(activity.id));
}
}, me);
me.trigger("merge:success");
me.success(model, response);
me.complete();
},
error: function(model, response) {
me.trigger("merge:error");
me.error(model, response);
me.complete();
}
});
};
Backbone.Collection.prototype.getIdsOfModels = function(models) {
return _(models).map(function(model) { return model.id; });
};
Simple Usage Scenario:
var MyCollection = Backbone.Collection.extend({
...
});
var collection = new MyCollection();
collection.merge();
Error Handling Usage Scenario:
var MyCollection = Backbone.Collection.extend({
...
});
var collection = new MyCollection();
var jqXHR = collection.merge({
success: function(model, response) {
console.log("Merge succeeded...");
},
error: function(model, response) {
console.log("Merge failed...");
handleError(response);
},
complete: function() {
console.log("Merge attempt complete...");
}
});
function handleError(jqXHR) {
console.log(jqXHR.statusText);
// Direct the user to the login page if the session expires
if(jqXHR.statusText == 'Unauthorized') {
window.location.href = "/login";
}
};
Make a duplicate collection. Fetch() it. Compare the two to find the deltas. Apply them.
/*
* Update a collection using the changes from previous fetch,
* but without actually performing a fetch on the target
* collection.
*/
updateUsingDeltas: function(collection) {
// Make a new collection of the type of the parameter
// collection.
var newCollection = new collection.constructor();
// Assign it the model and url of collection.
newCollection.url = collection.url;
newCollection.model = collection.model;
// Call fetch on the new collection.
var that = this;
newCollection.fetch({
success: function() {
// Calc the deltas between the new and original collections.
var modelIds = that.getIdsOfModels(collection.models);
var newModelIds = that.getIdsOfModels(newCollection.models);
// If an activity is found in the new collection that isn't in
// the existing one, then add it to the existing collection.
_(newCollection.models).each(function(activity) {
if (modelIds.indexOf(activity.id) == -1) {
collection.add(activity);
}
}, that);
// If an activity in the existing colleciton isn't found in the
// new one, remove it from the existing collection.
_(collection.models).each(function(activity) {
if (newModelIds.indexOf(activity.id) == -1) {
collection.remove(activity);
}
}, that);
// TODO compare the models that are found in both collections,
// but have changed. Maybe just jsonify them and string or md5
// compare.
}
});
},
getIdsOfModels: function(models) {
return _(models).map(function(model) { return model.id; });
},