simple backbone js view not working - backbone.js

I just copy pasted my previous code and still my code is not working. I am newbie and all i do is copy and paste from examples. And still its not working.
index.html
<body>
<div id="date">Loading...</div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.3/underscore-min.js" type="text/javascript"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/backbone.js/0.9.2/backbone-min.js" type="text/javascript"></script>
<script type="text/javascript" src="model.js"></script>
<script type="text/javascript" src="views.js"></script>
</body>
model.js
var model = Backbone.Model.extend({});
collection = Backbone.Collection.extend({
url:'date.json',
model:model,
parse:function(response){
return response;
}
});
date = new collection();
date.fetch();
view.js
var DateView = Backbone.View.extend({
el: $('#date'),
initialize: function(){
_.bindAll(this, 'render');
},
render: function(eventName) {
console.log(this.collection.length); //Throwing 0 as output
}
});
var dateview = new DateView({collection:date});
dateview.render();
date.json
[
{"date":"1288323623006"}
]

You do not have a binding for the collections changeor resetevent. Also your render method does not do anything to actually render the content into the html.
The date.fetch command (without any error handling!) is executed asynchronously, so it is likely to return when the render of the view has already been executed. Thus you will need an apropriate binding for the collections' events, like this:
view.js
var DateView = Backbone.View.extend({
el: $('#date'),
initialize: function(){
_.bindAll(this, 'render');
this.collection.on("change", this.render);
this.collection.on("reset", this.render);
},
render: function(eventName) {
console.log(this.collection.length); //Throwing 0 as output
return(this); // used for chaining
}
});
var dateview = new DateView({collection:date});
dateview.render();

In your flow, you have included model.js where it does a fetch
date.fetch();
This is an async method, which means it goes off to fetch the data, and javascript continue to run the next line of code.
Then in your view when you do this.collection.length, (which essentially is the date object), this collection may have not been retrieved from the server yet, meaning it is still empty. A proper way to render stuff after fetch is
1. bind the render to fetch, like Exinteris mentioned
date.fetch({reset:true}),
this will fire a "reset" event after the collection got response from server. In view.js
this.collection.on("reset",this.render);
meaning it will render after a reset event is fired.
do render in a promise manner. If you are new to this check here
promise
var self=this;
date.fetch().then(function(){
self.render();
});
Also, it is always a good practice to do fetch inside a view, not in model.js. For example:
var DateCollection=Backbone.Collection.extend(...);
var DateView = Backbone.View.extend({
el: $('#date'),
initialize: function(){
_.bindAll(this, 'render');
this.collection=new DateCollection();
this.collection.on('reset',this.render());
this.collection.fetch({reset:true});
},
render: function(eventName) {
console.log(this.collection.length); //Throwing 0 as output
}
});

Related

Backbone: fetch method can not update the view although the element content is correct

In Backbone app, I need to use localstorage to save some history data. And when the app was loaded for the second time, the history data will be loaded to the page
And the app is still the ToDo task app, the difference, I want to support multiple days record.
so the data structure for my app is as following: one collection (DayCollection), and the model of the collection (Daymodel). Here I use backbone relation extension to support the nested relationship, there is the second model (Todo). And Daymodel and Todo has 1:many relationship.
// A simple todo model
var app = {};
Todo = Backbone.RelationalModel.extend({
defaults: {
title: "New Todo",
completed : true
}
});
var Daymodel = Backbone.RelationalModel.extend({
day: 1,
relations: [{
type: Backbone.HasMany,
key: 'agenda',
relatedModel: 'Todo',
}]
});
var DayCollection = Backbone.Collection.extend({
model: Daymodel,
//localStorage: new Backbone.LocalStorage("test")
});
// A view for an individual todo item
var TodoView = Backbone.View.extend({
tagName: "li",
template: _.template( $('#eachitem-template').html() ),
initialize: function() {
this.listenTo(this.model, "change", this.render);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
// The view for each day panel
var DayView = Backbone.View.extend({
tagName:"div",
template: _.template( $('#eachday-template').html() ),
initialize: function() {
//console.log("call Dayview init")
this.listenTo(this.model, "change", this.render);
this.listenTo(this.model.get("agenda"), "add",this.addNewTodo);
//this.listenTo(this.model,"reset",this.addAll);
//this.model.fetch({reset: true});
//this.model.get("agenda").fetch();
//console.log("current model")
//console.log(this.model);
},
render: function(){
this.$el.html(this.template(this.model.toJSON()));
return this;
},
addNewTodo: function(todo){
//console.log("debugging addNewTodo");
//console.log(todo);
var newTodoView = new TodoView({model:todo})
//console.log("generated new todo view");
//console.log(newTodoView.render().el);
this.$("ul").append(newTodoView.render().el);
//console.log(this.$("ul").html());
},
addAll: function(){
this.model.get("agenda").each(function(eachitem){
var newTodoView = new TodoView({model:eachitem});
this.$("ul").append(newTodoView.render().el);
});
}
});
// The view for the entire application
var AppView = Backbone.View.extend({
el: $('#todoapp'),
events: {
"click #add-todo" : "createTodo",
"click #add-firebase":"addToFirebase"
},
initialize: function() {
this.daylist = this.$("#container"); // the daylist to append to
this.input = this.$("#new-todo"); // the textbox for new todos
// by listening to when the collection changes we
// can add new items in realtime
this.listenTo(this.collection, 'add', this.addOne);
this.listenTo(this.collection,'reset', this.addAll);
//this.collection.fetch({reset:true});
},
addOne: function(todo) {
//console.log("debugging add one more day")
//console.log(todo)
var view = new DayView({model:todo});
this.daylist.append(view.render().el);
},
addAll: function(){
var self = this;
this.collection.each(function(eachday){
self.addOne(eachday)
});
},
createTodo: function(e) {
if (!this.input.val()) { return; }
// create a new location in firebase and save the model data
// this will trigger the listenTo method above and a new todo view
// will be created as well
//this.collection.last().get("agenda").add({
this.collection.last().get("agenda").add({
title: this.input.val()
});
//this.collection.last().save();
this.input.val('');
},
addToFirebase: function(){
//this.collection.add({
this.collection.create({
day : this.collection.length + 1,
agenda: []
});
}
});
// Create a function to kick off our BackboneFire app
function init() {
// The data we are syncing from our remote Firebase database
var collection = new DayCollection();
var app = new AppView({ collection: collection });
}
// When the document is ready, call the init function
$(function() {
init();
});
<div id="todoapp">
<div id="container">
</div>
<input type="text" id="new-todo" placeholder="New Todo" />
<button id="add-todo">Add New Task</button>
<button id="add-firebase">Add New Day</button>
</div>
<script type="text/template" id="eachday-template">
<h3 class="which-day"> day <%= day %></h3>
<ul id="todo-list"></ul>
</script>
<script type="text/template" id="eachitem-template">
<p class="item-content"><%= title %></p>
</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone-relational/0.10.0/backbone-relational.min.js"></script>
I remove the non-business code, only show the related code here. To make it easier to read the post.
AppView is called with the DayCollection and for each day the DayView will work for that (for each todo, there is another view, I didn't put it here).
And the current status is: the localstorage data is correct (I use debugging method to verify) and the Day level view can work well. The problem, the todo tasks for each day can't be fetched to the page.
In fact I use console.log(this.$("ul").html()); this line to verify, in fact the element content is also correct. It just can't update to view.
so any idea about this?
Edit:
I make use of the Stack snippet tool to show the demo, but I think localstorage can't work within the environment, so I comment off the fetch and save method.
The behavior is: click "Add New Day" button will add a new day panel like day1, day2 and go on. And click the "Add New Task" button will add a new task to the last Day panel.
With localstorage, I hope to show all Days' data with their tasks when the page loaded again.

Backbone code in doc ready but not working

I'm going to lose it!!! how can this not be working
$(function() {
var someView = new Backbone.View.extend({
el: $('.whatever'),
...
render: function() {
this.$el.append('AAARGH');
}
});
// init view...
});
Cannot call method append of undefined, yet it is in ready block.
Try
this.$el
Instead of
this.el

Backbone nested views - child view attributes incorrectly set in the template

I'm trying to create a Backbone Model (lets call it Library) which contains a Collection of other Models (lets call them Books). I am providing a view - LibraryView, which creates an HTML of a book case with a set of books represented by HTML generated by BookView. Also, I am using Handlebars.js as my templating system.
The issue I am experiencing is that my BookView returns weird html on this.el element, before I even pass it through render() function.
LibraryModel Model
var LibraryModel = Backbone.Model.extend({
initialize: function() {
var books = new BookCollection();
_.each(book_data.books, function(value, index) {
books.add(new Book());
});
this.books = books;
}
});
LibraryView View:
var LibraryView = Backbone.View.extend({
el: "#library",
render: function() {
var t = this;
this.model.books.each(function(book, index) {
//create new view for each Book model in the books collection
var view = new BookView(book);
//append HTML produced by the BookView into LibraryView el element
t.$el.append(view.render().$el);
});
return this;
},
initialize: function() {
//snip
}
});
BookView View:
var BookView = Backbone.View.extend({
render: function() {
var viewmodel = this.model;
var source = $("#book-template").html();
var template = Handlebars.compile(source);
var html = template(viewmodel.toJSON());
console.log(html); //prints <div test="wtf" anotherTest="123"><b>wtf</b> 123</div>
this.$el.html(html);
return this;
},
initialize: function(book) {
console.log(this.el.outerHTML); //prints <div test="wtf" anotherTest="123"></div>
this.model = book;
this.listenTo(this.model, "change", this.render);
}
});
Template I am providing is: <b>{{test}}</b> {{anotherTest}}
BookModel Model
var BookModel = Backbone.Model.extend({
defaults: {
test: "wtf",
anotherTest: 123
},
initialize: function() {
//snip
}
});
Basically, the issue I am exeperiencing is that my BookView produces weird HTML where each of my model attributes is attached to the Backbone-generated div, like this:
<div test="wtf" anotherTest="123">
<b>wtf</b> 123
</div>
I am not setting any of the attributes anywhere else in the code - both values are only coming from the defaults.
Also, I confirmed this is not something that Handlebars is doing, as Model-attributes are inserted as HTML-atributes into Backbone generated div of the BookView model (note, I am not providing tagName or el manually, I want Backbone to create a div for me).
So here is where I am stuck. I have a perfectly working list of HTML generated by BookView for each of my models in the list, but for some reason Backbone-generated div wrapper contains each of Model-attributes in its HTML-attributes, like so:
<div id="#library">
<div test="wtf" anotherTest="123"><b>wtf</b> 123</div>
<div test="wtf" anotherTest="123"><b>wtf</b> 123</div>
<div test="wtf" anotherTest="123"><b>wtf</b> 123</div>
</div>
I am really pulling my hair out over this and I have suspicion it has something to do with the fact I am trying to use View-in-a-View.
Have you encountered similar problems before? Have you got good examples of Backbone application where MasterView renders collection of ChildViews?
You are doing a weird thing when creating new BookViews. That view is expecting a BookModel to come in its initialization. However, the initialize() method always expects an object which is called attributes by convention. You should modify your BookView so it matches this:
var BookView = Backbone.View.extend({
render: function() {
// keep the same code
},
initialize: function(attributes) {
this.listenTo(this.model, "change", this.render);
}
});
and in your LibraryView you should use:
var view = new BookView({ model : book });
instead of the previous:
var view = new BookView(film); // which is a typo, and should pass book and not film
So now you get your expected result.
<div id="library">
<div><b>wtf</b> 123</div>
<div><b>wtf</b> 123</div>
<div><b>wtf</b> 123</div>
</div>
Keep in mind that initialize(attributes) will automatically call set with that mapping, so you don't have to call set(attr, value) yourself. With that in mind, we can understand why that view is having two attributes which are in fact your two model's attributes (they are being set() on initialization).

backbone render not rendering select tags

I've got a simple div into which I'd like backbone to render a select box and options from my server.
The options seem to render just fine, but the select box does not. I'm sure it's a simple tweak, but can't seem to find it.
I created a simplified fiddle for it: http://jsfiddle.net/thunderrabbit/BNZY3/
The HTML
<div id="where_fields"></div>
The script I'm using uses fetch() to get the data. The Fiddle above hardcodes the data, but the issue is the same.
(function($){
var Field = Backbone.Model.extend();
var UnitFields = Backbone.Collection.extend({
url: '/<?php echo CONFIG_ADMIN_DIR; ?>/api/fieldnames/units',
model: Field
});
var BuildingFields = Backbone.Collection.extend({
url: '/<?php echo CONFIG_ADMIN_DIR; ?>/api/fieldnames/buildings',
model: Field
});
var FieldView = Backbone.View.extend({
tagName: "option",
initialize: function(){
_.bindAll(this, 'render');
},
events: {
"click":"clicked"
},
clicked: function(e) {
var data_type = this.model.get("DATA_TYPE");
if(data_type == "varchar") {
console.log("it's a varchar");
}
if(data_type == "int") {
console.log("it's an int");
}
},
render: function(){
$(this.el).attr('value', this.model.get('COLUMN_NAME')).html(this.model.get('display_name'));
return this;
}
});
var FieldsView = Backbone.View.extend({
tagName: "select",
el: $('#where_fields'),
initialize: function(){
_.bindAll(this, 'render', 'renderItem');
this.collection.bind('reset', this.render);
},
renderItem: function(model) {
console.log('rendr item');
var fieldView = new FieldView({model:model});
fieldView.render();
$(this.el).append(fieldView.el);
},
render: function(){
console.log('rendr');
this.collection.each(this.renderItem);
return this;
}
});
var units_fields = new UnitFields();
var buildings_fields = new BuildingFields();
var unitsView = new FieldsView({collection: units_fields});
var buildingsView = new FieldsView({collection: buildings_fields});
units_fields.fetch();
buildings_fields.fetch();
})(jQuery);
Why is my backbone script not rendering the select tags?
You have both tagName and el attributes in your FieldsView class. You don't need both. Use tagName if you want to render a view detached from the DOM and then backbone will use that tag instead of the default of div. However, in your render(), you don't ever actually get a select tag involved. $(this.el) is your #where_fields div and you just append fieldView.el, which is an option element. That's why there is no select element. Some quick tips:
use this.$el as a more efficient shorthand for $(this.el)
It's preferable to keep your view loosely coupled from the DOM, so el: $('#where_fields') is not as clean a design as rendering an element detached from the DOM and letting other code decide where exactly in the existing DOM it should be attached.
So you should remove your el properly, set tagName to select if you like, then your render() method will be doing what you want with is appending options to a select tag, then move the actual code to append your view's rendered el to the #where_fields div out of the view into your router perhaps.

Backbone.js el is not working

App.Views.VideoView = Backbone.View.extend({
initialize: function() {
_.bindAll(this, 'render');
this.model = this.options.model;
this.render();
},
render: function() {
JST.video({
model: this.model
});
return this;
}
});
App.Views.PlayListView = Backbone.View.extend({
el: $("#playlistWrapper"),
initialize: function(videos) {
_.bindAll(this, 'render');
this.modelViews = $.map(videos.models, function(model, i) {
return new App.Views.VideoView({
model: model
});
})
this.render();
},
render: function() {
var that = this;
$(this.el).clear();
$.each(this.modelViews, function(i, modelView) {
$(that).el.append(modelView.render().el);
});
return this;
}
});
i am always getting below error
$(this.el).clear is not a function
[Break On This Error] $(this.el).clear();
it seems my el of PlayerListView is empty.
i have div with id playlistWrapper.
if i use jquery selector for playlistWrapper it gives proper element.
what am i doing wrong.
I'm a little late to the party on this, but the problem is that you're specifying a jquery selector before the DOM is loaded.
A backbone object is defined with an object literal passed in to the extend method. For example, the following are functionally the same:
MyView = Backbone.View.extend({
el: "#foo"
});
var viewObj = {el: "#foo"};
MyView2 = Backbone.View.extend(viewObj);
values on the right-hand side of a key/value pair in an object literal are parsed and executed immediately. this means that a jQuery selector used for el will be parsed as soon as you declare it, not when the view is instantiated. chances are, you have your javascript file included in your app and it's being downloaded before the DOM is loaded, so the jquery selector can't find the element you're referring to.
There are a number of things you can do to work around this.
you can call $(this.el) whenever you need to use the element
you can set this.el in the view initializer
you can set {el: $("#whatever")} as a parameter to the view constructor, assuming the view is constructed after the DOM has loaded
you can use the javascript module pattern to defer definition of the views and other backbone objects until after the DOM is loaded
and probably a handful of other options that i'm not thinking of at the moment
Well clear is not a jQuery function... You might be looking for empty?
Comments on your code:
In you video view:
no need to assign the model from the options, this is done for you
you might want to append the result of the templating (JST) to this.el otherwise nothing will show up...
In your playlist view:
in your render, in your each loop, change $(that).el to $(that.el)
since you define el as a jQuery, you do not need to use $(this.el) over and over
use this.$el.clear();
and update jquery.js file like
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"> </script>
and first Bind Initialize then el: bind.

Resources