Preparing views when routing to initial route - backbone.js

I have a question about routing. The thing is, that I have trouble figuring out how to properly instantiate and render views when my app start for the first time on some route.
For example:
If a user accesses the app via the route /?#a/b/c
My app consists of different sections with subsections. This means that for the above route to work I have to render the view for section A before rendering and displaying the view for subsection B and at last render and display the view for subsubsection C.
The thing is, that this results in a bunch of ugly code in the various routing handlers
a: function(){
A.render();
}
b: function(){
A.render();
B.render();
}
c: function(){
A.render();
B.render();
C.render();
}
So I'm thinking that I'm approaching the problem the wrong way.
When introducing other Routers as the app grows this becomes even harder to maintain I would imagine.
A solution would be if there were an event being triggered before the callback for a route is called. But I can't find a such in the docs.
So my question is, how are these situations handled properly? Because my solution doesn't scale. Is there a way for a to always fire when visiting a "subroute"?

I haven't found a good way to do this but I'll share what I've done and it may or may not apply to your situation. What I wanted to do was have separate routers that respond to portions of the route but backbone doesn't work that way. Backbone will stop when it finds the first route that matches.
What I've done to handle it is to set up a router like this--notice the custom regex to define the route--hopefully it won't make your eyes bleed
initialize:
{
this.route(/([^\/]*)[\/]?([^\/]*)?[\/]?([^\/]*)?/, "renderViews", this.renderViews);
},
renderViews: function(mainView, subView, subSubView)
{
//here you can do something clever--mainView, subView and subSubView may or may not
// have values but they are the names of the views. route of "a/b" will pass
// ["a", "b", undefined] as your arguments
if (mainView)
(new App.Views[mainView]()).render();
if (subView)
(new App.Views[subView]()).render();
if (subSubView)
(new App.Views[subSubView]()).render();
}
I realize this isn't exactly what you're probably hoping for but it worked well for me in a project.
good luck

Related

Stable reactid for server-side rendering

When using React to render components on the server, I notice that the data-reactid attributes are effectively random. I understand that's expected. (https://groups.google.com/forum/#!topic/reactjs/ewTN-WOP1w8)
However it's a little surprising that this otherwise functional framework introduces such non-determinism in the view output. It means that successive renderings of a view with identical state will create different HTML, preventing, for instance, the view engine from returning a '304 Not Modified' or generating a reliable ETag. (I appreciate such caching could be handled at a higher infrastructure layer as well.)
Is there a way to seed the generation of the identifier so that the reactids are deterministic? Or is the reason that's a bad idea explained somewhere else?
In the final comment on the Google Group thread Ben Alpert says:
For server rendering, it's important that different rendered components don't have colliding IDs (even if they're rendered on different servers, for instance) so we pick them at random.
Also recently thought about it(just started to use reactjs),
Possible solution is quite simple - there is no requirement for ETag to be generated from real html... - it can be generated from displayed data.
So you can generate it from virtual dom - just use React.renderComponentToStaticMarkup(…) and generate ETag from it...
Or you can remove all reactid's from rendered html with regexp before hashing it(likely faster then separate render)...
In case if you are using express, this would be something like:
var virtualDom = React.createFactory(Handler)({});
var html = React.renderToString(virtualDom);
var etag = app.get('etag fn');
if (etag) {
etag = etag(React.renderComponentToStaticMarkup(virtualDom), 'utf8');
etag && res.set('ETag', etag);
}
res.render( ... );
This was bugging me too, so I did some digging to see what would happen if I made the root reactid deterministic.
It is possible override this in React 0.14.x if you're willing to put up with the following hack. In your server-side file where you call ReactDOM.renderToString, place this at the top:
// Override the ServerReactRootIndex.createReactRootIndex function
var ServerReactRootIndex = require('react/lib/ServerReactRootIndex');
ServerReactRootIndex.createReactRootIndex = function(){
return "x"; // Results in an attribute like data-reactid=".x"
};
// Use React as usual
// NB: require('react') must come AFTER overriding ServerReactRootIndex.createReactRootIndex
var React = require('react');
This isn't part of the React API, so this may break in the near future. However, this works for now if you absolutely need it. It also makes the data-react-checksum attribute stable for the same rendered DOM.
If you have multiple root React components in the one page, they MUST have different root ids, so you will need to modify this function to account for this.

Is there a way to stop a Backbone Router?

Is it possible to close or stop a Backbone Router from listening to its defined routes?
I ask because I have been testing Backbone SubRoute (https://github.com/ModelN/backbone.subroute), setting up an application that has many spaces in which a user could have many subapplications running.
As such, I have defined a a main router, subrouter structure that follows roughy:
MainRouter = Backbone.Router.extend
routes:
"":"root"
"spaces/:id/:module(/*subroute)":"invokeModule"
root: () ->
console.log "root trigger"
invokeModule: (id, module, subroute) ->
that = this
GigaApp.module(module).start({nested_root: "spaces/#{id}/#{module}"})
SubAppRouter = Backbone.SubRoute.extend
routes:
"":"app_home"
app_home: () ->
console.log 'at sub app home'
SubApp.on "start", (options) ->
SubApp.router = new SubAppRouter(options.nested_root)
This general structure works from the first time a sub application is initialized for a space, as the MainRouter starts the SubApp, which initializes its router with the correct nested route. Subsequently, other routes defined in the SubAppRouter also trigger fine.
However, if you navigate to a different space (of different id), and navigate back to the first space, this structure breaks because the SubAppRouter already initialized for that space overrides the MainRouter, and no call the start the SubApp is made from the MainRouter.
So, I'm wondering if there is a way to stop or disable unbind the route triggering of a Backbone router.
As of this moment, the answer is NO
Derick Bailey opened this issue on Backbone's repo suggesting some changes to backbone's structure to support such a change: https://github.com/jashkenas/backbone/pull/1630
There followed a discussion of the merits of such a change, where the following point was made:
To get started -- I'm afraid that I don't understand the premise here. There are a couple axioms of routing that contradict this patch:
Routers exist to match URLs to locations in your application.
The whole point of URLs is that they're always reachable -- once you have a URL, you can go back to it (bookmark it, paste it into a browser) at any point.
I also went over the Backbone source code, and there is no indication that the functionality I am thinking about is possible.
I'm curious why you're not using the AppRouter in Marionette? It should solve your problems regarding dividing routes into smaller AppRoute objects...
Have a look at BackboneRails for some screencasts on building large scale apps. It's not only relevant for devs using rails as backend. It gives a great way to layout your app structure in modules (each with their own App Routes). Highly recommendable.

Why is it considered bad practice to call trigger: true in the navigate function of backbone.js?

I have read in several places that calling the Backbone.history.navigate function is considered bad practice.
For example Addy Osmani sais in his book "Developing Backbone.js Applications"
It is also possible for Router.navigate() to trigger the route along
with updating the URL fragment by passing the trigger:true option.
Note: This usage is discouraged...
http://addyosmani.github.io/backbone-fundamentals/#backbone.history
Or Derick Bailey in his blog post even sais:
You shouldn’t be executing the route’s handler from within your application, most of the time.
But I don't really understand the reasoning behind it and what would be a better solution.
In my opinion it is not really bad to call the navigate function with the trigger:true option. The route function could upon calling always check if the considered data is already loaded and show this loaded data instead of doing the whole work all over again...
There seems to be some confusion about what Router#navigate does exactly, I think.
Without any options set it will update the URL to the fragment provided.
E.g. router.navigate('todo/4/edit') will update the URL to #todo/4 AND will create a browser history entry for that URL. No route handlers are run.
However, setting trigger:true will update the URL, but it will also run the handler that was specified for that route (In Addy's example it will call the routers editTodo function) and create a browser history entry.
When passing replace:true the url will be updated, no handler will be called, but it will NOT create a browser history entry.
Then, what I think the answer is:
the reason why the usage of trigger:true is discouraged is simple, navigating from application state to application state to application state requires most of the time different code to be run than when navigating to a specific application state directly.
Let's say you have states A, B and C in your application. But state B builds upon state A and state C builds upon B.
In that case when you navigate from B to C only a specific part of code will need to be executed, while when hitting state C directly will probably execute some state checking and preparation:
has that data been loaded? If not, load it.
is the user logged in? If not redirect.
etc.
Let's take an example: State A (#list) shows a list of songs. State B (#login) is about user authentication and state C (#list/edit) allows for editing of the list of songs.
So, when the user lands on state A the list of songs is loaded and stored in a collection. He clicks on a login-button and is redirected to a login form. He successfully authenticates and is redirected back to the song list, but this time with delete-buttons next to the songs.
He bookmarks the last state (#list/edit).
Now, what needs to happen when the user clicks on the bookmark a few days later?
The application needs to load the songs, needs to verify the user is (still) logged in and react accordingly, stuff that in the state transition flow had already been done in the other states.
Now for a note of my own:
I'd never recommend the above approach in a real application as in the example. You should check whether the collection is loaded when going from B to C and not just assume it already is. Likewise you should check whether the user really is logged in. It's just an example.
IMO the router really is a special kind of view (think about it, it displays application state and translates user input into application state/events) and should always be treated as such. You should never ever rely on the router to transition between states, but rather let the router reflect the state transitions.
I have to disagree with #Stephen's answer here. And the main reason why is because the use of router.navigate({trigger : true}) gives the router responsibility to handle the application's state. It should only reflect application state, not control it.
Also, it is not a View's responsibility to change the hash of the window, this is the router's only job! Don't take it away from it! Good modularity and separation of concerns makes for a scalable and maintainable application.
Forwarding a person to a new section within your application
Backbone is an event driven framework, use events to communicate. There is absolutely no need to call router.navigate({ trigger : true }) since functionality should not be in the router. Here is an example of how I use the router and I think promotes good modularity and separation of concerns.
var Router = Backbone.Router.extend({
initialize: function(app) {
this.app = app;
},
routes: {
'videoLibrary' : function() { this.app.videoLibrary(); }
}
});
var Application = _.extend({}, Backbone.Events, {
initialize: function() {
this.router = new Router( this );
this.listenTo( Backbone, 'video:uploaded', function() {
this.router.navigate('/videoLibrary');
this.videoLibrary();
});
},
videoLibrary: function() {
//do useful stuff
}
});
var uploadView = Backbone.View.extend({
//...
uploadVideo: function() {
$.ajax({
//...
success: function() { Backbone.trigger('video:uploaded'); }
});
}
});
Your view does not need or want to know what to do when the user is done uploading, this is somebody else's responsibility. In this example, the router is just an entry point for the application's functionality, an event generated by the uploadView is another. The router always reflects the application state through hash changes and history but does not implement any functionality.
Testability
By separating concerns, you are enhancing the testability of your application. It's easy to have a spy on Backbone.trigger and make sure the view is working properly. It's less easy to mock a router.
Modules management
Also, if you use some module management like AMD or CommonJS, you will have to pass around the router's instance everywhere in the application in order to call it. Thus having close coupling in your application an this is not something you want.
In my opinion it's considered bad practice because you should imagine a Backbone application not like a Ruby On Rails application but rather like a Desktop application.
When I say RoR, I'm just saying a framework supporting routing in sense that a route brings you to a specific call to the controller to run a specific action (imagine a CRUD operation).
Backbone.history is intended just as a bookmark for the user so he can, for example, save a specific url, and run it again later. In this case he will find the same situation he left before.
When you say:
In my opinion it is not really bad to call the navigate function with
the trigger:true option. The route function could upon calling always
check if the considered data is already loaded and show this loaded
data instead of doing the whole work all over again...
That to me sounds smelly. If you are triggering a route and you are checking for the data to see if you have it, it means that you actually already had them so you should change your view accordingly without loading again the entire DOM with the same data.
That said trigger:true is there so do we have reason use it? In my opinion it is possible to use it if you are completely swapping a view.
Let's say I have an application with two tabs, one allows me to create a single resource, the other one let me see the list of the created resources. In the second tabs you are actually loading a Collection so data is different between the two. In this case I would use trigger:true.
That said I've been using Backbone for 2 weeks so I'm pretty new to this world but to me it sounds reasonable to discourage the use of this option.
It depends on your context.
If you have done something in your current view that might affect the view you are about to navigate to, for example creating for deleting a customer record, then setting trigger to true is the right thing to do.
Think about it. If you delete a customer record don't to want to refresh the list of customers to reflect that deletion?

Backbone Events or Routes?

Since backbone provides two ways of responding to certain events, I was wondering what the general consensus. This is a very common situation - I have a link on a page, i can set up the href on the page to route it so the router can call a function to handle it, like so:
HTML
<a href='#posts/2' class='handleInView'>Item 2</a>
JS
var AppRouter = Backbone.Router.extend({
routes: {
"posts/:id": "getPost"
}
});
or I can respond to the event in the View like so:
var MyView = Backbone.View.extend({
...
events: {
"click .handleInView": "open",
},
...
open: function() {
...
}
});
I know routes provide you with the added benefit of history and direct links, but from a performance standpoint and code layout perspective what is a better approach if I dont care about history.
My routes could be a single place where i can see all of the interactions but it also could get cluttered very quickly.
If you don't care about history or bookmarks, events have fewer side effects (people won't try to bookmark them and they won't interfere with your history) and they're simpler / faster to implement and handle.
Performance-wise, they're slightly faster as well (but really neither method is slow enough to matter at all).
I agree with the comments. Anything that requires deeplinking, bookmarks etc should be handled by using routes. However if you have something like a TabView, or another view that should be inaccessible from a URL, and is nested in another view, then it might make more sense in dealing with that inside of your view code. As for the clutter, you might want to think about reorganizing your routes into separate files. Here are some examples
Backbone general router vs. separate routing files?
Multiple routers vs single router in BackboneJs
In general, routes are used when you are calling a drastic change in the state of your application, or you would like to maintain a browsing history (via backbone.history) so the user can navigate back & forth between states via the browser buttons.
Ideally, you would use both in different circumstances.
I like to think of it in terms of what is changing on my page. If the general page state is the same, but certain elements are changing or updating, I will use events. If the general page state is changing or if I'm loading a different UI Screen, I will use routes.

Should the backbone router or view handle fetching data and displaying loading status?

In many places in my app the following pattern happens:
User clicks some link triggering navigation
Data needs to be fetched to render the view
UI design requires a "loading" spinner to be shown while data is fetched
Once the data is fetched we show the rendered view
I have tried both of the following implementation patterns:
Router handles fetching
Router tells the container view to show the loading spinner
Router loads any collections/models
Router tells the container view to hide the loading spinner
Router passes the collections/models to the view and renders it
View handles fetching
Router just creates and renders the view
The view fetches the collections and models it needs
When the view is first rendered, it just shows the loading spinner since the data is still loading
When the data arrives, the models/collections fire events and the view is bound to those so it re-renders itself, thus hiding the loading spinner and showing the full view
I dislike #1 since the router becomes a gigantic ball of Model/Collection fetching logic and seems to have too much responsibility. #2 seems like a better allocation of responsibilities (router just decides which view to show, view figures out what data it needs to fetch), but it does make the view rendering a little trickier since it's stateful now.
What does the StackOverflow community think? 1, 2, or something else?
This post is pretty old, but we were reviewing it earlier today, so in case anyone else comes across it:
To me, I really see 2 separate questions:
Where should the mechanics of data fetching and the resulting view rendering happen, in the router or a view?
Should views expect already resolved models, or should they respond to models that may still be loading?
A bit of how we handle it mixed with some personal preferences:
Neither, although I'd lean closer to the router.  Routers should handle routing, views should handle viewing, and something else should handle the mechanics and workflow of Model/Collection fetching logic.  We call that something else a Controller, which the Router basically delegates to.
As Yuri alludes to, 'sometimes' is a reality.  I think this is probably a case by case decision, but should ultimately be a contract between a Controller and View, rather than between the Router/View.
I like Yuri's bullet points, with a couple caveats (indented bullets):
The router only knows where to send the user
The outer view only knows what the user should be viewing (given its data)
Assuming the outer view is specific to the inner view's use case and is 'owned' by another view (for clean up)
Otherwise for generic containers(like rendering into a 'main' location), we've found it useful to have a component that manages the views for a certain 'section' on the page - we call it a Renderer
The inner views only know how to show only their little piece of it all (and
can be used elsewhere)
and The render function always shows the right thing as of right now.
In the case of a generic container, it'd ultimately be the responsibility of the Renderer
The main reason for the Renderer is to handle things related to that section, like cleaning up existing views to avoid ghost views, scrolling to the top on render (our MainContentRenderer does that), or showing a spinner in this case.
A psuedo-code-ish example of what that might look like, for:
a generic content target 'main' (if it's use case specific, may be better off with a ComponentView as per Yuri's example, depending on your view lifecycle management strategy)
a model we have to fetch and wait on
a view that accepts an already loaded model
Router:
routes: {
"profile": "showProfile"
},
showProfile: function() {
return new ProfileController().showProfile();
}
ProfileController:
showProfile: function() {
//simple case
var model = new Model();
var deferredView = model.fetch.then(function() {
return new View(model);
};
MainContentRenderer.renderDeferred(deferredView);
}
MainContentRenderer:
var currentView;
renderDeferred: function(deferredView) {
showSpinner();
deferredView.then(function(view) {
this.closeSpinner();
this.closeCurrentView();
this.render(view);
}
},
render: function(view) {
currentView = view;
$('#main-content').html(view.render().el);
}
closeCurrentView: function() {
if (currentView and currentView.close()) {
currentView.close();
}
}
Introducing a Controller also has the added benefit of testability. For example, we have complex rules for performing searches around URL management, picking between a results view and a new search view, and picking between a cached 'last' search result and executing a new search. We have Jasmine tests for the controllers to verify that all that flow logic is correct. It also provides an isolated place to manage these rules.
I tend to use the second option with three views, the container, a loading view, and a content view. That is, the container is instantiated by the router and during each render it looks at what it has on hand to display—sometimes provided by the router, sometimes by itself—and decides what view(s) to instantiate. A simplistic, contrived example:
ContainerView = Backbone.View.extend({
initialize: function (options) {
options.data.bind("reset", this.render, this);
},
render: function () {
var view;
// If the loading view is only shown once, e.g., splashing, then isReady()
// may be better here.
if (this.options.data.isLoading()) {
view = LoadingView;
} else {
view = DataView;
}
this.$("div.content").html(new view().render().el);
}
});
I like this approach because:
The router only knows where to send the user;
The outer view only knows what the user should be viewing (given its data);
The inner views only know how to show only their little piece of it all (and can be used elsewhere); and
The render function always shows the right thing as of right now.
Clarification:
The purpose of the view, in this case, is to understand how what is has to show should best be shown to the user. In this case, a bit of data still loading is best shown with a loading view, while ready data is best shown with a data view. Most real views are actually composing their display with many more views, e.g., depending on the user authorization different action containers.

Resources