Below is the current code structure I have in place for a collection that I have manually constructed. I have a json file on my server which I am now trying to load in and basically remove the manual one and construct a collection based on that data. Was wondering what would I possibly need to change below to my code to help accommodate this.
var Game = Backbone.Model.extend({
defaults: {
name: 'John Doe',
age: 30,
occupation: 'worker'
}
});
var GameCollection = Backbone.Collection.extend({
model: Game,
url: 'path/to/json',
parse: function(response) {
return response;
}
});
var GamesView = Backbone.View.extend({
tagName: 'ul',
render: function() {
//filter through all items in a collection
this.collection.each(function(game){
var gameView = new GameView({model: game});
this.$el.append(gameView.render().el);
}, this)
return this;
}
});
var GameView = Backbone.View.extend({
tagName: 'li',
template: _.template($('#gameTemplate').html()),
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var gameCollection = new GameCollection([
{
name: 'John Doe',
age: 30,
occupation: 'worker'
},
{
name: 'John Doe',
age: 30,
occupation: 'worker'
},
{
name: 'John Doe',
age: 30,
occupation: 'worker'
}
]);
var gamesView = new GamesView({collection: gameCollection});
$(document.body).append(gamesView.render().el);
This is one of the many things to love about Backbone. I don't know what you are using for your backend, but you state that you have a json file on your server, hopefully a json file full of the models that should be in your collection. And now here is the magic code (drumroll please..):
var GameCollection = Backbone.Collection.extend({
model: Game,
url: 'path/to/json/on/external/server',
});
var gameCollection = new GameCollection();
gameCollection.fetch();
Not much to it, right? Of course there are several options you can add or change to a fetch, so check out the docs here: http://backbonejs.org/#Collection-fetch. Backbone uses jQuery.ajax() be default, so check out the docs here to see all of the options: http://api.jquery.com/jQuery.ajax/
You shouldn't need the custom parse in your collection unless your models on the server don't match your backbone models.
Things to know:
fetch is asynchronous. It takes time to talk to the server, and the rest of your javascript will move on and complete. You will probably need to at least add a callback function to the success option, which will be called when fetch is finished, and it is good to add something to error as well, in case something goes wrong. You can add data as a query string so that your backend can use it using the data option, the data has to be an object. Here is an example:
gameCollection.fetch({
data: {collection_id: 25},
success: function(){
renderCollection(); // some callback to do stuff with the collection you made
},
error: function(){
alert("Oh noes! Something went wrong!")
}
});
fetch should receive data as JSON, so your url should either exclusive return JSON or be set up to detect an AJAX request and respond to it with JSON.
Firstly you need to fetch it from server as RustyToms said. And the other consideration is how to force the collection view to render itself again once data collected from server, as muistooshort commented.
If you manipulating fetch or sync you'll need to do it multiple times when there are more than one collection in app.
Doing such is native with Marionette, but in plain Backbone you can mimic the method of Marionette's CollectionView and do such:
//For the collection view
var GamesView = Backbone.View.extend({
initialize: function({
this.listenTo(this.collection, 'reset', this.render, this);
});
// Others
});
Then, when collection data fetched from server, the collection will trigger a reset event, the collection view noticed this event and render itself again.
For more than one collections, you can extract the code into a parent object in app and inherit from that.
var App.CollectionView = Backbone.View.extent({
initialize: //code as above
});
var GamesView = App.CollectionView.extend({
//Your code without initialize
});
I know this is a bit old at this point, but wanted to answer for anyone else stuck on this.
The code seems to come from the tutorial found here: http://codebeerstartups.com/2012/12/a-complete-guide-for-learning-backbone-js/
I too re-purposed the demo app found in that tutorial and had trouble rendering using external data.
The first thing is that the data itself needs to be converted to valid JSON or else you'll get a .parse() error.
SyntaxError: JSON.parse: expected property name or '}' at line 3 column 9 of the JSON data
or
error: SyntaxError: Unexpected token n
In your data source file, object properties need to be surrounded by quotes. It should look something like this:
[
{
"name": "John Doe",
"age": 30,
"occupation": "worker"
},
{
"name": "John Doe",
"age": 30,
"occupation": "worker"
},
{
"name": "John Doe",
"age": 30,
"occupation": "worker"
}
]
Secondly, once it's clear the external data is loading, we need to get it to render. I solved this (perhaps ungracefully) by moving the render() command into the success function of your gameCollection.fetch().
gameCollection.fetch({
success: function(collection, response, options) {
console.log('Success!! Yay!!');
$(document.body).append(gamesView.render().el);
},
error: function(collection, response, options) {
console.log('Oh, no!');
// Display some errors that might be useful
console.error('gameCollection.fetch error: ', options.errorThrown);
}
});
There are certainly better ways to accomplish this, but this method directly converts the code learned in the tutorial into something that works with external data.
Related
My problem is that I am just starting out with Backbone.js and are having trouble wrapping my head around a complex problem. I want to save a form that have infinite fields, and some of the fields also needs to have infinite options. I'm just worried I might have started at the wrong end with a JSON response, instead of building the models/collections first. Here is a short pseudocode of what I try to achieve.
id:
parent: <blockid>
fields: array(
id:
title:
helpertext
options: array(
id:
type:
value:
)
)
Currently I am working with a faked JSON response from the server, which I built from scratch, and now I want to divide it into models and collections on the client side.
//Fake a server response
var JSONresponse = {
"formid":"1",
"fields":[
{
"fieldid":"1",
"title":"Empty title",
"helper":"Helper text",
"type":"radio",
"options":[
{
"optionid":"1",
"value":"Empty option.."
},
{
"optionid":"2",
"value":"Empty option.."
}
]
},
{
// fieldid2
}
]
};
The idea is to add fields as I see fit, and then if the field type is radio/checkbox/ul/ol there must also be an "options" array within the field.
My work so far:
var app = {};
app.Models = {};
app.Collections = {};
app.View = {};
app.Models.Option = Backbone.Model.extend({
});
app.Collections.Options = Backbone.Collection.extend({
model: app.Models.Option
});
app.Models.Field = Backbone.Model.extend({
options: new app.Collections.Options()
});
app.Collections.Fields = Backbone.Collection.extend({
model: app.Models.Field
});
app.Models.Form = Backbone.Model.extend({
formid : "1",
fields: new app.Collections.Fields(),
initialize: function() {
}
});
How do I split up my JSON response into all these models and collections?
(Perhaps I should re-evaluate my approach, and go for something like form.fieldList and form.optionList[fieldListId] instead. If so, how would that look like?)
Edit: Here is a little jsfiddle after many fixes, but I still don't really know how to make the inner options list work.
The easiest solution would be using Backbone Relational or Backbone Associations.
The documentation should be enough to help you get started.
If you don't want to use a library you could override the parse function on the Form model.
app.Models.Form = Backbone.Model.extend({
defaults: {
fields: new app.Collections.Fields()
},
parse: function(response, options) {
return {
formid: response.formid,
fields: new app.Collections.Fields(_.map(response.fields, function(field) {
if (field.options) {
field.options = new app.Collections.Options(field.options);
}
return field;
}))
};
}
});
Now if you fetch a form from the server, the response will be parsed into an object graph of models and collections.
form.get('fields') will return an app.Collections.Fields collection. form.get('fields').first().get('options') will return an app.Collections.Options collection, if any options exist.
Also, you could create the form model like this:
var form = new app.Models.Form(JSONresponse, {
parse: true
});
This would result in the same object structure.
It's quite hard to handle the case of nested models and collections right in plain Backbone.
Easiest way of handling this will be something like this:
var Option = Nested.Model.extend({
idAttribute : 'optionid',
defaults : {
optionid : Integer
value : ""
}
});
var Field = Nested.Model.extend({
idAttribute : 'fieldid',
defaults : {
fieldid : Integer,
title : "",
helper : "",
type : "radio",
options : Option.Collection
}
});
var Form = Nested.Model.extend({
idAttribute : 'formid',
defaults : {
formid: Integer,
fields: Field.Collection
});
https://github.com/Volicon/backbone.nestedTypes
And that's it. Yep, you'll get direct access to the attributes as free bonus, just form.fields.first().options.first().value, without that get and set garbage.
I am trying to render a view for a collection using Backbone.View. But I cannot render the comment view to show the individual comments as a list inside the comments view. Only comments form is rendered. when visited the url of the collection from the address bar the below array is returned as I have written it on the server side code with express.
What can be the issue here I cannot manage to fix? It seems very natural to achieve it with this code, but it is certain that I am missing something. General issue is I am stuck at such detailed points although I can learn a say mvc framework, Backbone, Node, express etc.
CommentsView:
var CommentsView = Backbone.View.extend({
initialize: function (options) {
this.post = options.post;
this.collection = new Comments({post: this.post});//injecting collection from this views options, was injected to this view form router.
this.collection.on('add', this.addComments, this);
},
addComments: function (comment) {
this.$el.append(new CommentView({ model: comment }).render().el);
},
render: function () {
this.$el.append("<h2> Comments </h2>");
this.$el.append(new CommentFormView({post: this.post}).render().el);
return this;
}
});
This is the array returned when the url of collection is visited form the address bar:
[
{
"_id": "547e36df0ca19b2c1a6af910",
"postId": "547d00e30ca19b2c1a6af90b",
"name": "comment one",
"date": "Mon 21 Oct 2014",
"text": "gibberjabber"
}
]
Router method when the route to the comments of the related post is routed to:
comments: function(_id) {
var csv = new CommentsView({ post: this.posts.findWhere( {_id: _id} ) });
this.main.html(csv.render().el);
}
I think it could have something to do with your constructor function for this.collection. When creating a Collection, you should pass in the array as the first parameter and object literal with the options as the second (if you didn't define it when creating the collection class. What I'm thinking is that the "add" event on the collection isn't getting fired so the comments are not being rendered.
var Comments = Backbone.Collection.extend({
model: Post
});
this.collection = new Comments(posts)
I'm guessing that posts is just an array of models
Overview
I have a JSON object being passed to my backbone model. I have a parse function in the backbone model to convert some of the incoming attributes. The issue is when I fetch this model the attributes are not parsed and are just added to the model. The image at the bottom shows that instead of converting password to Password and deleting password it just adds password to the attributes of the object.
Here is my code:
JSON
When I use postman to call my web service I get the response:
{"type":null,"idTeacher":1,"name":"Sean","password":"tst","email":null,"dob":1392940800000}
Model:
window.Teacher = Backbone.Model.extend({
urlRoot: "http://localhost:8080/SIMS/resource/teacher",
defaults: {
"id": null,
"Name": "",
"Password": "",
"email": "",
"dob": "",
"type": ""
},
parse: function(response){
response.id = response.idTeacher;
response.Password = response.password;
response.Name = response.name;
delete response.name;
delete resoponse.password;
delete response.idTeacher;
return response;
}
});
window.TeacherCollection = Backbone.Collection.extend({
model: Teacher,
url: "http://localhost:8080/SIMS/resource/teacher",
parse: function(response){
return response;
}
});
Main.js // This is
before: function(callback) {
if (this.teacherList) {
if (callback) callback();
} else {
console.log('........................................javascript........');
this.teacherList = new TeacherCollection();
console.log('Loading List: Size: ' + this.teacherList.length);
this.teacherList.fetch({success: function() {
console.log('........... ftech success...........');
$('#contents').html( new TeacherListView({model: app.teacherList}).render().el );
if (callback) callback();
}});
}
}
If I debug my Backbone I can see that my parse did not parse any of the variable and the delete calls in the parse did not work either.
UDATE ANSWER
Thanks for the help. The fact that I hadn't the code in the collection class was an issue. But the second reason was that I wasn't looping through the collection to change each of the attributes.
That's because when you call the fetch method for your collection, the parse method that is called is the parse of the collection and not the parse of your teacher model.
When you call the fetch method from the collection the collections expects to receive an array of models and not just one teacher as you described
You are defining your parse method in your Model but calling your Collection fetch method.
In this case, only the parse method of your Collection will be called.
It seems that ember data have many changing up to version 1.0 beta. All works great with version 0.13. now I want update to higher version. In my case we have an embedded model 'user'
App.Post = DS.Model.extend({
subject: DS.attr('string'),
created: DS.attr('number'),
fcreated: function(){
debugger;
var d = new Date(this.get('created'));
return d.toLocaleDateString();
}.property('created'),
reporter: DS.belongsTo('user')
}
App.ApplicationAdapter = DS.RESTAdapter.extend({
namespace: 'restws'
});
The Json from server looks like this.
{
"posts": [
{
"id": "5226f2670364e70ae7d77266",
"subject": "Text",
"created": 1325410935048,
"reporter": {
"id": "5226f2660364e70ae7d771e2",
"firstName": "Doris",
"lastName": "Baumertr"
}
}
I get the following error code 'Uncaught TypeError: Cannot call method 'toString' of undefined'. In the ember source code I see, that in ember-data.js line 2236 the function throw the error 'buildRecord: function(type, id, data) .. ' After debugging I see that the properties type is undefined id is set with the correct id and data is undefined?
What is the mistake? How I can map the embedded data?
Here's actually the exact extractSingle method that you need to implement
App.PostSerializer = DS.RESTSerializer.extend({
extractSingle: function(store, type, payload, id, requestType) {
if(typeof payload.post.reporter !== "undefined") {
var reporter_id = payload.post.reporter.id;
payload.users = [payload.post.reporter];
payload.post.reporter = reporter_id;
}
return this._super.apply(this, arguments);
}
});
Here's a jsbin http://jsbin.com/EKItexU/1/edit?html,js,output
Note, that I had to redefine ajax method in RESTAdapter to emulate the server returning your JSON.
Also, if you are sideloading users in your JSON, than you'll have to update this method so it doesn't overwrite sideloaded users in your payload (payload.users property)
Support for embedded records is gone (for now).
You can handle embedded records yourself by implementing extractSingle and reorganizing your JSON payload.
Please read here for more info about the transition: https://github.com/emberjs/data/blob/master/TRANSITION.md#embedded-records
Hope it helps.
I have a backbone setup for Workflows. There are multiple workflows/models visible on the page at any given time. If a workflow is not started yet (i.e. not in the database), it is displayed as a New model. Having trouble with passing the fetched model to the view. I'm only passing the attributes rather than the collection it seems? Anyone have any ideas?
When I fetch all workflows from the database, it returns a JSON array of workflows progress.
[{
"ref_id": 41959,
"parent_ref_id": 35,
"fk_workflow_id": 2,
"stage": 1,
"status_max": 4,
"updated": "2012-05-14 17:30:46"
}, {
"ref_id": 41960,
"parent_ref_id": 35,
"fk_workflow_id": 3,
"stage": 3,
"status_max": 4,
"updated": "2012-05-14 12:30:48"
}]
When the page is loaded, all workflows are created as "new models", because we don't know if they've been started yet. Once the fetch has returned the models that have been started, I have to pass the appropriate model to the view. I'm having trouble figuring out how to pass the model properly (i.e. I can pass the attributes, but it seems after that, I don't have access to the collection.create functions etc?)
var self = this;
this.workflows = new Workflow.WorkflowCollection();
this.workflows.fetch({
data:{
ref_id: REF_ID
},
success: function(model, response) {
for(var i = 0, ln = model.models.length; i< ln;i++) {
self.switchWorkflow(model.models[i].get("fk_workflow_id"), new Workflow(model.models[i].attributes));
}
}
});
This is the only way I can figure backbone knows how many models it needs to setup (short of initiating each of the workflows for each customer - ~1million rows)
setupWorkflowSpaces: function(model) {
for(var i =0; i<Workflow.FileWorkflows.length;i++) {
this.switchWorkflow(Workflow.FileWorkflows[i], false);
}
},
switchWorkflow: function(workflow_id, model) {
if(!model)
model = new Workflow;
switch(workflow_id) {
case 1:
this.applicationProcessView(model);
break;
case 2:
this.kitView(model);
break;
case 3:
this.posView(model);
break;
default:
alert('wut');
break;
}
},
kitView: function(model) {
console.log(model);
model.set("fk_workflow_id", 2);
if(this.kit) this.kit.close();
this.kit = new Workflow.PostageView({
model: model
});
},
Try defining the model property in your Backbone Collection:
var Workflow.WorkflowCollection = Backbone.Collection.extend({
model: Workflow
});
If defined, the raw attributes coming back from your server will be converted into a Workflow model.
Then you won't have to iterate through the collection that is returned via the success callback in fetch. All the added models will be in your collection and you can use the .create() method to continue adding more.
After fetch you can iterate through the updated collection using WorflowCollection.each. In the loop you can run your switch statement to assign the each model to an appropriate view.
I hope this helps.