ok I have
{{#each mainmenu}}
<li>
<h2>{{this.name}}</h2>
</li>
{{/each}}
and in my router
routes: {
'': 'index',
'pages/firstpage' : 'firstpage',
'pages/secondpage' : 'secondpage',
'pages/thirdpage' : 'thirdpage'
},
initialize: function () {
Backbone.history.start({pushState: false});
}
and my json file is:
{
"name":"First page",
"id":"1",
"url":"firstpage"
},
{
"name":"Second page",
"id":"2",
"url":"secondpage"
},
{
"name":"Third page",
"id":"3",
"url":"thirdpage"
}
the way it is right now my URL is "#pages/secondpage" - how can i get the URL to display "pages/secondpage" - i tried "pushState:true" which didnt work... then in my mainmenu.js view I added an event:
events: {
'click a.second': 'secondpage'
},
secondpage: function() {
var secondpageRouter = new Backbone.Router();
var route = 'pages/secondpage';
secondpageRouter.navigate(route, {trigger: true});
}
but that didnt work either... when I remove the "#pages/" from the anchor above, I almost get the URL I want "pages/secondpage" - but it says "URL could not be found" after clicking the link. So what's going on here???
Please help anyone?
Understand that you need to be able to serve these urls from the server if necessary, read the docs here: http://backbonejs.org/#History
Then you need to do at least three things.
First, after you have defined your routers, call Backbone.history and make sure pushState is true:
Backbone.history.start({ pushState: true })
Second, get rid of all the # in your links, like this:
<h2>{{this.name}}</h2>
Third, and this is important, you need to intercept all clicks on local links and prevent their default behavior, which would be to route you to a new page. This is easy with jQuery:
$(document).on("click", "a[href^='/']", function(event){
event.preventDefault();
RouterNameGoesHere.navigate($(event.target).attr("href"), {trigger: true});
});
If you aren't using jQuery, listen to link click events in your views deal with the url in your event handlers. I modified your code to add the event argument and event.preventDefault():
events: {
'click a.second': 'secondpage'
},
secondpage: function(event) {
event.preventDefault();
var secondpageRouter = new Backbone.Router();
var route = 'pages/secondpage';
secondpageRouter.navigate(route, {trigger: true});
}
Here is an interesting blog post about this issue
Related
This is the first time I am using Backbone and I seem to be stuck on the basics, so bear with me.
I just want to use Backbone for Routing, I'm currently testing it within the News section of my site but I can't get the routes to trigger the functions I want.
Here' my code:
var NewsRouter = Backbone.Router.extend({
routes: {
"*news": "init",
"news:tmpl": "loadTemplate",
},
init: function(params) {
//$("#main").load("/news/all");
console.log('news called')
},
loadTemplate: function(tmpl) {
console.log('loadTemplate function called')
}
});
var news_router = new NewsRouter;
Backbone.history.start();
I have this route working fine:
mysite.dev/news/ - console shows "news called"
mystic.dev/news/interviews - should call loadTemplate()
What am I mssing?
You missed slash after "news" in the route for 'loadTemplate':
"news/:tmpl": "loadTemplate",
Note that in your case router is configured only for hash-based navigation (like '#news/interviews' ). You may enable URL-based navigation by specifying additional options for 'start' method:
Backbone.history.start({ pushState: true });
I've tested. This works.
var NewsRouter = Backbone.Router.extend({
routes: {
"news": "init",
"news/:tmpl": "loadTemplate",
},
init: function(params) {
//$("#main").load("/news/all");
alert('news called');
},
loadTemplate: function(tmpl) {
alert('loadTemplate function called: ' + tmpl);
}
});
var news_router = new NewsRouter;
Backbone.history.start();
Only updated below part.
routes: {
"news": "init",
"news/:tmpl": "loadTemplate",
},
Basically, you also need to remove * (asterisk) apart from missing slash as answered by #Vitaliy Fedorchenko.
Backbone code is not as complex as jQuery. It's pretty readable. So best thing is go to code and read rather than finding documentation. I don't understand regex as much, but if you see splatParam variable, I think it is treating asterisk as wild match. Anyone can please correct me if I'm wrong.
I'm brand new to backbone and just learning the basics. I am building an image gallery with backbone. I am displaying a large version of an image. The routing is working properly. When a url is passed with an id the appropriate JSON is loaded into the model and the html is injected into the dom. Everything displays as expected.
However, I tried entering a url for the JSON for an image that didn't exist and noticed that the view still rendered but with the previously rendered view's properties (image url) still present. How do I ensure that the view is refreshed - all empty properties? Or is it the model that needs to be refreshed?
Note: I am re-using the view to avoid the overhead of creating and dystroying the view itself.
Here is the view in question:
var ImageView = Backbone.View.extend({
template: Handlebars.compile(
'<div class="galleryImageSingle">'+
'<h2>{{title}}</h2>' +
'<img id="image" src="{{imageUrl}}" class="img-polaroid" />' +
'<div class="fb-share share-btn small"><img src="img/fb-share-btn- small.png"/></div>'+
'</div>' +
'<div class="black-overlay"></div>'
),
initialize: function () {
this.listenTo(this.model, "change", this.render);
//this.model.on('change',this.render,this);
},
fbSharePhoto: function () {
console.log('share to fb ' + this.model.attributes.shareUrl)
},
close: function () {
//this.undelegateEvents();
this.remove();
},
render: function () {
this.$el.html(this.template(this.model.attributes));
this.delegateEvents({
'click .fb-share' : 'fbSharePhoto',
'click .black-overlay' : 'close'
});
return this;
}
})
Here is the router:
var AppRouter = Backbone.Router.extend({
routes: {
"" : "dashboard",
"image/:iId" : "showImage",
},
initialize: function () {
// this.galleriesCollection = new GalleriesCollection(); //A collection of galleries
// this.galleriesCollection.fetch();
this.imageModel = new Image();
this.imageView = new ImageView ({ model: this.imageModel });
},
dashboard: function () {
console.log('#AppRouter show dashboard - hide everything else');
//$('#app').html(this.menuView.render().el);
},
showImage: function (iId) {
console.log('#AppRouter showPhoto() ' + iId);
this.imageModel.set('id', iId);
this.imageModel.fetch();
$('#imageViewer').html(this.imageView.render().el);
}
});
Is is it that the model still has the old info or the view, or both?
For extra credit, how could I detect a failure to fetch and respond to it by not triggering the corresponding view? Or I am I coming at it wrongly?
Thanks in advance for any advice.
////////////////////////////////////////////////////////////////////////////
Looks like I found something that works. I think just the process of framing the question properly helps to answer it. (I'm not allowed to answer the question so I'll post what I found here)
It appears that its the model that needs refreshing in this case. In the app router when I call the showImage function I clear the model and reset its values to default before calling fetch and this did the trick. Ironically the trick here is showing a broken image tag.
showImage: function (iId) {
console.log('#AppRouter showPhoto() ' + iId);
this.imageModel.clear().set(this.imageModel.defaults);
this.imageModel.set('id', iId);
this.imageModel.fetch();
$('#imageViewer').html(this.imageView.render().el);
}
For my own extra credit offer: In the event of an error (if needed fetch() accepts success and error callbacks in the options hash). Still definitely open to hearing about a way of doing this thats baked in to the framework.
You can just update the model like this:
ImageView.model.set(attributes)
I began learning Backbonejs recently, by reading a book. and I feel a little bit confuse about this issue.Here is a Router:
define(['views/index', 'views/login'], function(indexView, loginView) {
var SelinkRouter = Backbone.Router.extend({
currentView: null,
routes: {
'home': 'home',
'login': 'login'
},
changeView: function(view) {
if(null != this.currentView)
this.currentView.undelegateEvents();
this.currentView = view;
this.currentView.render();
},
home: function() {
this.changeView(indexView);
},
login: function() {
this.changeView(loginView);
}
});
return new SelinkRouter();
});
and this is the boot method of a application:
define(['router'], function(router) {
var initialize = function() {
// Require home page from server
$.ajax({
url: '/home', // page url
type: 'GET', // method is get
dataType: 'json', // use json format
success: function() { // success handler
runApplicaton(true);
},
error: function() { // error handler
runApplicaton(false);
}
});
};
var runApplicaton = function(authenticated) {
// Authenticated user move to home page
if(authenticated) window.location.hash='home';
//router.navigate('home', true); -> not work
// Unauthed user move to login page
else window.location.hash='login';
//router.navigate('login', true); -> not work
// Start history
Backbone.history.start();
}
return {
initialize: initialize
};
});
My question is about the runApplication part. The example of the book that I read passed router into module just like this, but it used window.location.hash = "XXX", and the router wasn't touched at all.
I thought the "navigate" method would make browser move to the page I specified, but nothing happened. Why?
And for the best practice sake, what is the best way to achieve movement between pages(or views)?
thanks for any ideas.
You could also use the static method to avoid router dependency (while using requirejs for instance).
Backbone.history.navigate(fragment, options)
This way, you just need :
// Start history
Backbone.history.start();
// Authenticated user move to home page
if(authenticated)
Backbone.history.navigate('home', true);
// Unauthed user move to login page
else
Backbone.history.navigate('login', true);
According to the documentation, if you also want to call the function belonging to a specific route you need to pass the option trigger: true:
Whenever you reach a point in your application that you'd like to save
as a URL, call navigate in order to update the URL. If you wish to
also call the route function, set the trigger option to true. To
update the URL without creating an entry in the browser's history, set
the replace option to true.
your code should look like:
if(authenticated)
router.navigate('home', {trigger: true});
Once your router is created, you also have to call
Backbone.history.start();
Backbone.history.start([options])
When all of your Routers have
been created, and all of the routes are set up properly, call
Backbone.history.start() to begin monitoring hashchange events, and
dispatching routes.
Finally the runApplication logic will be something similar to this:
var runApplicaton = function(authenticated) {
var router = new SelinkRouter();
// Start history
Backbone.history.start();
// Authenticated user move to home page
if(authenticated)
router.navigate('home', true);
// Unauthed user move to login page
else
router.navigate('login', true);
}
I'm using the Backbone Layout Manager Boilerplate. Unfortunately, a quite frustrating bug occurred. I like render a list of items as subviews inserted by insertView function. At the first load everthing works fine. But after a reload the the click events doesn't work anymore :(. I already tried to call delegateEvents() on the TableItem View manually but nothing changed. I hope anyone can give me a clue.
App.Views.Item = Backbone.View.extend({
template: "templates/item",
tagName: "li",
events: {
"click .applyButton" : "apply",
"click .viewDetailsButton" : "showDetail"
},
serialize: function() {
return { table : this.model.toJSON() };
},
apply: function(ev) {
ev.preventDefault();
alert("apply button clicked");
},
showDetail: function(ev) {
ev.preventDefault();
var id = this.model.get("_id");
app.router.navigate("#events/"+ id, {trigger : true})
}
});
/*
* List View
*/
App.Views.List = Backbone.View.extend({
template: "templates/list",
tagNam: "ul",
className: "tableList",
beforeRender: function() {
var events = this.model.get("userEvents").get("hosting");
events.each(function(model) {
this.insertView(new App.Views.Item({ model : model }));
}, this);
},
serialize: function() {
return {};
}
});
I think you might want to add a cleanup function on your Item view to undelegate the events when layoutmanager removes the view. I don't know if this will fix your problem, but it seems like good practise.
When you say after a reload, do you mean reloading the page with the browser reload button? if so, how do you get it to work in the first place?
It would help if you could provide a jsfiddle of your setup, or point us to a repo so we can test it on our machines. Make sure you include the router so that we can have a look at how the view and the layout that contains it are initialised.
I'm trying to prevent full page reloads using Backbone's pushState. When I call navigate() from my view's event, I see the messages marked // 1 below, but not // 2. In addition, when I try to open the same tab, the page reloads again.
Must I stop the event myself? I tried using jQuery's preventDefault(), which does prevent the page reload, but I haven't seen this documented anywhere.
Below is my current code:
App.Router = Backbone.Router.extend({
routes:{
"analytics":"analytics"
, "realtime":"realtime"
}
, analytics:function(page) {
console.log("analytics route hit: %o", page); // 2
}
, realtime:function(page) {
console.log("realtime route hit: %o", page); // 2
}
});
App.TabSetView = Backbone.View.extend({
initialize:function() {
this.collection.bind("reset", this.render, this);
this.collection.bind("add", this.render, this);
this.collection.bind("change", this.render, this);
this.collection.bind("remove", this.render, this);
}
, events:{
'click li.realtime a': "onRealtime"
, 'click li.analytics a': "onAnalytics"
}
, render:function() {
// omitted for brevity
}
, onAnalytics:function() {
console.log("onAnalytics"); // 1
if (this.collection.activateAnalytics()) {
App.app.navigate("analytics", true);
this.render();
console.log("navigated");
} else {
console.log("do nothing"); // 1
}
}
, onRealtime:function() {
console.log("onRealtime");
if (this.collection.activateRealtime()) {
App.app.navigate("realtime", true);
this.render();
console.log("navigated");
} else {
console.log("do nothing"); // 1
}
}
});
var tabs = ...; // omitted for brevity
var tabSetView = new App.TabSetView({collection: tabs});
var App.app = new App.Router;
Backbone.history.start({pushState:true});
to stop the page reload when a user clicks a link, you have to call e.preventDefault() like you were suggesting.
MyView = Backbone.View.extend({
events: {
"click .some a": "clicked"
},
clicked: function(e){
e.preventDefault();
// do your stuff here
}
});
you're also right that this isn't documented in the backbone docs. events are handled by jQuery, though. so you can assume that any valid jQuery things you would do - such as have an e parameter to an event callback - will work with backbone's events.
as for this:
in addition, when I try to open the same tab, the page reloads again.
are you saying when a user opens a new browser tab to your site's url? if so, then there's nothing you can do about this. when the browser opens the tab it makes the request to the server to load the page.
if you're referring to a "tab" as part of your site's user interface, though, then the use of e.preventDefault() on your link / "tab" clicks should take care of that.
The answer is actually in here https://stackoverflow.com/a/9331734/985383, if you enable pushState you want links to work and not prevent them as suggested above, or well, is not just preventing them. here it is:
initializeRouter: function () {
Backbone.history.start({ pushState: true });
$(document).on('click', 'a:not([data-bypass])', function (evt) {
var href = $(this).attr('href');
var protocol = this.protocol + '//';
if (href.slice(protocol.length) !== protocol) {
evt.preventDefault();
app.router.navigate(href, true);
}
});
}
$('a').click(function(e){
e.preventDefault();
Backbone.history.navigate(e.target.pathname, {trigger: true});
});
Just a follow up to Derick answer.
It worked for me, but to keep it clean, I overwrote the Backbone.View class:
(coffeescript)
class NewBackboneView extends Backbone.View
events:
'click a' : 'pushstateClick'
pushstateClick: (event) ->
event.preventDefault()
Backbone.View = NewBackboneView
So every link from my backbone views have the prevent default.
It depends on how you've generated the HTML mark-up. It looks like you're using anchor tags (<a>), so if those anchor tag href have values or even an empty string, then you need to cancel the default browser behavior otherwise you'll get a page reload. You can cancel the default behaviour using jQuery're event.preventDefault() like you mentioned. Alternatively, if you're not concerned about progressive enhancement or SEO, then you can set your anchor tag href to # or javascript:void(0);, which will also prevent to the page from reloading. e.g.
Click me
or
Click me