Using mongodb, and backbone models I am trying to store an input value as an array inside a model?
So at barebones let's say I have a small little form.
<form id="send-message">
<input name="message" class="message"/>
<button type="submit" class="send">Send</button>
</form>
Then in my backbone code for the view that contains this form, I submit this data. I am using socket.io so I have some code thats like this. I don't think posting the full code for this view is necessary, but I will and hopefully this will prevent confusion.
var Marionette = require('backbone.marionette'),
MessagesView = require('./messages'),
UsersListView = require('./users_list'),
socket = io.connect();
module.exports = ChatView = Marionette.ItemView.extend({
template: require('../../templates/chat.hbs'),
events: {
'submit #send-message': 'sendMessage'
},
initialize: function() {
var self = this;
this.messagesView = new MessagesView({ collection: window.App.data.messages });
this.usersListView = new UsersListView({ collection: window.App.data.messages });
socket.on('new message', function(data) {
self.createMessage(data);
});
},
onRender: function() {
this.messagesView.render().$el.appendTo(this.$el.find('.message-content'));
this.usersListView.render().$el.appendTo(this.$el.find('.users-list'));
},
sendMessage: function(e) {
e.preventDefault();
var $message = this.$el.find('input.message');
socket.emit('send message', $message.val());
$message.val('');
},
createMessage: function(data) {
var model = window.App.data.messages.where({username: data.username});
_.each(model, function(model) {
var values = {
message: data.message
}
model.save(values);
});
window.App.core.vent.trigger('app:log', 'Add View: Created a new message!');
}
});
So summed up this just submits the input data to the node server then the server emits a response and triggers createMessage.
My question is I want to capture all these messages and store them as an array inside the model. So the data structure would look kind of like this.
// This represents the ideal structure of the model at barebones
var user = {
username: 'Grant',
message: {message1: "Hey guys", message2: "Michigan State will win the tourney"}
}
Lets take a closer look at the createMessage method.. You see I save the message like below, but I am unsure how to get the messages already saved on that model then append a new message, thus creating an array. I'll leave it like it is now, but I have tried using a .push() function and tried various ways all failing...
createMessage: function(data) {
var model = window.App.data.messages.where({username: data.username});
_.each(model, function(model) {
var values = {
message: data.message
}
model.save(values);
});
window.App.core.vent.trigger('app:log', 'Add View: Created a new message!');
}
The data goes to mongodb so I also have a controller that handles and (I THINK) overrides backbone.sync, but the controller is fired by a router when a PUT request is made app.put('/api/messages/:id', messages.update); Then the update method handles the data, so possibly I could create the array here.
update: function(req, res) {
models.Message.update({ _id: req.params.id }, {message: req.body.message //Somewhere here I could append?}, function(err, message) {
if (err) {
res.json({error: 'Update failed.'});
} else {
res.json(message);
}
});
}
Edit: So thinking about it, I do want to generate an array of objects.. My biggest problem is figuring out how to append a new object and generate a new key?
For example
var user = {
username: 'Grant',
message: {message1: "Hey guys"}
}
A user submits a new message, how do I create an object with a fresh key, I tried using backbone to get the length of the objects, but that got kind of hairy since this is mostly vanilla based now..
What you're trying to make is an object, not an array - that's why .push() doesn't work.
function(value) {
user.message['message' + (Object.keys(user.message).length + 1)] = value;
}
This will add a new value to the object, with the key 'message' + amount of old 'messages'.
Related
I have a newsletter which relies on meteor's email package.
As soon as an admin submits a new news & events entry to the collection, all subscribers receive this via email. This also works.
However, I want to have add the new concrete link of the news & events entry to the page.
The route for the news and events page:
// Specific news and events
Router.route('/news-and-events/:_id', {
name: 'newsAndEventsPage',
waitOn: function(){
return [
Meteor.subscribe('newsevents'),
Meteor.subscribe('images'),
Meteor.subscribe('categories'),
Meteor.subscribe('tags'),
]
},
data: function(){
return NewsEvents.findOne({_id: this.params._id});
},
});
The admin route (a form page) for adding a new entry:
// Admin news
Router.route('/admin-news-events', {
name: 'adminNewsEvents',
waitOn: function(){
return [
Meteor.subscribe('newsevents'),
Meteor.subscribe('images'),
]
},
data: function(){
return false
},
});
After submitting the post to the collection, I tried to catch the entry and pass the id, but I just get undefined.
My admin template.js (edited):
'submit form': function (evt, template) {
evt.preventDefault();
var temp = {};
temp.title = $('#title').val();
temp.description = $('#description').summernote('code');
temp.type = $('input[name=netype]:checked').val();
temp.createdAt = moment().format('ddd, DD MMM YYYY hh:mm:ss ZZ');
Meteor.call('NewsEvents.insert', temp);
Bert.alert("New entry added.");
//Fire the email to all Subscribers
var entry = NewsEvents.findOne(this._id);
var entryId = entry.id;
//NOT WORKING
var news = '<a href='+Meteor.absoluteUrl()+'news-and-events/'+entryId+'></a>';
for (i = 0; i < Subscribers.find().count(); i++) {
var email_ = Subscribers.find().fetch()[i].email;
Meteor.call('sendEmail',
email_, //To
'Open Strategy Network <xxx.yyy#zzz.yyy.xx>', //from
'Open Strategy Network News and Events', //subject
news);
}
}
Server methods:
Meteor.methods({
'NewsEvents.insert': function (doc) {
if (this.userId) {
NewsEvents.insert(doc);
}
}
});
...
//Send emails
'sendEmail': function (to, from, subject, text) {
// check([to, from, subject, text], [String]);
this.unblock();
Email.send({
to: to,
from: from,
subject: subject,
html: text
});
},
Thanks a lot.
.find() returns a cursor, not an object. You can either do:
var entry = NewsEvents.findOne(this._id);
var entryId = entry.id;
Or more simply since you already have the _id:
var entryId = this._id;
Or even more simply:
var news = '<a style:"text-decoration: none;"
href='Meteor.absoluteUrl()+'news-and-events/'+this._id+'></a>';
Also, you are trying to send the email while your insert is happening asynchronously.
Meteor.call('NewsEvents.insert', temp); // this might take some time to complete
var entry = NewsEvents.findOne(this._id); // `this` is not going to refer to the just added NewsEvent
Instead, do the notifications in a callback from the method:
Meteor.call('NewsEvents.insert', temp, function(err, result){
if ( !err ){
// assuming that `result` will be the _id of the inserted object!!
var news = '<a href='+Meteor.absoluteUrl()+'news-and-events/'+result+'></a>';
Subscribers.find().forEach(function(s){
Meteor.call('sendEmail',
s.email, //To
'Open Strategy Network <violetta.splitter#business.uzh.ch>', //from
'Open Strategy Network News and Events', //subject
news
);
}
}
});
Your NewsEvents.insert method needs to return the _id of the inserted object:
Meteor.methods({
'NewsEvents.insert'(doc) {
if (this.userId) return NewsEvents.insert(doc);
}
});
Now, even the above will be slow since you're doing Meteor.call() in a loop. Secondly, you've opened up your server as a mail relay since anyone can use the sendEmail method to send any email to anyone from the console inside your app. If you want to do this efficiently, put the notification code inside your NewsEvents.insert method and do it all on the server without all the back and forth!!
If I understood correctly, you want to have ID of the inserted document. Its fairly simple.
In the method that inserts:
var docId = Somethings.insert({ //fields here });
Now you can use that docId in the same method for sending emails.
If you also want to send the documentId to the client side, you can use error, result in Meteor.call() like this:
Meteor.call('methodName', arg, arg2, function(err, res){
if(!err){
//do something with res. in this case the res is inserted docId as I returned docId in the method
Router.go('/some-route/' + docId)
} else {
//do something with err
}
});
The error above comes from errors you throw in methods. For the result, you need to return a value which can be the inserted docId:
return docId
Tidied up method:
methodName: function (arg, arg2){
//equals to err in the `Meteor.call()`
if(arg !== 'something'){
throw new Meteor.Error('This is an error')
}
//insert new document
var docId = Somethings.insert({
fieldOne: arg,
fieldTwo: arg2
});
//send email to each subscriber. I don't know your exact DB fields so, its up to you. You did this in a different call.
var cursor = Subscribers.find();
cursor.forEach(function(ss){
//send email here. You can use each subscriber data like ss._id or ss.email. However you insert them...
});
//Equals to res in `Meteor.call()`. sends a result to the client side method call. inserted docId in this case
return docId
},
PS: If this doesn't answer you question, that means I didn't understand what you're trying to achieve exactly. Leave me a comment and I'll edit the answer.
EDIT
I used one method for both sending emails and inserting the document but still, you can pass error/result exactly like how I did and then do another call for emails using the id in result.
On my client side, I display a list of users and a small chart for each user's points stored in the DB (using jQuery plugin called sparklines).
Drawing the chart is done on Template.rendered method
// client/main.js
Template.listItem.rendered = function() {
var arr = this.data.userPoints // user points is an array of integers
$(this.find(".chart")).sparkline(arr);
}
Now I have a Meteor method on the server side, that is called on a regular basis to update the the user points.
Meteor.methods({
"getUserPoints" : function getUserPoints(id) {
// access some API and fetch the latest user points
}
});
Now I would like the chart to be automatically updated whenever Meteor method is called. I have a method on the template that goes and calls this Meteor method.
Template.listItem.events({
"click a.fetchData": function(e) {
e.preventDefault();
Meteor.call("getUserPoints", this._id);
}
});
How do I turn this code into a "reactive" one?
You need to use reactive data source ( Session, ReactiveVar ) together with Tracker.
Using ReactiveVar:
if (Meteor.isClient) {
Template.listItem.events({
"click a.fetchData": function(e) {
e.preventDefault();
var instance = Template.instance();
Meteor.call("getUserPoints", this._id, function(error, result) {
instance.userPoints.set(result)
});
}
});
Template.listItem.created = function() {
this.userPoints = new ReactiveVar([]);
};
Template.listItem.rendered = function() {
var self = this;
Tracker.autorun(function() {
var arr = self.userPoints.get();
$(self.find(".chart")).sparkline(arr);
})
}
}
Using Session:
if (Meteor.isClient) {
Template.listItem.events({
"click a.fetchData": function(e) {
e.preventDefault();
Meteor.call("getUserPoints", this._id, function(error, result) {
Session.set("userPoints", result);
});
}
});
Template.listItem.rendered = function() {
var self = this;
Tracker.autorun(function() {
var arr = Session.get("userPoints");
$(self.find(".chart")).sparkline(arr);
})
}
}
Difference between those implementation :
A ReactiveVar is similar to a Session variable, with a few
differences:
ReactiveVars don't have global names, like the "foo" in
Session.get("foo"). Instead, they may be created and used locally, for
example attached to a template instance, as in: this.foo.get().
ReactiveVars are not automatically migrated across hot code pushes,
whereas Session state is.
ReactiveVars can hold any value, while Session variables are limited
to JSON or EJSON.
Source
Deps is deprecated, but still can be used.
The most easily scalable solution is to store the data in a local collection - by passing a null name, the collection will be both local and sessional and so you can put what you want in it and still achieve all the benefits of reactivity. If you upsert the results of getUserPoints into this collection, you can just write a helper to get the appropriate value for each user and it will update automatically.
userData = new Meteor.Collection(null);
// whenever you need to call "getUserPoints" use:
Meteor.call("getUserPoints", this._id, function(err, res) {
userData.upsert({userId: this._id}, {$set: {userId: this._id, points: res}});
});
Template.listItem.helpers({
userPoints: function() {
var pointsDoc = userData.findOne({userId: this._id});
return pointsDoc && pointsDoc.points;
}
});
There is an alternative way using the Tracker package (formerly Deps), which would be quick to implement here, but fiddly to scale. Essentially, you could set up a new Tracker.Dependency to track changes in user points:
var pointsDep = new Tracker.Dependency();
// whenever you call "getUserPoints":
Meteor.call("getUserPoints", this._id, function(err, res) {
...
pointsDep.changed();
});
Then just add a dummy helper to your listItem template (i.e. a helper that doesn't return anything by design):
<template name="listItem">
...
{{pointsCheck}}
</template>
Template.listItem.helpers({
pointsCheck: function() {
pointsDep.depend();
}
});
Whilst that won't return anything, it will force the template to rerender when pointsDep.changed() is called (which will be when new user points data is received).
When running the following from the UserController on Google Chrome, with ember-couchdb-kit-0.9, Ember Data v1.0.0-beta.3-56-g8367aa5, Ember v1.0.0, and this couchdb adapter:
customerSignUp: function () {
var model = this.get('model');
var customer = this.get('store').createRecord('customer', {
description: 'Why hello sir',
user: model
});
customer.save().then(function() {
model.set('customer', customer);
model.save();
});
}
with these models:
App.User = App.Person.extend({
name: DS.attr('string'),
customer: DS.belongsTo('customer', {async: true })
App.Customer = DS.Model.extend({
user: DS.belongsTo('user', {async: true}),
description: DS.attr('string')
});
neither the user nor the customer has their relationship set properly (in the Ember Debugger the user has null and the customer has <computed>, rather than some sort of <EmberPromiseObject> which is what they have when it works).
This only happens when the object in question is persisted. If the save() calls are omitted, both have correctly set relationships, but of course the database hasn't been updated with this information. Whenever the saves happen, the relationships are overwritten with empty entries.
I found that the problem was in the adapter's serializeBelongsTo function, which I've now changed my copy to the following:
serializeBelongsTo: function(record, json, relationship) {
console.log("serializeBelongsTo");
console.log(record.get('user'));
console.log(json);
console.log(relationship);
var attribute, belongsTo, key;
attribute = relationship.options.attribute || "id";
console.log(attribute);
key = relationship.key;
console.log(key);
belongsTo = Ember.get(record, key);
console.log(belongsTo);
if (Ember.isNone(belongsTo)) {
return;
}
json[key] = Ember.get(belongsTo, attribute);
console.log(Ember.get(belongsTo, attribute));
console.log(json);
if (relationship.options.polymorphic) {
return json[key + "_type"] = belongsTo.constructor.typeKey;
}
else {
return json;
}
}
attribute, belongsTo, and key all log as correct, but
console.log(Ember.get(belongsTo, attribute)); returns undefined,
which I've tried to change to
console.log(Ember.get(Ember.get(belongsTo, 'content'), attribute));
since console.log(belongsTo); told me the id attribute was hidden inside a content object. Attached is a screenshot showing what I mean.
The change doesn't fix the problem though, and I keep getting undefined. No matter what method I use to try to get the id out of the belongsTo object, I always get either null or undefined. Here are some examples of things I've tried to get content out of the object:
var content = belongsTo.content;
var content = Ember.get(belongsTo, 'content');
var content = belongsTo.get('content');
console.log(json); returns Object {description: "Why hello sir", user: undefined}
Here's a pastebin showing relevant output: http://pastebin.com/v4mb3PJ2
Update
A very confusing update!
When I save the model from a different function:
saveModel: function() {
this.get('model').save().then(
function( data, textStatus, jqXHR ) {
console.log('Saved successfully.');
},
function( jqXHR, textStatus, errorThrown ) {
console.log(jqXHR);
console.log(errorThrown);
console.log(textStatus);
}
);
}
The model is correctly saved. Everything in serializeBelongsto works exactly as expected.
Here's a different pastebin showing output for this case: http://pastebin.com/Vawur8Q0
I figured out the problem. Basically the belongsTo object in serializeBelongsTo wasn't really resolved by the time it was being referenced, which I found out by querying isFulfilled. So I implemented by saving side this way:
function saveOn (target, attribute) {
target.addObserver(attribute, function () {
if (target.get(attribute)) {
console.log("Inside with %#".fmt(attribute));
target.removeObserver(attribute);
Ember.run.once(target, function() {
target.save();
});
}
});
};
customerSignUp: function () {
var model = this.get('model');
var customer = this.get('store').createRecord('customer', {
description: 'Why hello sir'
});
customer.save().then(function () {
model.set('customer', customer);
customer.set('user', model);
saveOn(customer, 'user.isFulfilled');
saveOn(model, 'customer.isFulfilled');
});
}
Now everything works like a charm. It might be a good idea for serializeBelongsTo to take this into account though. This line: console.log(Ember.get(belongsTo, 'isFulfilled')); was coming up false in my case. There was just a race condition of some sort between the creation of the record and it's serialization!
I'd like to make my saveOn function return a promise though, which I could then use to chain multiple saveOns together. That way I wouldn't have to do a customer.save() to make sure the id's were populated.
Below is my backbone view.
define([
'app',
'backbone',
'twig',
'templates/report',
'data/reportViewCollection',
'data/reportViewModel'
], function (app, Backbone, Twig, template, Collection, Model) {
var collection = new Collection();
var fetching;
return Backbone.View.extend({
setParams: function (rlId, viewId, categoryId, depth) {
// reset any possible pending previous repests.
fetching = false;
var model = collection.getModel(rlId, viewId, categoryId, depth);
if (!model) {
model = new Model({
rlId: rlId,
viewId: viewId,
categoryId: categoryId,
depth: depth
});
fetching = model.fetch({
success: function (model) {
collection.add(model);
},
error: function (model) {
alert('error getting report view');
}
});
}
this.model = model;
},
render: function () {
var that = this;
var done = function() {
app.vent.trigger('domchange:title', that.model.get('title'));
that.$el.html(Twig.render(template, that.model.toJSON()));
that.delegateEvents(that.events);
fetching = false;
};
if (fetching) {
app.loading(this);
fetching.done(done);
} else {
done();
}
return this;
},
events: {
'change select.view-select': 'viewSelect',
'click #dothing': function (e) {e.preventDefault(); alert('hi');}
},
viewSelect: function(e) {
var selectedView = $(e.target).val();
var rlId = this.model.get('rlId');
if (!rlId) rlId = 0;
var url = 'report/' + rlId + '/' + selectedView;
console.log(this, e, url);
Backbone.history.navigate(url, {trigger: true});
}
});
});
Description of functionality:
What happens is when a specific url is navigated to, the setParams() function is called to fetch the model from the server. When the render method is called, it checks if we are currently fetching the model and if so, uses sets a deferred callback to render the template when it gets done fetching. When the model is fetch-ed and we are ready to render, renders the template and fills in the view by that.$el.html().
Problem:
What happens is that my events work perfectly the first time I navigate to a url, but when I hit the back button, my events don't get attached.
I've stepped through the code and can't see any differences. The only real difference is that I'm loading the model from the cached collection immediately instead of doing an ajax request to fetch it.
Any clues what is going on?
try to change:
that.$el.html(Twig.render(template, that.model.toJSON()));
to
that.$el.html("");
that.$el.append(Twig.render(template, that.model.toJSON()));
had kind the same problem and this fixed it.
I resolved my issue. The comment by #mu set me in the right direction.
I am using Marionette and my view is contained in a region. In the Region's open function, it is doing this.$el.html(view.el); which wipes out the events in certain circumstances. I'm still not sure why it does in some but on in others.
The Solution proved to be to add an onShow function to my view that call's this.delegateEvents(). Everything is working smoothly now!
I eventually figured it out by stepping through the code and watching the events registered on the view's div.
I am creating a client view of an application and I need help with retrieving specific data from my JSON file. I am using Backbone.js along with Underscore.js to achieve this.
(function($) {
window.Node = Backbone.Model.extend({
getName: function(){
return this.get('Name');
}
});
window.Nodes = Backbone.Collection.extend({
model:Node,
url: '/packageview.json'
});
window.NodeView = Backbone.View.extend({
tagName: "div",
className: "package-template",
events:{
"click #display-name" : "displayname",
},
//.. I have a render and initialize function here which should not be a concern
displayname: function(){
var node = new Node();
alert(node.getName()); //trying to alert
},
});
});
I am trying to get the name from model and alert it. I have a button in my html with an id, and when I press that button I get "undefined" as an alert. Here is how my JSON file looks:
{
"Id": 2,
"Name": "Some Package",
"IsComplete": false,
"IsNodeTagComplete": false
}
I think I am making a silly mistake somewhere. Am I expecting way to much from model?
What I am doing here is this
window.jsonAccess = Node.extend({ // Here Node is my above mentioned model
getJSON: function(){
var collection = nodeInstance.toJSON(); // nodeInstance is an instance of my collection Nodes
return collection; //returns JSON
}
});
jAccess = new jsonAccess();
So here is what I am doing to access the JSON
getNodeId: function(){ //Function to get Node Id from JSON
objectJSON = jAccess.getJSON(); // Get JSON
_.each(objectJSON, function(action){
_.each(action.Nodes, function(action){
This solves my purpose but not quite the way getters would be used in backbone.
Since a lot of context is missing I too may be making a mistake but here's my guess - you are creating an empty Node Model. Try doing something like this in display:
displayName: function() {
var myJSON = window.getJSONObject(); //wherever your json object is or how to get it...
var node = new Node({
id:myJSON.Id,
name:myJSON.Name,
isComplete: myJSON.IsComplete,
...
});
alert(node.get('name'));
alert("Getter: "+node.getName()); //your version...
}
This is just a hunch though...maybe I'm missing your context but this seems to be the case for now...