How to properly use events in Backbone - backbone.js

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.

Related

Modifying Schema only for one document

I'm creating an application in the MERN stack and I stumbled upon a problem. I will start by explaining how that app is going to work.
So, in that application users can create their Collections. It can be anything - a collection of books, a collection of a favorite food - anything. Now in these Collections, they can create Items - for example, specific books.
We can navigate through the application to the different Collection Pages or the specific Item Pages in those Collections. You get the idea. There is a list of all the Collections on the main page and we can click e.g. Books Collection, then click on the Harry Potter item, and we will visit the Page for that specific book.
When a user creates an Item, he has to add a name and tag to it. But the user can set his own fields, like for example Author field for that Books Collection. Then every Item (book) will gain that Author field. It's obvious, that the main Item Schema is not affected by that additional field. Because we don't wanna the Author field in the Favourite Food Collection.
Anyway, I know how to modify data of already existing Items, but how to change that Schema for an Items that user gonna create in the future? Because if the user added Author field, we obviously want that field to show every time that user creates a new Item (book) in that certain Collection. Should I create a whole new Schema, only for the modified document? Or is there a different, more approachable way of achieving what I want right here?
As far as saving dynamic data in the collection, you could use Mixed Type or Object Type, see more info here
For keeping track of the fields the user has used previously, we could maintain a array within, having all the fields

How to save all changes in Backbone.js models and collection by click "Save" button?

I use things that are listed in the tags.
I have some model with many different relations.
The relations of model can be deleted or added(resource created) by user.
And I wanna save all changes in the main model and any relations update. By "clicking save button", that is, as one transaction at a time. It is desirable for one call to Tastypie.
How I can do that?

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

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?

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).

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