-
Notifications
You must be signed in to change notification settings - Fork 32
FlExt AS
Ext JS uses config object literals to declaratively specify (nested) UI components and other Ext-specific objects like layouts, stores, or applications. In FlExt AS, you can still use this declarative notion, because ActionScript also supports object literals. However, in ActionScript, object literals are untyped, resulting in no compiler type checks and - worse - no IDE support for this most important part of UI programming.
In Flex, the declarative part is done in MXML, so Jangaroo uses MXML instead of Config object literals to specify (Ext) component trees. Section [MXML] describes the subset of MXML Jangaroo uses for that purpose in detail. Here, it is shown how Ext JS Config objects map to MXML (and vice versa).
To get a basic understanding of Ext JS Config objects, please consult the corresponding Sencha documentation page. In the following, a short summary is given that illustrates the concept, which may suffice to get the idea how Jangaroo MXML works.
A Config object describes which class to instantiate and which config options (properties) to set on the
instance. In particular, certain config options can contain one config object or an array of config objects.
For example, every Container
has an items
property that describes all nested components through an array
of config objects.
For components, the class to instantiate is specified by the property xtype
. There is a mapping from xtype
identifier to the fully-qualified class name to use.
{
xtype: "viewport",
items: [{
xtype: "panel",
title: "Hello World!",
tbar: {
xtype: "toolbar",
items: [{
xtype: "button",
text: "Click me!",
handler: function onClick(button) {
...
}
}]
}
}]
}
In this example, a viewport
is specified (mapping to class Ext.container.Viewport
), containing a panel
(Ext.panel.Panel
) with a toolbar
with one button
using the text "Click me!"
and a handler function that
reacts on button clicks (left out here for brevity).
In Ext JS code examples, you'll often see config objects without any xtype
. This is possible because all
container classes can define a default xtype
for their items
, and also other properties assume a default
type that makes sense. However, for readability and clarity, it is recommended to always specify the xtype
.
To create the actual (component) instance from a config object, there are several options.
A lot of Ext JS methods that take an instance also accept a config object, for example the method
Ext.container.Container#add()
. Consult the Ext JS API documentation to find out about these methods and their
exact behavior).
The most general way is to hand over the config object to the utility method Ext.create(cfg)
.
The component tree from the previous example looks quite similar when specified in Jangaroo MXML:
<Viewport xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns="exml:ext.config">
<items>
<Panel title="Hello World!">
<tbar>
<Toolbar>
<items>
<Button text="Click me!"
handler="function onClick(button) {
...
}"/>
</items>
</Toolbar>
</tbar>
</Panel>
</items>
</Viewport>
Note that
- the
xtype
property value is converted to upper case (more precisely CamelCase) and used as the MXML node name, and - config properties are either turned to MXML attributes (like
title
,text
andhandler
) or to MXML elements nested inside the object element (likeitems
andtbar
).
Using CamelCase for MXML elements denoting objects is best practice because it allows to easily distinguish them from property elements.
To transfer Ext JS mapping xtype
values to classes, Jangaroo uses the Flex feature "component library".
Jangaroo Ext AS defines ActionScript type wrappers for all Ext JS classes and a component library for all
components with an xtype
and all layouts and other classes with an alias
under the namespace exml:ext.config
.
Actually, in Ext JS, xtype: "foo"
is just an abbreviation for alias: "widget.foo"
, so all components use the
alias prefix widget.
. Besides components, there are a few other types of classes that also use Ext's config
system and declare an alias, using other alias prefixes like layout.
or data.
. Instead of introducing separate
component libraries for each prefix, Jangaroo Ext AS chose to prefix the local names of all aliases that do not
start with widget.
, so the Ext alias data.stringfield
becomes the MXML element name data_StringField
.
In Jangaroo's GitHub repository ext-as
, you find the
full mapping from local name to fully-qualified class name in Ext AS.
In Ext JS, an alternative to using xtype
or alias
shortcuts is to use the fully-qualified name of a class and put
it in the attribute xclass
. This is not a bad choice, since for inheritance (extend
) and require
, you need to
use (and thus know) the fully-qualified class names, anyway. And for your own classes, you can even refrain from
declaring an xtype
.
Using this technique, the previous example could be written like so:
{
xclass: "Ext.container.Viewport",
items: [{
xclass: "Ext.panel.Panel",
title: "Hello World!",
tbar: {
xclass: "Ext.toolbar.Toolbar",
items: [{
xclass: "Ext.button.Button",
text: "Click me!",
handler: function onClick(button) {
...
}
}]
}
}]
}
The MXML equivalent is to not use the Ext AS component library, but instead declare namespaces for ActionScript packages and combine them with the class names:
<container:Viewport xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:container="ext.container.*"
xmlns:panel="ext.panel.*"
xmlns:toolbar="ext.toolbar.*"
xmlns:button="ext.button.*">
<container:items>
<panel:Panel title="Hello World!">
<panel:tbar>
<toolbar:Toolbar>
<toolbar:items>
<button:Button text="Click me!"
handler="function onClick(button) {
...
}"/>
</toolbar:items>
</toolbar:Toolbar>
</panel:tbar>
</panel:Panel>
</container:items>
</container:Viewport>
Of course, you can mix-and-match both techniques.
So far, all this is standard MXML as used by Flex. As said above, Jangaroo faces the challenge of using Ext JS components through Flex syntax, so this section discusses where this results in Ext-specific MXML constructs.
One main difference between Ext and Flex is how components are instantiated.
In Flex, components have a no-arg constructor and properties that can be assigned one by one. Then, they can be added to some container component.
In Ext JS, all config options to be set on the component are collected in a simple config object. Then, the component's constructor is called with this config object, performing the complete initialization.
The following code fragments illustrate both approaches.
In "real" Flex, the ActionScript code generated from the MXML fragment from last section would look like so:
var button:Button = new Button();
button.title = "Click me!";
button.handler = ...;
var toolbar:Toolbar = new Toolbar();
toolbar.addChild(button);
var panel:Panel = new Panel();
panel.title = "Hello World!";
panel.tbar = toolbar;
var viewport:Viewport = new Viewport();
viewport.addChild(panel);
In Ext JS, one would use nested config objects and hand them in to Ext.create()
:
Ext.create({
xclass: "Ext.container.Viewport",
items: [{
xclass: "Ext.panel.Panel",
title: "Hello World!",
tbar: {
xclass: "Ext.toolbar.Toolbar",
items: [{
xclass: "Ext.button.Button",
text: "Click me!",
handler: function onClick(button) {
...
}
}]
}
}]
});
The subtle difference is the order in which nested components get instantiated. While Flex instantiates a sub-component, configures it, and then adds it to its container, Ext JS config objects are converted to instances starting at the container and continuing down to sub-components. The problem is that while theoretically, Ext JS components could be instantiated like Flex components from inner to outer, in practice, that does not work for some classes (some containers hand down a back-reference to their sub-components), and even if it did, such code would be very inefficient in Ext JS, since updating a property after initial creation is more expensive.
The consequence for Jangaroo is that is tries to compile MXML to Ext config objects, allowing for lazy instantiation of the corresponding objects. To know when to use config objects and when to instantiate objects immediately, Jangaroo uses custom annotations in the Ext AS API. You should keep this in mind when you observe an unexpected instantiation order.
The most obvious and annoying difference resulting from the different approaches to instantiate objects with properties is that Ext AS and Flex constructors look different. They have a different signature, resulting in a different order of calling super and assigning properties.
In Ext AS:
public function MySubClass(config:Object) {
config.color = "red";
super(config);
}
In Flex:
public function MySubClass() {
super();
this.color = "red";
}
You see that in Flex, there is no such thing as a config
constructor parameter. This becomes a problem
when you want to use MXML to compute properties based on other config properties, like in this example:
public function MySubClass(config:Object) {
config.fullName = config.firstName + " " + config.surname;
super(config);
}
In Flex MXML, this could be expressed by using the object's own properties in a binding expression:
<ac:MyClass xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:ac="com.acme.*">
<fx:Declarations>
<fx:String id="fullName">{firstName + " " + surname}</fx:String>
</fx:Declarations>
</ac:MyClass>
The semantic difference is that Flex binding expressions are not evaluated once, but every time the observable ("bindable") source properties change. Constructor code is obviously evaluated only once.
To represent Ext JS config objects in ActionScript in a type-safe way, Jangaroo takes the approach to reuse
the type of the resulting instance for config objects. In Jangaroo 2 (EXML), config API has been declared
separately from instance API, resulting in an additional class for each Ext class that takes a config
parameter. Jangaroo 2 was tailored for Ext JS 3.4, where config API and instance API were similar, but
impossible to describe with one ActionScript class.
Things have changed with Ext JS 6, where the config system takes care of any config options being accessible
on the instance, too. Such config options are called bindable
in the Ext documentation, because the
instance accessors allow to change them any time after creation.
Unfortunately, Ext's config system does not utilize JavaScript 5's accessor functions, but uses classic
get/set methods. For example, declaring a config option foo
would implicitly declare two methods
getFoo
and setFoo
that have to be used to query and update the property after creation (especially
after rendering).
ActionScript 3 supported property accessor functions right from the start (when JavaScript 5 was not yet
implemented by all major browsers, especially IE lagged behind). Thus, in ActionScript, it feeld much
more natural to use accessors than to use get/set methods. The most important advantage is that with
accessors, reading and writing a property uses the name syntax, so an expression like myObj.foo
can
be used as-is on the left-hand side and on the right-hand side of an assignment. This again eases (two-way)
property binding. Also, you as an application developer no longer have to use different syntax for
initial configuration (Ext JS: config objects) and later modification (Ext JS: set-methods) of a component.
To give you full control in Ext AS over when to create a config object in contrast to directly instantiating the
target class, Jangaroo allows a special kind of type cast.
Type-casting primitive Object
s into a type that is annotated with [ExtConfig]
(including superclasses)
is allowed and results in an xclass
attribute with the fully-qualified target class name being added to
the object.
import ext.button.Button;
var buttonConfig:Button = Button({});
// buttonConfig now is { xclass: "ext.button.Button" }
var button:Button = Ext.create(buttonConfig);
The advantage is that you can now assign properties in a type-safe way and with the same syntax, no matter whether the object is a config or an instance:
import ext.button.Button;
var buttonConfig:Button = Button({});
buttonConfig.text = "Click me!";
// buttonConfig now is { xclass: "ext.button.Button" }
var button:Button = Ext.create(buttonConfig);
button.addListener("click", function():void {
button.text = "Thank you!";
// Jangaroo takes care of the setText() method being called!
});
As you can see in this example, the button property text
is assigned twice: First, on the config object
that is used to create the button instance. Then, in the click event callback, the same property is set
on the button instance. In Ext JavaScript, you would have to use button.setText("Thank you!")
here.
Also, tying to set buttonConfig.foo = "FOO!"
would result in a compile error, since ext.button.Button
does not declare a property foo
.
However, note that since Ext AS uses the same type for config object an instance, using buttonConfig.click()
would not result in a compile error. When config properties are accessed, the compiler does not try
to determine whether a variable points to a config object or an instance, but defers this decision to a
run-time utility function (AS3.getBindable()
, AS3.setBindable()
). Thus, trying to invoke methods on
config objects result in run-time errors, not compile errors.
To access the constructor parameter in Jangaroo MXML, since Flex does not support constructor parameters,
you have to declare a private field config
and a "native" constructor signature like so:
<fx:Script>
private var config:MyClass;
public native function MyClass(config:MyClass = null);
</fx:Script>
where MyClass
is the name of the MXML class being declared. Remember, the config type is always the same as
the instance type.
The constructor is declared native
(and thus has no body) because the actual constructor code is generated
from the declarative part of your MXML file.