Design pattern wanted.

Calling all design pattern gurus. I’ve got a challenge for all you object orientated persons. I’m currently working on an actionscript component framework, that includes modal behaviour. That is if a component is modal, when it is enabled, all other components are disabled. Think of an alert box that doesn’t allow you to progress through an application until you’ve chosen an option and you’ll see what i’m trying to achieve.

I’ve actually got this all working fine. My only reservation is that the solution i’ve come up with seems a bit clunky. Anyway on with the show. I’ll detail all the steps in the creation, with pseudo-code examples.

The basis for all this is the component class, which all my other components will inherit from. It has enable, and disable public functions.

class com.lookmum.view.Component{
//enable function
public function enable(){
//enable component code here
}

//disable function
public function disable(){
//disable component code here
}
}

Next comes the creation of the modal manager class. This is a singleton which all components will register with when they become enabled, and unregister from when disabling. It also includes methods to enable and disable all active components. Here’s the relevent parts of the class shown with pseudo-code

class com.lookmum.util.ModalManager
{
// the list of all enabled components
private var componentList : Array
//the list of all components that this class has disabled, stored ready for re-enabling
private var disabledList : Array
public function registerComponent (com : Component) : Void
{
//add a component to the active list
}
public function unregisterComponent (com : Component) : Void
{
//remove a component from the active list
}
public function disableModal () : Void
{
//disable all active components and add them to the disabled list
this.disabledList.push ([])
var currentLayer : Number = this.disabledList.length - 1
<![CDATA[for (var i = 0; i < this.componentList.length; i ++)]]-->
{
if (this.componentList [i].enabled)
{
this.componentList [i].disable ()
this.disabledList [currentLayer].push (this.componentList [i])
}
}
}
public function enableModal (forceAll : Boolean) : Void
{
//enable the last disabled batch of components and put them back into the enabled list
var currentLayer : Number = this.disabledList.length - 1
var currentLayerLength : Number = this.disabledList [this.disabledList.length - 1].length
<!--[CDATA[for (var i = 0; i < currentLayerLength; i ++)]]-->
{
this.disabledList [currentLayer][i].enable ()
}
this.disabledList.pop ()
}
}

We then revise the component class to register itself with the singleton on enable, and unregister on disable.

import com.lookmum.util.ModalManager
class com.lookmum.view.Component{
private var modalManager:ModalManager
//constructor function
public function Component(){
//get a reference to the modal manager singleton
this.modalManager = ModalManager.getInstance()
}
//enable function
public function enable(){
//register the component with the modal manager
this.modalManager.registerComponent(this)
//enable component code here
}
//disable function
public function disable(){
//unregister the component from the modal manager
this.modalManager.unregisterComponent(this)
//disable component code here
}
}

Our immediate problem is that the enable functions affect registration with the singleton, so we need to test wether it is the singleton doing the enabling, and not to register/unregister if it is. The quickest thing to do is to pass an argument to the enable functions telling the component that the function is being called by the manager.

//the modified enable modal function in modal manager
public function enableModal (forceAll : Boolean) : Void
{
//enable the last disabled batch of components and put them back into the enabled list
var currentLayer : Number = this.disabledList.length - 1
var currentLayerLength : Number = this.disabledList [this.disabledList.length - 1].length
<!--[CDATA[for (var i = 0; i < currentLayerLength; i ++)]]>
{
this.disabledList [currentLayer][i].enable (true)
}
this.disabledList.pop ()
}

and…

//the modified enable function
public function enable(modal:Boolean){
if(modal){
//register the component with the modal manager
this.modalManager.registerComponent(this)
}
//enable component code here
}

The main problem with this approach is that we are making what should be internal behaviour public. There’s no way to enforce that the boolean value will only be passed by the manager.

Another approach would be to have a pair of enable functions, enable() and modalEnable(). One for general use which performs the registration, and one for use by the manager. However this still makes public something that should happen behind the scenes. There’s nothing to stop another developer accidentally using modalEnable() instead of enable()

The solution I came up with was to have this second enable function, but make it a private member. I then create a function delegate within the normal enable function and pass it to the modal manager with the component registration.

//the modified enable function
public function enable(){
//register the component with the modal manager and pass a function delegate as a scond parameter
this.modalManager.registerComponent(this,mx.utils.Delegate.create(this,this.modalEnable())
}
doEnable()
} 	private function doEnable(){
//enable component code here
}

We then give the manager event dispatching behaviours and let it add and remove the listeners itself.

public function disableModal () : Void
{
this.disabledList.push ([])
var currentLayer : Number = this.disabledList.length - 1
var dispatchList:Array = new Array()
for (var i = 0; i < this.componentList.length; i ++)
{
if (this.componentList [i][0].enabled)
{
dispatchList.push(this.componentList [i][1])
this.disabledList [currentLayer].push (this.componentList [i])
}
}
for (var i:Number = 0; i
this.addEventListener(DISABLE,dispatchList[i])
}
this.dispatchEvent(new Event(DISABLE,this))
for (var i:Number = 0; i
this.removeEventListener(DISABLE,dispatchList[i])
}
}

What i really want is some way to make a method of a class A only available to instances of another class B, bearing in mind that these two classes are unrelated, and i want any errors to be compile time, which means doing fancy things with arguments.caller isn’t quite what i want.

Any ideas anyone, or am i just being a pedant?

  1. #1 by JesterXL on October 18th, 2006 - 5:23 pm

    If you are using Flash MX 2004 or Flash 8, PopUpManager has this built in.

    If you are using Flex 1.5 or Flex 2, same thing.

    If you are rolling your own because of it being a design project, you could use the old “mouse shield” trick.

    - make a MovieClip on _root at depth of 0 be your “app”. All content goes in this mc

    - when a popup is created, make a mc on _root at depth of 1. Make it as big as the stage, invisible (alpha 0) with an onPress = null, and a useHandCursor to false. This will block mouse clicks to components underneath (not focus nor keyboard tabbing, though)

    - make your new mc that needs to be modal above that; _root depth of 2

    A Singleton class, like PopUpManager, can handle the creation and destruction of these 2 MC’s.

  2. #2 by Phil on October 18th, 2006 - 5:27 pm

    Thats a nice solution, however it doesnt cover keyboard acessability which i quite often need.

    This is a roll your own kind of job, but it’s more an academic question, as like i said, i have it all working already.

    Just curious if someone has come across this before, not nessecarily for modal behaviour.

  3. #3 by daniel yuen on October 18th, 2006 - 8:38 pm

    b4 i also have same thinking……..how make it better….i try to share some idea here.

    i think in the oop,the approach “enable() and modalEnable()” is the best solution.

    but the side-effect: “There’s nothing to stop another developer accidentally using modalEnable() instead of enable()”, i think all OOP programming also face this problem. i dun know more about AOP , but it like to come to solve somthing like that problem.

  4. #4 by p on January 11th, 2007 - 12:17 pm

    You can use Observer patternt to solve this.

  5. #5 by Phil on January 11th, 2007 - 12:21 pm

    That’s kind of the solution i used, I just think it’s a bit clunky.

  1. No trackbacks yet.

Comments are closed.


  • SetPageWidth