I need to handle 2 separate paginations on one page.
At the moment I have only one pagination, the anchors contain just plain hashtags which are parsed by the Route Object like so
HTML
Backbone Router
routes: {
":number": "paginate"
}
How would I handle two paginations at once?
What I ended up doing was I wrote a function that parses hash query strings and triggers an event via an aggregator which then gets picked up by whoever needs to react to pagination. In my case it's a collection that clears its' items and adds the next page...
Here's the router in case it helps:
define(
function(){
var r_Paginate = Backbone.Router.extend({
/* Initialise Object */
initialize: function(options){
Backbone.history.start();
},
/* Events */
routes: {
":any" : "query",
},
/* Object Methods */
query: function(a){
if(a.indexOf('&') > -1){
var queries = a.split('&');
for (x in queries){
var query = queries[x].split('=');
Z.events.trigger('paginate:' + query[0], {'page' : query[1]});
}
}
}
});
return r_Paginate
});
Related
Short description of my program and finally the problem:
I have got two pages. The first page list products in rows with a short description. If you click on one you will land on a detail page.
The detail page lists the product details and underneath a couple of related products. If you click on one of the releated products the same page is rendered again with the new information fetched from a REST interface.
If I want to use the browser-back-button or the own back-button to get to the previous product-detail-page a blank page appears. This only happens on my iPad. Using Chrome on a desktop browser works fine. I debugged the application and I figured out, that the backbonejs route is never called. I have no idea why.
Here is my code of the details page:
define([
"jquery",
"lib/backbone",
"lib/text!/de/productDetails.html"
],
function(
$,
Backbone,
ContentTemplate
){
var PageView = Backbone.View.extend({
// product details template
template: _.template(ContentTemplate),
// back-button clicked
events:{
'click a#ac-back-button':'backInHistory',
},
// init
initialize: function(options){
this.options=options;
// bind functions
_.bindAll(this,
'render',
'renderRelatedSeriePlainproduct',
'backInHistory'
);
// listen for collection
this.listenTo(this.options.relatedCollectionPlainproduct, 'reset',this.renderRelatedSeriePlainproduct);
},
// back button
backInHistory: function(e){
e.preventDefault();
window.history.back();
},
// render template
render: function(){
// render template
this.$el.html(this.template(this.model.models[0].attributes));
return this;
},
// render related products
renderRelatedSeriePlainproduct: function (){
var models = this.options.relatedCollectionPlainproduct.models;
if(models.length==0){
$('.ac-plainproduct').hide();
} else{
var elem = $('#ac-related-listing-plainproduct');
var ct="";
ct+='<ul id="ac-list-related-plainproduct">';
$.each(models, function(key, value){
ct+='<li>';
ct+='<a href="index.html?article_id='+value.get('article_id')+'&type='+value.get('type')+'&serie='+value.get('series')+'#product-detail">Link';
ct+='</a>';
ct+='</li>';
});
ct+='</ul>';
elem.append(ct);
}
}
});
// Returns the View class
return PageView;
});
I follow one of the links from renderRelatedSeriePlainproduct.If I click on the back button on the new page the backInHistory function is called, but the window.history.back(); does not call the backbone router.
Maybe the problem is the #hash in the URL, that is not changed during page transition. But this would not explain, why it works perfectly with my Chrome on my desktop machine. For me it seemed to be a problem of asynchronous calls but even there I could not find a problem.
Maybe it helps to list my router code as well. First of all I was thinking it is an zombie issue in backbone, but I remove all events and views while making the transition.
// function called by the route
// details page
productdetail: function() {
$.mobile.loading("show");
_self = this;
// lazy loading
require([
'collection/ProductDetailCollection',
'collection/RelatedCollection',
'view/ProductDetailView'
],
function(ProductDetailCollection, RelatedCollection, ProductDetailView){
// get URL parameters
var articleID = _self.URLParameter('article_id');
var type = _self.URLParameter('type');
var serie = _self.URLParameter('serie');
// product - details
var productDetail = new ProductDetailCollection.ProductDetail({id: articleID});
// related products
_self.relatedCollectionPlainproduct = new RelatedCollection({serie:serie, type:"Electronics", article_id:articleID});
// assign binded context
productDetail.fetch({
// data fetched
success: function (data) {
// page transition
_self.changePage(new ProductDetailView({
model:data,
relatedCollectionPlainproduct:_self.relatedCollectionPlainproduct
}));
// fetch data
_self.relatedCollectionPlainproduct.fetch({reset:true});
}
});
});
},
// page transition
changePage:function (page) {
// remove previous page from DOM
this.page && this.page.remove() && this.page.unbind();
// assign
this.page = page;
// assign page tag to DOM
$(page.el).attr('data-role', 'page');
// render template
page.render();
// append template to dom
$('body').append($(page.el));
// set transition
var transition = "fade";
// we want to slide the first page different
if (this.firstPage) {
transition = "fade";
this.firstPage = false;
}
// make transition by jquery mobile
$.mobile.changePage($(page.el), {changeHash:true, transition: transition});
// page was rendered - trigger event
page.trigger('render');
$.mobile.loading("hide");
},
I tried to use allowSamePageTransition but with no success. Maybe someone could give me a hint. Thanks!
Looks like jQuery Mobile and Backbone's routers are conflicting. Take a look here:
http://coenraets.org/blog/2012/03/using-backbone-js-with-jquery-mobile/
Thats not the reason. I disabled the routing of jquery mobile.
// Prevents all anchor click handling
$.mobile.linkBindingEnabled = false;
// Disabling this will prevent jQuery Mobile from handling hash changes
$.mobile.hashListeningEnabled = false;
My single page web application consists of 4-5 views stacked vertically, when a user chooses a menu item, the page will scroll to the appropriate view. When you come into the application for the first time this is not a problem, however if you deep link to a menu item my page throws a fit because it's trying to access properties of an element that does not yet exists.
The problem I am having is understanding why the elements do not exist at the time the router is trying to scroll the page.
If you load / and then select home no problems, but if you directly hit #home via browser that when I get jQuery undefined errors.
Uncaught TypeError: Cannot read property 'top' of undefined
Inside router I am instantiating and rendering all of my views within the initialize function. The idea is the initialize will always happen before any of my routes, clearly not the case.
Again I've read a few threads that show how to have a before and after function for either all routes of individual routes but even using that approach scrollToById fails because it doesn't know what $(id) is at the time of being called.
define(function (require, exports, module) {
var Backbone = require('backbone');
return Backbone.Router.extend({
initialize: function(){
require(['ui/menu/menu','ui/home/home', 'ui/samples/samples', 'ui/resume/resume', 'ui/contact/contact'],
function(Menu, Home, Samples, Resume, Contact){
var menu = new Menu();
menu.render();
var home = new Home();
home.render();
var samples = new Samples();
samples.render();
var resume = new Resume();
resume.render();
var contact = new Contact();
contact.render();
});
},
routes: {
'' : 'init',
'home' : 'home',
'samples' : 'samples',
'resume' : 'resume',
'contact' : 'contact'
},
init: function(){
},
home: function (){
this.scrollToById($(".home-container"));
},
samples: function(){
this.scrollToById($(".samples-container"));
},
resume: function(){
this.scrollToById($(".resume-container"));
},
contact: function(){
this.scrollToById($(".contact-container"));
},
scrollToById: function(id) {
var val = $(id).offset().top - 127;
$('html, body').animate({
scrollTop: val
}, 2000);
}
});
});
Appreciate any tips or advice.
I think the routes event handlers in the router are getting initialized at the same time as the initialize function. Because of this, route events are getting triggered before the DOM elements are rendered.
I would try making a new function outside of Router that contains everything currently inside the initialize function. Then the final thing in that function can be to create an instance of the router. This will ensure that no routes events are called until your scripts and DOM are loaded.
Apologies for the possibly poorly formulated title. New to Backbone.
I'm having trouble wrapping my head around how to deal with routes in association with views. Basically I have a view (let's call it ListView) that, depending on its viewMode, renders ItemViews using different templates. It looks something like this:
var ListView = Backbone.View.extend({
// Cache a bunch of templates here
viewMode: 'list', // Default is list
render: function() {
switch(this.viewMode) {
case 'list':
// Render ItemView based on list template
break;
case 'gallery':
// Render ItemView based on gallery template
break;
}
// Render all items in list
this.collection.each(function(model, index) {
new ItemView(); // Maybe pass viewMode as a parameter
});
}
});
My goal is that whenever ListView uses the viewMode "list" or "gallery", this should be reflected in the address bar, and likewise manually entering or clicking a link that leads to e.g. mysite.com/page.html#items/list or #items/gallery should render the same results.
Is there a way of automating this process, or in some other way solve it?
Think your router would be something like:
var yourRouter = Backbone.Router.extend({
routes: {
"items/list": "showList",
"items/gallery": "showGallery"
},
showList: function() {
listView.viewMode = "list"
listView.render();
}
showGallery: function() {
listView.viewMode = "gallery"
listView.render();
}
});
Then in your view events, you can call the navigate method of your router. This will update the address bar.
yourRouter.navigate("items/list")
I am building an app using Meteor and am having trouble understanding the relationship between Routes and Views. I have Routers working properly, but after having done research on calling new Views am baffled.
Do I use App.navigate ? Do I call something like:
var newView = new MyView();
within the proper router function? This is the code I am using (that works) and my app only has two pages - the index page and item view:
var Aphorism = Backbone.Router.extend({
routes: {
"saying/:id": "showSaying"
},
showSaying: function (id) {
alert('Saying id ' + id + '.');
}
});
You define what routes exist in the Router. You usually only need one of those, unless you have a very complex app.
Then you hook up links and buttons in the app to execute app.navigate when clicked. You can do this with a view or do it yourself with something like jQuery, it's up to you.
For instance:
<div id="myButton">Click me!</div>
var myView = Backbone.View.extend({
el: "#myButton",
events: {
"click": "go"
},
go: function() {
myRouter.navigate("/someUrl", {trigger: true});
}
});
How should I be handling routing in BackboneJS? When routing, after new-upping my view, should I be triggering an event, or rendering the view directly?
Here are the two scenarios:
Trigger Event:
routes: {
'orders/view/:orderId' : 'viewOrder'
},
viewOrder: function (orderId) {
var viewOrderView = new ViewOrderView();
vent.trigger('order:show', orderId);
}
In my view, I have:
var ViewOrderView = Backbone.View.extend({
el: "#page",
initialize: function () {
vent.on('order:show', this.show, this);
},
show: function (id) {
this.id = id;
this.render();
},
render: function () {
var template = viewOrderTemplate({ id: this.id });
this.$el.html(template);
return this;
}
});
OR, should I go this route:
routes: {
'orders/view/:orderId' : 'viewOrder'
},
viewOrder: function (orderId) {
var viewOrderView = new ViewOrderView({id : orderId });
viewOrderView.render();
}
In my view, I have:
var ViewOrderView = Backbone.View.extend({
el: "#page",
initialize: function () {
//init code here
},
render: function () {
var template = viewOrderTemplate({ id : this.id});
this.$el.html(template);
return this;
}
});
I think it's the first scenario - given that backbone is event driven, but the 2nd obviously has less code.
Also, I suppose a third scenario would be to keep the view code in the first scenario, but grab the router scenario of the second... rendering the view on navigation, but exposing an event in case I want to trigger that elsewhere.
Thoughts?
So all backbone questions usually end up with many plausible answers. In this case, I believe your second example is a more canonical/typical backbone pattern. Putting aside the tricky issue of handling loading spinners and updating after data loads, the simplified basic pattern in your router would be:
routes: {
'orders/view/:orderId' : 'viewOrder'
},
viewOrder: function (orderId) {
//Use models to represent your data
var orderModel = new Order({id: orderId});
//models know how to fetch data for themselves given an ID
orderModel.fetch();
//Views should take model instances, not scalar model IDs
var orderView = new OrderView({model: orderModel});
orderView.render();
//Exactly how you display the view in the DOM is up to you
//document.body might be $('#main-container') or whatever
$(document.body).html(orderView.el);
}
I think that's the textbook pattern. Again, the issue of who triggers the fetching of data and rerendering after it arrives is tricky. I think it's best if the view knows how to render a "loading" version of itself until the model has fetched data, and then when the model fires a change event after fetch completes, the view rerenders itself with the loaded model data. However, some people might put that logic elsewhere. This article on building the next soundcloud I think represents many very good "state of the art" backbone patterns, including how they handle unfetched models.
In general, you can code things with callbacks or events as you prefer. However, a good rule of thumb is to ask yourself some questions:
Is more than one independent logical piece of work going to respond to this event?
Do I need to decouple the source of this event from the things that happen in response to it?
If both of those are "yes", then events should be a good fit. If both are "no", than straightforward function logic is a better fit. In the case of "navigating to this URL triggers this view", generally the answer to both questions is "no", so you can just code that logic into the router's route handler method and be done with it.
I'd use second scenario. Don't see any benefits of using first approach. It would make more sence this way (but still arguable):
/* ... */
routes: {
'orders/view/:orderId' : 'viewOrder'
},
viewOrder: function (orderId) {
vent.trigger('order:show', orderId);
}
/* ... */
vent.on('order:show', function(orderId) {
var viewOrderView = new ViewOrderView();
viewOrderView.render();
});
var ViewOrderView = Backbone.View.extend({
el: "#page",
initialize: function (options) {
this.orderId = options.orderId;
},
render: function () {
var template = viewOrderTemplate({
id: this.orderId
});
this.$el.html(template);
return this;
}
});
This way at least you'd be able to trigger route action without updating a url. But same effect might be achieved using Backbone.router.viewOrder(1) probably. Events are pretty powerful, but i wouldn't use them if i don't really need.