Backbone router with no hashbangs - javascript

I've set up a super simple Backbone app with just a router and two views to try and nail down the correct way to handle routing with no hashbangs.
var Router = Backbone.Router.extend({
routes: {
'': 'index',
'episodes': 'episodes'
},
index: function () {
var view = new IndexView();
view.render();
},
episodes: function () {
var view = new EpisodesView();
view.render();
}
});
var IndexView = Backbone.View.extend({
el: $('#primary'),
render: function () {
console.log('index view');
}
});
var EpisodesView = Backbone.View.extend({
el: $('#primary'),
render: function () {
console.log('episodes view');
}
});
var router = new Router;
Backbone.history.start({pushState: true});
I realize that the history object allows forward and backward navigation between pages, which is great. However, the way it's actually implemented seems a little messed up to me.
For instance, I created a simple MAMP server to serve an index.html file and the JS file. Navigating to http://backbone:8888/, the console logs index view like I'm telling it to. However, navigating to http://backbone:8888/episodes (by typing it into the address bar) returns a 404. BUT, if I navigate to http://backbone:8888/#episodes, the URL redirects to http://backbone:8888/episodes (without the hashbang) and I get episodes view logged to the console, which obviously means it's hitting that EpisodesView view.
From here, I can go back and forth between the index and episodes views. (back hits /, forward hits /episodes). That's all fine and dandy until I hit refresh while on /episodes again. 404...
So my question is this: how can Backbone be set up to handle URLs without relying on hashbangs? Everything I've found on the topic says "oh just pushState!". Well, I'm using pushState and, like I described above, you can't hit a URL like /episodes directly without getting a 404.

When you use push state, pages are served from the back end, which means that you have to define a corresponding route in your back end that corresponds to a front end route.
If the back end doesn't find the requested route, then it will deliver a 404 message, because it won't know what to serve. In your case, the episodes view gets triggered at the front end level, but the browser doesn't have a DOM to render the view when the page gets refreshed because nothing was served.
By default, the route http://backbone:8888/ will serve the index file because this is how the webserver is configured.
I'm not sure what back end technology you are using, but for serving a file from http://backbone:8888/episodes, just make sure that your back end has a router set up that serves the requested route and it should work.

Related

Unknown value in req.param in node.js

I'm am learning node.js and therefore try to build a simple web app that shows the current news. The API that I am using offers several categories for the news.
So I create a route that takes the category as a param. My routes/index.js:
const router = require('express').Router();
const renderHome = require('../controllers/newsController');
const quotesCookie = require('./../middleware/quotesCookie');
router.get('/', quotesCookie, renderHome);
router.get('/:category', quotesCookie, renderHome);
module.exports = router;
My controllers/newsController.js looks like this:
const newsService = require('./../services/newsService');
const renderHome = async ( req, res ) => {
const category = req.params.category;
console.log(req.params);
const quote = res.quoteOfTheDay;
const { status, msg } = await newsService.topHeadlines(category);
res.render('home', {
title: 'News2Go',
author: quote.author,
quote: quote.quote,
articles: msg.articles
});
};
module.exports = renderHome;
When I for instance call http://localhost:3000/entertainment the console.log in the controller prints this to the console:
{ category: 'entertainment' }
{ category: 'sw.js' }
I have absolute no clue where the sw.js comes from... It appears a few milliseconds after the real category and ensures that topHeadlines is called twice.
Did someone know what this is? Did I miss something?
Apparently your web page has a script in it named sw.js. Because of that, the browser will request that with the URL http://localhost:3000/sw.js and your :category route will handle that request and log a category of sw.js.
Remember, ALL resources used on your site will be requested by the browser and will be seen by your Express server as incoming requests. Not just the top level page, but all scripts, images, fonts, CSS files, etc... used by your pages.
It's generally not a good idea to define a wide-open top level route handler like this:
router.get('/:category', ...)
Because that will grab ALL top level URLs and leave none for the rest of your site to use. It would probably make more sense to use a structure like this:
router.get('/category/:category', ...)
With a URL of http://localhost:3000/category/entertainment. Then, you can more clearly separate out the actual category requests from all the other requests in your site. Either that or you will have to move ALL other URLs used on your site to routes that come before this and/or use sub-directories in their page such as:
http://localhost:3000/scripts/sw.js
http://localhost:3000/styles/main.css

Marionette app 2 way navigation with history api

I have node.js server application (rest API service).
I also have Backbone + Marionette(for my serverside RESTful app) multiple page app at the client side.
I currently have Marionette navigation which is work pretty good with links like domain.com/#feedbacks (pages are render on client side with ajax data). But I also want to add serverside navigation e.g. domain.com/feedbacks (for google and other seach engines).
The question is: How can I match serverside and clientside navigations?
Mb I should try to add event handler for all links on page, which is will do something like Backbone.history.navigate("/feedbacks")? But I have a lot different hash links(#feedbacks)... Mb there is a more elegant solution? Thank you.
My current marionette router:
var AppRouter = Backbone.Blazer.Router.extend({
routes: {
'': new HomeRoute(),
'sell': new SellRoute(),
'login': new LoginRoute(),
'feedbacks': new FeedbacksRouter(),
'product/:id': new ProductRoute(),
'profile/:id': new UserRoute()
}
})
I just had to use:
Backbone.history.start({pushState: true})
instead of:
Backbone.history.start();
It make backbone routing work without # symbols

Backbone Routing with Static HTML files

I'm working on a website where we are using 3rd Party web services to return dynamic content and using javascript to parse and display that data from to the page. We are already using backbone in a couple places on the site to post data to web services so I had the thought of trying to use Backbone's router to run specific functions based on the page url and grabbing the query because we are hashing the queries to the url of the page.
For example: global-site-search.html#query
This is the router code I have to this point:
var Router = Backbone.Router.extend({
routes : {
'' : 'indexRoute',
'global-site-search.html(:query)' : 'getSearchResults'
},
indexRoute: function(query) {
console.log("indexRoute");
},
getSearchResults: function(query) {
console.log("getSearchResults with query", query);
}
});
var WaRouter = new Router();
Backbone.history.start({
pushState: true,
root: '/'
});
But when I hit the page with a url like global-site-search.html#query the query value returns null. Has anyone tried this before or am I trying to extend Backbone's router to far in handling this?
Is global-site-search.html from your server?, if yes then the config for router should be
':query' : 'getSearchResults'
If no, then you can't do that, because Backbone.Router uses the hash part of the current page URL to track pages. And since global-site-search.html is not containing any backbone code, it can't do anything. It is possible only if you somehow can inject your router code into global-site-search.html which is illegal in this case
Updated: this should allow you to search with this route ':query' : 'getSearchResults'
Backbone.history.start({
pushState: true,
root: window.location.pathname
});
When using router, you need to set the correct root, so using window.location.pathname is the easiest way to do that. Also, according to Backbone documentation
and if a hash URL is visited by a pushState-capable browser, it will be transparently upgraded to the true URL. Note that using real URLs requires your web server to be able to correctly render those pages, so back-end changes are required as well. For example, if you have a route of /documents/100`
Since you are not having any real back-end to handle pushState, I suggest that you turn it off

How do I generate a unique link that will load a session with certain docs available to the client?

Sorry for the bad phrasing.
Essentially, I want to be able to generate a link to a page, which will load a session of certain docs.
For example, Links.find() returns to Client A Links.find({clientName:"A"}). Now Client A wants to send this series of elements to his friend, and wants to do so by sending him a link which loads a client instance that can see Links.find({clientName"A"}).
Any input at all would be greatly appreciated.
Add Iron Router to your project. Then create a route that puts the relevant query into the URL, for example (in a client-loaded JavaScript file):
Router.map(function () {
this.route('client', {
path: '/client/:_clientName',
before: function () {
this.subscribe('client', this.params._clientName).wait();
}
}
}
Then a URI like http://yourapp.com/client/A would cause the client template to render (by default it uses the same name as the route name, unless you specify a different name) subscribing to the client subscription using "A" as the subscription parameter. This would be paired on the server side with:
Meteor.publish('client', function (clientName) {
// Clients is a Meteor collection
return Clients.find({clientName: clientName});
});
So that's how to process links after they've been generated. As for creating them, just work backwards: what query parameters are you passing to your subscription (that in turn get put into the find() call to MongoDB)? Identify each of them and write some code that adds them to an appropriate URI—in this case, your function would simply concatenate "http://yourapp.com/client/" with clientName, in this case "A". Obviously much-more-complicated routes/URIs and queries are possible, for example http://yourapp.com/events/2012-01-01/2012-12-31 with an Iron Router route path of /events/:_fromDate/:_toDate and so on.

Ember Client Side Authentication, route authentication

I have been googling about this a lot but haven't been able to find satisfactory answer or solution you can say.
I have this Ember app, http://jsbin.com/aHiVIwU/28#.
My use case is pretty simple. I want to show the user whole app only after user gets authenticated. I am not using Ember Data as you can see, so the authentication will be through $.ajax as well.
If I am not wrong, I would have a template for login page like this,
<script type="text/x-handlebars" id="login">
<h1>Login</h1>
{{view Ember.TextField valueBinding="username"}}
{{view Ember.TextField type="password" valueBinding="password"}}
<button {{action 'login' class="btn"}}>Login</button>
</script>
Then I would map the resource,
App.Router.map(function() {
this.resource( 'login');
});
And then there would be a corresponding controller right?
App.LoginController = Ember.ObjectController.extend({
});
But the point where I am getting stuck is, how will I show only the login template first and then the whole app after the user gets authenticated? I would appreciate some explanation & help on this.
I can't say it any better than how Alex talked about it in his pull request with router facelift in ember. Look about a quarter of the way down, for 'How do I redirect to a login form for an authenticated route and retry the original transition later?':
https://gist.github.com/machty/5647589
Essentially at the root route of the resource where a user needs to be authenticated you will save the current transition, transition to the login route, then after they've authenticated, restart the previous transition.
He included a very simplistic example, where he's created a mixin that could be attached to all the routes requiring authentication and you wouldn't have to duplicate code etc.
http://jsbin.com/axarop/54/edit
App.NeedsAuthMixin = Ember.Mixin.create({
beforeModel: function(transition) {
// We're using beforeModel here to
// make sure the user is authenticated.
var loginController = this.controllerFor('login');
if (!loginController.get('hasLoggedIn')) {
alert('you must log in!');
loginController.set('afterLoginTransition', transition);
this.transitionTo('login');
}
}
});
App.ArticlesRoute = Ember.Route.extend(App.NeedsAuthMixin);
App.LoginRoute = Ember.Route.extend({
events: {
login: function () {
this.controller.set('hasLoggedIn', true);
var savedTransition = this.controller.get('afterLoginTransition');
if (savedTransition) {
this.controller.set('afterLoginTransition', null);
savedTransition.retry();
}
}
}
});
Take a look at the Client-side Authentication screen casts on http://www.embercasts.com/. They are from the same guy that made the examples that kingpin2k referenced, but provides a full, working solution.

Categories