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'
}
Related
I'm new to Backbone and I'm trying to create a simple Slideshow that show all the models in a Collection.
Models are created through a fetch from the server and here is the code:
var Post = Backbone.Model.extend({
defaults:{
text: "",
source: "",
image: "",
posted_at: "",
rendered : false,
},
});
In PostCollection there're modelBefore and modelAfter that return next and previous model respectively.
var PostCollection = Backbone.Collection.extend({
model: Post,
url: "https://milkytags.com/api/v1/boards/edcb2c43-1448-4c81-97d5-1c315c8f9589/posts",
initialize: function() {
this.fetch({ data: $.param({ page: pageCounter, per_page:3}) });
},
parse: function(response) {
return response.posts;
},
modelBefore: function(model) {
index = this.indexOf(model) - 1;
if (index < 0) {
index = this.length - 1;
}
return this.at(index);
},
modelAfter: function(model) {
index = this.indexOf(model) + 1;
if (index === this.length) {
index = 0;
}
return this.at(index);
},
});
I've created a view called SlideShowView that creates the view from a template relying on Post View: next and prev method dealing with rendering the next or previous template.
var SlideShowView = Backbone.View.extend({
tagName: 'div',
className: 'slideshow',
events: {
'click #close': 'close',
'click #next': 'next',
'click #prev': 'prev',
},
template: _.template($('#slideShowTemplate').html()),
initialize: function() {
this.render();
},
render: function() {
this.$el.html(this.template());
post = new PostView({ model: this.model });
this.$el.append(post.el);
return this.$el;
},
close: function(){
this.remove();
},
next: function(){
var next = this.model.collection.modelAfter( this.model );
post = new PostView({ model: next });
this.$el.html(this.template());
this.$el.append(post.el);
return this.$el;
},
prev: function(){
var prev= this.model.collection.modelBefore( this.model );
post = new PostView({ model: prev });
this.$el.html(this.template());
this.$el.append(post.el);
return this.$el;
},
});
Finally, Post View:
// The View for single Post
var PostView = Backbone.View.extend({
tagName: 'div',
className: 'post',
events: {
'click' : 'slideShow',
},
template: _.template($('#postTemplate').html()),
initialize: function() {
this.render();
},
render: function() {
this.$el.html(this.template(this.model.toJSON_milky()));
return this;
},
slideShow: function(){
test=new SlideShowView({model: this.model});
$('#milkyContainer').append(test.$el);
}
});
Problems arises when I press next or prev, in practice it is as if the collection was not updated with the latest rendered element, I have to find a way to tell to the collection what is the current collection element shown.
Tips?
Thanks
Right now your code is using this.model in SlideShowView as "the current model". However, you do not update it. Something like this would do it:
next: function(){
var next = this.model.collection.modelAfter( this.model );
post = new PostView({ model: next });
this.$el.html(this.template());
this.$el.append(post.el);
this.model = next; // <<---- Added this line.
return this.$el;
},
Similarly for prev.
I have a view named DatesView and a subview named DateView. I would like to execute a function in DatesView when an instance of DateView is clicked.
var Date = Backbone.Model.extend({});
var Dates = Backbone.Collection.extend({
model: Date
});
var DateView = Backbone.View.extend({
events: {
'click': 'clicked'
},
render: function () {
this.$el.text(this.model.get('date'));
return this;
}
})
var DatesView = Backbone.View.extend({
render: function () {
this.collection.each(function (date) {
var dateView = new DateView({model: date});
this.$el.append(dateView.render().el)
}, this);
return this;
},
clicked: function() {
console.log('clicked');
}
})
var dates = new Dates([
{date: '01'},
{date: '02'},
{date: '03'}
])
var datesView = new DatesView({collection: dates});
$('#container').html(datesView.render().el);
JSFiddle
As you can see in the example the DateView has an event setup for click, which I would like to register in the DatesView. The way it is written now does not work.
I quite like using Backbone events. You can use it as follows:
Trigger the event in your DateView
clicked: function () {
Backbone.trigger('date:clicked');
}
Listen for the event in the DatesView
initialize: function () {
this.listenTo(Backbone, 'date:clicked', this.clicked);
}
The simplest way to do this is to accept a "clicked" option in your sub view:
var DateView = Backbone.View.extend({
events: {
'click': 'clicked'
},
initialize: function (options) {
// allow parent to pass in a click handler
this.clicked = options.clicked;
}
// snip...
})
var DatesView = Backbone.View.extend({
render: function () {
this.collection.each(function (date) {
// pass parentView's click method to child,
// binding context to parent
var dateView = new DateView({
model: date,
clicked: this.clicked.bind(this)
});
this.$el.append(dateView.render().el)
}, this);
return this;
},
// snip...
jsfiddle
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 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;
});
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