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.
Related
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.
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);
}
I've been learning backbone.js over the past couple of weeks and am about to start using it in anger in an app I'm writing. My question to you is about a use case for models and collections in a Bootstrap 3 navbar.
On my server side I authenticate the user and, based on their profile, assign them a role (author, editor, administrator etc.). I then construct an object that contains the appropriate menu structure for the user's role and pass that to the client using Handlebars. The intent is for the browser to construct the HTML to render the menus according to the properties (key/values) in the object using backbone.
My thoughts are that the navbar itself is a collection of models (navbar); each dropdown menu or link on the navbar is a single model (navbarItem); each of these contains a collection of menu items (navbarItemMembers), these collections being of models of each individual menu item (navbarItemMember). I can then set event listeners against each navbarItemMember to trigger an appropriate route or render action as appropriate.
So, getting to the point... am I over-complicating things? A collection containing models each containing a collection of other models, each of those mapping to a view that renders a on the main page. Seems convoluted to me, but from my (albeit limited) understanding of backbone.js it does seem the right way to do this...?
Advice much appreciated from those more experienced (battle scarred?!) than I. Thank you.
Use a collection when it's going to provide some benefit to you over a plain model. The benefits could be either
Interacting with a RESTful service where you'll want to not just get a list of data but then separately fetch/modify/add/delete individual items is that list
Defining separate Views for each item in the list rather than a having a single View that iterates over the list.
I don't think a basic navbar benefits from either of those. If your navbar has some super fancy elements beyond just links then maybe #2 applies but otherwise keep it simple, use a single model with a single view/template
I have a Backbone view which represents a collection. When this collection is synced with the server, and new models are added to the collection, I would like to hide all the view instances that represent these new models in the collection, whilst continuing to display the old models' view instances. How can I do this?
I'm assuming you're using a Marionette.CollectionView or Marionette.CompositeView here, right? That being the case, you are trying to prevent these from showing the newly added models, when the come back from the server and are added to your collection, right?
I can see a few different ways of doing this, most of which begin at the same place: a collection that filters based on an attribute that tells you whether or not to show the model. You would need to have some piece of data on the models that tells you wether to show them or not... I'm not sure what this would look like off-hand, but once you have the logic set up for this, I think the rest of it becomes easier.
A Proxy Collection
My preferred method of handling this in the CompositeView or CollectionView would be to create the view instance with a proxy collection - one that is filtered based on the value to show or hide the models.
I've talked about proxy collections before, and I have a working JSFiddle to show how to build them, here: http://jsfiddle.net/derickbailey/7tvzF/
You would set up a filtered collection like this, and then send the filtered collection to your actual view instance:
var filtered = new FilteredCollection(myOriginalCollection);
filtered.filter({
showThisOne: true
});
var view = new MyCompositeView({
collection: filtered
});
myOriginalCollection.fetch();
From here, it comes down to how you want to manage the filtering and fetch/sync of the original collection. But I think the over-all solution hinges on the proxy/decorator collection and being able to filter the collection down to only the list of items that you want.
Views in Bbone are not automatically updated when the underlying model/collection is changed. You have to explicitly listen for events: change/destroy for models and add/change/remove/reset for collections and call render().
As WiredPrairie suggests, if you've registered during view initialization for example to listenTo() any of these events and explicitly render(), you can always use stopListening() to reverse the effect.
An alternative, in case it's a one-of case of suppressing the view from showing the changes, is to check manually in your view's render() which models have been changed and use the previous state of the changed attributes to avoid showing the new values. Check out: model.hasChanged() and model.previousAttributes() as well as the options.previousModels in case of a collection reset.
I've got two form fields, one select list, and another grid panel. Changing the selection in the select list will trigger its change event and fire a custom event called datasourcechange which the grid panel listens for. These two components have to be separate because they are used across different forms and not always together.
The problem is that when I sort the grid panel, it fetches an unfiltered list of records. I would like it to reapply the filter from the select list, if it's available.
I've tried remoteSort: false, but was thinking I could do something similar to how the select list fires an event that the panel is listening for, but I need a way to determine when the column headers have been clicked, either via a sort event or click event on the headers themselves, could anyone recommend a best practice approach for this?
We are using Extjs 4.0.5, and can't upgrade to 4.1.x. Any assistance is appreciated!
That´s a common problem. What you have to do is implement something like this:
When changing the selection in the select list, save the selected value on the grid´s store.
var store = getGridStore(); // you have to implement this method
store.selectedValue = theSelectedValue;
Next, you have to subscribe to the store´s load event in order to apply the filter before perform the request. This is, something like this:
getGridStore().on('beforeload', function (store) {
store.proxy.extraParams = { filteredByValue: store.selectedValue
}, this);
I don´t know how your implementation is, however this describes the general idea.