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 ifB
orC
changeB
is dependent onC
's state- So if
C
changes, so doesB
and both may end up causingA
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 incrementsS
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 decrementingS
check if count is 0. If it is, it redraws.
So if C
changes:
C
runsS.signal()
(S==1
)C
makes its changes, callback toB
B
runsS.signal()
(S==2
)B
makes its changesB
runsS.wait()
(S==1
, no redraw)B
finishes, C starts again.C
runsS.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.