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.
Related
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.
Heres my code:
var RowsSubView = Backbone.View.extend({
initialize: function() {
log.debug(this.collection);
},
render: function() {
var html = RowView();
this.setElement(html);
return this;
}
});
var View = BaseView.extend({
id: 'wrapper',
className: 'container-fluid',
events: {
},
initialize: function() {
_.bindAll(this, 'render');
log.debug('Initialized Queue View');
this.opportunities = new Opportunities();
this.opportunities.on('add', function(model){
});
this.opportunities.fetch({
success: function(response, options) {
},
error: function(response) {
}
});
},
render: function() {
var template = QueueView();
this.$el.html(template);
this.renderRowsSubView();
return this;
},
renderRowsSubView: function() {
// render rows
this.row = new RowsSubView({collection: this.opportunities});
this.row.render();
this.$el.find('tbody').append(this.row.el);
}
});
Heres my question:
Sorry for the noob question! I am learning Backbone and having a bit of an issue. I've looked at a bunch of tutorials/guides, but I think I've confused myself.
I am trying to create a list of items and render them in a table. I want to pass each item into my template and spit it out in the view.
I am stuck after passing my collection to my RowsSubView. I'm not sure how to render each object in the template. Then insert those.
PS: I am able to log this.collection in my RowsSubView and see an object with the array of items.
Thanks.
Ok well start with this. Looks like there's quite a bit of cleanup that needs to be done =)
var RowsSubView = Backbone.View.extend({
initialize: function() {
log.debug(this.collection);
},
render: function() {
//var html = RowView(); // Looks like you're already placing a tbody as the container
//this.setElement(html);
this.collection.forEach(function( model ){
this.$el.append( RowView( model.toJSON() ) ); // Assuming RowView knows what to do with the model data
});
return this;
}
});
Then change the renderRowsSubView to
renderRowsSubView: function() {
// render rows
this.row = new RowsSubView({collection: this.opportunities});
this.row.render();
this.$el.find('tbody').append(this.row.$el.html());
}
For those that this might help, heres what I ended up with:
var RowsSubView = Backbone.View.extend({
initialize: function() {
},
render: function() {
var html = RowView({
opp: this.model.toJSON()
});
this.setElement(html);
return this;
}
});
var View = BaseView.extend({
id: 'wrapper',
className: 'container-fluid',
events: {
},
initialize: function() {
_.bindAll(this, 'render', 'add');
log.debug('Initialized Queue View');
this.opportunities = new Opportunities();
this.opportunities.on('add', this.add);
this.fetch();
},
add: function(row) {
this.row = new RowsSubView({model: row});
this.row.render();
$('tbody').append(this.row.el);
},
fetch: function() {
this.opportunities.fetch({
data: $.param({
$expand: "Company"
}),
success: function(response, options) {
// hide spinner
},
error: function(response) {
// hide spinner
// show error
}
});
},
render: function() {
var template = QueueView();
this.$el.html(template);
return this;
}
});
return View;
});
When I loads the page , it is getting all the datas and I am displaying the datas. But When I add a record, that is I am submitting the form "addcontact", the datas are creating in the database. But It is not adding into the collection and that this.collection.on('add') is not getting triggered. So, I think the problem was because of this. Can any one tell me that where I am doing wrong? Is there any other way to solve this.
This code works functionally, but the only problem with this is , on creating new record using this.collection.create({new post},{wait: true}); the values are getting updated in the database. But it is not adding into the collection.
(function(){
Backbone.emulateHTTP = true;
//Backbone.emulateJSON = true;
window.App = {
Models : {},
Collections: {},
Views : {},
Router : {}
};
window.vent = _.extend({},Backbone.Events);
window.template = function(id){
return _.template( $('#'+id).html() );
};
// Contact Model
App.Models.Contact = Backbone.Model.extend({
validate: function(attrs) {
if( !attrs.first_name ||
!attrs.last_name ||
!attrs.email_address) {
alert('Fill the missing fields');
}
}
});
// Collection
App.Collections.Contacts = Backbone.Collection.extend({
model: App.Models.Contact,
url : 'index.php/ContactsController/contacts'
});
// Global View
App.Views.App = Backbone.View.extend({
initialize: function(){
vent.on('contact:edit',this.editContact,this);
//console.log(this.collection.toJSON());
App.addContactView = new App.Views.AddContact({collection: App.Contacts});
App.allContactsView = new App.Views.Contacts({collection: App.Contacts});
$('#allcontacts').append(App.allContactsView.el);
}
});
// Add Contact View
App.Views.AddContact = Backbone.View.extend({
el: '#addcontact',
initialize: function(){
this.first_name = $('#first_name');
this.last_name = $('#last_name');
this.email_address = $('#email_address');
this.description = $('#description');
//this will fix it
this.collection.on("change", this.render , this);
},
events: {
'submit' : 'addContact'
},
addContact: function(e){
e.preventDefault();
this.collection.create({
first_name: this.first_name.val(), // <===== same as $this.el.find('#first_name')
last_name: this.last_name.val(),
email_address: this.email_address.val(),
description: this.description.val()
},{wait: true});
this.clearForm();
},
clearForm: function(){
this.first_name.val('');
this.last_name.val('');
this.email_address.val('');
this.description.val('');
}
});
// All Contacts Views
App.Views.Contacts = Backbone.View.extend({
tagName: 'tbody',
initialize: function(){
this.collection.on('add',this.addOne,this);
this.render();
},
render: function(){
this.collection.each(this.addOne,this);
//console.log(this.el);
return this;
},
addOne: function(contact){
var ContactView = new App.Views.Contact({model: contact});
//console.log(ContactView.render().el);
this.$el.append(ContactView.render().el);
}
});
// A view for a single View
App.Views.Contact = Backbone.View.extend({
tagName: 'tr',
template: template('allContactsTemplate'),
initialize: function(){
this.model.on('change',this.render,this);
},
render: function(){
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
})();
So my application below is actually firing "FIRE!" in the console twice on page load. Not sure why backbone is firing the url function twice when I am only seeing the one fetch being made. Any ideas as to why this might be causing it to fire twice?
window.ScheduleApp = {
Models: {},
Collections: {},
Views: {}
};
window.template = function(id) {
return _.template($('#' + id).html());
};
//Define the Game Model.
ScheduleApp.Game = Backbone.Model.extend({
initialize: function() {
this.gameId = this.get('Id');
this.gameTime = this.get('Time');
}
});
//Define the Games Collection that contains Game Models.
ScheduleApp.Games = Backbone.Collection.extend({
model: ScheduleApp.Game
});
//Define the Day Model.
ScheduleApp.Day = Backbone.Model.extend({
initialize: function() {
this.games = new ScheduleApp.Games(this.get('Games'));
this.games.parent = this;
this.gameDayGDT = this.get('GeneratedDateTime');
this.gameDayDate = this.get('Date');
}
});
//Define the Days Collection that contains the Day Models.
ScheduleApp.Days = Backbone.Collection.extend({
model: ScheduleApp.Day,
url: function() {
console.log('FIRE!');
return '/js/test.json'
},
parse: function(data) {
var parsedSchedule = JSON.parse('[' + data.STUFF + ']');
return parsedSchedule;
}
});
ScheduleApp.DayCollectionView = Backbone.View.extend({
el: '.container', //Container where the views get rendered to.
initialize: function() {
this.listenTo(this.collection, 'reset', this.render);
},
render: function(event) {
if (this.collection.length === 0) {
$('.container-hidden').show();
}
//Cycle through collection of each day.
this.collection.each(function(day) {
var dayView = new ScheduleApp.DayView({
model: day
});
this.$el.append(dayView.render().el);
}, this);
return this;
}
});
ScheduleApp.DayView = Backbone.View.extend({
tagName: 'div',
className: 'game-date',
template: _.template($("#gameSchedule").html(), this.model),
initialize: function() {
this.listenTo(this.model, "reset", this.render);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var daysList = new ScheduleApp.Days();
daysList.fetch({
reset: true,
update: true,
cache: false,
success: function(collection, response) {
//console.log(collection);
},
error: function(model, resp) {
// console.log('error arguments: ', arguments);
// console.log("error retrieving model");
}
});
//create new collection view.
var daysCollectionView = new ScheduleApp.DayCollectionView({
collection: daysList
});
All models belonging to a collection build their URLs based on the collection URL, as stated here. My guess would be that your collection is calling the method once, then your model / models place the second call, in order to build the model URL.
Then again, this method seems pretty harmless to me: it's just a getter. I'd rather place the console.log call in the Collection#parse or Model#initializer methods, and count how many times it gets invoked there.
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