Backbone router, pushstate, and relative urls - backbone.js

I'm making my first backbone app. I have pushstate enabled. In the router I have two routes.
'link1(/)' : 'link1Route',
'link2(/)' : 'link2Route'
On my link1 page, I have a link pointing to link 2 like so:
Link2
I click on the link, the router takes over and I go to link2Route as expected. However, if I hover over the link, the status bar indicates that the link is at domain.com/link1/link2 rather than domain.com/link2. This is correct given the relative url structure on the link1 page. Unfortunately, if a user decides to open the link in a new window or tab, backbone's router will not take effect. This is expected since opening in a new window or tab results in a new page load from the server. The problem of course, is that domain.com/link1/link2 does not exist.
What are some ways of dealing with this situation? Thanks.

As mateusmaso indicated, you need to change your link target to "/link2" (i.e. including the "/").
In addition, when implementing pushState, don't forget you need your server to respond with content if a user requests the "domain.com/link2" URL. The response can be index.html if you wish, but the server must return a page for that URL.
If you return index.html for all your Backbone-related URLs, when your Backbone app gets started the route-handling code will get fired and the proper data will be displayed.
If you want to learn more about routing and how to manage it properly without needing to pass trigger: true when calling navigate, check out pages 32-46 of this free sample pdf: http://samples.leanpub.com/marionette-gentle-introduction-sample.pdf (full disclosure, I'm the book author)

You must serve the correct index.html at the server level — since this is before the JavaScript even loads on the page.
http://readystate4.com/2012/05/17/nginx-and-apache-rewrite-to-support-html5-pushstate/

Related

How to provide a specific link to angularJs routed page

I have an angularJs application, which uses $routeProvider to handle internal routing. The back end uses Rest controllers within a Spring Boot application. The application is based on this: https://spring.io/guides/tutorials/spring-security-and-angular-js/
What I want to do is send an email with an embedded link similar to "http://localhost:8080/link/key1234". The user should be able to click this, and go to specific content as indicated by the key1234 variable.
I have seen a similar item (Refreshing page gives "Page not found") which suggests using .htaccess to handle this, but I do not currently have a .htaccess file.
I also have html5mode on [$locationProvider.html5Mode(true);]
Another suggestion (Spring Boot with AngularJS html5Mode) is to intercept the request and redirect to the home page. Whilst this prevents the application from failing, I need the user to go to a specific page, not the Home page.
I have managed to get quite confused about the interactions of my different components. Can you point me in the right direction to enable a specific link to be provided ?
UPDATE
I think that the .htaccess file is a red herring.
The spring.io tutorial includes the following code:
#RequestMapping(value = "/{[path:[^\\.]*}")
public String redirect() {
return "forward:/";
}
And this segment performs a redirect for some of the pages.
However, my tests (until recently) had been using "http://localhost:8080/public/about" which is not getting picked up by the RequestMapping snippet above.
If I use a single level url (e.g. "http://localhost:8080/test") then my existing code works fine.
It looks like there is a flaw in this regex.
When a request like http://localhost:8080/link/key1234 hits your server, you would need to forward the request to home page. Then, the client should get the response of the home page, while the URL will still be http://localhost:8080/link/key1234. html5mode will then come to play, changing the view at the client side.
I think the best way to do it might be to use urlrewrite filter, which should forward all requests except those starting with, say /api/**, to the home page.

what is the difference between hashbang and HTML5 pushState? [duplicate]

I'm asking this because a couple of times now, I've tried to play around with the $locationProvider.html5Mode(true) command along with <base href="/"> and ran into a lot of errors calling the scripts/styles/images for my project. I guess there must be something I am doing wrong, but is there a certain folder structure you should follow so you don't run into these errors? Or is there a specific way that the base href works that I'm not quite understanding?
Recently, I thought I'd try it on a very, very small app. It's effectively a static website, but I want to take advantage of Angular's routing to make sure all of the pages can load instantly. So my structure would be something like this:
my-project
css
images
js
angular
app.js
app.routes.js
mainCtrl.js
views
home.html
about.html
contact.html
index.html
So I know that this folder structure isn't great, but I'll only be using Angular in this project for routing, nothing more, so it fits my needs.
I put into the head <base href="/">, put in body ng-app and ng-controller, and inside the body put a <div ng-view> somewhere too.
I added in the $locationProvider.html5Mode(true) and tried the app out. All of my scripts are then being loaded as http://localhost:8888/script.js which is incorrect. The project is located in a folder so that index.html is located in http://localhost:8888/my-project/index.html. So, it should be loading the scripts from http://localhost:8888/my-project/js/angular/app.js for example.
Is there something that I'm not understanding about the base href? Eventually I may host this app somewhere online, so I want the URLs to scripts etc to all be relevant to the file really. Anyone have any ideas?
Alright, so above the base href tag I would have my CSS styles which would be linked as css/style.css and at the bottom of my body tag I would have my scripts loaded as js/init.js or js/angular/app.js for example. This would try to load it as if the js folder is located directly at localhost:8888/js.
The Angular framework is a Single Page Application (SPA) that is able to run in a browser by essentially tricking the browser into running code snippets rather than make server calls, by making use of the "hash" (#) page anchor. Normally, a URL with a # would jump to a specific anchor point in the page; in the case of Angular or other similar SPA frameworks, the # is redirected to a code segment instead.
Ideally, you would like to not have to reference this # in your page URLs. This is where Html5Mode comes into play. Html5Mode is able to hide the #, by using the HTML5 Push State (aka history API).
When Html5Mode is enabled, the normal links on the page are silently replaced by Angular with event listeners. When these events are triggered, the current page is pushed into the browser history, and the new page is loaded. This gives the illusion that you are navigating to a new page, and even allows for the back button to operate.
This is all fine when you are dealing with links which are clicked from within the running application, but relying on event listeners can't work if you navigate to the page from an external source, where Angular isn't loaded into memory yet. To deal with this, you must be loading your pages from a web server which supports URL rewrites. When the server receives a request for a URL that there isn't a physical page for, it rewrites the URL to load the base HTML page, where Angular can be loaded and take over.
When Angular receives a request for a route which has been rewritten in this manner, it must first determine what the intended route was. This is where the Base HTML Tag comes into play. Angular uses the Base reference to help it to determine which part of the URL is on the server, and which part is a client route. Essentially, where the # in the URL would be if Html5Mode was not enabled.
Unfortunately, Base is an HTML Tag that is used by the browser for more than just Angular. The browser also uses this tag to determine the correct location to load scripts and resources using relative paths from, regardless of the path in the location bar. In general, this isn't a problem if all of the scripts and resources are relative to the location of the Index.html file. When Base is omitted, the browser will load scripts from the apparent base path determined by the current URI. However, once you provide it, the browser will use whatever value you have supplied.
In general, unless you are hosting angular on a sub-page of your site and you want your users to expect something specific in the URL string, you should always control the base on your server, and use Base="/" on the client side.

Backbone router: Use hashbangs

I was setting up a simple router in Backbone.js while I noted an issue, I googled for a long time and found some solutions.
The problem is that whenever I navigate to a url, this url will work while I'm inside the app itself, but once you bookmark it and return it will obviously return a 404 since that page doesn't exist.
Backbone.history.navigate('test', true);
This will navigate to http://www.domain.com/test, which is great, but once you refresh the same URL it returns a 404.
There are ways to get this to work with using a specific .htaccess file, but I'm building this inside an existing website, so I can't use this.
Is there a way in Backbone.js to revert to just using hashbangs? My url would then look like this `http://www.domain.com/#!/test
This might not be the cleanest URL, but at least it will work.
Or am I just missing something obvious here? It can't be that I'm the only person with this problem.
You're using pushState according to your url, so you've got 2 main options:
stop using pushState when starting Backbone history (which would be the equivalent of using a hashbang, except for SEO)
have your server return a page for every URL your app generates (it can be the same exact page)
In the second case, if you don't care about SEO, you can have a catch all rule always returning (e.g.) index.html containing your Backbone app. When you start Backbone's history, it will lokk at the URL and navigate to the proper page (although you might have a "double render" effect).
If you're worried about SEO, you need to return page content for SEO along with your Backbone app.

Alternative to the Backbone router without hash fallbacks and better back/forward state support?

I am currently using the Backbone router for my SPA. It sort of works OK, but I am having a number of small issues:
The problem is - I want to either have pushState navigation or none, meaning - app state gets executed (when I pass the "initial" URL to the router or I trigger a route via JS), but the address bar doesn't change. I don't want the hashChange fallbacks for IE9. It will work just fine if IE9 does not get the back button and the changing address bar, but still retains navigation state within the application. I can then show the users a URL which they can bookmark and the server will route that page and the app state will bootstrap based on the initial URL. The address bar not changing in IE during in-app navigation is a compromise I am willing to accept.
Another problem that I have is that I am using navigate(url, {trigger: true}) for my intercepted HREFs, and the back button doesn't work (does nothing). But I really need to change app state on forward/back navigation, even if it gets rebuilt for that particular URL - I'd rather rebuild the state.
What are my options in terms of routers that I could use? Like I said, I don't want to have hashbang fallbacks (meaning - I only want to have ONE way of representing URLs in the application, period).
What should I use? Director.js? History.js? There seems to be quite a number of router/history libraries out there but which one is the closest to what I'm looking for?..
Supposing you have Modernizr around scanning the HTML5 history support, if I understand well wouldn't a fast solution be, in your main layout js file, to add as event
'click a' : "navigate"
and add the function navigate to this layout as follow
navigate : function(e){
if(!Modernizr.history) document.location.href = $(e.currentTarget).attr("href");
},
to optimize, you could of course bind that only if history is not supported,don't forget to include "modernizr" in your layout. Hope this is an interesting answer for you..

Backbone.js Router history back to initial page

Okay I have googled and googled and can't find anything related to my actual problem.
Basically I have a simple router defined with a few routes all of which can be rendered directly by the server if called directly.
So the backbone history is called like this.
Backbone.history.start({
'pushState': true,
'silent': true
});
I then have simple a very simple navigate
this.navigate($(event.currentTarget).attr('href'), {
'trigger': true
});
This all works correctly except for one flaw.
Lets say you start on the home page of the site which I do not have a backbone route defined for and then click a few links on the site that use the backbone router, you will get routed to the correct page and everything is fine, but then when you click the browser's back button to get back to the home page you end up seeing the contents of the first backbone route and not the actual homepage's contents.
So I am realizing that I need to store the initial page contents so that I can reinsert them into the page once I detect that I am back to the initial page that does not match a route in my Router, and then from there I need to reload in the stored version of my page.
So I would like to know if this sounds right, or if I am going down the wrong path, also does backbone offer anything to help with this scenario that I am just missing? And I can't be the only one who has tried to do this so how have others gotten around this issue?
(Initially I thought there was actually a problem with my code, and not actually this fundamental issue, so I still want to ask this question to figure out how others have accomplished this)
You need to define a route for the homepage. What happens is that each time you follow a link, the current URL is pushed on pushState's stack, and each time you hit the back button the previous URL gets popped and Backbone tries to follow its route. However, when you pop the homepage URL, there's no route that it matches so Backbone doesn't actually do anything, leaving the content as whatever it was the last time a route really did match.

Resources