Sunday, August 10, 2008

Possibly Useful Links

These are links either recommended on the flexcoders mailing list, on other blogs, or that I have stumbled upon somewhere on the web. I am not vouching for these. This is primarily a list for my own reference. Obviously, if it is useful to somebody else, that is great.

Proxies

Charles
Service Capture
Fiddler2

Diagramming Tools

Enterprise Architect: Drag a folder in, and it creates the class diagram for the code in it.

Remote Desktop

Flash VNC

Flex & AIR Users Groups

Flex Online
Flex Los Angeles
AIR Los Angeles

Jobs

Flexgigs

Flex Issues Possibly Worth Voting for

Preserve Metadata

Monday, July 28, 2008

addChild Component Instantiation In Table Form

In reading through the documentation on creating an advanced ActionScript component, I realized that it would be easier for me to understand if I had a more visual presentation of what happens when the addChild() method is called. So I have turned the "About the component instantiation lifecycle," section of "Creating and Extending Adobe Flex 3 Components" document (p. 134 of the PDF), into the table below.

Within the table, time goes from top to bottom. Also I have use the following color coding to try to make things a little clearer:

  • Dispatch Event

  • Invalidation Method used to tell Flex framework to call a Render Method

  • Render Method called by Flex Framework, not by components. These are the methods normally overriden when creating a custom ActionScript component, especially one that extends UIComponent.


StatusFlexContainerComponent
ActionScript: myContainer.addChild(myComponent);
parent = myContainer
style = {computed by Flex}
dispatch preinitialize event
createChildren()
invalidateProperties()
invalidateSize()
invalidateDisplayList()
all of the component's children have been initialized, but it has not been sized or processed for layout.
dispatch initialize event
dispatch childAdd event
dispatch initialize event
Start next render event
commitProperties()
measure() unless the user set component.height and component.width
layoutChrome() if component is a Container
updateDisplayList()
dispatch updateComplete event
dispatch additional render events if render methods called invalidation methods
last render event finishes
component is sized and processed for layout
visible = true
dispatch creationComplete event
dispatch updateComplete event

Note that the updateComplete event is dispatched whenever the layout, position, size, or other visual characteristic of the component changes and the component is updated for display. Thus it may fire a lot. For example, it will fire at least twice as part of adding a child component to a container.

Wednesday, July 23, 2008

Making MXML Subcomponent Private

Recently someone asked on the flexcoders mailing list how to make a subcomponent private when working in MXML. The short answer is that this cannot be done since MXML does not support access control settings: i.e. anything declared in MXML is public, and cannot be changed to private, protected or internal. However, things are not that simple.

Let's assume that we have the following setup:

MyComponent.mxml extends VBox (or any other container).
MyComponent has a TextInput MXML tag within it.
MyApplication.mxml has a MyComponent MXML tag within it.

Our goal is to have the TextInput component treated as a private property of MyComponent: i.e. it should be accessible within MyComponent, but not from MyApplication.

Solution I: Omit Id Attribute

The simplest way to achieve this would be to not have an id attribute for the TextInput control within MyComponent. If the TextInput control does not have an id, there will be no way for MyApplication to access it. (For purposes of this discussion, we are not worried about the fact that the TextInput control will be accessible as a child of MyComponent in the display list.)

The obvious drawback of this approach is that the TextInput control will also be inaccessible within MyComponent: i.e. it will be impossible to bind to properties of the TextInput control, or to use script to set or get any of the TextInput control's properties. In cases where that limitation is acceptable, omitting an id tag is an easy way to achieve privacy.

Solution II: Coding Convention

A second way to achieve rough equivalency with privacy is through coding conventions. For example, one could decide as an organization that any property that began with an underscore would be respected as private. So if in MyComponent we declared our TextInput like this
<mx:TextInput id="_myText" />

It would be considered inappropriate within MyApplication to have code such as the following:
myComponent._myText.text

Of course, within MyComponent, it would be considered appropriate to have code such as the following:
_myText.text

or
this._myText.text

With this approach, it is now possible within MyComponent to bind to the properties of _myText, as well as to get and set its properties. Although this approach has the obvious disadvantage that the compiler will not enforce it, it could go a long way toward achieving the desired end.

Solution III: Proxy

A more complicated, but more powerful solution is to use a proxy variable. In this approach, the TextInput control does not have an id attribute. Instead, it triggers an event which calls a function that assigns a reference to the TextInput control to a private variable. Within MyComponent, our code might look like this:

<mx:Script>
<![CDATA[
[Bindable]
private var textProxy:TextInput;

private function subDone(event:Event):void {
textProxy = event.target as TextInput;
}
]]>
</mx:Script>
<mx:TextInput creationComplete="subDone(event);" />

Within MyComponent, one can bind to properties of the TextInput control, as well as set or get its properties, by using the textProxy variable. For example, we could add a text control which will mirror what is typed in the TextInput control by adding the following code:
<mx:Text text="{textProxy.text}" />

In essence, we have assigned an id property to the TextInput control, but one that is private. Thus, within MyApplication, there is no way to access the TextInput control since it has no id, and since textProxy is private.

Solution IV: the [Exclude] Metadata Tag

See the blog post at http://blog.ashier.com/2008/03/25/hiding-properties-in-flex-components/, including my comment on the limits of this approach.

Tuesday, July 22, 2008

An ActionScript Class for Converting Between HTML and Plain Text

I've written an HtmlConverter class which allows ActionScript to convert between HTML and plain text, in both directions, without using regular expressions.

The main functionality is provided by the TextInput component since it automatically converts plain text to HTML, and vice versa. The tricky part is that this conversion only happens when the TextInput control is on the stage. (After looking at the code for the TextInput control, my sense is that the conversion is done by the flash player, not by the control.) In order to minimize the impact of using the control for the conversion, the HtmlConverter object adds a TextInput control to the stage, waits for it to perform the conversion, and then immediately removes it from the stage.

Since we have to wait for the TextInput control to be fully placed on the stage, and to perform the conversion, the result of the conversion will not be available until at least one frame after the conversion is requested. This means that interactions with the HtmlConverter object have to be asynchronous: i.e. they have to use events rather than return values from functions.

Here is the file HtmlConverter.as:

package com.craftyspace.utilities {
import flash.events.EventDispatcher;

[Event(name="textConverted", type="flash.events.Event")]
public class HtmlConverter extends EventDispatcher {
import flash.events.Event;
import mx.events.FlexEvent;
import mx.controls.TextInput;
import mx.core.Application;
import mx.binding.utils.ChangeWatcher;

public static const TEXT_CONVERTED:String = "textConverted";

[Bindable(event="htmlTextChanged")]
public function get htmlText():String {
return this.component.htmlText;
}

[Bindable(event="textChanged")]
public function get text():String {
return this.component.text;
}

private var component:TextInput = new TextInput();

public function HtmlConverter() {
super(null);
}

public function toHtml(plain:String):void {
this.component.text = plain;
this.component.addEventListener(FlexEvent.UPDATE_COMPLETE, onComplete);
Application.application.addChild(this.component);
}

public function toPlain(HTML:String):void {
this.component.htmlText = HTML;
this.component.addEventListener(FlexEvent.UPDATE_COMPLETE, onComplete);
Application.application.addChild(this.component);
}

private function onComplete(event:FlexEvent):void {
component.removeEventListener(FlexEvent.UPDATE_COMPLETE, onComplete);
Application.application.removeChild(this.component);
this.dispatchEvent(new Event("htmlTextChanged"));
this.dispatchEvent(new Event("textChanged"));
this.dispatchEvent(new Event("textConverted"));
}

}
}

Within ActionScript, one would use the class's TEXT_CONVERTED event to get the results, like this:

private function init():void {
var converter:HtmlConverter = new HtmlConverter();
converter.addEventListener(HtmlConverter.TEXT_CONVERTED, onTextConverted);
converter.toPlain('<p><b>my</b> text</p>');
}

private function onTextConverted(event:Event):void {
trace((event.target as HtmlConverter).text);
trace((event.target as HtmlConverter).htmlText);
}

Alternatively, instead of handling the HtmlConverter.TEXT_CONVERTED event, one can bind to either the text or htmlText property of an HtmlConverter object:

<mx:Script>
<![CDATA[
import com.craftyspace.utilities.HtmlConverter;

[Bindable]
private var converter:HtmlConverter;

private function init():void {
converter = new HtmlConverter();
converter.toPlain('<p><b>my</b> text</p>');
}
]]>
</mx:Script>
<mx:Text text="{'html: ' + converter.htmlText}" />
<mx:Text text="{'plain: ' + converter.text}" />

Note that the above code stores the HtmlConverter object in a persistent variable that is declared bindable. If one does not do this, the MXML controls will not be able to bind to the HtmlConverter's properties.

One thing that cannot be done with an HtmlConverter object is to declare it as an MXML tag. This is because the HtmlConverter class is not a descendent of the UIComponent class.

Caveats

Although it was interesting going through the process of writing this class, I suspect that most of the time it would make more sense to use regular expressions.

Putting a TextInput control on the stage for a couple of frames each time it does a conversion has two costs. It requires the flash player to re-render twice: once to display the TextInput control, and once to remove it from the display. It may be the case that this could be mitigated by setting styles on the control, but I have not explored that.

The second cost is that the conversion must be dealt with asynchronously using events. Needless to say, this is significantly less convenient than a situation where one can do something like the following:

myPlaintext:String = myRegexBasedConverter.toPlain(myHtmlText);

and

myHtmlText = myRegexBasedConverter.toHtml(myPlaintext);

Of course, if someone wants not just any HTML, but the HTML which would be rendered by the flash player, then this class might be useful.

Tuesday, July 15, 2008

Storing Dates in the AIR SQLite Database

One thing that took me awhile to figure out is how to use dates with the SQLite database. It turns out that Adobe modified SQLite so it now supports a DATE data type. This is actually documented, but is a little bit buried. So far as I can tell, it is not in any of the PDF's that provide the standard documentation for flex and air, but only appears in an appendix to the language reference. For complete details, see the "SQL support in local databases" appendix of the ActionScript 3 Language and Components Reference.

In a nutshell, there is a DATE datatype which corresponds to the Date ActionScript class. You can directly insert a date ActionScript object into a database field of the DATE type. Similarly, when you select data from a DATE field of the database, it will be returned as an ActionScript date object. Finally, because the data will come out of the database as a date object, you can then upload it to a remote database, using AMF, and it should correctly map to the date data type on the remote database. I've only done this with SQL Server, but it worked correctly.

Thursday, July 10, 2008

Avoid Spaces in PDF File Names in Adobe AIR on OS X

Another odd quirk of an Adobe AIR application running in OS X is that it will not display a PDF if there is a space in the file name.

I had a menu item which would pop up a new native window, and display a PDF in that window by setting the location of an HTML component to the PDF file. This would work fine on Windows, but the window would launch with a black screen on OS X if there was a space in the filename of the PDF.

Removing the space fixed the problem.

FlexNativeMenu and Mac OS X

I don't know whether this is an Apple standard, or just a quirk, but Adobe AIR menus need to be a little more complicated to work in OS X.

In Windows, one can have a menu which does not have any sub-items, but which simply works as if it were a button in the menu bar. For example, when my application ran in Windows, it would display the following top-level menus: Observations, Account, Schools, Reports, and Help. The Account menu did not contain any items, but clicking Account would load the account page.

However, in OS X, it would display Observa... and the rest of the menu bar would be blank. Further, clicking the Observations menu would not work.

After much trial and error, I discovered that OS X did not like the single-item menus.

To get around this, I had to give the Account menu a sub-item so that clicking that menu would open it, and one could then click on the only item in the menu. In other words, one would open the Account menu, and then click on the Profile item within that menu. Once I made that change, everything worked the same on both Windows and OS X.