So some background.
Let's say I have a notice on the page (like a success or error message). This is managed by a collection of Notice models.
When a new notice is added to the collection a region displays it in my page.
But let's now say that I have a view somewhere deep and nested, which when shown should instead display the notice (instead of the other region) until the end of its life.
What method would be clean for managing this?
Adding an attribute to the model to say is_global or something similar
Registering upon view creation that this view will take over responsibility. Then unregistering upon removal.
Some other crazy method
If I am understanding your problem correctly:
You have a queue of notices and depending on the notice they can appear
in different parts of the page
I would say that you should add a property of some sort to the model and, on each add, emit an event related to that property value. This way any view can listen to the event and handle the notice appropriately. This will also keep your notification queue decoupled from the handling view
Related
The official UI5 documentation claims:
[...] We want to make sure that the memory allocated for this helper object is freed up when the component is destroyed. Otherwise our application may cause memory leaks.
To do so, we use the exit hook. The OpenUI5 framework calls the function assigned to exit when destroying the component. We call the destroy function of HelloDialog to clean up the helper class and end its lifecycle. Nevertheless, the instance itself would still exist in the browser memory. Therefore we delete our reference to the HelloDialog instance by calling delete this._helloDialog and the garbage collection of the browser can clean up its memory.
I've set a break point on this._helloDialog.destroy(); inside of
exit() {
this._helloDialog.destroy();
delete this._helloDialog;
}
And I expect that the break point will be triggered on leaving the view, which hosts this dialog. In fact, when I switched the view nothing happened, it looks like exit() wasn't executed at all.
Besides that, I've paid attention that after opening the dialog, typing a text there, closing and then reopening the dialog, the previously typed text remains untouched.
Such behavior forces me to assume that the dialog object is never released and it might be a potential place for the memory leak.
My questions:
Why the exit hook is not triggered when I leave the view, where this dialog had been defined?
Are there any best practices / common patterns for «resetting» the dialog's content in UI5?
Why the exit hook is not triggered when I leave the view, where this dialog had been defined?
Destroying a dialog has been always a somewhat questionable practice: when the user, for example, decides to open it again; the dialog with its entire tree of child nodes needs to be recreated and rerendered on the DOM, every single control there needs to be re-registered on the Core, as well as their events on the UIArea, models need to be propagated firing all kinds of events in the meantime, memory needs to be re-allocated, etc, etc.. To sum it up - it's quite costly.
The exit-hook, which is a low-level API, is however triggered automatically when the application calls destroy() on the ManagedObject. For example, the Fiori Launchpad automatically calls currentUIComponent.destroy() when the user navigates to Home triggering the exit-hook on every child element including the dialog.
Are there any best practices / common patterns for «resetting» the dialog's content in UI5?
Yes, see my answer on How to reset reset all screen's control values.
Let's say the user might pick one div from the set { A, B, C } and add it to div X, except each div has a different rule that may be dependent the application state and some intrinsic property of each div. Finally upon adding the div, each div might trigger different events.
for example, in one application state, the user:
try add A to X: A is added to X, nothing else happens
try add B to X: B is added to X, some additional event is triggered
try add C to X: not allowed, C is return to some defined original state ( for example if I try to add C to X by drag and drop, C is returned to original position ).
assume all three divs are already rendered and one model, view and controller exist for each one.
Feel free to tell me the pieces of logic you see in this process and where you would put it. But please answer:
should the validation process sit in the controller layer, or somewhere in the model layer
once validated, should one mediator object call the model and view to add new child, or should the model add the child, and then somehow triggers the view to do the same.
should controllers be aware of hierarchal relationships of the dom and model
I am implementing something like this in Ember.js which has certain ideas about things, but please answer even if you don't use it/have never heard of it.
The drag and drop action should be implemented in the view. When the item is dropped it should trigger an action that gets sent to the router. The router (functioning as the app's state machine) should respond to the action appropriately based on the current state. Deciding the appropriate action can be entirely state-based, or take into consideration aspects of the models or controllers.
The action should manipulate the model and/or controllers, and bindings/observers in the view layer should bring the view in sync with the new state of the models/controllers.
Controllers should not be aware of the DOM.
Hope that helps! I cover the division of responsibilities in a presentation available here: http://www.lukemelia.com/blog/archives/2012/08/23/architecting-ember-js-apps/
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.
I'm coming across a bit of an awkward problem. I have a Web page with quite a few buttons on it that need to be disabled and enabled at various points. Now if this were a Swing (or any otehr desktop UI interface for that matter), it would be quite trivial: I would simply add listeners for the model changes I was interested in and update the UI accordingly.
This is basic MVC stuff really.
Thing is, I'm at a bit of a loss as to how to handle this nicely in Javascript. I'm going down a route that will end up with some real spaghetti code where the click listeners for the buttons are updating the UI controls and that's just not going to end well.
EDIT: Let me give you a more concreate example.
Example
Imagine a screen that lists open orders. Those orders are presented in a table as each row (order) has multiple attributes against it, such as who is currently managing the order, who made the order, what it's for, when the order was made and the status of the order.
I've done it so you can select one (or more) of these orders by clicking on the rows. This adds a "selected" class, which changes the styling, much like a list.
As to the behaviour, if a user selects one order then certain actions become available, such as Open Order (to view the details), Take Owneship, Cancel and so on. The attributes of the order may also affect what actions are available eg if the order is "owned" by somebody else already, certain actions will be disabled.
Some of these options (like opening the order) aren't available if you've selected multiple orders.
Additionally via a background Ajax call the list refreshes with new orders periodically. The user can also click refresh or can filter the orders (by name, date range and so on) and then reload the orders. While the orders are reloading certain buttons get disabled.
I was going to do a second example but I think that one is sufficiently complex to illustrate the kind of problem. Now I've started this by giving various controls classes. For example, elements with the "select" class might be disabled/enabled/styled when an item is selected.
Now this works reasonably well in simple cases but I'm running into problems where the state of a control depends on multiple conditions. Also the classes are getting fractured by things like some elements want to be styled, some controls want to be disabled/enabled and in some cases both things need to happen.
In Swing I tended to handdle this kind of thing by having a sort of updateUI() method, which would be called whenever the state of a relevant control or model was changed. It would then set the state of all the controls explicitly. Now this is arguably not the most efficient way (eg if you have 30 controls and only need to update one of them it's a bit of a waste) but I found the simplicity was worth it. The alternative was that controls/models ended up with too information about what controls they depended on or those that depended on them. It go tmessy from a coupling point of view.
But I have no such (obvious) mechanism in Javascript. Inobtrusive Javascript as advocated by jQuery is great because it stops random code snippets being littered throughout your code. But I need to go a step further nad have some way of managing the complexity of this (because it is quite a complex screen and will only get more complex).
If you want to preserve your sanity, use a state machine.
You don't really give details about what your UI does, so I'll make up an example. Let's say you have a file upload page. The user should be able to select a file, click an upload button, and then be returned to the page they came from, when uploading is complete. So you could have three states, "SelectFile", "Uploading", "Finished". In the "SelectFile" state, controls should be enabled to allow the user to select a file. In the "Uploading" state, these control should be disabled, and the user should see a progress indicator. In the "Finished" state, the user should be redirected.
Of course, it sounds like your case is more complicated, but the same ideas will apply. You may need more than one state machine, if portions of user interface interact. That's fine.
When you want to change the enabled/disabled elements on the user interface, you just change the state of the state machine. The state machine itself tells the various user interface controls to update themselves based on the current state. You can use a Bharani's (good, up voted) suggestion of using classes to do this. Or whatever mechanism works for you.
The nice thing is that the controls no longer interact with each other. They only interact with the state machine. So you get rid of all the cases where states bounce around incorrectly or endlessly recurse.
Assign specific class names to the divs. That way even if they overlap in functionality you can simply keep adding class names to the div and because of the chain nature of jquery all registered event handlers will be executed.
For controlling form elements during ajax call - you can add ajaxStart and ajaxEnd or you can use ajaxComplete and handle all your code inside the callbacks.
I think i get your problem. I have worked on such screens before and i always ended up refactoring to structure the code better. But at times it will be easier if you could re-organize the functionality itself so that you don't have to handle all things in one place. I think GUI should also be treated like a function - do one thing and one thing well.
I think data modeling is important for JavaScript apps. I am working on this scheduling project for medical clinics and I have a JSON data structure that models chairs/patients in an office. Ember updates the views (UI widgets) automatically when the business logic changes in the models. So right off the bat a lot of sphagetti code is eliminated. If you are doing something graphically intense with user interaction it is almost a crime not to use an existing MVC pattern or to create your own MVC JS classes. The structured discipline is off putting at first but when you see later how it lowers your blood pressure and makes maintenance so much more enjoyable it is worth it. I wouldn't use it for simple one-off projects if they are small. It is better for medium complexity or advanced complexity. Anything that will take me more than a week I will use Ember. I have used knockout.js and Angular and I really like Ember with its Handlebars templating syntax. Easy on the eyes and efficient.
The question, in brief:
In MVC, how do you distinguish between a checkbox click (or a selectbox or listbox change) from a human meaning "Controller, modify the model", and a checkbox click (or a selectbox or listbox change) from the Controller meaning "I'm updating the view because the model has changed"?
The example:
I have a JS app (all one big HTML+JS page; there's a server behind it, and AJAX going on, but it's not important to the example) which has the notion of "Vertices" connected by "Edges". The UI lets you add and remove Vertices on a map, and enable or disable Edges between pairs of Vertices.
There are two ways to disable an Edge from Vertex A to Vertex B:
click on the Edge to make the "Edge Details" window provide you with a "Disable This Edge" button; or
click on Vertex A (or B) to make the "Vertex Details" window provide you with a checklist of nearby Vertices, from which you can uncheck Vertex B (or A).
Here's how this works under the hood in MVC (but see the end of this post for an update, where I correct problems in my understanding):
Model: a list of Vertex objects and a list of Edge objects.
View: a GMaps UI, with markers and polylines, plus checkboxes and buttons and "Vertex Details" and "Edge Details" DIVs.
Controller:
JS functions that update the model when events on the checkboxes and buttons fire; and
JS functions that update the view when events on the models fire.
Here's the specific inelegance:
The user has the Vertex Details Window focused on Vertex A, and the Edge Details Window focused on the Edge from Vertex A to Vertex B.
The user clicks "Disable This Edge" in the Edge Details window.
Controller function 1 gets the click event, and calls disable() on the Edge model object.
The Edge model object fires the "I just got disabled" event.
Controller function 2 receives the "I just got disabled" event, and
redraws the Edge Details Window to say "I'm disabled!" and
unchecks Vertex B in the Vertex Details Window.
Crap! This fires Controller function 1 again, which was listening for UI events that mean an edge was disabled!
So there's an unnecessary re-update of the Model, and re-update of the View. In a more complex view with events that fire events that fire events, this can make for dozens of extraneous updates!
Update: a great answer.
I misunderstood MVC a bit. I don't have just one View, as I described above: I have several Views into several Models. In particular, I have a checkbox-list View of Edges to a particular Node, and a separate, "detailed window-style" View of an Edge.
Furthermore, I shouldn't have one controller function updating all views when the Model changes: each View should modify itself when the Model changes.
So if each View registers for "state updated" events on the Model, and each View updates itself upon receipt of those events, then the answer to my circular events question is simply this:
The checkbox-list View will disable checkbox events for the moment that it is updating the checkboxes after a Model state change.
Now if a user disables an Edge via the Edge Detail window, the Controller updates the Edge Model, the checkbox-list View receives notification of the update, and the checkbox-list View is smart enough to silence checkbox events while changing the status of the appropriate checkbox.
This is much more palatable than my original solution, where one Controller updates ALL Views -- and thus has to know which views need special care and feeding to avoid loops. Instead, only the single View with troublesome UI elements has to deal with the problem.
Thanks to those who answered my question!
Just to recap the MVC model. Views should generally update themselves. Here's how it works: a controller changes the state of the model, the model sends updates to its views, the views pull in new state from the model and update themselves. While controllers and views are generally bundled (i.e. drilling down on data in a graphic representation) they should never interact directly, only through the model. This in general of course.
So the JS functions that update your views are not actually controllers, which is an important distinction. They should be considered part of your view. This might not be helpful to the problem at hand but I thought it merited pointing out.
You can also not delete your model, I assume you mean you're deleting someting from your model, since no views or controllers can actually exist (or be in a functional state) if they're not backed by a model.
Not being a JS code jockey and not having used gmaps I don't really see where the problem is. Does changing the state of a checkbox(checked property) fire the onClick() event? It really shouldn't IMHO but perhaps they implemented it that way, otherwise you could just attach your controller to the onClick() and add some logic to the checkbox (or, this being JS, in a function somewhere) to change the checkbox state. If that's not possible, option 1 and 2 are definitely your best bet.
addition: user interacting with a view
So what happens when a user wants to interact with a view? Frequently a widget will include both a view and the controller. A checkbox has a view (you can see if it's checked or not) and also a controller (you can click it). When you click the checkbox, in principle the following should happen:
checkbox controller receives the event
checkbox controller changes the state for the value this checkbox represents in the model
model updates listeners (including the checkbox)
checkbox updates its look to reflect that that value has changed
The first step, how the controller receives the event is somewhat language dependent, but in OOP languages it's likely a listener object attached to user interface events on this particular widget which either is the controller or notifies the controller of the user interaction.
This is a tough one. If I understand correctly, the problem results because you've exposed a click handler on your model, and the model's click event is caught by the controller. The controller updates the view, which in turn toggles the same event.
From my point of view, I would consider it inappropriate for the Controller to attach itself to the Edge's Click event, because it exposes too much detail about how the Edge is implemented and used. Controller does not care about how the Edge is used or any other implementation details.
In fact, canonical MVC style doesn't require the Controller to hook onto any Model events at all, generally because the Model's state is not mutated by the View or any other Controllers. Its not necessary for the Model to notify the Controller that it's been changed.
To fix your problem, you should define View's interface to have a single method, such as ToggleEdge:
public interface GraphView
{
event Action ToggleEdge;
}
Its tempting to want to create two methods, EdgeClicked and CheckboxClicked, but insisting on two independent methods like that violates the principle of encapsulation. It exposes too many implementation details to your Controller or anyone else who wants to hook onto those events. Remember, the Controller only cares that the View's state changed, it doesn't care how it changed.
When you implement the View interface onto your user interface, you should take care to ensure that the ToggleEdge event is invoked from one location. You can do this by hooking onto the Edge.Clicked event in your View and using it to toggle your checkbox; this makes your checkbox responsible for raising the Toggle vent up to the controller:
public class UI : UserControl, GraphView
{
public event Action ToggleEdge;
void OnToggleEdge(Edge edge)
{
if (ToggleEdge != null)
ToggleEdge(edge);
}
protected void Edge_Clicked(object sender, EventArgs e)
{
CheckBox chkbox = FindCheckBoxThatCorrespondsToEdge((Edge)sender);
chkbox.Checked = !chkbox.Checked;
}
protected void chkEdge_CheckChanged(object sender, EventArgs e)
{
Edge edge = FindEdgeThatCorrespondsToCheckbox((CheckBox)sender);
OnToggleEdge(edge);
}
}
You can make an argument that the View knows too much about its implementation now: its aware that edges and checkboxes are fundamentally connected. Maybe this is another hack, but it can probably be dismissed as "UI logic" need to keep the View's display syncronized.