AngularJS: Preserving the state of a view when the user goes to a sub-view - angularjs

Within AngularJS, is there a way of preserving the state of a model & view between invocations?
Say, you have a master view that displays a tables or tables of information; the user has manipulated the view and applied filters and sort orders, etc to the information on display in this master view. The user then selects an item on this master view and is taken to another, child view, that displays information about the item they have selected; if the user then decides to return to the previous view I want that view to be in the same state as it was before, with its filters etc intact.
I'm currently using the technique described here: Maintain model of scope when changing between views in AngularJS where I preserve the model state using a service and that service is then injected back into the controller when its recreated. Is this the AngularJS way, or is there a better way of preserving model/view state between invocations?

Related

How to update the view without re-rendering in marionette.js

How to update the view with the model.fetch(), i.e when i fetch the model, the view shouldn't be destroyed or re-rendered. It should just update the new model into view with reseting the previous model.
this.model.fetch({success: this.render.bind(this)});
This code is re-rendering my view..How can i just update the view with new model
Thanks.
There are multiple ways of updating the view based on your need.
If the view is fairly simple, and all that is needed is to automatically update the view on model's fetch, rendering the view again is the simplest(Backbone's sync event on the model can be used, and model events can be handled declaratively using Marionette View's modelEvents hash -http://marionettejs.com/docs/marionette.view.html#viewmodelevents-and-viewcollectionevents)
modelEvents: {'sync': 'render'}
If you have few model attributes that change in a complex view, you could directly update the dom elements via jquery by listening to change events on the model attributes:
modelEvents: {'change:name':'nameChanged'},
nameChanged: function(model, value){ this.$('#name').val(value);}
If Two way data binding between view and model is needed, frameworks like Backbone.stickit can be used to this purpose - https://github.com/NYTimes/backbone.stickit#usage
Whenever you establish a double binding with your model attributes to the templates, your views need to be rendered again to show the changes. So, you can't avoid rendering to show the updated status of your model.
But what I would suggest you to do is to divide your view into subviews. Since, you are using marionette, you can create a main layout which contains all the regions and for each small region, you can define a view .
For example , suppose I have a basic form with my name, current time and age . All of these variables are stored in a model . So, you have a scenario where your name and age hardly changes but the current time is changing every millisecond causing your whole view to re-render which is bad in terms of performance as well as to the eyes.
So, in order to solve the above scenario, if you could create a separate view for the current-time element, you can render is separately and other elements don't need to be rendered again and again. You can always define a separate view for a single button element if you think that its functionality can be abstracted.

Handle Views in routing backbone js

What is the best way to switch to a different view when user navigates to a different url. In angular there is ng-view that takes care of this and inserts corresponding templates and in ember its all route based.
Is it better to just hide other views elements on routing using css or destroying other views and inserting current view?
EDIT
It would be great if someone could give an example how to re-render the view on navigating back to it again and restoring its previous state.
Eg.
if you have a check-box in a view that user can select to add some item to the cart , but in the middle he/she moves to some other url and then comes back, that check-box should be checked.
I would have a main content view with subviews and call remove on it, which is responsible for cleaning up any subviews too (calling remove on them first and going up the hierarchy tree). The concept of subviews doesn't come for free with backbone but isn't hard to implement. And finally attach a new content view.
This ensures you can cleanup and the browser is using a consistent amount of resources.
I would abstract this into some kind of layout view which has a content subview and a function like setContent(view) which handles the remove of any existing content view and the attach of the new one.
Personally I would have a router with sub routers in modules, e.g. a main router which finds a route starting with "checkout" and passes it over to a sub router in the checkout module which is responsible for attaching a new content view.
In Backbone the implementation is up to you which is both good and bad, depending on how nice you do it ;)
Always remove the view as opposed to just hiding it. If you don't remove (and unbind) your views properly, all bindings, handlers and references to models/DOM elements will linger around.
Depending on the size of your app, you can have a module that handles layouts (as suggested by dominic-tobias), or have a method on the router that takes care of this for you. At its most basic, this method (let's call it _switchView) takes a view and holds onto an instance of the currentView. Upon view change, it removes the current view, sets the new view to the current view and then renders it to the DOM.
Something like this:
_switchView(view) {
this.currentView && this.currentView.remove();
this.currentView = view;
this.$rootEl.html(view.render().$el);
}

Decoupling Backbone views where one triggers change in other view

I have a page which contains two Backbone Views. A view for list of contacts ContactsListView and a view for navigating NavView.
ContactsListView has collection of Contact models. My list of contacts collections is quite bigger so I don’t want to display all of them on page load.
So I have implemented contacts collection to fetch only a range (initially 0 to 50) of contacts from server and ContactsListView displays it. And use NavView to display the range of contacts ContactsListView is displaying currently. It also has next and previous buttons on click of which ContactsListView should display next or previous range of contacts.
Here my NavView is actually controlling what range of contacts ContactsListView should be displaying. But I want to decouple ContactsListView from NavView.
I realized I can do this with creating a Range model (with from and to attributes with default set to 0 and 50) and passing it to ContactsListView and NavView. While ContactsListView adds a listener, ContactsListView.show(from, to), to change event on Range model and NavView is updating the Range model when clicked on next or previous button. As the Range model changes ContactsListView listener would fetch contacts to update it's view.
Or
I can create a Range model and pass it to NavView. NavView then updates the range on click of prev or next button and triggers an event rangeUpdated with range values on (NavView) itself. I add listener on NavView in some additional code piece (probably my own controller object) and invoke ContactsListView.show(from, to) explicitly.
Which one is better way to decouple ContactsListView and NavView? Or is there other way to decouple them?
As long as your question is tagged with backbone.marionette, I assume you are using it, or thinking about using it.
One option could be using the marionette.compositeview, that lets you define both a model and a collection in the same view, so you wouldn't need to share models between views.
But maybe, that could not the best approach if you really want to decouple both views. For decoupling them, your second option using your own controller (or marionette.controller) is the one I like more, as you don't need the range number as explicit data contained in a model within the ContactListView (you already have that information in NavView).
Lastly, I like to do the fetching/saving data from the Controller, and not from within the view. The View in MVC patterns just show things, getting information from the model. Retrieving data from server that will be stored in the model seems more a Controller operation. I don't know how are you binding the data with the view, but maybe the ideal will be fetching the data from the Controller, and let the view render itself when the change of model is detected.
Initialize both views with Range model.
In Contact List view listen to Range model change event to request new data from Contacts collection and show them.
When click on Nav view just set the Range model with new values (it will trigger change event and your Contact List view will be updated).

How to properly use events in Backbone

I am building a three-way selector: companies, departments and users.
I am trying to figure out the best way to structure this in my Backbone app. Here's the current problem I'm having.
Say a user has selected a company. Then, the departments and user collections will be populated and the view will update:
The user can then select a department from the list, which will further refine the user select. This I have working well.
Or, the user can go straight to User list and find a user (without first having to specify a department). In this case, the view for both departments and users needs to update:
The department should become selected on the user's department.
The users should refine to all users in the selected user's department, rather than all user's in the selected company.
I am struggling with the best way to do this. So far, my departments and users collections have a selected property, so that's how I'm maintaining state. Currently I'm doing something like
When the user selects a department, the department view
Sets the selected department directly on the departments collection
Triggers an event
The users collection hears the event, clears out any selected user, and triggers another event
The users view hears the event, and re-renders. Since it knows about the departments collection, it knows a department has been selected and that it should refine the users down to the department
I do this because if I had the view only trigger the event (without setting the departments selected property first), I would have a race condition: both the departments and users collections would be responding to the event, and depending on the timing the users may not be properly refined.
The second piece:
When the user selects a User (without specifying a department), the user view
Sets the selected user directly on the users collection
Sets the selected department directly on the departments collection (which it knows about)
Triggers an event
and this is where I'm stuck. The departments collection doesn't really need to do anything, since its selected property is already correct; really, its view just needs to re-render. And so does the users' view.
But this is not all, because there are lots of other things that can happen. I feel like it's getting out of control.
What's the best to structure this?
Am I using events properly?
How do you deal with a view that needs to hear about other views and other collections changing?
Update: Should I just use routes to save application state? This may simplify things...
Update 2: This question has been helpful to me. Having a separate model to manage state definitely seems the way to go.
Update 3: Having a separate model to store state + the use of jQuery deferreds is amazing. Seriously. It completes me.
I find it useful to use a model for keeping track of the state. That way you can pass that model around to different views and don't have views referencing each other directly.
You can use built-in and custom events on the state model to manage state transitions.
In your case collections would not need to store selected. Instead selectedUser and selectedDepartment could be attributes of the state model.
Then you could have logic in your model that triggers custom events ('update:users:view' or 'update:departments:view') depending on what is selected.
I hope that makes sense.

Backbone.js Approach to Managing UI State / Handling Selections in UI

My question deals with this UI sample.
Having trouble with approach to managing the "selected" state of various UI view components. For example, I have menus above from which the user makes various selections. These selections should cause updates in the menus themselves (HL selected items) and also cause updates in the results, which would be based on the selections made. Also, the menus have different kinds of rules. For example, you can only have one "list" selected at a time, but you can have multiple "tags" selected.
One approach that I was thinking about was to create a Backbone model that holds the state of the UI "selection". For example, I could have a model SearchCriteria that holds this information. Then, when a user makes choices in the UI, I could update this model. I could have the various view components listen for changes in this model (as well as changes in the primary data models.) Then, the views would update their visual state by updating which items are shown as selected.
One item I am struggling with in this approach is who should be responsible for updating the selected state of an item. For example, on the list of tags, I might have the following pieces defined...
Tag (model to represent a tag)
TagCollection (collection to represent a collection of tags)
TagMenuView (view that represents the menu of tags available to select)
TagMenuItemView (view that represents a single item in the menu)
Should I...
Set up an event listener on the TagMenuItemView for click, and then try to handle 1) updating the SearchCriteria model, and 2) updating the visual state of the menu, e.g. selected items?
Or, should I have the higher level view (the TagMenuView) listen for events such as the user selecting a tag, and perform the work there?
Also, the tags menu in this example allows multiple items to be selected, but the lists menu would only allow one list at a time to be selected. Where would this "UI" rule (or is this really a business rule related to a search?) be enforced? For example, if I listened for click events on each individual list menu item, I could certainly update the visual state of that item, but, I also need to make sure the higher level menu view deselects any other selected lists. So, would it be better to manage the "UI" state of something like the to-do list menu in the view that would represent that entire menu (a ToDoListMenuView) rather than on each individual menu item view?
Sorry for so many questions. I am just having a hard time moving to this model of development.
You're almost to an answer similar to the one I would use.
If you appreciate that list, search, due and tags are search filters on a big collection of to-dos, you are 90% of the way to enlightenment. In fact, other than search, all of those are just "kinds of tags"! (Unless you have 10,000 to-do items, there are no performance or memory-related reasons to have lists of lists of to-dos; "Work", "Project #1", and "Personal" are just specialized tags by which you filter items out of your view, showing only those related to one sphere of your life or another.)
Create the SearchCriteria model. (You are not technically searching, you're filtering: you're excluding from your view those things that don't match your search criteria.) This model will hold a lot of attributes about your client state. Your search criteria are almost entirely driven by data present in the client (since the search applies to only one ToDoList at a time), so it's entirely SearchCriteria related, not ToDo object related.
All Views bind to change/add/remove events on SearchCriteria. When the user clicks on any of the views (list, view, tag), that message is forwarded to SearchCriteria. It makes the appropriate internal changes, which in turn triggers the views to re-render themselves. One of the event recipients in the main ToDoListView, which during its render then checks the search criteria. Something like:
ToDoListView = Backbone.View.extend({
...
render: function() {
var self = this,
toDraw = this.collection.filter(
function(c) { return this.searchCriteria.passes(c); });
$(this.el).html('');
_.each(toDraw, function(c) {
(new ToDoItemView({model: c, parent: self})).render(); });
}
That may be a little personally idiomatic, passing in the parent object and letting the item insert itself into the parent's DOM object. You could pass in anything: the element to be appended to. Alternatively, render could return a DOM object and the ListView could do the appending. That's a matter of taste. I've done both.
You do have to dig a little into backbone's parent library, underscore, to grok the essential wonderfulness of the _.each() usage.
Also, I've often contained an entire Backbone application in a self-executing anonymous function, and leaving "searchCriteria" as a variable accessible to all objects within the scope of the SEAF, so it wouldn't be this.searchCriteria, but just searchCriteria.
You can also write SearchCriteria so it calls sync, writing event state to the server, which you can then save as a raw JSON object; the nice thing about sync is that if what you send and what you receive are the same, no events are triggered, so you don't get a double-render effect, and the nice thing about using JSON is that it's client-appropriate, but contains nothing that the server's ToDo relationships care about.
Furthermore, you can specify specific client-side behavior rules. Such as: when you change ToDo Lists, you can apply the text-search criteria, or, as an alternative, you can decide that changing lists clears the text-search criteria field; doing so will trigger an event that will cause the "TextSearchView" to clear its input box (you'll have to write that event handler, but it'll be obvious you meant to do that). You can make up any rule you like, such as "changing lists clears all selections," but that doesn't seem sensible. I can easily imagine trying to tackle the bugs in my "project" list and in my personal life. But clearing the search box just seemed more... sensible, if you know what I mean.

Resources