How to handle a simple click event in Backbone.js? - javascript

I am having difficulty with something very simple in Backbone. I want to wire up the <h1> in my page so that when the user clicks on it, it returns seamlessly to the homepage, without a postback.
This is the HTML:
<h1><a id="home" href="/">Home</a></h1>
(UPDATE: fixed ID as suggested by commenter.) And this is my Backbone view and router:
var HomeView = Backbone.View.extend({
initialize: function() {
console.log('initializing HomeView');
},
events: {
"click a#home": "goHome"
},
goHome: function(e) {
console.log('goHome');
e.preventDefault();
SearchApp.navigate("/");
}
});
var SearchApp = new (Backbone.Router.extend({
routes: {
"": "index",
},
initialize: function(){
console.log('initialize app');
this.HomeView = new HomeView();
},
index: function(){
// do stuff here
},
start: function(){
Backbone.history.start({pushState: true});
}
}));
$(document).ready(function() {
SearchApp.start();
});
The console is showing me
initialize app
initializing HomeView
But when I click on the <h1>, the page posts back - and I don't see goHome in the console.
What am I doing wrong? Clearly I can wire up the <h1> click event simply enough in jQuery, but I want to understand how I should be doing it in Backbone.

If you enable pushState you need to intercept all clicks and prevent the refresh:
$('a').click(function (e) {
e.preventDefault();
app.router.navigate(e.target.pathname, true);
});
Something like:
$(document).ready(function(){
var HomeView = Backbone.View.extend({
initialize: function() {
console.log('initializing HomeView');
}
});
var AboutView = Backbone.View.extend({
initialize: function() {
console.log('initializing AboutView');
}
});
var AppRouter = Backbone.Router.extend({
routes: {
"": "index",
"about":"aboutView"
},
events: function () {
$('a').click(function (e) {
e.preventDefault();
SearchApp.navigate(e.target.pathname, true);
});
},
initialize: function(){
console.log('initialize app');
this.events();
this.HomeView = new HomeView();
},
index: function(){
this.HomeView = new HomeView();
},
aboutView : function() {
this.AboutView = new AboutView();
}
});
var SearchApp = new AppRouter();
Backbone.history.start({pushState: true});
});

Your tag id is invalid, try this:
<h1><a id="home" href="/">Home</a></h1>

Related

Cannot read property '_listenId' of undefined when using listenTo in a view

I am creating a view but it isn't rendering in my page.
I am building a SPA with backbone, and I need that my template can open inside of div in my body, but I don't know what is my problem here.
What can be?
Show this error:
Uncaught TypeError: Cannot read property '_listenId' of undefined
at child.Events.(anonymous function) [as listenTo] (http://localhost:9000/bower_components/backbone/backbone.js:222:19)
at child.initialize (http://localhost:9000/scripts/views/RepositoriesView.js:21:12)
at child.Backbone.View (http://localhost:9000/bower_components/backbone/backbone.js:1001:21)
at new child (http://localhost:9000/bower_components/backbone/backbone.js:1566:41)
at child.repositories (http://localhost:9000/scripts/routes/AppRouter.js:46:7)
at child.execute (http://localhost:9000/bower_components/backbone/backbone.js:1265:30)
at Object.callback (http://localhost:9000/bower_components/backbone/backbone.js:1254:16)
at http://localhost:9000/bower_components/backbone/backbone.js:1481:19
at Function.some (http://localhost:9000/bower_components/lodash/dist/lodash.compat.js:4304:25)
at Backbone.History.loadUrl (http://localhost:9000/bower_components/backbone/backbone.js:1479:16)
My AppRouter is:
/*global Sice, Backbone*/
Sice.Routers = Sice.Routers || {};
Sice.Views = Sice.Views || {};
(function() {
'use strict';
Sice.Routers.AppRouter = Backbone.Router.extend({
//map url routes to contained methods
routes: {
"": "repositories",
"repositories": "repositories",
"search": "search",
"starreds": "starreds"
},
deselectPills: function() {
//deselect all navigation pills
$('ul.pills li').removeClass('active');
},
selectPill: function(pill) {
//deselect all navigation pills
this.deselectPills();
//select passed navigation pill by selector
$(pill).addClass('active');
},
hidePages: function() {
//hide all pages with 'pages' class
$('div#content').hide();
},
showPage: function(page) {
//hide all pages
this.hidePages();
//show passed page by selector
$(page).show();
},
repositories: function() {
this.showPage('div#content');
this.selectPill('li.repositories-pill');
new Sice.Views.RepositoriesView();
},
search: function() {
this.showPage('div#content');
this.selectPill('li.search-pill');
},
starreds: function() {
this.showPage('div#content');
this.selectPill('li.starreds-pill');
}
});
Sice.Views.AppView = Backbone.View.extend({
//bind view to body element (all views should be bound to DOM elements)
el: $('body'),
//observe navigation click events and map to contained methods
events: {
'click ul.pills li.repositories-pill a': 'displayRepositories',
'click ul.pills li.search-pill a': 'displaySearch',
'click ul.pills li.starreds-pill a': 'displayStarreds'
},
//called on instantiation
initialize: function() {
//set dependency on Sice.Routers.AppRouter
this.router = new Sice.Routers.AppRouter();
//call to begin monitoring uri and route changes
Backbone.history.start();
},
displayRepositories: function() {
//update url and pass true to execute route method
this.router.navigate("repositories", true);
},
displaySearch: function() {
//update url and pass true to execute route method
this.router.navigate("search", true);
},
displayStarreds: function() {
//update url and pass true to execute route method
this.router.navigate("starreds", true);
}
});
//load application
new Sice.Views.AppView();
})();
My View is:
/*global Sice, Backbone, JST*/
Sice.Views = Sice.Views || {};
(function() {
'use strict';
Sice.Views.RepositoriesView = Backbone.View.extend({
template: JST['app/scripts/templates/RepositoriesView.ejs'],
tagName: 'div',
id: 'repositoriesView',
className: 'page-repositories',
events: {},
initialize: function() {
this.listenTo(this.model, 'change', this.render);
},
render: function() {
this.$el.html(this.template());
}
});
})();
What's happening?
In the following router function, you're instantiating a new View instance.
repositories: function() {
this.showPage('div#content');
this.selectPill('li.repositories-pill');
new Sice.Views.RepositoriesView(); // <-- here
},
But you're not passing a model object which the view listens to.
initialize: function() {
this.listenTo(this.model, 'change', this.render);
},
So it's calling this.listenTo with this.model as undefined.
What's the solution?
Pass a model instance
new Sice.Views.RepositoriesView({ model: new MyModel() });
Create a model instance in the view
initialize: function() {
this.model = new MyModel();
this.listenTo(this.model, 'change', this.render);
},

Attach View of Model to existing DIV in Backbone

need help, can't understand how to attach each View of the model to each already existing DIV in DOM ( have and div.container with div.widget array ).
// Model
V.Models.Shortcode = Backbone.Model.extend({});
// Shortcodes Collection Init
V.Collections.Shortcodes = Backbone.Collection.extend({
model: V.Models.Shortcode,
});
When Iframe load, push storage from server to collection:
$('#preview').on('load', function() {
var ShortcodesCollection = new V.Collections.Shortcodes( Vision.ShortcodeStorage );
var Preview = new V.Views.Preview({
collection: ShortcodesCollection,
el: $('.container')
});
Preview.render();
});
Render Preview with collection:
// Collection View in iframe
V.Views.Preview = Backbone.View.extend({
initialize: function() {
this.collection.on('add', this.addOne, this);
},
render: function() {
this.collection.each(this.addOne, this);
return this;
},
addOne: function(ad) {
var shortcodeView = new V.Views.Shortcode({ model: ad });
shortcodeView.render();
}
});
View for each Model:
// Shortcode View
V.Views.Shortcode = Backbone.View.extend({
events: {
'click .widget' : 'SomeActionOnView'
},
render: function(){
//console.log(this.el);
//console.log(this.model.toJSON());
},
SomeActionOnView: function(){
console.log(this);
}
});
Question is, how to attach V.Views.Shortcode to each div with "widget" class to bind events. Thanks!
Can you please try this?
V.Views.Shortcode = Backbone.View.extend({
events: {
'click .widget' : 'SomeActionOnView'
},
render: function(){
//code here to write stuff of this.$el
$("div.widget").append(this.$el);
},
SomeActionOnView: function(){
console.log(this);
}
});

Backbone: Move a model to another Collection

This is my first time using backbone, so I'm pretty confused about everything. I'm trying to make a todo list. Once I click "finished" on the todo, I want it to append to the "Completed" list.
I've been following this tutorial, and I tried to replicate the code(I tried to create a new completedTodo view and stuff like that), and I tried to do when clicking "finished" it would delete the $el, and I would add to the completedTodos. I think the problem here is even if it's added, it's not doing anything.
done: function() {
var completed = new CompletedTodo({
completedTask: this.$('.task').html(),
completedPriority: this.$('.priority').html()
});
completedTodos.add(completed);
this.model.destroy();
},
I put in a debugger there to see if it actually added to the collection, and when i did completedTodos.toJSON();, it does give me back the new thing I just added.
However, it does not append to my collection list.
Here is my whole entire script file, in case I named anything wrong.
var Todo = Backbone.Model.extend({
defaults: {
task: '',
priority: ''
}
});
var CompletedTodo = Backbone.Model.extend({
defaults: {
completedTask: '',
completedPriority: ''
}
});
var Todos = Backbone.Collection.extend({});
var todos = new Todos();
var CompletedTodos = Backbone.Collection.extend({});
var completedTodos = new CompletedTodos();
//Backbone view for one todo
var TodoView = Backbone.View.extend({
model: new Todo(),
tagName: 'tr',
initialize: function() {
this.template = _.template($('.todos-list-template').html());
},
events: {
'click .finished-todo': 'done',
'click .delete-todo' : 'delete'
},
done: function() {
var completed = new CompletedTodo({
completedTask: this.$('.task').html(),
completedPriority: this.$('.priority').html()
});
completedTodos.add(completed);
this.model.destroy();
},
delete: function() {
this.model.destroy();
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
//Backbone view for all todos
var TodosView = Backbone.View.extend({
model: todos,
el: $('.todos-list'),
initialize: function() {
this.model.on('add', this.render, this);
this.model.on('remove', this.render, this);
},
render: function() {
var self = this;
this.$el.html('');
_.each(this.model.toArray(), function(todo) {
self.$el.append((new TodoView({model: todo})).render().$el);
});
return this;
}
});
//View for one Completed Todo
var CompletedTodoView = Backbone.View.extend({
model: new CompletedTodo(),
tagName: 'tr',
initialize: function() {
this.template = _.template($('.completed-todos-template').html());
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
//View for all Completed Todos
var CompletedTodosView = Backbone.View.extend({
model: completedTodos,
el: $('.completed-todos-list'),
initialize: function() {
this.model.on('add', this.render, this);
},
render: function() {
var self = this;
this.$el.html('');
_.each(this.model.toArray(), function(completedTodo) {
self.$el.append((new CompletedTodoView({model: completedTodo})).render().$el);
});
return this;
}
});
var todosView = new TodosView();
$(document).ready(function() {
$('.add-todo').on('click', function() {
var todo = new Todo({
task: $('.task-input').val(),
priority: $('.priority-input').val()
});
$('.task-input').val('');
$('.priority-input').val('');
todos.add(todo);
});
});
After this, I also have to figure out how to use Parse to make it persist to the database. I figured I'd get everything working in backbone first, and then try to do put in the database. I'm also suppose to use node/express, so would that help? I'm pretty much a Ruby on Rails kind of person, so I don't really know any of these javascript framework type of stuff.
Thanks for your help!
Alright,
It was just because I didn't initialize the view.
var completedTodosView = new CompletedTodosView();
This fixed it.

Backbone: Best way to prevent routes (and url change)

I have an editor view and if there are unsaved changed I am prompting on window closes and also on backbone routes.
Problem is that Backbone.Router.execute runs after the url change and so I am trying to implement the most reliable and elegant way of preventing the url change.
In the example below clicking the "About" route will prevent the route callback and then rewind the url change - it seems less than ideal that I have to use window.history.back() (because it creates a history entry).
Can you think of a better way? I know a jQuery on-click can catch the event before url change but I'm not sure how to nicely integrate that with a Backbone.Router. Thanks.
var HomeView = Backbone.View.extend({
template: '<h1>Home</h1>',
initialize: function () {
this.render();
},
render: function () {
this.$el.html(this.template);
}
});
var AboutView = Backbone.View.extend({
template: '<h1>About</h1>',
initialize: function () {
this.render();
},
render: function () {
this.$el.html(this.template);
}
});
var ContactView = Backbone.View.extend({
template: '<h1>Contact</h1>',
initialize: function () {
this.render();
},
render: function () {
this.$el.html(this.template);
}
});
var AppRouter = Backbone.Router.extend({
routes: {
'': 'homeRoute',
'home': 'homeRoute',
'about': 'aboutRoute',
'contact': 'contactRoute'
},
execute: function(callback, args, name) {
if (window.location.hash === '#/about') {
window.history.back();
return false;
}
if (callback) {
callback.apply(this, args);
}
},
homeRoute: function () {
var homeView = new HomeView();
$("#content").html(homeView.el);
},
aboutRoute: function () {
var aboutView = new AboutView();
$("#content").html(aboutView.el);
},
contactRoute: function () {
var contactView = new ContactView();
$("#content").html(contactView.el);
}
});
var appRouter = new AppRouter();
Backbone.history.start();
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="http://underscorejs.org/underscore.js"></script>
<script src="http://backbonejs.org/backbone.js"></script>
<div id="navigation">
Home
About
Contact
</div>
<div id="content"></div>
The only thing I can think of is listening to clicks and doing things with jQuery, or saving the last hash and doing window.history.replaceState(undefined, undefined, "#last_hash_value").

Backbone: bind event happen multiple times

I make a simple todo app:
var Todo = Backbone.Model.extend({
});
var Todos = Backbone.Collection.extend({
model: Todo
});
var todos = new Todos();
var ItemView = Backbone.View.extend({
tagName: "li",
template: _.template($("#item-template").html()),
render: function () {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
initialize: function () {
this.listenTo(todos, 'remove', this.remove);
},
events: {
"click .delete": "clear"
},
clear: function () {
todos.remove(this.model);
}
});
var AppView = Backbone.View.extend({
el: $("body"),
initialize: function () {
this.listenTo(todos, 'add', this.addOne);
},
addOne: function(todo) {
var view = new ItemView({
model: todo
});
this.$("#list").append(view.render().el);
},
events: {
"click #create": "create"
},
create: function () {
var model = new Todo({
title: this.$("#input").val()
});
todos.add(model);
}
})
var app = new AppView();
and DEMO online is here: http://jsfiddle.net/JPL94/1/
I can add item correctly, but when I want delete some item, all of them been removed;
I found it related to the bind event in ItemView, when I click one delete button, all of them are triggered.
But how can I solve this problem?
You are listening to remove events from the collection, and if my memory serves me right a collection will dispatch a remove event whenever a model is removed, so when you remove a model from the collection, all the views will see the event.
I changed your initialize in the view to
initialize: function () {
this.listenTo(this.model, 'remove', this.remove);
},
And it seems to work.
http://jsfiddle.net/JPL94/5/

Categories