I'm playing around with Rails application that uses Backbone to handle some data on a different pages, I have no problems with fetching Collections and creating new Models, but I can't fetch a single model from the server in order to perform, say, show or edit. I'm trying to declare urlRoot as per answers found in other similar questions here on SO:
class Entities.Product extends Backbone.Model
urlRoot: ->
if #isNew
'/products/'
else
'/products/' + #id
I'm using the following route to start the show:
class ProductsUnit.Router extends Marionette.AppRouter
appRoutes:
"products/:id" : "showProduct"
API =
showProduct: (id) ->
ProductsUnit.Show.Object.showProduct()
Later in the code I request the single entity of the product:
Show.Object =
showProduct: (id) ->
product = App.request "product:entity"
Request then handled by the following handler:
App.reqres.setHandler "product:entity", ->
API.getSingleProduct id
And it fires a fetch of a single product:
getSingleProduct: (id) ->
product = new Entities.Product
id: id
product.fetch()
product
As far as I understand I can pass an id from the route itself "products/:id" to the chain of functions that will eventually fetch the model and the urlRoot will be /products/+ #id where this will represent the id that was initially taken from the route. But it seems that it's not the case here, since on the success isNew returns as true, even though I'm trying to pass the id to make it false.
getSingleProduct: (id) ->
product = new Entities.Product
id: id
product.fetch success: ->
console.log product.isNew()
return
product
I'm fairly new both to the Backbone and Javascript, so maybe there's a critical flaw in how I see the logic. Any guidance will be much appreciated, I've been stuck with this quite simple issue for few days already.
I think you've swapped your urlRoot logic: when a model isNew it doesn't have an id yet.
So it should be:
urlRoot: ->
if #isNew
'/products/'
else
'/products/' + #id
Related
I am new to Backbone-relational, I am not sure what is the right way to use HasMany.
I have a Parent model which have many children (by "many" I mean thousands of children). In order to avoid performance issue, I query children by their foreign key: /child/?parent=1, instead of create a huge list of child_ids in Parent. But seems this is not the way Backbone-relational work.
So I am wondering what is the right way to handle this.
1, Change my json api to include list of child id in parent, then send thousands of ids as Backbone-relational recommend:
url = function(models) {
return '/child/' + ( models ? 'set/' + _.pluck( models, 'id' ).join(';') + '/' : '');
}
// this will end up with a really long url: /child/set/1;2;3;4;...;9998;9999
2, override many method in Backbone-relational, let it handle this situation. My first thought is :
relations: [{
collectionOptions: function(model){
// I am not sure if I should use `this` to access my relation object
var relation = this;
return {
model: relation.relatedModel,
url: function(){
return relation.relatedModel.urlRoot + '?' + relation.collectionKey + '=' + model.id;
}
}
}
}]
// This seems work, but it can not be inherent by other model
// And in this case parent will have am empty children list at beginning.
// So parent.fetchRelated() won't fetch anything, I need call this url my self.
3, Only use Backbone-relational as a Store, then use Collection to manage relations.
4, Some other magic way or pattern or backbone framework
Thanks for help.
Here's the solution that I have going on my current project. Note that Project hasMany comments, events, files, and videos. Those relations and their reverse relations are defined on those models:
Entities.Project = Backbone.RelationalModel.extend({
updateRelation: function(relation) {
var id = this.get('id'),
collection = this.get(relation);
return collection.fetch({ data: $.param({ project_id: id }) });
}
});
I have the REST endpoint configured to take parameters that act as successive "WHERE" clauses. So project.updateRelation('comments') will send a request to /comments?project_id=4 I have some further logic on the server-side to filter out stuff the user has no right to see. (Laravel back-end, btw)
I asked this question elsewhere. But I would ask it here too in case who knows the answer.
I recently wrote a simple web app. It has two views in total, and users should be able to go back and forward between the two views. It seems nature to me that setView method can the job of changing the app view. It works all good when switching from Index view to Show view by clicking a link. However, it cannot go back to Index view when I click a link. It just pops up this error message "Please set View#manage property with selector '' to true". Since the old view objects should have been destroy when switching to another view, why it would have this error unless it's not working as I thought it would be.
Below is a snippet of my coffeescript code.
MyApp.Routers.Home = Backbone.Router.extend(
routes:
'books' : 'showBooksIndex'
'books/:id' : 'showBook'
initialize: (options) ->
#buildLayout()
buildLayout: ->
#layout = app.useLayout("main")
showBooksIndex: ->
#cleanLayout()
#books = new MyApp.Collections.Book();
#viewIndex = new MyApp.Views.BooksIndex(
collection: #books;
)
#layout.setView('#app', #viewIndex)
showBook: (id) ->
#cleanLayout()
#book = new MyApp.Models.Book id: id
#view = new MyApp.Views.BookShow model: #book
#layout.setView('#app', #view)
cleanLayout: ->
appView = #layout.getView('#app')
if appView
appView.remove()
MyApp.Views.BookShow = Backbone.View.extend(
id: "book-show"
className: "book-card"
manage: true
template: JST['templates/books/show']
initialize: ->
#model = #options.model
_this = #
#model.fetch(
success: (model, resp)->
_this.render();
)
MyApp.Views.BooksIndex is has similar code to BookShow view.
Caching the views is probably a solution too. This approach will work around this issue because recreation of view objects is avoided. But since my app have lots of photos, I would prefer to fetch data from server every time the app view changes. I am not sure if it is the right way to go. Thanks in advance for sharing your insights.
Backbone relational is too messy for me and I can't seem to debug it. I am trying to avoid using this asset.
I have two collections in my Backbone app.
Voice.Collections.Posts and Voice.Collections.Comments
This is my router:
class Voice.Routers.Posts extends Backbone.Router
routes:
'': 'index'
initialize: ->
#collection = new Voice.Collections.Posts()
#collection.reset($('#container').data('posts').reverse())
index: ->
view = new Voice.Views.PostsIndex(collection: #collection)
$('#container').html(view.render().el)
I want my router to have a method that filters my comment collection according to a url with the post id ( as my comments - post relational key, post_id) so basically "posts/12"(posts/:id) will call a function showComments: (id) -> which will take the id and initialize a collection of comments which only contain comments where 'post_id' is equal to 12 ("id").
Could I sort the collection from my router?
something like this? (this doesnt work)
class Voice.Routers.Posts extends Backbone.Router
routes:
'': 'index'
'post/:id/': 'showComments'
initialize: ->
#collection = new Voice.Collections.Posts()
#collection.reset($('#container').data('posts').reverse())
index: ->
view = new Voice.Views.PostsIndex(collection: #collection)
$('#container').html(view.render().el)
showComments: (id) ->
#comCollection = new Voice.Views.Collections.Comments()
#comCollection = #comCollection.where ->
#model.get('post_id') = id
comview = new Voice.Views.CommentsIndex(collection: #comCollection)
$('#comContainer').html(comview.render().el)
but this doesn't work because the #comCollection needs to be intialized. I'm just not sure how I should do this. I would also settle for the comment collection being rendered as view from another views event trigger.Help is appreciated.
EDIT:
Would I have to use Backbone.navigate? Backbone.navigate creates a bad smell.
My CoffeeScript is a bit rusty, so I can't remember exactly what:
#comCollection = #comCollection.where ->
#model.get('post_id') = id
translates as in normal Javascript. However, it absolutely should work if used right, so perhaps if you tried a simpler syntax:
this.comCollection = this.comCollection.where({post_id: id});
you might have better success? If not, you may want to drop in to the debugger and check what #comCollection actually has in it after you make that call.
I'm testing Localstorage of Backbone with the following code. After saving the model, it's supposed to create an id and also a cid that is accessible at Model.cid. It's logging the id in the console (see below), but not the cid. Here's a fiddle that recreates the problem http://jsfiddle.net/mjmitche/haE9K/2/
Can anyone see what I'm doing wrong?
// Model Creation with defaults
var Todo = Backbone.Model.extend({
localStorage: new Backbone.LocalStorage("todos"),
defaults: {
title:'new todo...',
description:'missing...',
done:false
}
});
var myTodo = new Todo({});
console.log("Before save id:"+myTodo.get('id')); // Unique Id from Server
console.log("Before save cid:"+myTodo.cid); // Client side id
myTodo.save();
console.log(myTodo.get('title'));
console.log("After save id:"+myTodo.get('id'));
console.log("After save cid:"+myTodo.cid);
Console results
Before save id:undefined app.js:16
Before save cid:c0 app.js:17
new todo... app.js:20
After save id:99bc7f4c-8837-39f4-91e9-90760d8ee8cd app.js:21
After save cid:c0 app.js:22
The cid is created when the model is created and never change then. See the docs:
A special property of models, the cid or client id is a unique
identifier automatically assigned to all models when they're first
created
Lets take a look in the constructing function of Backbone.Model:
var Model = Backbone.Model = function(attributes, options) {
.
.
.
this.cid = _.uniqueId('c');
.
.
.
};
As you can see, the cid is created there using underscores uniqueId method.
I currently have two Backbone models: user and project. I would like to have a Backbone view containing a form that enables the creation of a new project, and the association of currently existing users (entries from the database users table) with this project. When a user completes this form and clicks the save button, the new project should be saved into the database (in the projects table) and the relationship between the saved project and the related users should be saved into a relationship table (projects_users table, containing the corresponding project id and the user id for each relationship). Currently, I can save the project information, but could not get any data into the projects_users table using Backbone-relational.
What do you think the best approach to achieve the above functionality is? It would be great if you could point me to specific code that I could use as a template.
Thank you,
Alexandra
After some frustrating trial-and-error period, I finally managed to get my code working! It's not beautiful, but it is functional, and now I can start thinking about improving it. Hopefully, some other people will find this information useful ...
The one thing that put me on the right track was the understanding that what needed to be changed was not only in the backbone view code (the one with the new projects form), but also in the corresponding rails models.
For the rails part (Rails 3.2.2), I made sure that the following model files had the necessary information:
project.rb
class Project < ActiveRecord::Base
has_and_belongs_to_many :users
#attr_accessible :name, :description, :users_attributes
#has_many :projects_users, foreign_key: "project_id", dependent: :destroy
#has_many :users, through :projects_users
#accepts_nested_attributes_for :users
end
user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :projects
end
projects_users.rb
class ProjectsUsers < ActiveRecord::Base
belongs_to :project
belongs_to :user
end
I have read in many different places that has_and_belongs_to_many is not the best way to set many-to-many relationships in rails. Nevertheless, I could not get the same functionality using the has_many definition - the commented part in project.rb is the way I tried this different approach. The file user.rb had some corresponding code, which I removed for simplicity.
Now, what I needed to get done in the backbone form view was to send a POST request with a JSON object that the rails projects_controller.rb could recognize. Initially, I tried several POST requests without success (and no errors to guide me). But then, I remembered to have previously implemented a form for teams where users could be added to a particular team (HABTM Checkboxes - there is a railscast for this functionality). After looking at this example, I realized what was needed from my POST request. This is what I wanted to see in the rails server log file:
Started POST "/projects" for 127.0.0.1 at 2012-06-27 00:35:22 +0000
Processing by ProjectsController#create as JSON
Parameters: {"project"=>{"description"=>"with some description", "user_ids"=>["101", "1", "99"], "name"=>"some new project"}}
Backbone relevant files to achieve the above request:
project.js
App.Models.Project = Backbone.Model.extend({
urlRoot: '/projects',
// Default attributes for the project.
defaults: {
description: "",
user_ids: []
},
/* getters */
});
user.js
App.Models.User = Backbone.Model.extend({
/* getters */
});
form.js
App.Views.Projects.Common.Form = Backbone.View.extend({
...
events: {
"submit #new_project_form" : "formSubmit"
},
formSubmit: function(event) {
this.submitted($(event.target));
return false;
},
submitted: function(formElement) {
var newData = this.serializeFormData(formElement);
this.model = new App.Models.Project({
name : newData.name,
description : newData.description
});
this.saveFormData(newData);
return false;
},
serializeFormData: function(formElement) {
var fields = formElement.serializeArray();
var serializedData = {};
$.each(fields, function(index, field) {
serializedData[field.name] = field.value;
});
return serializedData;
},
// THE IMPORTANT PART FOR THE POST REQUEST
saveFormData: function(newData) {
// preserve reference to view for callbacks
var self = this;
var project = this.model;
project.set({
// a list of user ids associated with a project
"user_ids" : this.view_variables.user_ids
});
var project_object = ({
"project" : _.clone(project.attributes)
});
$.ajax({
type: 'POST',
url: '/projects',
data: project_object,
dataType: "json",
success: function() {
self.$el.hide();
self.addNewModelToCollection();
}
});
},
...
});
The code is kind of verbose, and includes some code that is specific to my project. Still, the relevant part is in the saveFormData function, where the jQuery ajax function is used.
In case you have any suggestions, either for the rails or for the Backbone part, please let me know. I will be happy to learn how to improve this solution.
I know it's old post but this looks interesting: http://backbonerelational.org/