I am trying to learn backbone and I was following along the code school backbone.js course to build my own backbone app. So far I have this code but I am having problems with rendering anything.
var PostsApp = new (Backbone.View.extend({
Collections: {},
Models: {},
Views: {},
start: function(bootstrap){
var posts = new PostsApp.Collections.Posts(bootstrap.posts);
var postsView = new PostsApp.Views.Posts({collection: posts});
this.$el.append(postsView.render().el);
}
}))({el : document.body});
PostsApp.Models.Post = Backbone.Model.extend({});
PostsApp.Collections.Posts = Backbone.Collection.extend({});
PostsApp.Views.Post = Backbone.View.extend({
template: _.template("<%= name %>"),
render: function(){
this.$el.html(this.template(this.model.toJSON()));
}
});
PostsApp.Views.Posts = Backbone.View.extend({
render: function(){
this.collection.forEach(this.addOne, this);
},
addOne: function(post){
var postView = new PostsApp.Views.Post({model:post});
this.$el.append(postView.render().el);
}
});
var bootstrap = {
posts: [
{name:"gorkem"},
{name: "janish"}
]
}
$(function(){
PostsApp.start(bootstrap);
});
I am just trying to create a very simple backbone app, CodeSchool is great but it not good at combining the pieces together and when I try to do that myself I am having problems.
So far the error I am getting is "Uncaught TypeError: Cannot read property 'el' of undefined" in the addOne function of the Posts View. Any help would be much appreciated.
edit: The answer below solved my initial problem, but I also set up an express server to send data to the front end with the code :
app.get('/tweet', function(req,res){
res.send([{ name: 'random_name' }, {name: 'diren_gezi'}] );
});
and then I am trying to fetch this data to my collection like this :
var PostsApp = new (Backbone.View.extend({
Collections: {},
Models: {},
Views: {},
start: function(bootstrap){
var posts = new PostsApp.Collections.Posts(bootstrap.posts);
posts.url = '/tweet';
posts.fetch();
var postsView = new PostsApp.Views.Posts({collection: posts});
postsView.render();
this.$el.append(postsView.el);
}
}))({el : document.body});
But in the page the initial data (name: gorkem and name: janish) is displayed instead of the recently fetched data..
This is the problem line (I see it in a few spots).
this.$el.append(postsView.render().el);
Try changing it to
postsView.render();
this.$el.append(postsView.el);
Render function doesn't return a function to self (the object with a reference to el).
Related
I have a model that looks like this:
var BasicModel = Backbone.Model.extend({
defaults: {
a: '',
b: '',
c: '',
d: '',
e: ''
},
idAttribute: "f",
parse: function (data) {
return data;
},
initialize: function () {
console.log('Intialized');
},
constructor: function (attributes, options) {
Backbone.Model.apply(this, arguments);
}
});
Collections like this:
var BasicCollection = Backbone.Collection.extend({
model: BasicModel,
url: urlCode
});
var ACollection = BasicCollection.extend({
parse: function (data) {
return data.a.b.c.d;
}
});
var aCollection = new ACollection ();
And Views like this:
var BasicView = Backbone.View.extend({
tagName: 'tr',
template: _.template($('#basic-status-template').html()),
render: function () {
this.$el.html(this.template(this.model.attributes));
return this;
}
});
var BasicsView = Backbone.View.extend({
initialize: function () {
this.render();
},
});
This is how the collection fetch looks (Which builds the views):
aCollection.fetch({
success: function () {
// View
var aView = BasicsView.extend({
el: '#foobar #table-body',
render: function () {
this.$el.html('');
aCollection.each(function (model) {
var x = new BasicView({
model: model
});
this.$el.append(x.render().el);
}.bind(this));
return this;
}
});
var app = new aView();
}
});
But now I face a problem when trying to add another piece of detail to the tables that the views will populate. One of the columns will require data that will come from a seperate url. But I still want it to be part of the same process.
Is there are way to form a collection from the result of two URL's. (i.e. a, b, d and e come from URL 1, and c comes from URL 2)?
This way all I would need to change was the template and it should all work the same. Instead of having to alter a load of other stuff as well.
Thanks.
You have few options:
Update the endpoint to send required data. This is the proper way to do it. Collection should Ideally have single endpoint
Send a seperate AJAX request to get data from one URL before fetching collection, then in collection's parse method add the data to the response fetched from collection's URL
Do something like:
$.when(collection.fetch(), collection.fetchExtraData())
.done(()=> { /* create view here */ });
fetchExtraData here is a custom function that sends extra request and updates collection properly with the data. This way both requests are sent simultaneously. You need to make sure parse doesn't reset the data from other endpoint.
I have properly coded a simple REST api and several backbone models. My parent model is called Topic and child model called Questions.
I'm trying to call a get method on the REST api and display the received Topic object to the user in a presentable manner. I am receiving the json (can be seen in the network tab on Chrome), but it is not getting sent to the view correctly.
Model:
var Topic = Backbone.Model.extend({
urlRoot: ROOT + '/topic',
idAttribute: 'topicId',
initialize: function () {
this.questions = new Questions([], {parent: this});
},
toJSON: function () {
var json = Backbone.Model.prototype.toJSON.call(this);
json.questions = this.questions.toJSON();
return json;
}
});
var Topics = Backbone.Collection.extend({
model: Topic,
url: ROOT + 'topic',
parse: function (response) {
return response.results;
}
})
REST URL:
http://localhost/Project/index.php/rest/resource/topic/
Backbone View: This is where I think the error is...(console log below prints an empty object)
var TopicListView = Backbone.View.extend({
el: '.page',
render: function () {
var that = this;
var topics = new Topics();
topics.fetch({
success: function (topics) {
console.log(topics);
var template = _.template($('#topic-list-template').html(), {topics: topics.models});
that.$el.html(template);
}
})
}
});
Using the above functions:
var topic = new Topic();
topic.fetch();
topicListView = new TopicListView();
var Router = Backbone.Router.extend({
routes: {
"": "home"
}
});
var router = new Router;
// render topic list for 'home'
router.on('route:home', function () {
topicListView.render();
});
Edit: Solution: Overriding the parse function in the collection proved to be the error. I wonder why...
The argument topics in your success handler is shadowing the variable topics.
The argument contains the parsed JSON response, not the Backbone Collection. You don't need that, so you can remove the argument.
The reference to topics will now be to your Collection, so topics.models will have the value you expect.
topics.fetch({
success: function () { // argument removed here so `topics` is no longer shadowed
var template = _.template($('#topic-list-template').html(), { topics: topics.models });
that.$el.html(template);
}
})
I am fairly new with Marionette.js and seem to be having some trouble rendering a Collection View.
I am receiving the following console error message when trying to show the view: Uncaught TypeError: Cannot read property 'toJSON' of undefined.
It seems that the collection is not binding to the child view. The instance of the Collection View looks OK, meaning I see the childView property and the collection property with the fetched models. The docs seem pretty straightforward on how to construct a collection view so not sure what I'm missing. Thanks.
Child view:
var UserProfile = Marionette.ItemView.extend({
template: '',
initialize: function() {
this.template = Marionette.TemplateCache.get("#userprofile");
},
render: function() {
console.log(this.collection); //undefined
var data = this.collection.toJSON(); //error message here
this.$el.html(this.template({data:data}));
}
});
//Collection view:
var UserView = Marionette.CollectionView.extend({
childView: UserProfile
});
// Fetch collection and show:
var myQuery = new Parse.Query(app.Models.User);
myQuery.limit(200).containedIn(option, uiArray);
var Contacts = Parse.Collection.extend({
model: app.models.User,
query: myQuery
});
var contacts = new Contacts();
contacts.fetch().then(function(contacts) {
var userview = new UserView({collection:contacts});
app.regions.get("content").show(userview);
})
You are asking a "collection" in an ItemView. ItemView operates with an item. So it make sense to write something like this:
render: function() {
console.log(this.model); //undefined
var data = this.model.toJSON(); //error message here
this.$el.html(this.template({data:data}));
}
});
I'm trying to learn Backbone by diving right in and building out a simple "question" app, but I've been banging my head against the wall trying to figure out how to use models and/or collections correctly. I've added the code up to where I've gotten myself lost. I'm able to get the collection to pull in the JSON file (doing "var list = new QuestionList; list.getByCid('c0') seems to return the first question), but I can't figure out how to update the model with that, use the current model for the view's data, then how to update the model with the next question when a "next" button is clicked.
What I'm trying to get here is a simple app that pulls up the JSON on load, displays the first question, then shows the next question when the button is pressed.
Could anyone help me connect the dots?
/questions.json
[
{
questionName: 'location',
question: 'Where are you from?',
inputType: 'text'
},
{
questionName: 'age',
question: 'How old are you?',
inputType: 'text'
},
{
questionName: 'search',
question: 'Which search engine do you use?'
inputType: 'select',
options: {
google: 'Google',
bing: 'Bing',
yahoo: 'Yahoo'
}
}
]
/app.js
var Question = Backbone.Model.Extend({});
var QuestionList = Backbone.Collection.extend({
model: Question,
url: "/questions.json"
});
var QuestionView = Backbone.View.extend({
template: _.template($('#question').html()),
events: {
"click .next" : "showNextQuestion"
},
showNextQuestion: function() {
// Not sure what to put here?
},
render: function () {
var placeholders = {
question: this.model.question, //Guessing this would be it once the model updates
}
$(this.el).html(this.template, placeholders));
return this;
}
});
As is evident, in the current setup, the view needs access to a greater scope than just its single model. Two possible approaches here, that I can see.
1) Pass the collection (using new QuestionView({ collection: theCollection })) rather than the model to QuestionView. Maintain an index, which you increment and re-render on the click event. This should look something like:
var QuestionView = Backbone.View.extend({
initialize: function() {
// make "this" context the current view, when these methods are called
_.bindAll(this, "showNextQuestion", "render");
this.currentIndex = 0;
this.render();
}
showNextQuestion: function() {
this.currentIndex ++;
if (this.currentIndex < this.collection.length) {
this.render();
}
},
render: function () {
$(this.el).html(this.template(this.collection.at(this.currentIndex) ));
}
});
2) Set up a Router and call router.navigate("questions/" + index, {trigger: true}) on the click event. Something like this:
var questionView = new QuestionView( { collection: myCollection });
var router = Backbone.Router.extend({
routes: {
"question/:id": "question"
},
question: function(id) {
questionView.currentIndex = id;
questionView.render();
}
});
I am trying to implement a simple app which is able to get a collection for a given object_id.
The GET response from the server looks like this:
[
{object_id: 1, text: "msg1"},
{object_id: 1, text: "msg2"},
{object_id: 1, text: "msg3"},
.......
]
My goal is:
render a collection when the user choose an object_id.
The starting point of my code is the following:
this.options = {object_id: 1};
myView = new NeView(_.extend( {el:this.$("#myView")} , this.options));
My question is:
* What is the best way:
1) to set the object_id value in the MyModel in order to
2) trigger the fetch in MyCollection and then
3) trigger the render function in myView?* or to active my goal?
P.S:
My basic code looks like this:
// My View
define([
"js/collections/myCollection",
"js/models/myFeed"
], function (myCollection, MyModel) {
var MyView = Backbone.View.extend({
initialize: function () {
var myModel = new MyModel();
_.bindAll(this, "render");
myModel.set({
object_id: this.options.object_id
}); // here I get an error: Uncaught TypeError: Object function (){a.apply(this,arguments)} has no method 'set'
}
});
return MyView;
});
// MyCollection
define([
"js/models/myModel"
], function (MyModel) {
var MyCollection = Backbone.Collection.extend({
url: function () {
return "http://localhost/movies/" + myModel.get("object_id");
}
});
return new MyCollection
});
//MyModel
define([
], function () {
var MyModel = Backbone.Model.extend({
});
return MyModel
});
There's a few, if not fundamentally things wrong with your basic understanding of Backbone's internals.
First off, define your default model idAttribute, this is what identifies your key you lookup a model with in a collection
//MyModel
define([
], function () {
var MyModel = Backbone.MyModel.extend({
idAttribute: 'object_id'
});
return MyModel
});
in your collection, there is no need to define your URL in the way you defined it, there are two things you need to change, first is to define the default model for your collection and second is to just stick with the base url for your collection
// MyCollection
define([
"js/models/myModel"
], function (MyModel) {
var MyCollection = Backbone.MyCollection.extend({
model: MyModel, // add this
url: function () {
return "http://localhost/movies
}
});
return MyCollection // don't create a new collection, just return the object
});
and then your view could be something along these lines, but is certainly not limited to this way of implementing
// My View
define([
"js/collections/myCollection",
"js/models/myFeed"
], function (MyCollection, MyModel) {
var MyView = Backbone.View.extend({
tagName: 'ul',
initialize: function () {
this.collection = new MyCollection();
this.collection.on('add', this.onAddOne, this);
this.collection.on('reset', this.onAddAll, this);
},
onAddAll: function (collection, options)
{
collection.each(function (model, index) {
that.onAddOne(model, collection);
});
},
onAddOne: function (model, collection, options)
{
// render out an individual model here, either using another Backbone view or plain text
this.$el.append('<li>' + model.get('text') + '</li>');
}
});
return MyView;
});
Take it easy and go step by step
I would strongly recommend taking a closer look at the exhaustive list of tutorials on the Backbone.js github wiki: https://github.com/documentcloud/backbone/wiki/Tutorials%2C-blog-posts-and-example-sites ... try to understand the basics of Backbone before adding the additional complexity of require.js