Backbone localstorage - A "url" must be specified (without view) - backbone.js

I know there are more similar questions like this, but I really couldn't find the answer to my problem..
Here is my jsfiddle: http://jsfiddle.net/ktyghnL1/3/
Code:
var Todo = Backbone.Model.extend({
});
var Todos = Backbone.Collection.extend({
model: Todo,
localStorage: new Backbone.LocalStorage('todos-backbone'),
comparator: 'order'
});
todos = new Todos();
I am only using models and collections for my angularjs app.
When I try to create an new todo, it fails.

The models your create with var todo = new Todo(); todo.save(); don't have any information related to a local storage, you only set it on the collection.
Create a model from your todos collection, the storage will be automatically provided:
var todo = todos.create();
todo.save();
See http://jsfiddle.net/nikoshr/56awrstr/1/for a demo
Or you can create a model instance, assign it a store and add it to the collection:
var store = new Backbone.LocalStorage('todos-backbone');
var todo = new Todo();
todo.localStorage = store;
todo.save();
todos.add(todo);
http://jsfiddle.net/nikoshr/56awrstr/2/

Related

Meteor Reactivity Using a Collection and SubCollection (e.g. Reactive Joins)

Is it possible to implement reactivity by a sub-class with a transformed collection?
This is the example code of jamgold on the Meteor forum; the sub-class subcollection is joined to the main-class collection_name. If something changes on the collection_name collection, Meteor is in fact reactive. However, when something changes on the sub-collection subcollection, this is not reactively pushed to this publish/subscription.
Collection = new Meteor.Collection('collection_name');
if(Meteor.isServer)
{
Meteor.publish('collection', function(query,options) {
var self = this;
var handler = null;
query = query == undefined ? {} : query;
options = options == undefined ? {} : options;
//
handler = Collection.find(query,options).observeChanges({
added: function (id, doc) {
var object = null;
doc.object = Meteor.subcollection.findOne({_id: doc.objectId},);
self.added('collection_name', id, doc);
},
changed: function (id, fields) {
self.changed('collection_name', id, fields);
},
removed: function (id) {
self.removed('collection_name', id);
}
});
self.ready();
self.onStop(function () {
if(handler) handler.stop();
});
});
}
if(Meteor.isClient)
{
Meteor.subscribe('collection');
}
To make it reactive for the SubCollection, you would need to also observe it's changes as well. Keep in mind that this becomes very complex fast and my example only works if there is a 1 to 1 relationship between your Collection and SubCollection. You could implement something that works for a 1 to many relationship, but you will have some logic issues to address (e.g. when a doc in SubCollection changes...does that invalidate all associated Collection docs that were already published with that SubCollection doc. If so then do you emit a removed then an added to re-send them with their updated SubCollection doc, etc.).
Here is the full example.
const Collection = new Meteor.Collection('collection_name');
const SubCollection = new Meteor.Collection('sub_collection_name');
if (Meteor.isServer) {
Meteor.publish('collection', function(query,options) {
var self = this;
var handler = null;
query = query == undefined ? {} : query;
options = options == undefined ? {} : options;
// enable reactivity for Collection
handler = Collection.find(query, options).observeChanges({
added: function (id, doc) {
// find the associated object (using it's id) and add it to the doc
doc.object = SubCollection.findOne({_id: doc.objectId});
// now pass the original doc + the associated object down to client
self.added('collection_name', id, doc);
},
changed: function (id, fields) {
// doc.object is assumed to already exist on the doc...so just notify the subscriber
// of the changes in Collection
self.changed('collection_name', id, fields);
},
removed: function (id) {
// doc.object is assumed to already exist on the doc...so just notify the subscriber
// of the changes in Collection
self.removed('collection_name', id);
}
});
var handleSubCollectionDocChange = function(callbackThis, id) {
// find the doc from Collection that has a reference to the new SubCollection doc
var parentCollectionDoc = Collection.findOne({objectId: id});
// only do something if one exists
if (parentCollectionDoc) {
// remove the previously published doc since the SubCollection doc changed (if it was previously published)
self.removed('collection_name', parentCollectionDoc._id);
// store the new SubCollection doc in Collection.object
parentCollectionDoc.object = doc;
// send down the Collection doc (with new SubCollection doc attached)
self.added('collection_name', parentCollectionDoc._id, parentCollectionDoc);
}
};
// enable reactivity for SubCollection
subhandler = SubCollection.find().observeChanges({
added: function (id, doc) {
// find the doc from Collection that has a reference to the new SubCollection doc
var parentCollectionDoc = Collection.findOne({objectId: id});
// only do something if one exists
if (parentCollectionDoc) {
// remove the previously published doc since the SubCollection doc changed (if it was previously published)
self.removed('collection_name', parentCollectionDoc._id);
// store the new SubCollection doc in Collection.object
parentCollectionDoc.object = doc;
// send down the Collection doc (with new SubCollection doc attached)
self.added('collection_name', parentCollectionDoc._id, parentCollectionDoc);
}
},
changed: function (id, fields) {
// get the full SubCollection doc (since we only get the fields that actually changed)
var doc = SubCollection.findOne({_id: id});
// find the doc from Collection that has a reference to the new SubCollection doc
var parentCollectionDoc = Collection.findOne({objectId: id});
// only do something if one exists
if (parentCollectionDoc) {
// remove the previously published doc since the SubCollection doc changed (if it was previously published)
self.removed('collection_name', parentCollectionDoc._id);
// store the new SubCollection doc in Collection.object
parentCollectionDoc.object = doc;
// send down the Collection doc (with new SubCollection doc attached)
self.added('collection_name', parentCollectionDoc._id, parentCollectionDoc);
}
},
removed: function (id) {
// find the doc from Collection that has a reference to the new SubCollection doc
var parentCollectionDoc = Collection.findOne({objectId: id});
// only do something if one exists
if (parentCollectionDoc) {
// remove the previously published doc since the SubCollection doc no longer exists (if it was previously published)
self.removed('collection_name', parentCollectionDoc._id);
}
}
});
self.ready();
self.onStop(function () {
if (handler) handler.stop();
if (subhandler) subhandler.stop();
});
});
}
With that said, if you are only trying to achieve reactive joins then you really should look into the Meteor Publish Composite package. It handles reactive joins very easily and will keep your publication up to date with the parent collection changes or any of the child collections change.
Here is what a publication would look like (based on your example) using publish composite.
const Collection = new Meteor.Collection('collection_name');
const SubCollection = new Meteor.Collection('sub_collection_name');
Meteor.publishComposite('collection', function(query, options) {
query = query == undefined ? {} : query;
options = options == undefined ? {} : options;
return {
find: function() {
return Collection.find(query,options);
},
children: [{
find: function(collectionDoc) {
return SubCollection.find({_id: collectionDoc.objectId});
}
}],
};
});
With this example, anytime Collection or associated SubCollection docs change they will be sent to the client.
The only gotcha with this approach is that it publishes the docs into their respective collections. So you would have to perform the join (SubDocument lookup) on the client. Assuming we have subscribed to the above publication and we wanted to get a SubCollection doc for a certain Collection doc on the client, then it would look like this.
// we are on the client now
var myDoc = Collection.findOne({ //..search selector ..// });
myDoc.object = SubCollection.findOne({_id: myDoc.objectId});
The composite publication ensures that the latest SubCollection doc is always on the client. The only problem with the above approach is that if your SubCollection doc changes and is published to the client, your data will be stale because you have stored an static (and unreactive) version of the SubCollection doc in myDoc.object.
The way around this is to only perform your join when you need it and don't store the results. Or, another option is to use the Collection Helpers package and create a helper function that dynamically does the join for you.
// we are on the client now
Collection.helpers({
object: function() {
return SubCollection.findOne({_id: myDoc.objectId});
},
});
With this helper in place, anytime you need access to the joined SubCollection doc you would access it like this.
var myDoc = Collection.findOne({ //..search selector ..// });
console.dir(myDoc.object);
Under the covers, the collection helper does the SubCollection lookup for you.
So long story short, take your pick (roll your own reactive join publication or use Publish Composite + Collection Helpers). My recommendation is to use the packages because it's a tried and true solution that works as advertised out of the box (fyi...I use this combination in several of my Meteor apps).

Firebase AngularFire using $priority and the $save overwrites the object

Based on the AngularFire documentation I'm trying to set $priority on an object, then use $save() to update the data on Firebase.
The result I get instead is the object getting overwritten as empty (it's entry in Firebase).
Here is my code
var ref = new Firebase(firebaseListPath);
var myList = $firebase(ref).$asArray();
myList.$add({
created: Firebase.ServerValue.TIMESTAMP,
user: userId
}).then(function(newItemRef){
var newItemObj = $firebase(newItemRef).$asObject();
newItemObj.$priority = userId;
newItemObj .$save();
});
Try passing the priority directly into $add:
myList.$add({
created: Firebase.ServerValue.TIMESTAMP,
user: userId,
$priority: userId
});
There is also no reason to call $asArray and $asObject on the same synchronized data (it's already synchronized). This would work in your current model, but be unnecessary:
myList.$add({
created: Firebase.ServerValue.TIMESTAMP,
user: userId
}).then(function(newItemRef){
var item = myList.$getRecord(newItemRef.name());
item.$priority = userId;
myList.$save(item);
});
However, there is really no reason to be using AngularFire here. If you aren't binding anything to the view, then you can simply make the call directly to the ref:
var ref = new Firebase(firebaseListPath);
ref.push().setWithPriority({ ... }, userId);

why does backbone think I'm treating an object like a function?

Adding Backbone to a Rails app, I created a post model inside an app namespace like this
var app = {
this.models.post = new app.Models.Post();
In the router, I created the following route
"posts/:id": "postDetails"
When I navigate to /posts/4, I'm getting an Uncaught TypeError: object is not a function error when I try to call fetch on the model like this
postDetails: function (id) {
console.log(id);
var post = new app.models.post({id: id});
this.post.fetch({
success: function (data) {
$('#content').html(new PostView({model: data}).render().el);
}
});
}
According to the Backbone docs http://backbonejs.org/#Model-fetch, I should be able to call fetch on a model to retrieve the data from the server. Why does Backbone think I'm treating an object like a function?
You're doing this:
this.models.post = new app.Models.Post();
to, presumably, set app.models.post to an instance of the app.Models.Post model. Then you try to do this:
var post = new app.models.post({id: id});
But you can only use the new operator on a function:
new constructor[([arguments])]
Parameters
constructor
A function that specifies the type of the object instance.
You probably want to say:
var post = new app.Models.Post({ id: id });
or something similar.
The problem is you've declared post as a local variable var post, but then tried to access it as a member this.post. You need either this:
this.post = new app.models.post({id: id});
this.post.fetch({ ...
Or this:
var post = new app.models.post({id: id});
post.fetch({ ...
(The difference being that a local variable var post is declared in transient scope and thrown away after postDetails completes; while instance variable this.post gets added to the Router object and will typically live for the whole lifetime of the application.)

Possible to have sub Collections in Collection?

I am learning Backbone.js and try to develop revamp a project using Backbong.js. As the project was already have APIs and was using in different platforms, iphone app, andorid app and web.
My problem is, there is a API return as below
{
success: true,
response: {
posts: [{...post data...}, {...post data...}, {...post data...}],
users: [{...user data...}, {...user data...}],
categories: [{...category data...}, {...category data...}]
}
}
as the backbone documentation
a Collection can contain many models
Typically the design is
// Post Model
var PostModel = Backbone.Model.extend({
...
})
// User Model
var UserModel = Backbone.Model.extend({
...
})
// Category Model
var UserModel = Backbone.Model.extend({
...
})
var PostListCollection = Backbone.Collection.extend({
model: PostModel
})
So... How can I put these 3 types of "objects" in the a Collection? Thanks
Unsure why you'd want a collection that isn't a single collection. It sounds like (and do correct me if this is wrong), you want a neat way of fetching your data from the API and turning it in to your three collections.
You could create a model that fetches from your API, that implements a custom parse function to create three collections within it.
Granted, you'll want to define UserCollection, PostCollection and CategoryCollection beforehand.
ApplicationModel = Backbone.Model.extend({
url: '/apiUrl/',
parse: function(data)
{
var result = {};
result.users = new UserCollection(data.users);
result.posts= new PostCollection(data.posts);
result.categories = new CategoryCollection(data.categories);
return result;
}
});
you can have a model that contain the collections
var ResultModel = Backbone.Model.extend({
default: {
Users: [],
Posts: [],
Categories: []
}
});
Then implements the collection in usual way.
So, when you try to get the child collections ...
var result = new ResultModel("your result JSON object");
var users = new UserCollection(result.get("Users"));
var posts = new PostCollection(result.get("Posts"));
var categories = new CategoryCollection(result.get("Category"));
This is working well for me ... in my case

Backbone collection export issues

I have a collection of users (model user)
model has a boolean value: isExport
i have a button that on click supposed to post to the server all the users that isExport=true
Can anyone suggest a suitable solution for this problem?
I know it's possible to wrap the collection as a model and overwrite the toJSON function
but couldn't manage it so far (can someone please give a code example?)
App.User = Backbone.Model.extend({ defaults: {isExport: false}...});
App.Users = Backbone.Collections.extend({model: App.User...});
Thanks!
Roy
Backbone collections by default don't have any write operations to the server, so you'll need to add a new method to your Collection subclass such as doExport, use .where to get the models with isExport=true and their toJSON() to get an array of objects which you can then send to the server with Backbone.sync or $.post.
Backbone comes with RESTful support.
So, if you give each collection a url pointing to the collection rest service, then with a few functions (create,save) you can handle server requests.
App.Models.User = Backbone.Model.extend();
App.Collections.Users = Backbone.Collection.extend({
url: 'users',
model: App.Models.User
});
So, this way, you can:
var users = new App.Collections.Users();
users.fetch(); // This will call yoursite.com/users
// Expecting a json request
And then you can:
users.create({ name: 'John', isExport: true });
This will send a post request to the server, in order to create a new record.
And you can check on server side if it has the flag you want.
App.Views.ExportAll = Backbone.View.extend({
el: '#exportAll',
events: {
'click': 'exportAll'
},
exportAll: function(e){
e.preventDefault();
console.log('exporting all');
console.log(this.collection.toJSON());
var exportModel = new App.Models.Export;
exportModel.set("data", this.collection.toJSON());
console.log(exportModel.toJSON());
exportModel.save();
}
});
I think this is the best solution for the problem

Resources