Managing AJAX control loading sequence on a page
I think I am experiencing race conditions with an AJAX web application. I am using JQuery 1.4.4 to handle the AJAX requests.
I have a menu control/class that the user clicks to display controls for manipulating data. Clicking the menu can display multiple controls which initialise independently of each other and load at different speeds and become available in an unpredictable order (the A in AJAX I guess...).
The controls interact with each other, for example selecting an item in one control adds it to another. The interaction happens by triggering the event in the source control using JQuery and binding to that event in the destination control. The issue seems to be that when you start interacting with a control before all controls are finished initialising you can cause problems. For example, adding an item appears to have worked in the source control whilst the destination control is still loading but when it has loaded it doesn't show the added item. I think that this is because the add item request is fired and completed whilst the load is still happening which then overwrites any changes.
开发者_如何学JAVAThe intention in the design of the controls was to keep them as independent of each other as possible and for them to not call directly into each other's methods and properties if possible. The same controls are used in a variety of contexts and are unaware of what other controls are being displayed at the same time. There is a central class that controls the display and hiding of the controls - the menu.
Could anyone suggest an approach that would keep the controls / classes loosely coupled but be able to manage the dependencies between them and to know when it is OK to allow interaction?
A few ideas that I have are below. Any thoughts on these or further suggestions are appreciated.
The menu class could maintain some kind of flag that indicates when all controls are initialised - this would be based say on an incrementing count that is incremented on the successful load of each control. This is the only class that is aware of the controls that are currently in use but it is done in a generic way where the controls pass themselves in to be shown.
Loaded events are fired by each control and other controls being shown in that context bind to each other's loaded event. The controls would need to know more about the context in which they are being used though so that they know when they were the last control to be loaded and thus the page was ready to be intereacted with.
Implement a queue of requests that get executed once the page is loaded. This seems a little too synchronous though.
Upgrading to JQuery 1.6.2 is on the cards for consideration but not scheduled. I am not sure if deferred would be the correct option here anyway due to the generic / anonymous nature of the controls.
Thanks for your help
In 2007/08 I used to work on a project which was heavily Ajax powered which means that several pages did quite some page interaction and processing before being reloaded by a different page. The main idea was that the application (or pages) had a central controller that decoupled individual controls from each other while providing the functionality for control inter communication. A typical page scenario was that it displayed a tree-like structure (with various items of different types) that user interacted with. Based on this interaction other controls (like master list or details list or create/change form etc.) was displayed and interacted with.
Anyway. How did the whole thing work?
Control inter-communication - predefined finite set of events
I've written a kind of javascript page controller so every page interacted with it. I also introduced a set of predefined events. Most of these events had an attached metadata about the item/group whatever they were about. There were about 50 different predefined events in the application. Each event was defined by these:
- particular type: ie.
ActivityGroupEvent
- event action: select, selected, create, created, delete, deleted, change, changed, delete, deleted, discard, discarded, insignificant
- optional metadata: metadata was mostly related to the item that this event was about
Control decoupling - central event handling
All event handling (that wasn't related to control's internal workings like click events etc) were handled through page controller.
Every control (when it created itself) had an initialization function that automatically subscribed to particular event types (ie: item details view control subscribed to select item event). Event subscription made it possible for controls to be completely independent from each other. So when there was a select item event raised a subscribed control received it no matter where it originated from. Was it from a master view or the left tree that may have also displayed individual items.
Dynamic page controls - control creation on demand
Page processing also initialized event on demand control loading. So when a particular event was raised, page controller Ajax loaded particular controls prior to event dispatching. Controls themselves then took care of their life time. But they all included (inherited the same javascript prototype) functionality to inform page controller of their removal, so page controller re-loaded them when needed.
Why event action couples like delete/deleted?
The idea is, that when a user clicked a delete item icon (for instance) a delete event
was raised on the page level. Page controller than dispatched this event to subscribed controls that processed it.
Example: currently page displays left tree, master view and details view.
- User clicks delete item in the tree control.
- This particular control raises delete item event with item metadata on the page level (calls
pageController.raise(...)
). - Page controller loads a delete confirmation control (well this one wouldn't initiate an Ajax request since it doesn't need to get any server data)
- Page controller then dispatches delete item event to this loaded/created control
- User then interacts with it and confirms deletion.
- Control takes care of item deletion on the server (Ajax request)
- After deletion was completed this delete confirmation control raises a deleted item event on the page level.
- Page controller dispatches it to all subscribed controls (in this case that would be the tree, master and details view) that consume this event by doing their own stuff (most of them would remove the item from their list (not necessarily issuing an Ajax request)
- After delete confirmation control raised deleted item event it also destrys itself and informs page controller about it so it knows it will have to recreate is again when a certain event will be raised.
What're the benefits of using a page controller?
Page controller takes care of several things:
- Centralised page »dirty state« handling that all controls can use – controls can claim themselves as »dirty« and they can also check if page is dirty before initiating certain actions (i.e. menu will check dirty state before navigating to a new page); When a control wanted to inform user of the page being dirty (or better said that they can't execute the action they wanted) page controller provides a function that uniformly informs user of dirty state; Each control (when claiming dirty state) has the possibility of including a string that describes its dirty state (ie. "Change item form must be saved or discarded.")
- Control decoupling while still providing inter-control communication through event raising and dispatching – controls are not aware of each other; only page controller knows of these controls and is therefore responsible for their creation, initialization, page level event handling and disposal;
- Controller actions – similar to control event handlers, but these are executed by the page controller on certain events before event gets dispatched to receivers (i.e. when a user selects an item, controller executes an action to create detail form on the fly before dispatching event to it – if it registers for this event in its initialization cycle);
How can this be helpful in your case?
The main idea here is that you need control decoupling that also centrally raise events (whether you will need sophisticated list of events or just some simple predefined set is up to you and your business process). This makes it possible that controls may be loaded asynchronously and independently interacted with as well. Page controller (as I called it) will take care of event dispatching after the control will be initialized. So it can delay them up to the appropriate moment.
Can existing libraries be used?
Oh one last thing. I forgot to mention that we used ExtJS for rich client side visual controls. But we've extended lots of them and written lots of our own as well. Especially we needed all this page-level interactivity. Nowadays there are javascript libraries that may help you in this regard, but I can't say since I'm completely unfamiliar with them. But since their name applies they do provide some controlling on page level. And these would be Backbone and JavascriptMVC. But it's up to you to check whether they provide appropriate functionality or not.
Appendix - page controller public functions
void setDirty(object sender[, string description])
– every control must call this method when its state has been changed or are exposed to change; Description is used to textually describe control's dirty state and is used when displaying a message to the user (used inisPageDirty
function)void clearDirty(object sender)
– after user saved or discarded data, controls must call this method to allow other controls to execute their »clean page functionality«;bool isPageDirty([bool displayMessage])
– before controls execute some »clean page functionality« they must call this function; if they provide a parameter of valuetrue
, controller will automaticaly display a message to the user (save or discard) if the page is dirty;void createAction(type eventType, enum actionType, function func[, bool runOnce])
– when you implement a new controller you prepare yourself actions using this function;void removeAction(type eventType, enum actionType, function func)
– this will remove a previously prepared action;void registerReceiver(type eventType, enum actionType, object receiver, function func)
– controls use this function to register themselves for particular events receiving;void unregisterReceiver(type eventType, enum actionType, object receiver, function func)
– controls must unregister themselves before they are destroyed;void raiseEvent(enum actionType, object eventInstance)
– controls call this method when they fire a page-level event;void initialize()
– when you inherit a new page controller from aControllerBase
class you have to implement this method. It will automatically get called when page loads; you should normally set initial page state in it (create all controls that need to be created at the beginning etc.)
I would go for the menu class option, where controls of interest register themselves, amd the class simply indicates when all registered controls are loaded ( the incrementing count option is probably the right one ).
You have the advantage that this keeps the controls independent, allows for other contrls to be added etc, which is an important aspect of what you seem to be trying to do.
The other idea, which I presume has been considered or done, is to only bind on the document onready. This means that the bindings will only be available when all of the contorls on the page are loaded. Is there some reason that this is not appropriate or does not work?
精彩评论