I have a Backbone + RequireJS application with root views that are rendered upon page load. In addition I have a bunch of views that are opened on top of the root views or on top of each other.
The idea is that each view triggers a change, adding a fragment to the URL. Multiple views can be rendered on top of each other with certain parameters defined by the view that launched them.
How do I achieve a structure that passes required data to the view that is rendered and that browser history stays aware of the changes and switches views accordingly?
In practice for example, if I press the back button, the front most view is removed and the underlying view is activated with all the data that it was initialized with.
EDIT:
What I need:
Router that listens for hash value changes and maintains a stack for views that are rendered
A way to initiate and render a view from inside another view with certain parameters
USE CASE: Every time a user opens a new view on top of an existing view, the router stores the new view in the stack. With a dedicated "close" button or pressing the browser's "back" button the router automatically removes the top view from the stack and the previous view is exposed.
What kind of a structure should I build to achieve this kind of functionality?
Related
I am using Backbone + RequireJS to develop an app. When a user links to the web page, he can select one of the products (render the first view). After the selection, the next page should render the second view according to the selection. That is, as the second view is rendered, it should know the selection by the user in the first view. The code looks like this:
for viewA.js:
define(['jquery','backbone','underscore','text!template/ViewA.html'],function($,Backbone,_,templateA) {
// render for view A
// switch to view B after user do selection
});
for viewB.js:
define(['jquery','backbone','underscore','text!template/ViewA.html'],function($,Backbone,_,templateA) {
// render for view B according to selection in view A
});
Is there any formal method to achieve this other than using a global variable?
I've done some study and it seems that one solution is to use an event aggregator to trigger in the first view and listen to the second view. However, in the current scenario, the second view is not instantiated (as shown in viewA.js in which viewB is not included yet) when the user selection event is triggered in the first view. And it looks strange to include viewB in viewA.js just for the event aggregator to work properly...
Should Backbone routers be used for nested views also, as it it is used for navigating in between more complete page views and some other sorts? When to use a router and when to use inner views?
For example a home view page has tabs and each tab for showing another view with its model and collection. This home view is similar to Twitter or Facebook. How should this complete page be rendered:
By navigating via router to subviews on tab clicks and rendering by subviews render function and placing them in home view page by homeview render, or routers is not and not good for this purpose. Advantage: a.bookmark-able subviews, b. ?more maintainable code. Disadvantage: on page refresh homeview part not rendered, only subview does.
Instead, on tab clicks subviews should be created in homeview and
rendered in themselves and placed in the home view page by homeview
render(). Advantage: no disadvantage above. Disadvantage: no advantage a, ?b above.
UPDATE:
A hybrid solution to refresh problem in 1. To have each subview to render homeview parts, tabs etc., by depending on a seperte single little template for that or from their templates having written those parts code to have those homeview parts. Disadvantage here is like separation of modules decreases a bit by subviews requiring (as dependancy) or including (in their template) something that is not sub at all but something like sup, main, belongs to an upper level module.
Or is there another, better, way?
Ultimately it's subjective; there is no rule for when you should start a new page View triggered by a Backbone.Router route and when you should just re-render a sub-view for a part of the DOM without involving a route. Really, it's just a matter of whether you want the user to feel like they've gone to a new page or not. Ask yourself:
do you want them to be able to click back/forward in their browser?
do you want them to be able to bookmark the "page"?
is most of the DOM changing or just a small part?
To put it another way, when a user goes to a Backbone.Router page it indicates that a significant change in state has occurred on your site. Really all the bookmarking/history entry/significant DOM changes are just reflections of that. So if you feel that a significant change, whatever that means to you and your site, is happening, make a route for it. Otherwise just re-render a View.
I am developing my client-side app with YUI3's APP Framework. I am having the following problem: I want to be able to have a few views (let's call them widgets) that are going to stay in the same place on page but under App's container Node, so that events can be registered within App's logic. For example I want a left menu which will have dynamic content (user's navigation panel).
This can be done by creating the menu as a subview, but navigating to another page will result in a page transition and thus, the menu will be included in page transition. I want this subview to be a shared view within many other pages(where page is formed from multiple subviews) but excluded from app's navigation behavior and rendered only once(and updated via custom events).
Does anyone with more experience using YUI App Framework knows hot can I tackle this problem? Thanks.
Yes it can be done. After a closer look on YUI's APP Framework API I found that there are 2 separate properties: container and viewContainer. The former is the node in which the app will reside and the later one is used for dynamically changing the active view on the page. Having this 2 separate properties you have the power to add watever content you want in App besides the pages (which are going to change based on events && routes).
So to conclude you can have a div element which is going to be app's container. Within this element you can write whatever html you want. You can also have another View class here which is going to change based on events(and YUI's custom events are very powerfull). And besides all this "static" html you must have another div(or of course, another html element) which is going to be the active view's container(that'll change based on events or in majority of cases, based on page's URL).
In a single page application, is there a way of switching back and forth to an AngularJS route and to display it back in the same state as it was shown before?
Usually this would be implemented by binding data in a parent scope. While this is easy to set up for lightweight view, it can be cumbersome when doing it for views holding lots of graphical elements.
Here is an example, where having the previous route state remembered could enhance user experience: on the following page, imagine that
you stand on Item 1 and select Tab 2
then move to Item 2
finally switch back to Item 1: Tab 2 is not selected anymore :-(
http://angular-route-segment.com/src/example/#/section1/1
It seems the views are destroyed/constructed when switching back and forth between routes.
A solution would be about storing the state of the user interface in a parent scope but it has the following pitfalls:
creating an object storing all the little details of the user interface
creating complex logic about -saving and- resetting the UI in the same state as before
storing UI state in a data model does not sound that MVC-ish
Using show/hide of div storing the views saves the state but then no route is used and the switching business must be implemented by hand. I like using routes because 1. of the browser history navigation (hash in the url) and 2. it is easy to set up.
Having the UI state not remembered is like having Chrome to reload pages when switching back and forth between tabs: not very user friendly.
Is there an Angular-way?
Your $routeSegment approach is very interesting. The $routeSegment service could plug into the $routeChangeStart event in order to
Somehow keep a "sub path history" on all paths seen so far, maybe only for those explicitly configured to keep their UI state. In your example for the path "/section1/1" the stored sub path would be "/Y" if tab 2 was selected. Things get interesting, as also dynamic paths with $routeParams might need to be covered.
Use this history for redirecting by using $location.path in the event handler. So a $routeChangeStart event with next.originalPath being "/section1/1" might be redirected to "/section/1/Y"
I have a backbone.js app, whose views have multiple states, which differ substantially from each other ("View","Edit", etc). There are at least 2 different templates for every view. This is OK. My problem is with the JS view managing code.
I rely on an initalize-thin-render-thick approach (which, I think is pretty bad), where the render method is where 80%-90% of the logic occurs. When I want to change the state, I simply call the render method with a specific parameter ("view","edit"). On the basis of that, the view decides what to show and what not, to which events to bind, etc.
I think this is bad, because, on one side it puts bottlenecks on the rendering process, on another, it is not proper state machine, which means that I am not carrying about possible callbacks that might have been bound previously. When I receive the view, I simply clean the view and that's it.
I also observed, that I am not using the delegated event system, provided by backbone, which I think is another minus, because I think, it is very well implemented (BTW, does it make sure to unbind callbacks, when a certain DOM element is removed?)
I think I need some serious refactoring. Please, help with some advice, as to what the best approach for a multi-state Backone view would be.
What I tend to do for these cases is to make a toplevel view that manages a subview for each individual state (index, show, edit, etc.). When a user action is invoked, e.g. "edit this user", "delete this user", "save my changes", the active state view signals the router (directly, or through a hyperlink), and the router will tell the toplevel view to update its state.
Continuing the user editor example, let's say that I have a top level view called UserEditorView. It renders a basic container for the user editor (title bars, etc.) and then, by default, instantiates and renders Users.IndexView inside that container.
Users.IndexView renders the list of users. Next to each user is an edit icon, which is a link to "#users/555/edit". So, when the user clicks it, that event goes to the router, which tells UserEditorView, "hey, I want to edit user #555". And then UserEditorView will remove the IndexView (by calling its .remove() method), instantiate Users.EditView for the appropriate user model, and put the EditView into the container.
When the user is done editing the user, she clicks on "Save", and then EditView saves the model. Now we need to get back to the IndexView. EditView calls window.router.navigate('users', { trigger: true }), so the URL gets updated and the router gets invoked. The router then calls .showIndex() on the UserEditorView, and the UserEditorView does the swap back to IndexView from EditView.
On a simple way to manage unloading of events, I've found this article on zombie views quite useful.
Basically, I don't have a toplevel view, but I render all the views using a view handler that takes care of the views for a given container.
To make your renderer thinner, I would recommend using routes. They are easy to setup, and you can have different views for each route. Or, what I'm used to do is just to have different templates. Using a general Backbone.View overwrite:
Backbone.View = Backbone.View.extend({
initialize: function(attrs) {
attrs = attrs || {}
if(!_.isUndefined(attrs.template)) {
this.template = attrs.template;
}
}
});
I've noticed that I reuse views in two ways:
1. edit views differ only in the underlying model and template, but not the associated logic (clicking the submit validates and saves the model)
2. the same view can be reused in several places with different templates (like a list of users as a ranking or you accounts)
With the above extension, I can pass {template: '/my/current/template/} to the view, and it will be rendered as I want. Together with routes, I finally got a flexible, easy to understand and thin setup.