backbone + layoutmanager (backbone-boilerplate) collection empty in beforeRender - backbone.js

I working on a project to test this backbone-boilerplate (https://github.com/tbranyen/backbone-boilerplate) but i have a problem to render a view.
I have the first call to beforeRender, the collection (pics) is empty because the fetching of Flickr photos is still in progress.
But when the data are loaded, i can see them in the "parse" function of my Pics.Collection (with a breakpoint), the beforeRender of my view is never called again.
My router.js :
define([
// Application.
"app",
"modules/pics"
],
function(app, HomePage, Pics) {
// Defining the application router, you can attach sub routers here.
var Router = Backbone.Router.extend({
initialize: function() {
// Init collections
var collections = {
pics: new Pics.Collection()
};
console.log('collec');
// Register in the router
_.extend(this, collections);
},
routes: {
"": "pics"
},
pics: function() {
this.reset();
app.useLayout().setViews({
"#content": new Pics.Views.List({ pics: this.pics }),
"header": new Backbone.View({
tagName: "span",
beforeRender: function() {
this.$el.html("Header Pics");
}
})
}).render();
this.pics.fetch();
},
reset: function() {
if (this.pics.length) {
this.pics.reset();
}
}
});
return Router;
});
My module, pics.js :
define([
"app"
], function(app) {
var Pics = app.module();
Pics.Model = Backbone.Model.extend({ });
Pics.Collection = Backbone.Collection.extend({
model: Pics.Model,
url: function() {
return "http://api.flickr.com/services/feeds/photos_public.gne?format=json&jsoncallback=?";
},
cache: true,
parse: function(obj) {
return obj.items;
}
});
Pics.Views.Item = Backbone.View.extend({
template: "pics/item",
tagName: "li",
serialize: function() {
return { model: this.model };
},
initialize: function() {
this.listenTo(this.model, "change", this.render);
}
});
Pics.Views.List = Backbone.View.extend({
template: "pics/list",
serialize: function() {
return { collection: this.options.pics };
},
beforeRender: function() {
this.options.pics.each(function(pic) {
this.insertView("#pics-list", new Pics.Views.Item({
model: pic
}));
}, this);
},
initialize: function() {
this.listenTo(this.options.pics, {
"reset": this.render(),
"request": function() {
this.$("ul").parent().html("<img src='/app/img/spinner-gray.gif'>");
}
});
}
});
return Pics;
});
list.html (template) :
<h1>Liste des photos</h1>
<div>
<div class="loading"></div>
<ul id="pics-list"></ul>
</div>
item.html (template) :
<li>
<img src="<%= model.get("link") %>" />
</li>
index.html :
<main role="main" id="main">
<nav>
<ul>
<li>Home</li>
</ul>
</nav>
<header></header>
<aside></aside>
<section id="content"></section>
<footer>
Copyright 2013
</footer>
</main>
I search if an existing issue exists, on forums and by myself since two days but i can't find what is going wrong.
Thanks in advance, Pierrick.

I found the problem, listen the "sync" event instead of "change".

Related

BackboneJS - Subviews navigation

I have a pretty simple mainmenu with 4 anchors and the relevant views to them. Anyhow, on one of those views I want to add a little submenu with 3 tabs, which after clicking them they show a different view. I figured out how to do it with pushState:false but what I want is a clean URL. Right now, my URL would look like http://localhost/myproject/#secondpage/subview1 or http://localhost/myproject/#secondpage/subview2 etc etc. So does anyone know how I do achieve http://localhost/secondpage no matter which subview/tab is triggered?
Im using RequireJS and HandlebarsJS (for HTML-templating)
So right now my code (snippets) look like this:
Router.js
routes: {
'': 'index',
'firstpage' : 'firstpage',
'secondpage' : 'secondpage',
'secondpage/sub1' : 'sub1',
'secondpage/sub2' : 'sub2',
'secondpage/sub3' : 'sub3',
'thirdpage' : 'thirdpage'
},
Backbone.history.start({
pushState: false
});
My HTML with the anchors:
<ul>
<li>
<a class="sub1" href="#secondpage/sub1">Bands</a>
</li>
<li>
<a class="sub2" href="#secondpage/sub2">Koncert</a>
</li>
<li>
<a class="sub3" href="#secondpage/sub3">Locations</a>
</li>
</ul>
and my View looks like
define(['backbone','handlebars', 'text!templates/SubMenu.html'],
function(Backbone,Handlebars, Template) {
'use strict';
var SubMenuView = Backbone.View.extend({
template: Handlebars.compile(Template),
initialize: function () {
_.bindAll(this);
},
render: function() {
$(this.el).html(this.template());
return this;
}
});
return SubMenuView;
}
);
Another thing is: should I move the actions to the View by setting events? I kind of tried that but it didnt work since the Views are defined in the router...
What I tried is to set pushState:true, then I removed the secondpage/sub1 thingies in my router, then in my View I wrote:
events: {
'click a.sub1': 'sub1',
},
sub1: function(event) {
event.preventDefault();
var sub1Router = new Backbone.Router();
var route = '/secondpage/';
sub1Router.navigate(route, {trigger: true});
},
but that didnt work, that gave me URL not found so...
Any help is welcome! Thanks in advance...
[UPDATE]
OK, so by request, here is my (new) router:
var Router = Backbone.Router.extend({
routes: {
'': 'index',
'firstpage' : 'firstpage',
'secondpage' : 'secondpage',
'thirdpage' : 'thirdpage'
},
initialize: function () {
var self = this;
//Views
this.mainMenuView = new MainMenuView({el:'#mainMenu'}).render();
this.subMenuView = new SubMenuView();
Backbone.history.start({
pushState: true
});
},
index: function () {
var self = this;
},
firstpage: function() {
this.firstpageView = new FirstpageView({el:'#topContent'}).render();
},
secondpage: function() {
this.secondpageView = new SecondpageView({el:'#topContent'}).render();
this.subMenuView = new SubMenuView({el:'#subMenu'}).render();
},
thirdpage: function() {
var thirdpageView = new ThirdpageView({ el:'#topContent', collection:this.categoryCollection}).render();
},
sub1: function() {
this.sub1View = new Sub1View({el:'#subContent_2'}).render();
},
sub2: function() {
this.sub2View = new Sub2View({el:'#subContent_2'}).render();
},
sub3: function() {
this.sub3View = new Sub3View({el:'#subContent_2'}).render();
}
});
return Router;
}
And my (new) View looks like:
var SubMenuView = Backbone.View.extend({
template: Handlebars.compile(Template),
events: {
'click .sub1': 'sub1',
'click .sub2': 'sub2',
'click .sub3': 'sub3',
},
sub1: function(event) {
var sub1Router = new Backbone.Router();
var route = '/secondpage';
sub1Router.navigate(route, {trigger: true});
},
sub2: function(event) {
event.preventDefault();
var sub2Router = new Backbone.Router();
var route = '/secondpage';
sub2Router.navigate(route, {trigger: true});
},
sub3: function(event) {
event.preventDefault();
var sub3Router = new Backbone.Router();
var route = '/secondpage';
sub3Router.navigate(route, {trigger: true});
},
initialize: function () {
_.bindAll(this);
},
render: function() {
$(this.el).html(this.template());
return this;
}
});
return SubMenuView;
And my (new) HTML template:
<ul>
<li>
<a class="sub1" href="/secondpage/">Sub1</a>
</li>
<li>
<a class="sub2" href="/secondpage/">Sub2</a>
</li>
<li>
<a class="sub3" href="/secondpage/">Sub3</a>
</li>
</ul>
Hope this can contribute to more input/suggestions... This is really driving me nuts which make me consider using .show() and .hide() jquery method even if i dont really want...
What you're describing is how backbone routing works, either you use '/secondpage/sub1' and use server routes, or use '#secondpage/sub1' and hit backbone routing. Either way the address bar is going to update with your URL.
One alternative option is to use a events inside the view, handling a click event and updating the view's template accordingly.
However, if you're intent on using routes then maybe have a look at clickify.js. I haven't used it myself yet, although I have it bookmarked for potential future use... sounds like it might do what you want.
try to use this in your router :
Backbone.history.navigate('secondpage');
after all the work is done (the models are fetched the views are rendered)

How to use require.js in my backbone project

I am new to backbone underscore and require.js.
I followed this tutorial to create a project using backbone.js and underscore.js .
Then I want to add require.js to that project.
This is what I modify in the theater.html :
<body>
<h1>My Theater</h1>
<script src="libs/require.js" type="text/javascript"></script>
<script src="main.js" type="text/javascript"></script>
<div id="mainContainer"></div>
<input type="button" value="Add Item" id="butAddItem" />
<script type="text/template" id="tmplt-Movies">
<ul>
</ul>
</script>
<script type="text/template" id="tmplt-Movie">
<div>*******************************************************</div>
<div><%= Id %> </div>
<div><%= Name %> </div>
<div><%= AverageRating %> </div>
<div><%= ReleaseYear %> </div>
<div><%= Url %> </div>
<div><%= Rating %> </div>
</script>
</body>
I added fews line of code to main.js file :
require.config({
paths: {
jquery: 'libs/jquery-1.7.1',
underscore: 'libs/underscore',
backbone: 'libs/backbone'
} });
Then I got 2 errors :
1. ReferenceError: Backbone is not defined , Theater.Models.Movie = Backbone.Model.extend({});
This is main.js file :
require.config({
paths: {
jquery: 'libs/jquery-1.7.1',
underscore: 'libs/underscore',
backbone: 'libs/backbone'
} });
var Theater = {
Models: {},
Collections: {},
Views: {},
Templates:{}
};
Theater.Models.Movie = Backbone.Model.extend({});
Theater.Collections.Movies = Backbone.Collection.extend({
model: Theater.Models.Movie,
url: "data/movies.json",
initialize: function(){
console.log("Movies initialize");
}
});
Theater.Templates.movies = _.template($("#tmplt-Movies").html());
Theater.Views.Movies = Backbone.View.extend({
el: $("#mainContainer"),
template: Theater.Templates.movies,
//collection: new Theater.Collections.Movies(), //Not needed
initialize: function () {
//_.bindAll(this, "render", "addOne", "addAll");
this.collection.bind("reset", this.render, this);
this.collection.bind("add", this.addOne, this);
},
render: function () {
console.log("render");
console.log(this.collection.length);
$(this.el).html(this.template());
this.addAll();
},
addAll: function () {
console.log("addAll");
this.collection.each(this.addOne);
},
addOne: function (model) {
console.log("addOne");
view = new Theater.Views.Movie({ model: model });
$("ul", this.el).append(view.render());
}
});
Theater.Templates.movie = _.template($("#tmplt-Movie").html());
Theater.Views.Movie = Backbone.View.extend({
tagName: "li",
template: Theater.Templates.movie,
//events: { "click .delete": "test" },
initialize: function () {
//_.bindAll(this, 'render', 'test');
this.model.bind('destroy', this.destroyItem, this);
this.model.bind('remove', this.removeItem, this);
},
render: function () {
return $(this.el).append(this.template(this.model.toJSON())) ;
},
removeItem: function (model) {
console.log("Remove - " + model.get("Name"));
this.remove();
}
});
Theater.Router = Backbone.Router.extend({
routes: {
"": "defaultRoute" //http://localhost:22257/Theater/theater.htm
},
defaultRoute: function () {
console.log("defaultRoute");
Theater.movies = new Theater.Collections.Movies();
new Theater.Views.Movies({ collection: Theater.movies }); //Add this line
Theater.movies.fetch();
console.log(Theater.movies.length);
}
});
var appRouter = new Theater.Router();
Backbone.history.start();
//This is a hack for demonstration purposes
$("#butAddItem").click(null, function () {
var movie = new Theater.Models.Movie(
{
"Id": "BVP3s",
"Name": "Lord of the Rings: The Return of the King: Extended Edition: Bonus Material",
"AverageRating": 4.3,
"ReleaseYear": 2003,
"Url": "http://www.netflix.com/Movie/Lord_of_the_Rings_The_Return_of_the_King_Extended_Edition_Bonus_Material/70024204",
"Rating": "PG-13"
}
);
Theater.movies.add(movie);
console.log(Theater.movies.length);
});
And i have no idea how to convert the main.js and create a app.js file for using require.js.
Any idea please.
Thank you so much.
Firstly...
<script src="libs/require.js" type="text/javascript"></script>
<script src="main.js" type="text/javascript"></script>
Could be
<script src="libs/require.js" data-main="main.js" type="text/javascript"></script>
Secondly
require.config({
baseUrl: '.',
shim: {
'backbone': {
deps: ['underscore'],
exports: 'Backbone'
}
},
deps: ['backbone','jquery'],
paths: {
jquery: 'libs/jquery-1.7.1',
underscore: 'libs/underscore',
backbone: 'libs/backbone'
}
});
require(['app']);
And finally wrap your app.js in a define.
define(function () {
Theater.Models.Movie = Backbone.Model.extend({});
Theater.Collections.Movies = Backbone.Collection.extend({
model: Theater.Models.Movie,
url: "data/movies.json",
initialize: function () {
console.log("Movies initialize");
}
});
Theater.Templates.movies = _.template($("#tmplt-Movies").html());
Theater.Views.Movies = Backbone.View.extend({
el: $("#mainContainer"),
template: Theater.Templates.movies,
//collection: new Theater.Collections.Movies(), //Not needed
initialize: function () {
//_.bindAll(this, "render", "addOne", "addAll");
this.collection.bind("reset", this.render, this);
this.collection.bind("add", this.addOne, this);
},
render: function () {
console.log("render");
console.log(this.collection.length);
$(this.el).html(this.template());
this.addAll();
},
addAll: function () {
console.log("addAll");
this.collection.each(this.addOne);
},
addOne: function (model) {
console.log("addOne");
view = new Theater.Views.Movie({
model: model
});
$("ul", this.el).append(view.render());
}
});
Theater.Templates.movie = _.template($("#tmplt-Movie").html());
Theater.Views.Movie = Backbone.View.extend({
tagName: "li",
template: Theater.Templates.movie,
//events: { "click .delete": "test" },
initialize: function () {
//_.bindAll(this, 'render', 'test');
this.model.bind('destroy', this.destroyItem, this);
this.model.bind('remove', this.removeItem, this);
},
render: function () {
return $(this.el).append(this.template(this.model.toJSON()));
},
removeItem: function (model) {
console.log("Remove - " + model.get("Name"));
this.remove();
}
});
Theater.Router = Backbone.Router.extend({
routes: {
"": "defaultRoute" //http://localhost:22257/Theater/theater.htm
},
defaultRoute: function () {
console.log("defaultRoute");
Theater.movies = new Theater.Collections.Movies();
new Theater.Views.Movies({
collection: Theater.movies
}); //Add this line
Theater.movies.fetch();
console.log(Theater.movies.length);
}
});
var appRouter = new Theater.Router();
Backbone.history.start();
//This is a hack for demonstration purposes
$("#butAddItem").click(null, function () {
var movie = new Theater.Models.Movie({
"Id": "BVP3s",
"Name": "Lord of the Rings: The Return of the King: Extended Edition: Bonus Material",
"AverageRating": 4.3,
"ReleaseYear": 2003,
"Url": "http://www.netflix.com/Movie/Lord_of_the_Rings_The_Return_of_the_King_Extended_Edition_Bonus_Material/70024204",
"Rating": "PG-13"
});
Theater.movies.add(movie);
console.log(Theater.movies.length);
});
});
You could switch out your version of backbone for backbone-amd which is a AMD compatible version available through Bower, and use Lodash instead of Underscore. On top of that, you should start thinking about abstracting your backbone models, collections, views and router into separate files.
Hope this helps.

Nested views in Backbonejs, a post & comments relation

Array Of Objects
The data is received from server
var Updates = [
{"post_id":"1","post_desc":"This is my first post",
"comments":[{"id":1,"comment":"some comments","like":7},
{"id":9,"comment":"some comments","like":3}
]
},
{"post_id":"2","post_desc":"This is my second post",
"comments":[{"id":5,"comment":"some comments","like":5}]
}]
Model:
var Update = Backbone.Model.extend({
defaults:{
photo: "default.png"
}
});
Collection:
var latestUpdates = Backbone.Collection.extend({
model: Update
});
Single View:
var UpdateView = Backbone.View.extend({
tagName: "div",
className: "post-container",
template: $("#postTemplate").html(),
render: function () {
var tmpl = _.template(this.template);
this.$el.html(tmpl(this.model.toJSON()));
return this;
}
});
Master view:
var UpdatesView = Backbone.View.extend({
el: $("#postContainer"),
initialize: function () {
this.collection = new latestUpdates(Updates);
this.render();
},
render: function () {
var that = this;
_.each(this.collection.models, function (item) {
that.renderUpdates(item);
}, this);
},
renderUpdates: function (item) {
var updateView = new UpdateView({
model: item
});
this.$el.append(updateView.render().el);
}
});
//create app instance
var wallUpdates = new UpdatesView();
How can I render comments section under each post?
Trying to achieve layout similar to facebook post-comment system
I'd use a CommentListView, owned by your UpdateView. tagName: "ul", className: "post-comments"
Then have a CommentView owned by the CommentListView. CommentView's render should not append anything to the DOM, but return its $el.
CommentListView would tell each of the CommentView's to render, appending each of their $el's to the CommentListView's $el.
For the containers, I'd use:
<div class="post-container" data-post-id="<%= YourPostId %>">
<div class="post-body">
<!--Your post can go in here-->
</div>
<ul class="post-comments">
<!--Append your comments in here-->
</ul>
</div>

Backbone Marionette Nested Composite View

So I am stuck. I got the great Backbone.Marionette to handle my nested childs/parents relationships and rendering(doing it with the bare backbone was a nightmare), but now i'm facing problems with my nested composite view,
I'm always getting a The specified itemViewContainer was not found: .tab-content from the parent composite view - CategoryCollectionView, although the itemViewContainer is available on the template, here is what I'm trying to do, I have a restaurant menu i need to present, so I have several categories and in each category I have several menu items, so my final html would be like this:
<div id="order-summary">Order Summary Goes here</div>
<div id="categories-content">
<ul class="nav nav-tabs" id="categories-tabs">
<li>Appetizers</li>
</ul>
<div class="tab-content" >
<div class="tab-pane" id="category-1">
<div class="category-title">...</div>
<div class="category-content">..the category items goes here.</div>
</div>
</div>
Here is what I have so far:
First the templates
template-skeleton
<div id="order-summary"></div>
<div id="categories-content"></div>
template-menu-core
<ul class="nav nav-tabs" id="categories-tabs"></ul>
<div class="tab-content" ></div>
template-category
<div class="category-title">
<h2><%=name%></h2>
<%=desc%>
</div>
<div class="category-content">
The menu items goes here
<ul class="menu-items"></ul>
</div>
template-menu-item
Item <%= name%>
<strong>Price is <%= price%></strong>
<input type="text" value="<%= quantity %>" />
Add
Now the script
var ItemModel = Backbone.Model.extend({
defaults: {
name: '',
price: 0,
quantity: 0
}
});
var ItemView = Backbone.Marionette.ItemView.extend({
template: '#template-menuitem',
modelEvents: {
"change": "update_quantity"
},
ui: {
"quantity" : "input"
},
events: {
"click .add": "addtoBasket"
},
addtoBasket: function (e) {
this.model.set({"quantity": this.ui.quantity.val() });
},
update_quantity: function () {
//#todo should we do a re-render here instead or is it too costy
this.ui.quantity.val(this.model.get("quantity"));
}
});
var ItemCollection = Backbone.Collection.extend({
model: ItemModel
});
var CategoryModel = Backbone.Model.extend({
defaults: {
name: ''
}
});
var CategoryView = Backbone.Marionette.CompositeView.extend({
template: '#template-category',
itemViewContainer: ".menu-items",
itemView: ItemView,
className: "tab-pane",
id: function(){
return "category-" + this.model.get("id");
},
initialize: function () {
this.collection = new ItemCollection();
var that = this;
_(this.model.get("menu_items")).each(function (menu_item) {
that.collection.add(new ItemModel({
id: menu_item.id,
name: menu_item.name,
price: menu_item.price,
desc: menu_item.desc
}));
});
}
});
var CategoryCollection = Backbone.Collection.extend({
url: '/api/categories',
model: CategoryModel
});
var CategoryCollectionView = Backbone.Marionette.CompositeView.extend({
el_tabs: '#categories-tabs',
template: '#template-menu-core',
itemViewContainer: ".tab-content", // This is where I'm getting the error
itemView: CategoryView,
onItemAdded: function (itemView) {
alert("halalouya");
//this.$el.append("<li>" + tab.get("name") + "</li>");
//$(this.el_tabs).append("<li><a href='#category-" + itemView.model.get("id") + "'>"
//+ itemView.model.get("name") + "</a></li>")
}
});
I know It's a bit hard to follow but you guys are my last resort. There is no problems with the templates and the cateogry fetching and the other stuff(it was already working before converting the CategoryCollectionView from a Marionette collection to a composite view.)
Edit 1
Added App initalizer on request:
AllegroWidget = new Backbone.Marionette.Application();
AllegroWidget.addInitializer(function (options) {
// load templates and append them as scripts
inject_template([
{ id: "template-menuitem", path: "/js/templates/ordering-widget-menuitem.html" },
{ id: "template-category", path: "/js/templates/ordering-widget-category.html" },
{ id: "template-menu-core", path: "/js/templates/ordering-widget-menu-core.html" },
{ id: "template-skeleton", path: "/js/templates/ordering-widget-skeleton.html" }
]);
// create app layout using the skeleton
var AppLayout = Backbone.Marionette.Layout.extend({
template: "#template-skeleton",
regions: {
order_summary: "#order-summary",
categories: "#categories-content"
}
});
AllegroWidget.layout = new AppLayout();
var layoutRender = AllegroWidget.layout.render();
jQuery("#allegro-ordering-widget").html(AllegroWidget.layout.el);
// Initialize the collection and views
var _category_collection = new CategoryCollection();
var _cateogories_view = new CategoryCollectionView({ api_key: window.XApiKey, collection: _category_collection });
_category_collection.fetch({
beforeSend: function (xhr) {
xhr.setRequestHeader("X-ApiKey", window.XApiKey);
},
async: false
});
//AllegroWidget.addRegions({
/// mainRegion: "#allegro-ordering-widget"
//});
AllegroWidget.layout.categories.show(_cateogories_view);
});
AllegroWidget.start({api_key: window.XApiKey});
You are adding to the collection via fetch before you call show on the region.
Marionette.CompositeView is wired by default to append ItemViews when models are added to it's collection. This is a problem as the itemViewContainer .tab-content has not been added to the dom since show has not been called on the region.
Easy to fix, rework you code as below and it should work without overloading appendHtml.
// Initialize the collection and views
var _category_collection = new CategoryCollection();
// grab a promise from fetch, async is okay
var p = _category_collection.fetch({headers: {'X-ApiKey': window.XApiKey});
// setup a callback when fetch is done
p.done(function(data) {
var _cateogories_view = new CategoryCollectionView({ api_key: window.XApiKey, collection: _category_collection });
AllegroWidget.layout.categories.show(_cateogories_view);
});
okay this is pretty weird but adding this in the CategoryCollectionView class:
appendHtml: function (collectionView, itemView, index) {
//#todo very weird stuff, assigning '.tab-content' to itemViewContainer should have been enough
collectionView.$(".tab-content").append(itemView.el);
}
solved the problem, however i have no idea why it works, asssigning '.tab-content' to the itemViewContainer should have been enough, any idea?

backbone.js history with only one route?

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>​

Resources