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.
Related
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?
I have a parent view with a nested view in the middle.
On a state change, the nested view seems to stick for a second or two before loading the next state. It's as though the nested view is lagging behind or something.
For example, after logging in, the login form is still visible for a second or two in the middle of the page after the state change. The parent view changes instantly, but that nested view just seems to stick.
I've been pretty careful about items on the watch list, and use one-time binding wherever possible.
But I really don't think it has to do with that, because this happens even early on in the application (from login to the main page), and other than this issue, application performance is fine.
I've googled a lot about this, but haven't turned up anything useful.
Any ideas on what to check or how to debug this?
You say it only happens the first time you transition after loading the app. So it could be you are injecting a service into the child view that you are using the first time in your app. This service is taking some time to instanciante. Servises are singletons, so this lag is only visible the first time.
Look at the answer in this thread for a possible solution, somebody had the exact some problem:
How to instantiate a service dynamically?.
Another solution might me to inject that service into the parent view as well, so you get the lag while loading the app not on first transition.
I need to use two states in parallel, one for my page and an other for a modal with several sub states.
Right now calling the modal state will wipe out my page since the page state changed.
Create a child state child of my page wouldn't be a solution since the modal will be used on several pages.
Example:
$stateProvider
.state('user', {}) // page
.state('bookshelf', {}) // page
.state('books', {}) // modal
.state('books.read', {}) // sub state of modal
So if I'm on user and open my modal then the state would change to books, my modal would then have the content but the page content will be wiped out.
How do I fix it?
I believe the way you're looking to do this is not possible with UI.Router currently. What you're describing is a modal component (which would ideally be written as a directive), which tracks it's state independently from the main state.
The way to think about it, is that UI.Router works by creating a state tree. At any given time you can only be looking at one branch of the tree. You can go deeper down a branch (ie: book, book.open, book.open.checked), but you can't be in two places at once.
Another issue with the problem above is how do you serialize the state of the two different trees into one url? It's not to say it can't be done, it's just a hard problem to solve.
Checkout these issues:
https://github.com/angular-ui/ui-router/issues/119
https://github.com/angular-ui/ui-router/issues/384
https://github.com/angular-ui/ui-router/issues/475
Also checkout these repos, they might be further along the lines of solving the problem.
https://github.com/afterglowtech/angular-detour
https://github.com/stu-salsbury/angular-couch-potato
As far as solving your immediate problem, I think the 'easiest' way would be to ditch controlling the state of the modal inside your state config.
Instead, I would add some sort of root or abstract state, and then track whether the modal is open there. Then, you can communicate between controllers using events as shown here. Note: There are performance implications with listening to $rootScope, so be sure to research those. However (someone feel free to correct me), the implementation here doesn't have those problems, because the AppCtrl is never destroyed.
Jan 15, 2015 Edit
Turns out this is a pretty popular use case, and one of the core contributors to UI Router maintains a plugin/addition called UI Router Extras
It also includes utilities for lazy loading, called "Future States" which are very helpful.
That being said, one feature I'm hoping to get time to work on is maintaining all state within the URL (or perhaps, local storage) and allowing for reusable state "components". The latter is in the UI Router roadmap as well.
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).
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.