Versions Compared

Key

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

...

For example, to have a text input field also include password and prompt features, you might use inheritance to create additional classes and wind up copying code between them. Another option is to add these properties to the text input field and overload it with features. Feature overloading makes it convenient for developers but most of the time only a basic set of features are ever used. This means applications can be very large (byte-wise) because every component has code and properties it rarely needs.

Gliffy Diagram
sizeL
nameStrands and Beads

FlexJS solves these problems by allowing developers to add just the features they need when they need them. Instead of every text input field having password and prompt capabilities, only a few text input controls have those features and the application remains lighter as a result.

A component in FlexJS consists of strands onto which beads are added. A strand is the component wrapper while a bead encapsulates a particular bit of functionality. In the example above, the password and prompt features are beads that can be added to any component having a view bead that implements the ITextFieldView interface; the actual text input control is provided by a view bead and the data (the text itself) is managed by a model bead. In this way, components in FlexJS embody the model-view-controller (MVC) paradigm.

Gliffy Diagram
sizeL
nameTextInput Beads

Beads can interact with each either through the strand, with events, or direct manipulation. For example, a bead that makes text red might listen for changes to the component's model and, when it detects a key word, changes the text in the model; the change to the model would get reflected in the view bead. Beads can add visual elements to components (view beads) or just change data or handle events.

...

Create a new ActionScript class called PasswordInputBead and have it implement the IBead interface. Beads do not normally have their own user interface or display; they normally use the strand. Beads can create new UI components, but then they make the strand the parent of those components.

Code Block

// actionscript
package org.apache.flex.html.staticControls.beads
{
...
    public class PasswordInputBead implements IBead

Do the same for JavaScript and create the PasswordInputBead class - JavaScript versions must have the same class path as their ActionScript counterparts.

Code Block

// javascript
goog.provide('org.apache.flex.html.staticControls.beads.PasswordInputBead');
...
org.apache.flex.html.staticControls.beads.PasswordInputBead = function()

...

Declare a private ivar called “_strand” and implement the setter function:

Code Block

// actionscript
public function set strand(value:IStrand):void
{
    _strand = value;
}
Code Block

// javascript
PasswordBead.prototype.set_strand = function(value)
{
    this.strand_ = value;
}

...

Add the following line to the strand setter function:

Code Block

//actionscript
IEventDispatcher(value).addEventListener(“viewChanged”,viewChangeChandler);

...

For ActionScript, create the function to handle the “viewChanged” event:

Code Block

// actionscript
private function viewChangedHandler(event:Event):void
{
    var textView:ITextFieldView = _strand.getBeadByType(ITextFieldView) as ITextFieldView;
    if (textView) { 
        var textField:CSSTextField = textView.textField; 
        textField.displayAsPassword = true; 
    }
}

...

For Javascript, add the following line to the set_strand function:

Code Block

// javascript
value.element.type = 'password';

so the set_strand function now reads:

Code Block

// javascript
PasswordBead.prototype.set_strand = function(value)
{
    this.strand_ = value;
    value.element.type = 'password';
}

...

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 for a bead that restricts input to be numeric and makes sure the text being entered is valid.

Code Block

// actionscript
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 viewChangeHandler(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) {
        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);
    }
}

...

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

// actionscript
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();
}

...

The JavaScript version of this bead works similarly to the JavaScript PasswordInputBead where you set things up in the strand setter and listen for the appropriate event, a keypress event in this case:

Code Block

// javascript strand setter
    value.addEventListener('keypress',goog.bind(this.validateInput, this));

Unlike ActionScript, JavaScript input elements (prior to HTML 5) do not have any way to restrict input characters, so you have to do that yourself, which is done in the validateInput() event handler:

Code Block

// javascript
  var code = event.charCode;
  
  // backspace or delete
  if (event.keyCode == 8 || event.keyCode == 46) return;
  
  // tab or return/enter
  if (event.keyCode == 9 || event.keyCode == 13) return;
  
  // left or right cursor arrow
  if (event.keyCode == 37 || event.keyCode == 39) return;
  
  var key = String.fromCharCode( code );

  var regex = /[0-9]|\./;
  if( !regex.test(key) ) {
    event.returnValue = false;
    if(event.preventDefault) event.preventDefault();
    return;
  }
  var cursorStart = event.target.selectionStart;
  var cursorEnd   = event.target.selectionEnd;
  var left = event.target.value.substring(0,cursorStart);
  var right = event.target.value.substr(cursorEnd);
  var complete = left + key + right;
  if (isNaN(complete)) {
    event.returnValue = false;
    if(event.preventDefault) event.preventDefault();
  }

...

Your bead must be compiled and place into the FlexJS SWC library. To do, find the FlexJSUIClasses.as file and add your bead. For example:

Code Block

import org.apache.flex.html.staticControls.beads.PasswordInputBead; PasswordInputBead;

To allow your bead to use the FlexJS namespace, it must be present in the FlexJS manifest file. Find the basic-manifest.xml file and add the bead, such as:

Code Block

<component id="PasswordInputBead" class="org.apache.flex.html.staticControls.beads.PasswordInputBead" />

...

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:TextInput>
	<basic:beads>
		<basic:PasswordInputBead />
	</basic:beads>
</basic:TextInput>

...

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

Code Block

<basic:TextInput>
	<basic:beads>
		<basic:PasswordInputBead />
		<based:TextPromptBead prompt=”password” />
	</basic:beads>
</basic:TextInput>

...

One thing you will not see in the Slider.as (strand) code is the application or naming of any specific beads. The beads for a component are identified by the style for the bead. If you look at the defaults.css file, you'll find the Slider has the following style defined:

Code Block

Slider
{
    IBeadModel: ClassReference("org.apache.flex.html.staticControls.beads.models.RangeModel");
    iBeadView:  ClassReference("org.apache.flex.html.staticControls.beads.SliderView");
    iBeadController: ClassReference("org.apache.flex.html.staticControls.beads.controllers.SliderMouseController");
    iThumbView: ClassReference("org.apache.flex.html.staticControls.beads.SliderThumbView");
    iTrackView: ClassReference("org.apache.flex.html.staticControls.beads.SliderTrackView");
}

...

If you open the SliderView.as file and look at the strand setter function, you can see how the track and thumb beads are identified and added.

Code Block

_track = new Button();
Button(_track).addBead(new (ValuesManager.valuesImpl.getValue(_strand, "iTrackView")) as IBead);

_thumb = new Button();
Button(_thumb).addBead(new (ValuesManager.valuesImpl.getValue(_strand, "iThumbView")) as IBead);

...

This is the typical pattern for getting the strand's model and setting up the listener. Note the use of interfaces instead of concrete classes.

Code Block

// actionscript
var model:IBeadModel = _strand.getBeadByType(IBeadModel) as IBeadModel;
IEventDispatcher(model).addEventListener("valueChanged",modelListener);

...

If you open Slider.js and look at the set_element() function, you'll see that it creates its base element (a <div>) and then creates the track and thumb beads (corresponding SliderTrackView and SliderThumbView JavaScript classes).

Code Block

// javascript
  this.element = document.createElement('div');
  this.element.style.width = '200px';
  this.element.style.height = '30px';

  this.track = new org.apache.flex.html.staticControls.beads.SliderTrackView();
  this.addBead(this.track);

  this.thumb = new org.apache.flex.html.staticControls.beads.SliderThumbView();
  this.addBead(this.thumb);

...

Once the track and thumb beads are created, the mouse controller bead is created and added to the strand.

Code Block

 this.controller = new org.apache.flex.html.staticControls.beads.controllers.
                    SliderMouseController();
  this.addBead(this.controller);

To complete a FlexJS JavaScript component, make sure to set or create the positioned member and the flexjs_wrapper. FlexJS relies on the positioner to be set and uses it to size and position the component.

Code Block

  this.positioner = this.element;
  this.element.flexjs_wrapper = this;

...

If you open SliderMouseController.js, you see that its set_strand() function gets the track and thumb beads from its strand, via getBeadByType() and adds event listeners to them.

Code Block

this.track = this.strand_.getBeadByType(
        org.apache.flex.html.staticControls.beads.SliderTrackView);
this.thumb = this.strand_.getBeadByType(
        org.apache.flex.html.staticControls.beads.SliderThumbView);

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

goog.events.listen(this.thumb.element, goog.events.EventType.MOUSEDOWN,
                     this.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 is the handleTrackClick function:

Code Block

var xloc = event.clientX;
var p = Math.min(1, xloc / parseInt(this.track.element.style.width, 10));
var n = p * (this.strand_.get_maximum() - this.strand_.get_minimum()) +
          this.strand_.get_minimum();
 
this.strand_.set_value(n);
 
this.origin = parseInt(this.thumb.element.style.left, 10);
this.position = parseInt(this.thumb.element.style.left, 10);
 
this.calcValFromMousePosition(event, true);
 
this.strand_.dispatchEvent(new org.apache.flex.events.Event('valueChanged'));

...

  1. Compile your Flex application using Falcon JX.
  2. Open the index.html file that was generated.
  3. Insert the following lines into the <head> portion of the file.

    Code Block
    
    <link rel="stylesheet" href="http://code.jquery.com/ui/1.10.2/themes/smoothness/jquery-ui.css" />
    <script src="http://code.jquery.com/jquery-1.9.1.js"></script>
    <script src="http://code.jquery.com/ui/1.10.2/jquery-ui.js"></script>
    

    In the FlexJS directories you will find a small set of components in the org/apache/flex/html/jquery folder. If you compare these to the standard components in the basic package, you will see there are few differences, the most obvious being the inclusion of jQuery function calls to modify the components.

Open the TextButton.js file and look for the createElement() function:

Code Block

org.apache.flex.jquery.staticControls.TextButton.prototype.createElement =
    function() {
  this.element = document.createElement('button');
  this.element.setAttribute('type', 'button');
  $(this.element).button();

  this.positioner = this.element;
  this.element.flexjs_wrapper = this;
};

...

  1. Compile your Flex application with the Falcon JX compiler.
  2. Open the JavaScript file that was generated from your main Flex application (e.g., if your Flex application's main file is Main.mxml, open Main.js).
  3. Insert the following lines before the goog.provide() statements:

    Code Block
    
    var mainjs = document.createElement('script');
    mainjs.src = './createjs/easeljs-0.6.0.min.js';
    document.head.appendChild(mainjs);
    
  4. Be sure to put a copy of the CreateJS component files in a sub-directory called, createjs, next to the other source directories.

If you open the TextButton.js file in the CreateJS package, you will find that the createElement() function is rather different from either the basic TextButton or jQuery version of the TextButton:

Code Block

org.apache.flex.createjs.staticControls.TextButton.prototype.createElement =
function(p) {

    this.buttonBackground = new createjs.Shape();
    this.buttonBackground.name = 'background';
    this.buttonBackground.graphics.beginFill('red').
      drawRoundRect(0, 0, 200, 60, 10);

    this.buttonLabel = new createjs.Text('button', 'bold 24px Arial',
      '#FFFFFF');
    this.buttonLabel.name = 'label';
    this.buttonLabel.textAlign = 'center';
    this.buttonLabel.textBaseline = 'middle';
    this.buttonLabel.x = 200 / 2;
    this.buttonLabel.y = 60 / 2;

    this.element = new createjs.Container();
    this.element.name = 'button';
    this.element.x = 50;
    this.element.y = 25;
    this.element.addChild(this.buttonBackground, this.buttonLabel);
    p.addChild(this.element);

    this.positioner = this.element;
    this.element.flexjs_wrapper = this;
};

...

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: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"
	>

Once you have declared the namespaces, you use the namespaces to identify the components:

Code Block

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

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

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

...

Take the DataGrid component as an example. This component is built from Container, ButtonBar, and List. There isn't anything Flash-specific about it nor does it need customization on the JavaScript side. All of the DataGrid beads (models, views, itemRenderer) are purely ActionScript.

Another good example is the BarChart. This component was developed in a project in its own package (org.apache.flex.charts). Everything but the bars in the chart translated to JavaScript easily. When it came time to draw the bars, a component was created in ActionScript, FilledRectangle, that used the Flash Shape class. This does not have a JavaScript counterpart, so a FilledRectangle.js class was hand-crafted in the JavaScript portion of the SDK (using the same package name, of course). This allowed the BarChart component classes to be cross-compiled to JavaScript. When run in a browser, the FilledRectangle.js used a <div> element for each bar vs. the FilledRectangle.as class that used a Shape.

There is nothing special about the process: create a Flex project the usual way and develop your component in ActionScript. Any framework elements you use will already have JavaScript equivalents. If you know you will need to refine the JavaScript result, then develop as much as possible in ActionScript and then do the cross-compile; the result will be well worth it as it will save you a lot of time making the JavaScript version.