backbone.js history with only one route? - backbone.js

I'm developing my first backbone project and I have requirement that I'm not sure how to meet. I'm sure the solution has something to do with properly routing my app, but I'm not sure...
App.Router = Backbone.Router.extend({
initialize: function(options) {
this.el = options.el;
},
routes: {
'': 'search',
'search': 'search'
},
search: function() {
var search = new App.SearchView();
search.render();
}
}
});
I have three views:
// Defines the View for the Search Form
App.SearchView = Backbone.View.extend({
initialize: function() {
_.bindAll(this, 'render');
this.render();
},
template: _.template($('#search-form').html()),
el: $('#search-app'),
events: {
'click .n-button' : 'showResults'
},
showResults: function() {
this.input = $('#search');
var search = new App.ResultsSearchView();
var grid = new App.GridView({ query: this.input.val() });
search.render();
grid.render();
},
render: function() {
$(this.el).html(this.template());
return this;
},
name: function() { return this.model.name(); }
}); // App.SearchView
//Defines the View for the Search Form when showing results
App.ResultsSearchView = Backbone.View.extend({
initialize: function() {
_.bindAll(this, 'render');
this.render();
},
template: _.template($('#results-search-form').html()),
el: $('#search-input'),
render: function() {
$(this.el).html(this.template());
return this;
},
events: {
'click .n-button' : 'showResults'
},
showResults: function() {
this.input = $('#search');
var grid = new App.GridView({ query: this.input.val() });
grid.render();
},
name: function() { return this.model.name(); }
}); // App.ResultsSearchView
// Defines the View for the Query Results
App.GridView = Backbone.View.extend({
initialize: function(options) {
var resultsData = new App.Results();
resultsData.on("reset", function(collection) {
});
resultsData.fetch({
data: JSON.stringify({"query":this.options.query, "scope": null}),
type: 'POST',
contentType: 'application/json',
success: function(collection, response) {
$('#grid').kendoGrid({
dataSource: {
data: response.results,
pageSize: 5
},
columns: response.columns,
pageable: true,
resizable: true,
sortable: {
mode: "single",
allowUnsort: false
},
dataBinding: function(e) {
},
dataBound: function(){
}
});
},
error: function(collection, response) {
console.log("Error: " + response.responseText);
}
});
_.bindAll(this, 'render');
this.render();
},
el: $('#search-app'),
template: _.template($('#results-grid').html()),
render: function() {
$(this.el).html(this.template());
return this;
}
}); // App.GridView
The issue I am having is that we want our users to be able to use the back button to navigate back to the initial search and also from there, be able to move forward again to their search results. I just have no idea how to do this. Any assistance would be a huge help.
Thanks!

Backbone handles the browser history -- all you have to do is call Backbone.history.start() on startup. Well, that and make sure to call Router.navigate whenever you want to save the current navigation state.
In your example, the appropriate time would be when the user clicks "search". In the searchView.showResults method, instead of creating and rendering the results view, call:
myRouter.navigate("results/" + this.input.val(), { trigger: true });
This causes the router to go to the results/query route, which you have to add:
'results/:query': 'results'
Finally, create the results method within your router, and put the view-creating logic there:
results: function(query) {
var search = new App.ResultsSearchView();
var grid = new App.GridView({ query: query });
search.render();
grid.render();
}
Here's a working demo -- it's a bit hard to see on JSFiddle because the page is within an iFrame, but you can confirm it's working by hitting Alt+Left, Alt+Right to call the browser's back and forward respectively.
And for contrast, here's a similar demo, except it uses a single route. It calls router.navigate without trigger: true. You can see that, using this single-route method, you're able to navigate back; however, you can't go forward again to the results view, because Backbone has no way to re-trace the steps to get there.
App
var HomeView = Backbone.View.extend({
initialize: function() {
this.render();
},
el: "#container",
events: {
"submit #search": "search"
},
template: _.template($("#search-template").html()),
render: function() {
var html = this.template();
this.$el.html(html);
},
search: function(e) {
e.preventDefault();
router.navigate("results/" + $(e.target).find("[type=text]").val(), { trigger: true });
}
});
var ResultsView = Backbone.View.extend({
initialize: function() {
this.render();
},
el: "#container",
render: function() {
var html = "Results test: " + this.model.get("query");
this.$el.html(html);
}
});
var Router = Backbone.Router.extend({
routes: {
"" : "search",
"results/:query": "results"
},
search: function() {
console.log("search");
var v = new HomeView();
},
results: function(query) {
console.log("results");
var v = new ResultsView({ model: new Backbone.Model({ query: query }) });
}
});
var router = new Router();
Backbone.history.start();
HTML
<script type='text/template' id='search-template'>
<form id="search">
<input type='text' placeholder='Enter search term' />
<input type='submit' value='Search' />
</form>
</script>
<div id="container"></div>​

Related

Backbone views events firing repeatedly in routes

I have a backbonejs application that contains a router file and some views , and also i'm using requirejs to add views to routes and add templates to views. here is my codes :
routes.js
var AppRouter = Backbone.Router.extend({
routes: {
"": "getLogin",
"login": "getLogin",
"register": "getRegister",
"forget-password": "getForgetPassword"
},
getLogin: function() {
require(['views/auth/loginView'], function(view) {
view = new this.LoginView();
});
},
getRegister: function() {
require(['views/auth/registerView'], function() {
view = new this.RegisterView();
});
},
getForgetPassword: function() {
require(['views/auth/forgetPasswordView'], function() {
view = new this.ForgetPasswordView();
});
},
});
var route = new AppRouter();
Backbone.history.start();
loginView.js
var LoginView = Backbone.View.extend({
el: '#wrapper',
initialize: function() {
NProgress.start();
this.render();
},
render: function() {
require(['text!partials/auth/login.html'], function(t) {
var json = { title: 'title', formName: 'frmLogin' };
var template = _.template(t);
$('#wrapper').html(template(json));
});
NProgress.done();
},
events: {
"click #btnLogin": "login"
},
login: function(e) {
e.preventDefault();
alert('some message');
}
});
also registerView.js and forgetPasswordView.js are similar to loginView.js.
now! when i change routes multiple times and hit #btnLogn it fires alert('some message'); function multiple times...!
Have you tried un-delegating the events in the view, on route change?
You could override the route method (annotated source) in your AppRouter and run it before each route is rendered.
route: function(route, name, callback) {
view.undelegateEvents();
return Backbone.Router.prototype.route.apply(this, arguments);
}
Note: Just an idea, not tested with your code

How to filter a backbone collection and render the results

Total newbie to Backbone so apologize if this is a simple question.
I am have successfully loaded a collection and rendered the view. However I have a dropdown with A tags that I would like to use the filter the data displayed. I'm trying to set an event listener in my VIEW and then trigger a function within the view to filter the results and re-render the view.
Here's my code:
IBA.NewsModel = Backbone.Model.extend({});
IBA.NewsCollection = Backbone.Collection.extend({
model: IBA.NewsModel,
url: "/news/api"
});
IBA.NewsView = Backbone.View.extend({
el: '#main',
template: _.template($("#news-article").html()),
events: {
"change .dropdown-item": "filterNews"
},
initialize: function () {
this.collection = new IBA.NewsCollection();
this.listenTo(this.collection, 'reset', this.render);
this.collection.fetch({
success: function() {
console.log("JSON file load was successful");
view.render();
},
error: function(){
console.log('There was some error in loading and processing the JSON file');
}
});
},
render: function () {
this.$el.html(this.template({
articles: this.collection.toJSON()
})
);
return this;
},
filterNews: function (e){
e.preventDefault();
var items = this.collection.where({cat_name: "interviews"});
console.log(items);
this.$el.html(this.template({articles: this.items.toJSON()}));
}
});
var view = new IBA.NewsView();
Easiest way to do it would be to reset the actual collection with the filtered results:
IBA.NewsModel = Backbone.Model.extend({});
IBA.NewsCollection = Backbone.Collection.extend({
model: IBA.NewsModel,
url: "/news/api"
});
IBA.NewsView = Backbone.View.extend({
el: '#main',
template: _.template($("#news-article").html()),
events: {
"change .dropdown-item": "filterNews"
},
initialize: function() {
this.collection = new IBA.NewsCollection();
this.listenTo(this.collection, 'reset', this.render);
this.fetchNews();
},
fetchNews: function() {
var view = this;
this.collection.fetch({
success: function() {
console.log("JSON file load was successful");
view.render();
},
error: function() {
console.log('There was some error in loading and processing the JSON file');
}
});
},
render: function() {
this.$el.html(this.template({
articles: this.collection.toJSON()
}));
return this;
},
filterNews: function(e) {
e.preventDefault();
this.collection.reset(this.collection.where({
cat_name: "interviews"
}));
}
});
var view = new IBA.NewsView();
When you want to go back to original data, fetch it again using the fetchNews() method.
You also had a syntax error that view was not defined in your initialize

Backboe memory leak in subviews

I still strugling with memory leak in my app. I wannted to do it without huge changes in code.
var ItemsView = Backbone.View.extend({
id:'products', // If I change it to el: document.getElementById('products') and without passing views into items object, my views are properly rendered but with memory leak
events: { },
initialize: function() {
_.bindAll(this);
this.listenTo(this.collection, 'reset', this.reset);
this.listenTo(this.collection, 'add', this.addItem);
this.listenTo(this.collection, 'change', this.changeItem);
this.listenTo(this.collection, 'destroy', this.delItem);
this.items = [];
},
reset: function(){ console.log("reset");
this.el.innerHTML = null;
this.render();
},
render: function(){
for(var i=0; i < this.collection.length; i++){
this.renderItem(this.collection.models[i]);
}
},
renderItem: function( model ){
var itemView = new ItemView({ model: model });
itemView.render();
this.items.push(itemView);
jBone(this.el).append(itemView.el);
},
addItem: function(){ console.log("addItem");
this.renderItem();
},
changeItem: function(){ console.log("changeItem"); },
delItem: function(){ console.log("delItem"); },
remove: function() {
_.invoke(this.items, 'remove');
this.items = [];
Backbone.View.prototype.remove.call(this);
}
});
return ItemsView;
This is my Itemsview it is executed when user hit orderview, there is created ItemView for every model in collection:
var ItemView = Backbone.View.extend({
tagName: "li",
className: "productcc",
initialize: function () {
_.bindAll(this, 'addItem', 'removeItem', 'updateItem');
this.listenTo(this.model, 'remove', this.removeItem);
this.listenTo(this.model, 'change', this.updateItem);
},
events: {},
render: function () {
var model = this.model.toJSON();
this.el.innerHTML += '<div class="tabody"><h4 class="tablename">'+model.title+'<h4>'+model.status+'</div>';
return this;
},
addItem: function(){
this.collection.create({"table_no":"demo"});
},
changeItem: function(e){
e.preventDefault();
this.model.save({ table_no: 'demo' });
},
updateItem: function(newstuff){
console.log("updateItem");
console.log(this.el);
},
delItem: function(){
this.model.destroy({ silent: true });
},
removeItem: function(model){
console.log("removeItem");
console.log(model);
var self = this;
self.el.remove();
}
});
return ItemView;
MY ROUTER:
var AppRouter = Backbone.Router.extend({
routes: {
'' : 'home',
'home' : 'home',
'customer/:customer_id': 'showItems'
}
});
var initialize = function(options) {
window.app_router = new AppRouter;
window.socket = io.connect('www.example.com');
this.socketOrdersCollection = new SocketOrdersCollection();
this.ordersView = new OrdersView({ collection: this.socketOrdersCollection });
this.socketOrdersCollection.fetch({ reset: true });
app_router.on('route:home', function() { });
app_router.on('route:showItems', function(customer_id) {
if (this.itemsView) {
this.itemsView.remove();
}
this.socketItemsCollection = new SocketItemsCollection();
this.socketItemsCollection.fetch({ data: { id: customer_id}, reset: true });
this.itemsView = new ItemsView({
collection: this.socketItemsCollection,
model: { tableName: customer_id }
});
});
Backbone.history.start();
};
I have to remove also ItemsView after click to another order...
Thanks for any opinion.
Ok. Let me take a stab at what you're attempting here.
var ItemsView = Backbone.View.extend({
el: document.getElementById('products'),
events: { },
initialize: function() {
// everything you had before
this.items = [];
},
// etc.
renderItem: function( model ){
var itemView = new ItemView({ model: model });
itemView.render();
this.items.push(itemView);
jBone(this.el).append(itemView.el);
},
// etc.
// we're overloading the view's remove method, so we clean up our subviews
remove: function() {
_.invoke(this.items, 'remove');
this.items = [];
Backbone.View.prototype.remove.call(this);
}
});
return ItemsView;
And then in the router:
var initialize = function(options) {
// etc.
app_router.on('route:home', function() { });
app_router.on('route:showItems', function(customer_id) {
if (this.itemsView) {
this.itemsView.remove();
}
// everything else the same
});
Backbone.history.start();
};
So now, your ItemsView will clean up any child items it has, and when you change customers, you'll clean up any ItemsView you have open before generating a new one.
EDIT
I see what you're having a problem with now.
In your route handler, you're going to need to do something along these lines:
app_router.on('route:showItems', function(customer_id) {
// everything you already have
jBone(document.getElementById('container')).append(this.itemsView);
});

Backbone.LayoutManager Delegated View Events

I've been working on a prototype Backbone application using Backbone.LayoutManager and I'm running into something I don't understand.
The scenario is that I have a form for adding "people" {firstname, lastname} to a list view, I save the model fine and the new item shows up in the list. I also have a remove function that works when after the page is refreshed, but if I try to delete the person I just created without a page refresh, the removeUser() function never gets called.
My code is below. Can someone help me out? I'm just trying to learn Backbone and if you have the answer to this question as well as any other criticisms, I'd be grateful. Thanks.
define([
// Global application context.
"app",
// Third-party libraries.
"backbone"
],
function (app, Backbone) {
var User = app.module();
User.Model = Backbone.Model.extend({
defaults : {
firstName: "",
lastName: ""
}
});
User.Collection = Backbone.Collection.extend({
model: User.Model,
cache: true,
url: "/rest/user"
});
User.Views.EmptyList = Backbone.View.extend({
template: "users/empty-list",
className: "table-data-no-content",
render: function (manage) {
return manage(this).render().then(function () {
this
.$el
.insertAfter(".table-data-header")
.hide()
.slideDown();
});
}
});
User.Views.Item = Backbone.View.extend({
template: "users/user",
tagName: "ul",
className: "table-data-row"
events: {
"click .remove": "removeUser"
},
removeUser: function () {
console.log(this.model);
this.model.destroy();
this.collection.remove(this.model);
this.$el.slideUp();
if (this.collection.length === 0) {
this.insertView(new User.Views.EmptyList).render();
}
}
});
User.Views.List = Backbone.View.extend({
initialize: function () {
this.collection.on("change", this.render, this);
},
render: function (manage) {
if (this.collection.length > 0) {
jQuery(".table-data-no-content").slideUp("fast", function() {
$(this).remove();
});
this.collection.each(function(model) {
this.insertView(new User.Views.Item({
model: model,
collection: this.collection,
serialize: model.toJSON()
}));
}, this);
} else {
this.insertView(new User.Views.EmptyList());
}
// You still must return this view to render, works identical to
// existing functionality.
return manage(this).render();
}
});
User.Views.AddUser = Backbone.View.extend({
template: "users/add-user",
events: {
"click input#saveUser": "saveUser"
},
render: function (manage) {
return manage(this).render().then(function () {
$("input[type='text']")
.clearField()
.eq(0)
.focus();
});
},
saveUser: function () {
var user = new User.Model({
firstName: $(".first-name").val(),
lastName: $(".last-name").val()
});
this.collection.create(user);
this
.$("input[type='text']")
.val("")
.clearField("refresh")
.removeAttr("style")
.eq(0)
.focus();
}
});
return User;
});
The problem turned out to be an incorrect response from the server. Once the server sent back the correct JSON object, everything worked correctly.

using localStorage in Backbone.js

I am putting together a backbone example in which models are created edited and deleted. I am able to save new models and edits to local storage, but am having a problem getting localstorage to properly display on refresh. It seems to be loading as a single object, and therefore gives me one model regardless of how many were added.
var Thing = Backbone.Model.extend({
defaults: {
title: 'blank'
}
});
var ThingView = Backbone.View.extend({
template: _.template('<b><button id="remove">X</button> <b><button id="edit">Edit</button> <%= title %></b>'),
editTemplate: _.template('<input class="name" value="<%= name %>" /><button id="save">Save</button>'),
events: {
"click #remove": "deleteItem",
"click #edit": "editItem",
"click #save": "saveItem",
},
deleteItem: function () {
console.log('deleted');
this.model.destroy();
this.remove();
},
editItem: function () {
console.log('editing');
this.$el.html(this.editTemplate(this.model.toJSON()));
},
saveItem: function () {
console.log('saved');
editTitle = $('input.name').val();
console.log(editTitle);
this.model.save({
title: editTitle
});
this.$el.html(this.template(this.model.toJSON()));
},
render: function () {
var attributes = this.model.toJSON();
this.$el.append(this.template(attributes));
return this;
}
});
var ThingsList = Backbone.Collection.extend({
model: Thing,
localStorage: new Store("store-name"),
});
var storeVar = localStorage.getItem("store-name");
console.log(storeVar);
var thingsList = new ThingsList;
thingsList.reset(storeVar);
var ThingsListView = Backbone.View.extend({
el: $('body'),
events: {
'click #add': 'insertItem',
},
initialize: function () {
this.render();
this.collection.on("add", this.renderThing, this);
},
insertItem: function (e) {
newTitle = $('input').val();
newThing = new Thing({
title: newTitle
});
this.collection.add(newThing);
newThing.save();
console.log(this.collection.length);
},
render: function () {
_.each(this.collection.models, function (items) {
this.renderThing(items);
}, this);
},
renderThing: function (items) {
var thingView = new ThingView({
model: items
});
this.$el.append(thingView.render().el);
}
});
var thingsListView = new ThingsListView({
collection: thingsList
});
You need to add the items to your collection, and then to read it in you need to call fetch. You also have a couple of extra trailing commas in your objects.
Here's a slightly modified version of your code which seems to work.
var Thing = Backbone.Model.extend({
defaults:{
title:'blank'
}
});
var ThingView = Backbone.View.extend({
//el: $('body'),
template: _.template('<b><button id="remove">X</button> <b><button id="edit">Edit</button> <%= title %></b>'),
editTemplate: _.template('<input class="name" value="<%= name %>" /><button id="save">Save</button>'),
events: {
"click #remove": "deleteItem",
"click #edit": "editItem",
"click #save": "saveItem",
},
deleteItem: function(){
console.log('deleted');
this.model.destroy();
//remove view from page
this.remove();
},
editItem: function(){
console.log('editing');
this.$el.html(this.editTemplate(this.model.toJSON()));
},
saveItem: function(){
console.log('saved');
editTitle = $('input.name').val();
console.log(editTitle);
this.model.save({title: editTitle});
this.$el.html(this.template(this.model.toJSON()));
},
render: function(){
var attributes = this.model.toJSON();
this.$el.append(this.template(attributes));
return this;
}
});
var storeVar = localStorage.getItem("store-name");
var ThingsList = Backbone.Collection.extend({
model: Thing,
localStorage: new Store("store-name")
});
var things = [
{ title: "Macbook Air", price: 799 },
{ title: "Macbook Pro", price: 999 },
{ title: "The new iPad", price: 399 },
{ title: "Magic Mouse", price: 50 },
{ title: "Cinema Display", price: 799 }
];
console.log(things);
var thingsList = new ThingsList;
var ThingsListView = Backbone.View.extend({
el: $('body'),
events: {
'click #add': 'insertItem'
},
initialize: function () {
this.render();
this.collection.on("add", this.renderThing, this);
},
insertItem: function(e){
newTitle = $('input').val();
newThing = new Thing({ title: newTitle });
this.collection.add(newThing);
newThing.save();
//this.model.saveItem;
console.log(this.collection.length);
},
render: function(){
_.each(this.collection.models, function (items) {
this.renderThing(items);
}, this);
},
renderThing: function(items) {
//console.log('added something');
var thingView = new ThingView({ model: items });
items.save();
this.$el.append(thingView.render().el);
}
});
var thingsListView = new ThingsListView( {collection: thingsList} );
thingsList.fetch();
console.log(thingsList.toJSON());
thingsList.reset(things);
Edit: I see you are trying to read in the value stored in local storage under "store-name", the way backbone-localStorage works is that it uses the name of the store (in your case "Store-name") to store the ids of the rest of the models and then saves each model under a combination of the store name and the id, so say you had three models, you would end up with 4 entries in local storage,
localStorage["store-name"] //id1, id2, id3"
localStorage["store-name-id1"] //model with id1
localStorage["store-name-id2"] // model with id2
localStorage["store-name-id3"] // model with id3
EDIT 2
Here's a link to a jsfiddle of your code, to start I'm leaving the line thingsList.fetch(); commented out, uncomment that line and comment out thingsList.add(things); and run it a second time and it should pull the models from local Storage (I left an alert in there).
var Thing = Backbone.Model.extend({
defaults: {
title: 'blank'
}
});
var ThingView = Backbone.View.extend({
template: _.template('<b><button id="remove">X</button> <b><button id="edit">Edit</button> <%= title %></b>'),
editTemplate: _.template('<input class="name" value="<%= name %>" /><button id="save">Save</button>'),
events: {
"click #remove": "deleteItem",
"click #edit": "editItem",
"click #save": "saveItem",
},
deleteItem: function () {
console.log('deleted');
this.model.destroy();
this.remove();
},
editItem: function () {
console.log('editing');
this.$el.html(this.editTemplate(this.model.toJSON()));
},
saveItem: function () {
console.log('saved');
editTitle = $('input.name').val();
console.log(editTitle);
this.model.save({
title: editTitle
});
this.$el.html(this.template(this.model.toJSON()));
},
render: function () {
var attributes = this.model.toJSON();
this.$el.append(this.template(attributes));
return this;
}
});
var ThingsList = Backbone.Collection.extend({
model: Thing,
localStorage: new Store("store-name"),
});
var storeVar = localStorage["store-name-7ee7d1e3-bbb7-b3e4-1fe8-124f76c2b64d"];
console.log(storeVar);
var thingsList = new ThingsList;
//thingsList.reset(storeVar);
var ThingsListView = Backbone.View.extend({
el: $('body'),
events: {
'click #add': 'insertItem',
},
initialize: function () {
thingsList.fetch();
thingsList.toJSON();
this.render();
this.collection.on("add", this.renderThing, this);
},
insertItem: function (e) {
newTitle = $('input').val();
newThing = new Thing({
title: newTitle
});
this.collection.add(newThing);
newThing.save();
console.log(this.collection.length);
},
render: function () {
_.each(this.collection.models, function (items) {
this.renderThing(items);
}, this);
},
renderThing: function (items) {
var thingView = new ThingView({
model: items
});
this.$el.append(thingView.render().el);
}
});
var thingsListView = new ThingsListView({
collection: thingsList
});

Resources