I'm trying to make a nested view. Backendview call ListPostView and ListPostView call SinglePostView. ListPostview and SinglePostView recursively creates a list by a collection.
BackendView is used only to wrap the list in a page html.
The collection passed to BackendView is retrieved by fetch and by method reset.
The problem is I can't render my collection and error is "undefined" inside SinglePostView.
If I call directly ListPostView it works perfectly.
I think maybe depends by event "bind" inside initialize function.
This is collection:
var Attori = Backbone.Collection.extend({
model:Attore,
idAttribute: "id",
fetch: function(options) {
var collection = this;
var cb = new Codebird;
cb.setConsumerKey("1Cx*mfA", "YedD*4s");
cb.__call(
"oauth2_token",
{},
function (reply) {
var bearer_token = reply.access_token;
console.log(bearer_token);
cb.setBearerToken(bearer_token);
}
);
console.log(options);
cb.setToken("259**g4ONJYi2","z8LLm52M**PS");
var params = {
q: "jim carrey"
//screen_name:"brad"
};
cb.__call(
"users/search",
params,
function (reply) {
console.log(reply);
collection.reset(reply);
}
);
}
});
return Attori;
});
this is Backendview:
var BackendView = Backbone.View.extend({
tagName: "ul",
id: "list",
events: {
"touchend": "goToDetails"
},
template: Handlebars.compile(template),
initialize: function () {
this.collection.bind("reset", this.render, this);
},
render: function (eventName) {
console.log(this.collection.length);
/* _.each(this.collection.models, function (ad) {
$(this.el).append(new ListPostView({
collection: ad
}).render().el);
}, this);*/
/* $(this.el).append(new ListPostView({
collection: this.collection
}).render().el);*/
if (typeof this.collection !== 'undefined' && this.collection.length > 0) {
// the array is defined and has at least one element
var List=new ListPostView({collection:this.collection});
//List.render();
}
//console.log(List);
return this;
},
goToDetails: function () {
Parse.history.navigate("ads/" + this.model.cid, {trigger: true});
}
});
return BackendView;
});
this is ListpostView:
var ListPostView = Backbone.View.extend({
tagName: "ul",
id: "list",
template: Handlebars.compile(template),
initialize: function () {
console.log(this.collection);
this.collection.bind("reset", this.render, this);
},
render: function (eventName) {
console.log(this.collection.models);
$(this.el).empty();
_.each(this.collection.models, function (a) {
$(this.el).append(new SinglePostView({
model: a
}).render().el);
}, this);
return this;
}
});
return ListPostView;
});
and this is SinglePostView:
var SinglePostView = Backbone.View.extend({
tagName: "li",
events: {
"touchend": "goToDetails"
},
template: Handlebars.compile(template),
initialize: function () {
console.log(this.model);
this.model.bind("change", this.render, this);
this.model.bind("destroy", this.close, this);
},
render: function (eventName) {
var ad = this.model.toJSON();
ad.cid = this.model.cid;
$(this.el).html(this.template(ad));
return this;
},
goToDetails: function () {
Parse.history.navigate("ads/" + this.model.cid, {trigger: true});
}
});
return SinglePostView;
});
Related
I'm generating a drop down list from Backbone.View.
After attaching it to the DOM, change event is not fired. The delegateEvents doesn't fixes it. Can somebody show me where the blind spot is?
Model and collection:
App.Models.DictionaryItem = Backbone.Model.extend({
default: {
key: '',
value: '', id: 0
}
});
App.Collections.Dictionary = Backbone.Collection.extend({
model: App.Models.DictionaryItem,
initialize: function (models, options) {
},
parse: function (data) {
_.each(data, function (item) {
// if(item){
var m = new App.Models.DictionaryItem({ key: item.code, value: item.name });
this.add(m);
// }
}, this);
}
});
Views:
App.Views.ItemView = Backbone.View.extend({
tagName: 'option',
attributes: function () {
return {
value: this.model.get('key')
}
},
initialize: function () {
this.template = _.template(this.model.get('value'));
},
render: function () {
this.$el.html(this.template());
return this;
}
});
App.Views.CollectionView = Backbone.View.extend({
tagName: 'select',
attributes: {
'class': 'rangesList'
},
events: {
'change .rangesList': 'onRangeChanged'
},
initialize: function (coll) {
this.collection = coll;
},
render: function () {
_.each(this.collection.models, function (item) {
this.$el.append(new App.Views.ItemView({ model: item }).render().el);
}, this);
// this.delegateEvents(this.events);
return this;
},
selected: function () {
return this.$el.val();
},
onRangeChanged: function () {
alert('changed');
}
});
Rendering:
var coll = new App.Collections.Dictionary(someData, { parse: true });
var v= new App.Views.CollectionView(coll);
var vv=v.render().el;
// new App.Views.CollectionView(coll).render().el;
$('body').append(vv)
The tagName and attributes on CollectionView:
tagName: 'select',
attributes: {
'class': 'rangesList'
},
say that the el will be <select class="rangesList">. But your events:
events: {
'change .rangesList': 'onRangeChanged'
},
are listening to 'change' events from a .rangesList inside the view's el. From the fine manual:
Events are written in the format {"event selector": "callback"}. [...] Omitting the selector causes the event to be bound to the view's root element (this.el).
So you're trying to listen for events from something that doesn't exist. If you want to listen for events directly from the view's el then leave out the selector:
events: {
'change': 'onRangeChanged'
}
I have a dupe check within my collection, where I'm overriding the add function and it seems to work until a page refresh.
Duplicates are blocked with an alert saying "You've already added this item to the todo list!" but it seems like when the page refreshes, the duplicate is added to localStorage either way. Would love a solution to this issue -- been scratching my head for the past few days on this issue.
My collection below:
app.TodoList = Backbone.Collection.extend({
model: app.Todo,
localStorage: new Store("backbone-todo"),
completed: function() {
return this.filter(function(todo){
return todo.get('completed');
});
},
remaining: function(){
return this.without.apply(this, this.completed());
}
});
app.TodoList.prototype.add = function(todo) {
var isDupe = this.any(function(_todo){ return _todo.get('title').toLowerCase() === todo.get('title').toLowerCase();
});
return isDupe ? alert("You've already added this item to the todo list!") : Backbone.Collection.prototype.add.call(this, todo);}
// instance of the Collection
app.todoList = new app.TodoList();
Here is the model:
app.Todo = Backbone.Model.extend({
defaults: {
title: '',
completed: false
},
toggle: function(){
this.save({ completed: !this.get('completed')});
}
});
The View:
app.TodoView = Backbone.View.extend({
tagName: 'li',
template: _.template($('#item-template').html()),
render: function(){
this.$el.html(this.template(this.model.toJSON()));
this.input = this.$('.edit');
return this; // enable chained calls
},
initialize: function(){
this.model.on('change', this.render, this);
this.model.on('destroy', this.remove, this); // remove: 'Convenience Backbone'
},
events: {
'dblclick label' : 'edit',
'keypress .edit' : 'updateOnEnter',
'blur .edit' : 'close',
'click .toggle' : 'toggleCompleted',
'click .destroy' : 'destroy'
},
edit: function(){
this.$el.addClass('editing');
this.input.focus();
},
close: function(){
var value = this.input.val().trim();
if(value) {
this.model.save({ title: value });
}
this.$el.removeClass('editing');
},
updateOnEnter: function(e){
if(e.which == 13){
this.close();
}
},
toggleCompleted: function(){
this.model.toggle();
},
destroy: function(){
this.model.destroy();
}
});
// renders the full list of todo items calling TodoView for each one.
app.AppView = Backbone.View.extend({
el: '#todoapp',
initialize: function () {
this.input = this.$('#new-todo');
app.todoList.on('add', this.addAll, this);
app.todoList.on('reset', this.addAll, this);
app.todoList.fetch(); // Loads list from local storage
},
events: {
'keypress #new-todo': 'createTodoOnEnter'
},
createTodoOnEnter: function(e){
if ( e.which !== 13 || !this.input.val().trim() ) { // ENTER_KEY = 13
return;
}
app.todoList.create(this.newAttributes());
this.input.val(''); // clean input box
},
addOne: function(todo){
var view = new app.TodoView({model: todo});
$('#todo-list').append(view.
render().el);
},
addAll: function(){
this.$('#todo-list').html(''); // clean the todo list
// filter todo item list
switch(window, filter){
case 'pending':
_.each(app.todoList.remaining(), this.addOne);
break;
case 'completed':
_.each(app.todoList.completed(), this.addOne);
break;
default:
app.todoList.each(this.addOne, this);
break;
}
},
newAttributes: function(){
return {
title: this.input.val().trim(),
completed: false
}
}
});
The Router:
app.Router = Backbone.Router.extend({
routes: {
'*filter' : 'setFilter'
},
setFilter: function(params){
console.log('app.router.params = ' + params);
window.filter = params.trim() || '';
app.todoList.trigger('reset');
}
})
And the initializer:
app.router = new app.Router();
Backbone.history.start();
app.appView = new app.AppView();
If any more information is needed, would gladly provide it. Thanks!
In Backbone, when you call create, both add and save are called. Read the source here: http://backbonejs.org/docs/backbone.html#section-113
So you blocked the add from happening, but the save still happened when adding a duplicate.
You can use Backbone's built in validation to accomplish what you were trying to do:
app.Todo = Backbone.Model.extend({
defaults: {
title: '',
completed: false
},
initialize: function() {
this.on('error', function(model, error) {
alert(error);
});
},
toggle: function(){
this.save({ completed: !this.get('completed')});
},
validate: function(attrs, options) {
if ( this.collection.isExistingTodoTitleOnOtherTodo(attrs) ) {
return "You've already added this item to the todo list!";
}
}
});
app.TodoList = Backbone.Collection.extend({
model: app.Todo,
localStorage: new Store("backbone-todo"),
completed: function() {
return this.filter(function(todo){
return todo.get('completed');
});
},
remaining: function(){
return this.without.apply(this, this.completed());
},
isExistingTodoTitleOnOtherTodo: function(attrs) {
return this.any(function(todo) {
var titleMatch = todo.get('title').toLowerCase() === attrs.title.toLowerCase();
var idMatch = attrs.id === todo.id;
return titleMatch && !idMatch;
});
}
});
BTW, your Backbone is outdated so the docs on the site don't reflect what you can do in your code.
i'm newbie, trying to learn Backbone.js, and i've got this error:
Cannot convert 'this.model' to object
I'm trying to build an easy architecture for audioplayer, but this error makes me mad! Don't understand why it happens, but browser console shows error in line this.model.bind("reset", this.render, this) of ListOfSongsView.
Here's my code:
$(function () {
var Player = Backbone.Model.extend({
defaults:{
//blablabla
}
});
var Playlist = Backbone.Collection.extend({
model: Player
});
var MyPlaylist = new Playlist([
{
//blablabla
}
//here comes more songs
]);
var ListOfSongsView = Backbone.View.extend({
tagName: 'ul',
id: "tracks",
initialize: function () {
this.model.bind("reset", this.render, this);
var self = this;
this.model.bind("add", function (player) {
$(self.el).append(new OneSongView({model:player}).render().el);
});
},
render: function (eventName) {
_.each(this.model.models, function (player) {
$(this.el).append(new OneSongView({model:player}).render().el);
}, this);
return this;
}
});
var OneSongView = Backbone.View.extend({
tagName: "li",
calssName: "clearfix",
template:_.template($('#tpl-one-song').html()),
initialize: function () {
this.model.bind("change", this.render, this);
},
render: function (eventName) {
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
var AppRouter = Backbone.Router.extend({
routes:{
"": "list",
"!/": "list"
},
initialize: function () {
$('#block').html(new ListOfSongsView().render().el);
},
list: function () {
this.playlist = new Playlist();
this.ListOfSongsView = new ListOfSongsView({model:this.playlist});
this.playlist = MyPlaylist;
$('#block').html(new ListOfSongsView().render().el);
}
});
var app = new AppRouter();
Backbone.history.start();
});
What i'm doing wrong?
Please, help me, my head is already cracked :(
initialize: function () {
$('#block').html(new ListOfSongsView().render().el);
}
This is called the moment you construct the AppRouter.
You pass no model in argument of ListOfSongsView(), so this.model is undefined.
Then the initialize of the view is called:
initialize: function () {
this.model.bind("reset", this.render, this);
var self = this;
this.model.bind("add", function (player) {
$(self.el).append(new OneSongView({model:player}).render().el);
});
},
this.model is undefined, so you can't call bind on it.
When a new model is added (via "set" function of the collection), I want the model be inserted at the index maintaining sort order, instead at the end.
Thanks
var Ts = (function () {
var Result = Backbone.Model.extend({
idAttribute : 'PAG_ID'
});
var ResultList = Backbone.Collection.extend({
model: Result,
comparator: function(result) {
//console.log(result);
return result.get('SORT_KEY');
},
});
var resultsCollection = new ResultList(data);
data = undefined;
var TableView = Backbone.View.extend({
tagName: 'table',
initialize : function() {
_.bindAll(this, 'render', 'renderRow');
this.collection.on("add", this.renderRow, this);
},
render: function() {
$(this.el).attr('id', 'tsTable').addClass('resulttable');
this.renderHeader(this.collection.shift());
this.collection.each(this.renderRow);
return this;
},
renderHeader : function(model) {
var col=new HeaderView({model:model});
this.$el.append(col.render().$el);
return this;
},
renderRow : function(model) {
var row=new RowView({model:model});
this.$el.append(row.render().$el);
return this;
}
});
var HeaderView = Backbone.View.extend({
tagName: 'tr',
model: resultsCollection.models,
initialize: function() {
this.model.on('change',this.render,this);
},
render: function() {
var html=_.template(colTemplate,this.model.toJSON());
this.$el.html(html);
return this;
}
});
var RowView = Backbone.View.extend({
tagName: 'tr',
initialize: function() {
this.model.on('all',this.render,this);
},
remove: function () {
debug.log("Called remove event on model");
$(this.el).remove();
},
model: resultsCollection.models,
render: function() {
var html=_.template(rowTemplate,this.model.toJSON());
this.$el.html(html);
return this;
},
attributes : function () {
return {
id : this.model.get('PAG_ID')
};
}
});
var tableView = new TableView({collection: resultsCollection});
$("body").append( tableView.render().$el );
resultsCollection.set(initialdata);
resultsCollection.set(someotherdata, {merge: true});
I have changed to as below and it works.Not sure if this is the best implementation
renderRow : function(model) {
var row = new RowView({model:model});
var index = model.get('SORT_KEY') - 1;
row.render().$el.insertAfter(this.$el.find('tr:eq('+ index +')'));
return this;
}
If you provide a comparator function on your collection, Collection.set will perform a silent sort after the new models have been spliced in.
From backbones source http://backbonejs.org/docs/backbone.html:
set: function(models, options) {
var sortable = this.comparator && (at == null) && options.sort !== false;
var sortAttr = _.isString(this.comparator) ? this.comparator : null;
...
if (toAdd.length) {
if (sortable) sort = true;
this.length += toAdd.length;
if (at != null) {
splice.apply(this.models, [at, 0].concat(toAdd));
} else {
push.apply(this.models, toAdd);
}
}
if (sort) this.sort({silent: true});
Here is a fiddle demonstrating that collection.set respects a comparator.
http://jsfiddle.net/puleos/sczV3/
I am getting an
Object function (a){return new n(a)} has no method 'has'
error on calling the fetch() method on my model. Heres the code:
var Exercise = Backbone.Model.extend({
defaults: {
idAttribute: 'e_id',
e_id: "-1",
exerciseName: "Exercise",
exerciseDescription: "Address",
exerciseURL: "vimeo.com",
reps: "0",
sequence: "0"
},
initialize: function() {
alert("Exercise!");
}
});
var ExerciseList = Backbone.Collection.extend({
url: "/getWorkoutList.php",
model: Exercise,
initialize: function() { }
});
var Workout = Backbone.Model.extend({
urlRoot: "/getWorkoutList.php",
url: function() {
return this.urlRoot + "?workoutID=" + this.get('workoutId');
},
defaults: {
idAttribute: 'workoutId',
workoutId: "-1",
workoutName: "WorkoutName",
workoutDescription: "WorkoutDescription",
exercises: new ExerciseList()
},
initialize: function() {
_.bindAll(this);
directory.renderWorkout(this);
},
parse: function(response) {
return response;
}
});
var WorkoutList = Backbone.Collection.extend({
url: "/getWorkoutList.php",
model: Workout,
initialize: function() {
_.bindAll(this);
},
parse: function(response) {
return response;
}
});
var WorkoutView = Backbone.View.extend({
tagName: "div",
className: "workout-container",
template: $("#tmp-workout").html(),
initialize: function() {
_.bindAll(this);
this.model.bind('change', this.render, this);
},
render: function(){
console.log("WorkoutView");
var tmpl = _.template(this.template);
this.$el.html(tmpl(this.model.toJSON()));
return this;
},
//add ui events
events: {
"click #workout-details": "getWorkoutDetails"
},
getWorkoutDetails: function (e) {
e.preventDefault();
this.model.fetch();
}
});
var ExerciseView = Backbone.View.extend({
tagName: "exercise",
className: "exercise-container",
template: $("#tmp-exercise").html(),
initialize: function() {
_.bindAll(this);
alert("ExerciseView");
},
render: function(){
console.log("render exercise view");
var tmpl = _.template(this.template);
this.$el.html(tmpl(this.model.toJSON()));
return this;
}
});
var WorkoutListingView = Backbone.View.extend({
el: $("#workouts"),
initialize: function() {
var collection = new WorkoutList();
collection.fetch();
},
render: function() {
var that = this;
_.each(this.collection.models, function(item){
that.renderWorkout(item);
});
},
renderWorkout: function(item) {
var workoutView = new WorkoutView({
model:item
});
this.$el.append(workoutView.render().el);
var that = this;
_.each(workoutView.model.get('exercises').models, function(exercise) {
that.renderExercise(exercise);
});
},
renderExercise: function(item) {
var exerciseView = new ExerciseView({
model:item
});
this.$el.append(exerciseView.render().el);
}
});
Everything works fine when I am retrieving the Workout Collection the fist time. However, when I call getWorkoutDetails, I get the error. By inserting alerts and console.logs in parse() of Workout Model, I've found out that it does get the correct response from server, but for some reason, its giving this error.
Any ideas? Thanks.
OK, after spending a lot of time in the beautiful world of minified javascript spaghetti, I found out that the underscore.js version I was using didnt had the function 'has' in it. Updating underscore.js from 1.2.2 to 1.4.4 solved the problem. Also, my backbone.js version is 0.9.1