Academic Integrity: tutoring, explanations, and feedback — we don’t complete graded work or submit on a student’s behalf.

Given: some sort of widget based web app lots of JS functionality high coupling

ID: 647837 • Letter: G

Question

Given:

some sort of widget based web app
lots of JS functionality
high coupling (communication/callbacks between widgets)
widgets draw themselves
certain widgets need to do a complete and fairly expensive redraw every time one of their dependencies is changed.
Problem: there can be complicated dependency chains and it becomes difficult to make sure redraws only happen once per user action.

For example:

A needs to be redrawn if B or C change
B is dependent on C's state
So if C changes, so does B and both may end up causing A to redraw.
If this only happens a few times, it's easy to solve. But when it happens all over, it becomes a nightmare to engineer and makes maintenance even worse.

I assume this is a somewhat common problem, so there must be some method of dealing with. That or my designs are flawed in some way.

Any suggestions? Either some design pattern or an algorithm to deal with limiting redraws would be very much appreciated.

Edit

Alright, based on Randall's suggestions, here's what I'm thinking:

Semaphore like global variable but without any multi-threading stuff so basically just a global count. Let's name this S. I would have one per widget that requires expensive redraws.
S has a signal and a wait function. Signal increments S and wait decrements it.
S is initialized with a callback function to the draw of the widget.
Every time something is triggered (user event, ajax return, etc) S.signal() is called.
Once the event is finished changing things within an object (after callbacks are made), S.wait() is called.
S.wait(), after decrementing S check if count is 0. If it is, it redraws.
So if C changes:

C runs S.signal() (S==1)
C makes its changes, callback to B
B runs S.signal() (S==2)
B makes its changes
B runs S.wait() (S==1, no redraw)
B finishes, C starts again.
C runs S.wait() (S==0, redraw)
Like the dirty flag but more of a dirty-count and I think this is easier to implement. It can be extended for additional functionality fairly easy. If, for some reason, I want to optimize it so not everything is redrawn in certain cases, I can add some flags that represent things to be redrawn and reset them every draw.

Also, it could be replaced with a stack and do basically the same thing but with some better extension capabilities.

Edit 2

Izkata's solution turned out to be trivial to implement and only slightly less flexible. Only 2 changes are needed:

Add a timerID property to the widget that needs redrawn.
Wrap redraw function in a timer:
JS code:

A.draw = function(){
    var that = this;
    clearTimeout(this.drawTimer);
    this.drawTimer = setTimeout(function(){that._draw();},0);
}
A._draw = function(){
    //old draw code goes here
    //could be made "private" or nested in A.draw
}
No other changes are needed so your interface stays the same and it works unless you need it to work with Async code like ajax calls.

Explanation / Answer

Good question. I think you need to separate capture of the need for a redraw from the act of redrawing. Basically, as dependent things change, they notify their parent. The parent then marks itself as being "dirty" which means it needs a redraw. At a later time, the redraw occurs and the parent clears its "dirty" flag. This is common in event-driven windowing and UI systems (sorry, I can't remember a google-able name for this right now).

In your case, if the dirty flag is going from clear to set, you might want to schedule a redraw operation for a short time in the future. Then, other events can occur which will trigger the dirty flag to be set multiple times. After time has passed, you can redraw what needs it. Hopefully the wait time can be short enough to not be noticeable to the user, but long enough to collect all the likely changes that need to be redrawn.

An alternative to the time-based approach proposed above is to use logic to somehow detect that the system has stabilized and defer redraws until that point. I'm not sure what triggers the changes that necessitate the redraws, but perhaps there is an obvious point where the changes have all been made.

A third approach might be to have your system use an event queue to drive its work. The queue could be prioritized, such that data modification events come before redraw events, and that there need be only a single redraw event per widget (or a single event for the entire page).

Since SO sometimes collapses comments, people should read Izkata's suggestion below for using a zero-delay timeout and JavaScript's single-threaded nature to create what is effectively an ad hoc queue to ensure that the redraws get done after the main processing. Clever.

Hire Me For All Your Tutoring Needs
Integrity-first tutoring: clear explanations, guidance, and feedback.
Drop an Email at
drjack9650@gmail.com
Chat Now And Get Quote