Is it possible to edit the placement of the hashtag in hashmode from /#/ to /#?
When html5mode is not supported (in older browsers IE9 and down) and I get hash URL / hashbang mode
what i get :
'index.com/#/path/id'
What I would like to get :
'index.com/path/#id'
Is it possible to edit the URL / placement of the hash-tag from within the angular app config ? Or is what I want an unaccepted/invalid URL for hash mode?
add this code in your router page
$locationProvider.html5Mode(true);
You cannot remove # from URL in a browser that does not support history API.
From AngularJS Doc, when mode=true in html5Mode :
enabled – {boolean} – (default: false) If true, will rely on
history.pushState to change urls where supported. Will fall back to
hash-prefixed paths in browsers that do not support pushState.
From $locationProvider source code, if history.pushStatefails, it restores the old URL values:
function setBrowserUrlWithFallback(url, replace, state) {
var oldUrl = $location.url();
var oldState = $location.$$state;
try {
$browser.url(url, replace, state);
// Make sure $location.state() returns referentially identical (not just deeply equal)
// state object; this makes possible quick checking if the state changed in the digest
// loop. Checking deep equality would be too expensive.
$location.$$state = $browser.state();
} catch (e) {
// Restore old values if pushState fails
$location.url(oldUrl);
$location.$$state = oldState;
throw e;
}
}
Related
I've got a page that has a base URL of /foo/ and have set up my state to expect a param.
So, if the user hits /foo/user1/, user1 is the param.
Is it possible to watch for both types of urls? For example, can I declare /foo/user1/ and /foo/user1 (notice, there's no forward slash) in the same state or do I need to create another state to specifically watch for the trailing slash?
Currently, it can be one or the other, but not both. Is that an accurate statement, or am I missing something in the documentation?
if you are using latest version of ui-router add this in your config block $urlMatcherFactoryProvider.strictMode(false) but for older versions of ui-router add this code
$urlRouterProvider.rule(function ($injector, $location) {
var path = $location.url();
// check to see if the path already has a slash where it should be
if (path[path.length - 1] === '/' || path.indexOf('/?') > -1) {
return;
}
if (path.indexOf('?') > -1) {
return path.replace('?', '/?');
}
return path + '/';
});
you should checkout their FAQ area, there is a dedicated section for trailing slash
I'm looking for something to accomplish in Backbone. Here is the explanation.
I have backbone routes like
{
'': 'defaultRoute',
'test': 'test',
'testing': 'testing'
}
Assuming my root url is '/routes'
Now when I say 'router.navigate('test', {trigger: true});' url changes to /routes/test.
Similar way when I call 'router.navigate('testing', {trigger: true});' url changes to /routes/testing.
But when I call 'router.navigate('', {trigger: true});' url changes to /routes/.
You know i didn't expect that / at the end. I never passed that. It should have been back to root url i.e. '/routes'.
Adding / at the end makes lot of difference/meaning. Checkout 'http://googlewebmastercentral.blogspot.in/2010/04/to-slash-or-not-to-slash.html'
Any fix for that (i.e. not having / at the end for default route)?
Try to override Backbone.History.prototype.navigate.
Copy default function and change this line :
var url = this.root + fragment;
to :
if(this.root !== '/' && fragment === ''){
var url = this.root.replace(trailingSlash, '');
}else{
var url = this.root + fragment;
}
First things first:
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: Backbone.history.start({pushState: true, root:
"/public/search/"})
Backbone#History
In your case, it is most likely to be something like root: "/routes". Something you need to keep in mind is that your website is seen in the browser as either a resource or a remote folder, therefore the default route without a trailing slash may not fully work by just leaving it as "" because that means usually "the current folder". You could try to set your root url as root: "/" instead (just as it should be by default) and create a "routes" resource as your default route, something like the following:
{
'test': 'test',
'testing': 'testing',
'routes': 'defaultRoute',
'*': 'catchAll'
}
Another recommendation that I am doing to you (as you can see it above) is to set your a catch all URL at the end in case someone enters a non-existent one, and you can also use it to redirect your users to your default route.
// your code above ...
defaultRoute: function() {
// your default route code...
},
catchAll: function() {
this.navigate('routes', {trigger: true});
}
// your code below ...
Finally, by any reason mess up with Backbone URLs manually, you are highly risking yourself to break the whole thing AND if in the future the API changes it should be easier to update if you just follow the intended use.
Update:-
This has been already fixed in Backbone's latest version. I asked a question on there forum and found the answer. I'm adding it here as an answer if it helps anyone who is looking for the same thing.
https://github.com/jashkenas/backbone/issues/2871
I have multiple routers in my app, in general way it looks like this:
// Start backbone.js
if (!Backbone.History.started) {
Backbone.history.start({pushState: true, hashChange: false});
}
// Perform some RPC requests ...
// Depending on user role, received from the server should be created suitable router:
var router;
if (typeof app.user.role === 'manager') {
router = new routers.manager();
} else {
router = new routers.guest();
}
Problem is that after page is loaded and script is executed routers do not do. anything. They do not load route for current url automatically. So, i had to fix it this way (i am not sure that it is a right way):
routers.guest.initialize = routers.manager.initialize = function() {
var defaultRoute = 'default';
if (typeof this.routes[Backbone.history.fragment] !== 'undefined') {
this[this.routes[Backbone.history.fragment]]();
} else {
this.navigate(defaultRoute, true);
}
};
It is working fine, except one bug: when i use route with params, for example /reset-password-confirm/:code - it is unable to find in in routes property. I could write some more code to fix it, but i suppose that i am doing something wrong, if i have to write such things - as i understand router should handle routes just after it was created.
So, questions:
Why my router(s) does not handle routes for current url after it is being created? Perhaps i need to start backbone history later? (but this bug will happen again later then)
How it is possible to make routes with params like /user/:id work there?
Perhaps it is bad idea to re-create routers? Perhaps it is better to create all of them one time?
P.S. I've tried to create both routers and keep them, also i've trie to call backbone history start method after all routers were created.. but this didn't help :/
Assuming you route is declared as the following:
routes : {
'/user/:id' : 'user'
}
Your initialize code is not working because when you initialize your router with a url such as: /user/1234. Backbone.history.fragment will be /user/1234 (not /user/:id). Since the this.routes object doesn't have a key of /user/1234, your else clause calls the default route.
If you first instantiate your router then call Backbone.history.start(), you will be able to remove your router initialize code. When you navigate to a url as /user/1234 your router will match the /user/:id route and call the user function.
The following should work for you without adding your initialize code:
var router = (app.user.role === 'manager') ? new routers.manager()
: new routers.guest();
Backbone.history.start({pushState: true, hashChange: false});
Looking at the code, seems like you're starting the backbone history before initializing any routes. That's most likely not goning to work.
The correct way of doing this type of seperation is by creating all the routes based on the role received from the server and then start the backbone history. Here's an SO thread that talks about it with code samples as well : How to protect routes for different user groups
I try to study and use Backbone/Marionette in my project. Now I stuck with Router navigation which work not as I though it should.
class MyApp.Router extends Marionette.AppRouter
appRoutes :
'info/:place/(:what)' : 'places_page'
MyApp.Controller = ->
places_page: (place,what)->
console.log 'Triggered places_page'
MyApp.addInitializer( ->
controller = new MyApp.Controller()
new MyApp.Router
controller: controller
Backbone.history.start( pushState: false )
)
MyApp.vent.on('do:search', ->
console.log 'triggered do:search'
place = 'Moscow'
what = 'Пицца'
info_model.set place: place, item:what
new_url = 'info/'+where+'/'+what
if new_url != decodeURIComponent(Backbone.history.fragment)
Backbone.history.navigate(new_url, {trigger: false})
On initial load of site.com/#info/Budapest/Vine page or reload it, I get Triggered places_page message as I expect.
But when I fire do:search event which update url to site.com/#info/Moscow/Пицца, I get Triggered places_page again! So it reload all my views from scratch instead of just change url and re-render one model.
What I can do wrong here?
Update 2:
Found strange thing. If I use latin letters in new url, everything work like it should.
But if I use cyrillic in new url path, it will trigger route function.
Backbone: 1.0, Marionette:v1.0.3, jquery: 1.9.1
Mystery solved!
That happens because of non-latin symbols in url.
Correct code:
new_url = 'info/'+encodeURIComponent(where)+'/'+encodeURIComponent(what)
if new_url != Backbone.history.fragment
Backbone.history.navigate(new_url, {trigger: false})
Because Backboune.navigate don't execute navigate if url didn't change and trigger is false by default, I can write it simple like that:
new_url = 'info/'+encodeURIComponent(where)+'/'+encodeURIComponent(what)
Backbone.history.navigate(new_url)
Backbone.history takes an object when starting. Try this syntax in CoffeeScript:
Backbone.history.start
pushState: false
In addition, pushState is false by default, so you can just have Backbone.history.start()
Does this solve your issue?
Proper URL encoding is required. I couldn't find this in the docs of Backbone related to the router functionality.
I had the same issue, alas with spaces in the query part, i.e.:
#app/terms?filter=java ee
Together with the encodeURIComponent solution as described in your answer, I also found the following lines of comments in backbone.js (1.0.0), pertaining to the navigate function:
// Save a fragment into the hash history, or replace the URL state if the
// 'replace' option is passed. You are responsible for properly URL-encoding
// the fragment in advance.
By enabling HTML5 mode in AngularJS, the $location service will rewrite URLs to remove the hashbang from them. This is a great feature that will help me with my application, but there is a problem with its fallback to hashbang mode. My service requires authentication, and I am forced to use an external authentication mechanism from my application. If a user attempts to go to a URL for my app with a hashbang in it, it will first redirect them to the authentication page (won't ever touch my service unless successfully authenticated), and then redirect them back to my application. Being that the hash tag is only seen from the client side, it will drop off whatever parts of the routes come after by the time they hit my server. Once they are authenticated, they may re-enter the URL and it will work, but its that one initial time that will cause a disruption to the user experience.
My question is then, is there any way to go from $location.html5Mode(true) to the fallback of full page reloads for un-supportive browsers, skipping the hashbang method of routing entirely in AngularJS?
The best comparison of available implementations of what I'm aiming for would be something such as browsing around folders on github.com. If the browser supports rewriting the URL without initiating a page refresh, the page will asynchronously load the necessary parts. If the browser does not support it, when a user clicks on a folder, a full-page refresh occurs. Can this be achieved with AngularJS in lieu of using the hashbang mode?
DON'T overwrite the core functionality.
Use Modernizr, do feature detection, and then proceed accordingly.
check for history API support
if (Modernizr.history) {
// history management works!
} else {
// no history support :(
// fall back to a scripted solution like History.js
}
Try to wrap $location and $routeProvider configuration in browser's HTML5 History API checking, like this:
if (isBrowserSupportsHistoryAPI()) {
$location.html5Mode(true)
$routeProvider.when(...);
}
Also may be you need to create a wrapper to $location if you use it to change path.
(Sorry for terrible english)
Why not handle the un-authenticated redirect on the client side for this situation? I'd need to know a bit more about exactly how your app works to give you a more specific solution but essentially something like:
User goes to a route handled by AngularJS, server serves up the AngularJS main template and javascript
User is not authenticated, AngularJS detects this and redirects to the authentication page
You could have something in the module's run block for when the AngularJS application starts:
module('app',[])
.configure(...yadda...yadda...yadda...)
.run(['$location', 'authenticationService', function($location, auth) {
if (!auth.isAuthenticated()) {
$location.url(authenticationUrl)
}
});
I've subbed in a service which would find out if you were authenticated somehow, up to you how, could be checking a session cookie, could be hitting your API to ask. Really depends on how you want to continue to check authentication as the client application runs.
You can try and override the functionality of the $location service. The general idea would be to rewrite the URL according to whether someone is already authenticated or not, or just use a single approach (without hashbangs) for all URLs, regardless to whether html5mode is on or not.
I'm not sure that I fully understand the use-case so I can't write the exact code that you need. Here is a sample implementation of how to overrides/implements and registers the $location service, just making sure that hashbang is always eliminated:
app.service('$location', [function() {
var DEFAULT_PORTS = {
ftp: 21,
http: 80,
https: 443
};
angular.extend(this, {
absUrl: function() {
return location.href;
},
hash: function(hash) {
return location.hash.substr(1);
},
host: function() {
return location.host;
},
path: function(path) {
if (!path) {
return location.pathname;
}
location.pathname = path;
return this;
},
port: function() {
return location.port ? Number(location.port) : DEFAULT_PORTS[this.protocol()] || null;
},
protocol: function() {
return location.protocol.substr(0, location.protocol.length - 1);
},
replace: function() {
return this;
},
search: function(search, paramValue) {
if (search || paramValue) {
return this;
}
var query = {};
location.search.substr(1).split("&").forEach(function(pair) {
pair = pair.split("="); query[pair[0]] = decodeURIComponent(pair[1]);
});
return query;
},
url: function(url, replace) {
return this.path();
}
});
}]);