Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

The FlexJS framework is designed to work well with ActionScript and JavaScript. The philosophy is that if it works in ActionScript, it should work in JavaScript, too, and vice-versa. You create a component (or bead) in ActionScript, designating platform-specific functionality or elements, using the COMPILE::AS3 SWF and COMPILE::JS compiler directives. See FlexJS Component Source Code Patterns for details about this development pattern for FlexJS.

You want to make things as efficient as possible in both environments. For instance, the TextInput component in ActionScript is comprised of view and model beads. The JavaScript TextInput has neither since HTML has a view and model already (via the HTMLElement); the FlexJS TextInput component is just a thin wrapper for the HTML control. As you begin creating your component, take the time to understand the best way to represent it so the component is as optimal as possible for both SWF and JS execution, take the time to understand the best way to represent it so the component is as optimal as possible for both SWF and JS execution.

Bead Life Cycle

There are three ways a bead can be used with a component:

  1. Place an instance of the bead in-line in an MXML file using the component's <js:beads> property.
  2. Reference a bead's class in a CSS style for the component.
  3. Create the bead in ActionScript code (using new operator) and place it onto the component's strand using the addBead() function.

When a component is added to the display list via the addElement() function of its parent, the FlexJS framework invokes a function on the component called, addedToParent().

  1. The addedToParent() function will look at the <js:beads> list and add those beads to the strand. In doing so, each bead's strand setter (see below) is called. 
  2. Once the <js:beads> are handled, addedToParent() then sees if there are model, view, and controller beads that need to be added to the strand. Using the model bead as an example, addedToParent() checks to see if the model is already on the strand (it may have been in the <js:beads> list). If not, then the style for the component is checked to see if iBeadModel was specified and if it is, a new model is created and added to the strand. This sequence applies to the view and controller beads as well.
  3. Finally, addedToParent() dispatches the "beadsAdded" event on the component.

By first processing the <js:beads>, the FlexJS framework allows a developer to replace default beads set up in CSS. If you write your own components and have custom beads, follow this pattern:

  1. Override addedToParent() and call super.addedToParent() immediately. This will perform the three steps above. It will also dispatch "beadsAdded".
  2. Check to see if your custom bead is already on the strand. 
  3. If the bead is not on the strand, create a default bead either by looking in CSS to see if a class has been specified or just create one using the new operator. Then add it to the strand.
  4. Dispatch a custom event or dispatch "initComplete" (if your component is not subclassing a container class which will dispatch "initComplete" for you) to signal that all beads are now present.

Some tips are where to perform certain tasks are given in the topics below.

Creating a Bead

The first example shows how to make the password bead. You will see how to compose the bead and how to add it to an existing component's strand.

...

The implementation of the bead differs between JavaScript and SWF. In the browser, the underlying component is an input element and all that this bead needs to do is set its type to be "password". For the SWF platform, it is more complicated with events being intercepted and the text being changed to obscure the password. The strand setter function has these additional lines to complete itlines to complete it.

The strand setter is a good place to initialize values and to set up event listeners, such as "viewChanged", "beadsAdded", or "initComplete" (or a custom event dispatched by your component).

Code Block
COMPILE::AS3SWF {
    IEventDispatcher(value).addEventListener("viewChangedbeadsAdded",viewChangeHandlerbeadsAddedHandler);
}
COMPILE::JS {
    var host:UIBase = value as UIBase;
    var e:HTMLInputElement = host.element as HTMLInputElement;
	e.type = 'password'; 
}

The compile directives will have the input element set up for password input with just a few lines when they are cross-compiled into JavaScript. For ActionScript, an event listener is set up to listen for changes to the view (the text input field itself)when the standard beads (model, view, controller) have been added to the strand; this code is not needed in JavaScript and so it is isolated to ActionScript using the COMPILE::AS3 SWF directive.

Implement the Event Handler

For ActionScript, create the function to handle the “viewChanged” “beadsAddedEvent” event (note that the COMPILE::AS3 SWF directive is placed above the function definition so all of the function is included only in the ActionScript build).

Code Block
COMPILE::AS3SWF
private function viewChangedHandlerbeadsAddedHandler(event:Event):void
{
    var textView:ITextFieldView = _strand.getBeadByType(ITextFieldView) as ITextFieldView;
    if (textView) { 
        var textField:CSSTextField = textView.textField; 
        textField.displayAsPassword = true; 
    }
}

...

The PasswordInputBead is fairly simple: it just changes the type of input control using the built-in input control's property (displayAsPassword for ActionScript and type = 'password' for JavaScript). If you want the bead to do more, you can have the bead listen to events and take its own actions. Here is the valueChangedHandler beadsAddedHandler for a bead that restricts input to be numeric and makes sure the text being entered is valid.

Code Block
private var _decimalSeparator:String = ".";
public function get decimalSeparator():String
{
    return _decimalSeparator;
}
public function set decimalSeparator(value:String):void
{
    if (_decimalSeparator != value) {
        _decimalSeparator = value;
    }
}
private function viewChangeHandlerbeadsAddedHandler(event:Event):void
{			
    // get the ITextFieldView bead, which is required for this bead to work
    var textView:ITextFieldView = _strand.getBeadByType(ITextFieldView) as ITextFieldView;
    if (textView) {
        COMPILE::AS3SWF {
            var textField:CSSTextField = textView.textField;
            textField.restrict = "0-9" + decimalSeparator;

            // listen for changes to this textField and prevent non-numeric values, such
            // as 34.09.94
            textField.addEventListener(TextEvent.TEXT_INPUT, handleTextInput);
        }
        COMPILE::JS {
            var host:UIBase = _strand as UIBase;
            var e:HTMLInputElement = host.element as HTMLInputElement;
            e.addEventListener('keypress', validateInput);
        }
    }
}

...

To have the bead act as an input validator, the bead listens for the TEXT_INPUT event on the TextField using the handleTextInput() function:

Code Block
COMPILE::AS3SWF
private function handleTextInput(event:TextEvent):void
{
    var insert:String = event.text;
    var caretIndex:int = (event.target as CSSTextField).caretIndex;
    var current:String = (event.target as CSSTextField).text;
    var value:String = current.substring(0,caretIndex) + insert + current.substr(caretIndex);
    var n:Number = Number(value);
    if (isNaN(n)) event.preventDefault();
}

...

To make use of the bead, go to your application or initial view MXML file and create a TextInput and add the PasswordInputBead to it:

Code Block
<basic<js:TextInput>
	<basic<js:beads>
		<basic<js:PasswordInputBead />
	</basicjs:beads>
</basicjs:TextInput>

The FlexJS framework first adds any beads that are declared in MXML. After that, FlexJS adds beads for the component that are declared in a style sheet. FlexJS uses defaults.css as its style sheet where TextInputView is declared as the view bead for TextInput (more on this later). When the TextInputView bead is added, it triggers the “viewChanged” event which allows the PasswordInputBead to make its modifications.

To see how powerful adding beads can be, add another bead to this strand:

Code Block
<basic<js:TextInput>
	<basic<js:beads>
		<basic<js:PasswordInputBead />
		<based:TextPromptBead prompt=”password” />
	</basicjs:beads>
</basicjs:TextInput>

When the application is run, not only can you enter text as a password, a prompt will appear as long as the field is empty.

Of course, if you find that you frequently need a set of components, creating a composite, custom component that combines the beads needed is also possiblethe beads needed is also possible; add the beads in your custom component's addedToParent() override function.

Bead Guidelines

  • Most beads use their strand for their UI parent. Beads can make or use other components or visual parts, then they add them to their strand’s display list.
  • Try to use interfaces whenever possible. This keeps implementation code separate and lets modules work better.
  • Use the paradigm of separation of concerns as much as possible. That is, do not have beads do more than necessary and separate functions into multiple beads. Very often a component will have one bead for the visual display (a view bead), one bead to hold its data (a model bead), and one bead to handle user interactions (a control bead). This allows a developer to swap out beads as necessary. For example, a mobile developer might want to replace the Slider's mouse controller bead with a touch controller bead: the component's other parts (track, thumb, model, etc.) remain the same.

...

Once the pieces of the view are created, event listeners are set up so the view knows what's happening to the strand, especially the size and the value. Changes to the size cause the view to layout out its children. Changes to the value position the thumb over the track.

Be sure to add any sub-components to the strand's display list shortly after creating them. This will fire off those sub-components' life cycles and allow them to trigger their own compositions. This way, when it comes time for your view bead to size and position the sub-elements, they will have dimension. 

RangeModel (model bead)

If you open the RangeModel.as (or any of the other model files) you'll see they are a collection of property setters and getters (and their backing variables, of course). Every property has an event dispatched when it changes. Note that the event is dispatched from the model, not its strand. Beads that need to listen for changes to the model should fetch the strand's model and set up listeners for those properties it is interested in. For example, the Slider's mouse controller bead can update the model value which will be picked up the Slider's view bead and the thumb will change position.

...

If you open SliderMouseController.as, you see that its strand setter function gets the track and thumb beads from its strand, via getBeadByType() and adds event listeners to them. Notice that there are differences between the ActionScript and JavaScript platforms, so these are coded accordingly.

Code Block
COMPILE::AS3SWF {
    var sliderView:ISliderView = value.getBeadByType(ISliderView) as ISliderView;
    sliderView.thumb.addEventListener(MouseEvent.MOUSE_DOWN, thumbDownHandler);
    sliderView.track.addEventListener(MouseEvent.CLICK, trackClickHandler, false, 99999); 
}
COMPILE::JS {
    track = value.getBeadByType(
        org.apache.flex.html.staticControls.beads.SliderTrackView);
    thumb = value.getBeadByType(
        org.apache.flex.html.staticControls.beads.SliderThumbView);

    goog.events.listen(track.element, goog.events.EventType.CLICK,
                     handleTrackClick, false, this);

    goog.events.listen(thumb.element, goog.events.EventType.MOUSEDOWN,
                     handleThumbDown, false, this);

}

The purpose of the controller is to coordinate the input from the mouse with values from the model which are then reflected in the views. Here are the click handlers for the track:

Code Block
COMPILE::AS3SWF
private function trackClickHandler( event:MouseEvent ) : void
{
	event.stopImmediatePropagation();

   var sliderView:ISliderView = _strand.getBeadByType(ISliderView) as ISliderView;

	var xloc:Number = event.localX;
	var p:Number = xloc/UIBase(_strand).width;
	var n:Number = p*(rangeModel.maximum - rangeModel.minimum) + rangeModel.minimum;


	rangeModel.value = n;

	IEventDispatcher(_strand).dispatchEvent(new Event("valueChange"));
}
 
COMPILE::JS
private function handleTrackClick(event:BrowserEvent):void
{
    var host:Slider = _strand as Slider;
    var xloc = event.clientX;
    var p = Math.min(1, xloc / parseInt(track.element.style.width, 10));
    var n = p * (host.get_maximum() - host.get_minimum()) +
          host.get_minimum();
 
    host.value = n;
 
    origin = parseInt(thumb.element.style.left, 10);
    position = parseInt(thumb.element.style.left, 10);
 
    calcValFromMousePosition(event, true);
 
    host.dispatchEvent(new org.apache.flex.events.Event('valueChanged'));
}

...

If you open the TextButton.js file in the CreateJS package, you will find two definitions for the TextButton class. One in a COMPILE::AS3 block SWF block and another in a COMPILE::JS block. This was done because the two versions have enough differences that it would be awkward to mingle the compiler directive blocks; it was cleaner to define the classes separately.

...

Once you've created your jQuery or CreateJS (or other component library) beads and components, you can add them to your Flex application MXML using namespaces. This is the root tag for a version of MyInitialView.mxml:

Code Block
<basic<js:ViewBase xmlns:fx="http://ns.adobe.com/mxml/2009"
		xmlns:basic="library://ns.apache.org/flexjs/basic"
		xmlns:jquery="library://ns.apache.org/flexjs/jquery"
                xmlns:createjs="library://ns.apache.org/flexjs/createjs"
	>

...

Code Block
<!-- the basic tex button component -->
<basic<js:TextButton label="Basic" />

<!-- the jQuery text button component -->
<jquery:TextButton label="jQuery" />

<!-- the CreateJS text button component -->
<createjs:TextButton label="CreateJS" />

...

The ability to generate JavaScript code and components from ActionScript sources is built into the FlexJS development patterns. You guide the compilation process through the use of COMPILE::AS3 SWF (exclude from cross-compilation, SWF only) and COMPILE::JS (exclude from SWF, cross-compile to JavaScript) directives; any code not in these blocks is cross-compiled to JavaScript and winds up in the SWF. 

...