How to use standard URL in Backbone.Router? - backbone.js

According to the Backbone.js page:
Until recently, hash fragments (#page) were used to provide these
permalinks, but with the arrival of the History API, it's now
possible to use standard URLs (/page).
I tried to add this router rule:
routes: {
'test': function() {
alert('ok'); }
}
And called the Backbone.history.start({pushState: true, root: '/myroot/'}). I have a link in my page as:
test me
I intercepted the link's click event with:
$('a[href=test]').click(function(e) {
router.navigate('test');
e.preventDefault(); });
When I click on the link, a request is not made, which I believe the interception was succeeded. But the event is not triggered.
So, please help me understand how this History API works. Or point out where I have done wrong.

You need to turn on pushState:
Backbone.history.start({pushState: true});
Your link is going to force a full refresh from your server and your server must respond with the content of that url.
You need to intercept that link's click and tell your router to navigate to the "test" route:
myRouter.navigate("test");
For more info on the HTML5 history api: http://diveintohtml5.info/history.html
for some intro level info on using pushState with Backbone:
http://lostechies.com/derickbailey/2011/09/26/seo-and-accessibility-with-html5-pushstate-part-1-introducing-pushstate/
http://lostechies.com/derickbailey/2011/09/26/seo-and-accessibility-with-html5-pushstate-part-2-progressive-enhancement-with-backbone-js/
And a video of a presentation I gave that covers all of this:
http://lostechies.com/derickbailey/2011/10/06/seo-and-accessibility-with-html5-pushstate-part-3-the-video/
Hope that helps.

You'll need to add {trigger: true} to the navigate call to actually trigger routing events.

To indicate that you'd like to use HTML5 pushState support in your application, use Backbone.history.start({pushState: true}). If you'd like to use pushState, but have browsers that don't support it natively use full page refreshes instead, you can add {hashChange: false} to the options.
PS! If your application is not being served from the root url / of your domain, be sure to tell History where the root really is, as an option (otherwise the navigation won't work!):
Backbone.history.start({pushState: true, root: "/public/search/"})

Related

Passport.js, Express.js, and Angular.js routing: how can they coexist?

I apologize this question turned out a bit long, but I have worked on this for some time and really needed to explain all the story.
Background: App based on MEAN stack, trying to authorize Facebook logins using Passport.js.
Following Passport.js guide I implemented something similar to:
// HTML
Add a Facebook login
// send to facebook to do the authentication
app.get('/connect/facebook',isLoggedIn, passport.authorize('facebook',
{ scope : 'email' })
);
// handle the callback after facebook has authorized the user
app.get('/connect/facebook/callback',
passport.authorize('facebook', {
successRedirect : '/profile',
failureRedirect : '/profile'
}));
Notice the target=_self in the html in order to skip Angular routing.
Clearly, authorization works fine. However, redirection does not work, as the routing is handled by Angular. After authorization I never land on /profile (but on the default Angular route).
Therefore, I tried with a custom callback as suggested by Passport.js here, with the hope of passing json data to Angular, and let Angular do the routing. I ended up doing something like:
// In the controller
$http.get("/connect/facebook").success(function(data){
// here I wait for json data from the server and do the routing
});
// I call this route from Angular
app.get('/connect/facebook',isLoggedIn,passport.authorize('facebook',
{ scope : 'email' })
);
// But Facebook lands here!
app.get('/connect/facebook/callback',function(req, res, next) {
passport.authorize('facebook', function(err, user, info) {
res.json({something:smtg});
...
Clearly custom callbacks work for local-login, as Passport.js explains. But here do you see the problem? I call /connect/facebook from Angular, but I should receive some json from /connect/facebook/callback.
I am about to give up Passport, but before this, do you see any solution which would allow landing on /profile after FB authorization, perhaps with a custom message? Many thanks for reading through.
EDIT:
The same question had been reported as an issue on the Passport-Facebook GitHub account. Some additional attempts have been posted there, but not quite the fix yet.
This is a bit more in depth than can be described in one answer, but I'll try to start pointing you in the right direction.
Essentially, Angular.js routes are not really HTML routes at all, but an internal route structure that happens to use the URL for use of the end user. Remember that Angular.js is a client script, and that a full page reload is not desired, as this will reload the entire script. Therefore, /# is used to trick the browser into jumping to a specific bit of code within the already loaded script. (as opposed to the traditional anchor location in the HTML document). Unfortunately (or fortunately), HTML 5 mode allows you to hide the /# part of the url, so instead of seeing http://somesite.com/#/someroute you just see http://somesite.com/someroute. Rest assured, however, that the /# is still there. Angular.js uses the HTML5 pushState (AKA HistoryAPI) to perform the magic replacement.
Given this, if you have called a server route, you are outside the Angular.js script, and any call to load the angular script again will start from the very beginning. You can't actually call your Angular.js route from the server without a full reload. Therefore, you are really doing a double route redirect here. Your server should be calling it's default route for angular, appending /#/someroute to the call. The angular.js page will load, parse off the /#, and redirect to the correct angular route. Keep in mind, however, that if there was any dependency on already loaded objects, those are no longer in memory. Therefore, any route accessed this way should operate as if it is an entry point to your application.
Effectively, you should try using successRedirect : '#/profile', keeping in mind that the profile route in angular should be treated as an app entry point.
Hopefully this gets you started.
If #Claies's way is not working, is it possible you have not get rid of the #= fragment from the facebook callback.
Have a read of this post

Dart Polymer Routing

I have a single page app using Dart and Polymer. I'm trying to add a route to another page with route_hierarchical
What I've tried so far was configuring the router like this:
router = new Router()
router.root
..addRoute(name: 'games', path: '/games')
..addRoute(name: 'login', path: '/login')
..addRoute(name: 'home', defaultRoute: true, path: '/');
router.listen();
I've found that I can use
new Router(useFragment: ...);
to enable the hashbangs in the URLs or not. The problem is that when I don't use hashbangs, I can't access a page using the URL directly. (meaning the app routes me when I click on buttons and such). Is there a way of having sharable URLS with no hashbangs? I've seen AngularJs using something like
$locationProvider.html5Mode(true)
to remove the hashbangs and still have sharable URLs... I don't know if I really should bother having URLs without hashbangs though. I've read having them is going to give me trouble with SEO, is that right?
I'm only using Polymer and route_hierarchical right now, and didn't want to go for angular.dart just for routing.
You need to use a router on the server side too when you want to use URLs without fragments.
see also
httpd.conf and HTML5 pushstate()
Angular dart bookmarking views
AngularJS routing without the hash '#'

Angularjs Normal Links with html5Mode

I am working with angularjs in html 5 mode. Which appears to take control of all href's on the page. But what if I want to have a link to something within the same domain of the app but not actually in the app. An example would be a pdf.
If i do <a href="/pdfurl"> angular will just try to use the html5mode and use the route provider to determine which view should be loaded. But I actually want the browser to go to that page the normal way.
Is the only way to do this is to make a rule with the route provider and have that redirect to the correct page with window.location?
in HTML5 mode, there are three situations in which the A tag is not rewritten:
from the angular docs
Links that contain a target attribute. Example: link
Absolute links that point to a different domain Example: link
Links starting with '/' that lead to a different base path when base is defined Example: link
so your case would be 1. add target="_self"
As of Angular v1.3.0 there is a new rewriteLinks configuration option for the location provider. This switches "hijacking" all the links on the page off:
module.config(function ($locationProvider) {
$locationProvider.html5Mode({
enabled: true,
rewriteLinks: false
});
});
While disablig this behavior for all links may not be your intention, I'm posting this for others who, like me, want to use $location in html5 mode only to change the URL without affecting all links.
If you don't want Angular to take control of the href. Place a target attribute on the link.
So PDF will by pass the html5mode and the routeProvider and the browser will just go to that url.
Other solution. All links will work normally (reload page). Links marked by ng-href="/path" will play on pushState. Small JS hack help with it.
.config(["$locationProvider", function($locationProvider) {
// hack for html5Mode customization
$('a').each(function(){
$a = $(this);
if ($a.is('[target]') || $a.is('[ng-href]')){
} else {
$a.attr('target', '_self');
}
});
$locationProvider.html5Mode(true);
}])

Marionette PushState URL Handeling - How to do it?

I have been unable to find any articles on Backbone.Marionette using pushstate withOUT Node.js, or grunt, or require where a serious discussion is made about URL handeling. A user should be able to send a link to an internal page to her grandmother and have the link work, for example. The pushstate functionality seems uniquely ill-suited to the real world. Can someone comment and perhaps provide some links to serious articles on the subject?
Generally speaking, there's really just 2 things you need to do...
Tell Backbone to use pushstate by calling Backbone.history.start({ pushState: true });. You may also need to add something like root: 'myApp' to that parameter if your Marionette app is served from http://example.com/myApp instead of directly on http://example.com.
Configure your web server so that no matter what URL the user requests, the same content (your Marionette app) is returned (without doing a redirect).
The catch is that #2 is implemented differently depending on what type of web server you are using. And you haven't told us what stack you're on. In ASP.NET, for example, this can be handled by setting up a 'catch all' route by adding something like this to your RouteConfig.cs file:
// all requests (except those explicitly handled by another route)
// go to HomeController.Index and then the Backbone router examines
// the URL client-side to determine client-side what to do
routes.MapRoute(
name: "Default",
url: "{*clientRoute}", // this is wildcard which captures the entire URL
defaults: new { controller = "Home", action = "Index" }
);
Other web server stacks will have different ways of achieving the same type of thing.

How to handle non-root URLs in a singlepage app?

I try to make a single page app with Rails 3.2 and Backbone.js with pushState option but faced with something that I do not understand.
If I load the root URL of the app (/), everything goes right: Rails return an HTML-layout with JS which bootstraps Backbone which makes some XHRs for JSON-entities and renders the content.
But if I start using app from non-root URL (e.g. by manually typing it in the browser's address bar) then Rails will try to handle this request using theirs routing rules from routes.rb - that's wrong, cause it's a "Backbone's" route. How do I load the page and bootstrap Backbone for handling this URL in that case?
Finally I found the solution.
I put the following code into my routes.rb
class XHRConstraint
def matches?(request)
!request.xhr? && !(request.url =~ /\.json$/ && ::Rails.env == 'development')
end
end
match '(*url)' => 'home#index', :constraints => XHRConstraint.new
With this matcher all non-XHR requests are routed to HomeController which returns an HTML page. And XHR requests will be handled by other controllers which return JSON responses.
Also I left requests ending with ".json" as valid in development environment for debugging.
This is a somewhat tricky issue, but basically in a nutshell, you need to respond to all valid (HTML) requests in rails with the same (root) page, from there backbone will take over and route to the correct route handler (in your bakckbone router).
I've discussed this issue in more detail here: rails and backbone working together
Basically what I do is to create actions for every page that I want to handle, and blank views. I use respond_with to return the page (which is the same in each case) and because I handle GET actions only for HTML requests, I add this line at the top of the controller:
respond_to :html, :only => [ :show, :new ]
JSON requests are handled with respond_with as well, but unlike the HTML requests actually return the requested resource (and perform the requested action in the case of PUT, POST and DELETE).
Backbone will not be informed of your url change if you do it manually. This change will be catch by the browser and it will do its job sending the request to the server as usual.
Same if you click in a normal link, it will follow its href without inform Backbone.
If you want Backbone being in charge of a url change you have to do it through the Backbone tools you have available and this is the own Router.
So if you want to make an URL change in the Backbone way you have to do it explicitly, something like:
app.router.navigate("my/route", {trigger: true});

Resources