Adobe MAX 2009 – Los Angeles

This year Adobe Max 2009 is in my own backyard, well sort of 6 miles up the street, which means about an hour away in LA traffic. I am hoping to get in for free some how, either through Adobe Max Awards or by posting the Adobe Max Widget. See everyone there, maybe…

Posted in AIR, Flex, Flex News | Tagged , , , , , | Leave a comment

AIR:: Adding Application Menu Bar Items

Just a quick how-to post on adding application menu bar items to your Adobe AIR application when running. Certain operating systems can support application menu bars, so this example will not be true for all systems. You can check this using “NativeApplication.supportsMenu == true”, which basically means you are on Mac OSX dude. If you were on a Windows or Linux box you would check for “NativeWindow.supportsMenu == true” and add the menu items to the NativeWindow rather than the NativeApplication.

As it turns out, I ran into just a small confusing snag that initially prevented the added application menu bar item from firing a select event. I eventually reached out to the Adobe AIR Tight discussion group and Jesse Warden had a quick fix. It turns out that if you do not use strong item references, they are swept away by Garbage Collection (GC) before you actually use the items. For example instead of creating the NativeMenuItem within the scope of the function, I had to create it within the scope of the class or in this case, the WindowedApplication.

In my application I only needed to add a single menu bar item. So instead of blowing away the entire application menu and then creating all my own menu items (as many of the examples show). I just wanted to add a single item to the exiting application menu bar. On Mac OS, you get 4 application menu items: YourAppName, File, Edit, and Window. YourAppName would be your applications name or if you are running in the Eclipse IDE, just simply “adl”. In my case I wanted to add a “Preferences” option under the YourAppName. You can do this by accessing the NativeMenuItem representing the YourAppName. Something like NativeApplication.nativeApplication.menu.items[0] will return a NativeMenuItem with that reference. Using that reference you can then add a new NativeMenuItem to the submenu of the referenced NativeMenuItem. In addition to adding items to the submenu, you can also detect when the new item is selected.

In any event, here is the complete application code example:

Read More »

Posted in AIR, Flex 3 | Tagged , , , , , , | 4 Comments

AIR :: TextField bug when resetting HTML text.

I ran into a really strange issue that only occurs within Adobe AIR applications (AIR 1.5.1/1.5.2). When I create a TextField control, set it’s style, and then assign it HTML text it appears fine. However, if I wish to reset that TextField content and the original content has links, the entire replaced content becomes one HTML link. I think the issue can be better explained with some images and some code samples.

Issue Context

The first image is a TextField with one HTML link. The link fires a TextEvent which is handled by a function which replaces the text. The original TextField out put is in the image below withe the “more” HTML link at the end:

The second image shows the result when the “more” link is clicked and the text is replaced. You can see that when the mouse is over the text the entire text becomes a “more” link even though the new content has no HTML links. The rollover style on the TextField underlines the text.

Read More »

Posted in AIR, Bugs, Flex | Tagged , , , , , | Leave a comment

Truncating HTML Text

With Flex truncating text within a Label control is easily done by setting the property “truncateToFit” to true. This parameter doesn’t do much when using the Text or TextArea controls in Flex. To truncate text in these controls you would have to build your own function to count characters and add the ellipses. There is one good example of creating your own custom Text control that truncates the text in the same manner as the Label control based on size of the control.

Now if you want to truncate HTML text within a Text or TextArea control thinks become more complicated. Currently in Flex doing anything with HTML is pretty dismal as there are not many ways to control the display of HTML or capture user interaction with HTML text. This seems to be changing with the upcoming release of Flash Builder 4 and the new spark components for displaying text and also the new Text Layout Framework . In the mean time you still might like to truncate HTML text in Text and TextArea controls.

I did find one blog post describing one method for truncating HTML text based on the size of the control. However, I wanted more restriction of the length of my HTML displayed and also wanted a way to better handle links that use the TextEvent to fire events from links clicks. There seem to be plenty of examples of HTML truncation within JavaScript and Java. I am lucky to have a girlfriend who also develops Java (and is also good looking :D ). I asked her for an example of HTML truncation and then translated that code in to working Flex code, which was surprisingly easy to convert.

Below is an example application that will truncate HTML text to any character length given. The truncation will not occur mid-link tag, only the text inside the links is counted you have the option to put a “… more” link at the end of the truncation to expand the text to its full length. I also gave the HTML a little extra formatting to distinguish links from regular text and add some rollover behavior for HTML links (something also missing from Flex). If the text Some limitations include image tags, break tags, and wrapping the entire block of text into a tag (However there is an easy work around that you can see in source code). The reason these limitations exist is because I only needed to truncate text that contain links. So the example will have to be expanded to include other tags.

Example

Complete Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="588" height="393" applicationComplete="init()">
    <mx:Script>
        <![CDATA[
           
            import flash.net.navigateToURL;
           
            // string data to play with
            private var loremIpsum:String = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
                        "Curabitur ultrices orci non felis luctus non sollicitudin magna aliquet." +
                        " Curabitur lacinia dignissim <a href=\"event:link:http://www.google.com\">accumsan</a>. " +
                        "Mauris ac dui in enim tristique egestas eu ac arcu. " +
                        "Quisque scelerisque, odio et luctus tempus, felis ante euismod felis, quis laoreet quam " +
                        "turpis in diam <a href=\"event:link:http://www.google.com\">http://www.google.com</a>. " +
                        "Integer ut lectus id justo feugiat posuere. Vestibulum tempor porttitor " +
                        "justo, sed consequat dui lobortis ut. <a href=\"event:link:http://www.google.com\">Aliquam</a> " +
                        "a posuere diam. Vestibulum turpis purus, " +
                        "dapibus id sagittis nec, volutpat ultricies leo. <a href=\"event:link:http://www.google.com\">Pellentesque</a> tempus pulvinar ornare. " +
                        "Cras vel sapien vitae mauris tincidunt <a href=\"event:link:http://www.google.com\">http://www.google.com</a>" +
                        " laoreet quis congue nisl. Aliquam pharetra nunc " +
                        "quis tortor adipiscing quis <a href=\"event:link:http://www.google.com\">elementum</a> nulla venenatis. " +
                        "Suspendisse potenti. Mauris " +
                        "vitae enim sed nisl viverra venenatis <a href=\"event:link:http://www.google.com\">http://www.google.com</a>. " +
                        "Proin auctor <a href=\"event:link:http://www.google.com\">mattis</a> mollis. Phasellus ultrices " +
                        "ornare ullamcorper. Sed <a href=\"event:link:http://www.google.com\">turpis</a> quam, tempus eget luctus eget, posuere vitae lorem. " +
                        "In vitae sem id lorem aliquam <a href=\"event:link:http://www.google.com\">viverra</a>."
       
            private function init():void
            {
                this.destinationText.styleSheet = this.getTextStyle();
                this.destinationText.addEventListener(TextEvent.LINK, handleLinkClick);
                this.destinationText.htmlText = "<span class=\"body\">" + loremIpsum + "</span>";
                this.sourceText.addEventListener(TextEvent.LINK, handleLinkClick);
                this.sourceText.styleSheet = this.getTextStyle();
                this.sourceText.htmlText = "<span class=\"body\">" + loremIpsum + "</span>";
                return;
            }
           
            private function truncateText():void
            {
                var limit:Number = limitStepper.value;
                this.destinationText.htmlText = "<span class=\"body\">" + this.truncateHTMLText(loremIpsum, limit, true) + "</span>";
                this.destinationText.styleSheet = this.getTextStyle();
                return;
            }
           
            /**
             * Handle the click action on links within the html text using TextEvent.
             * */

            private function handleLinkClick(event:TextEvent):void
            {
                var prefix:String = event.text;
                var split:Array = prefix.split(":");
                var type:String = split[0];
                var id:String = split[1];
               
                if(split.length > 2) id = split[1] + ":" + split[2]; // recreate the http link
               
                if(type == "more"){
                    this.destinationText.htmlText = "<span class=\"body\">"  + loremIpsum + "</span>"; // reset to original text length
                    this.destinationText.styleSheet = this.getTextStyle();
                    return;
                } else {
                    var request:URLRequest = new URLRequest(id);
                    navigateToURL(request,"_blank");
                }

                return;
            }
           
            /**
             * Truncate html text.
             * @param value The original text value
             * @param limit The maximum number of characters to show
             * @param ellipses  Boolean value to show elipses at the end of truncation.
             * */

            private function truncateHTMLText(value:String, limit:Number, ellipses:Boolean = true):String
            {
                if(limit == 0)
                    return "";
               
               var original:String = value;
               
               value = value.replace("[\\t\\n\\x0B\\f\\r\\u00A0]+", "");
               
                var isTag:Boolean = false;
                var count:int = 0;
                var position:int = 0;
                var limitLength:int = value.length - 1;
                var closeTag:Boolean = false;
               
                for(var i:int = 0; i < value.length; i++) {
                    var c:String = value.charAt(i);
                    if(isTag) {
                        if(c == '>') {
                            isTag = false;
                            if(closeTag || i == limitLength) {
                                position = i;
                                break;
                            }
                            continue;
                        } else {
                            continue;
                        }
                    } else {
                        if(c == '<') {
                            isTag = true;
                        } else {
                            count++;
                            if(i == limitLength || (count == limit)) {
                                if(((i+1) < limitLength) && ((i+2) < limitLength)) {
                                    if(value.charAt(i+1) == '<' && value.charAt(i+2) == '/') {
                                        closeTag = true;
                                        continue;
                                    }
                                }
                                position = i;
                                break;
                            }
                        }
                    }
                }
               
                var result:String = value.substring(0, position + 1);
                var last:String = result.charAt(result.length - 1);
                var length:int = result.length;
               
                var nextChar:String = (length >= value.length) ? ' ' : value.charAt(length);
               
                if(last != ' ' && last != '>' && nextChar != ' ' && nextChar != '<')
                    result = result.substring(0, result.lastIndexOf(' ') + 1);
                var lastStartTag:int = result.lastIndexOf('<');
               
                if(lastStartTag != -1) {
                    var ch:String = result.charAt(lastStartTag + 1);
                    if(ch != '/') {
                        result = result.substring(0, lastStartTag);
                    }
                }
               
                if(original.length == result.length)
                    return original;
   
                if(result.length == 0)
                    return result;
           
                var pattern:RegExp = new RegExp("(.*?)(\\s*\.\.\.\\s*)([\</[a-z]*?\>\\s*$]+)", "i");
               
                if(result.search(pattern) == -1 && ellipses)
                    result += "...";
   
                return (result +  "<a href=\"event:more\">[more]</a>");
            }
           
            /**
             * Let's add a little style to the HTML text and mouse roll-over actions.
             * */

            private function getTextStyle() : StyleSheet
            {
                var fonts:String = "Arial, _sans";
                var textStyle:StyleSheet = new StyleSheet();
                    textStyle.setStyle(".body", {fontFamily:fonts, fontSize:"12", fontWeight:"normal", color:"#222222", textDecoration:"none"});
                    textStyle.setStyle("a", {color:"#245290", textDecoration:"none"});
                    textStyle.setStyle("a:link", { textDecoration: "none", color: "#245290" });
                    textStyle.setStyle("a:hover", { textDecoration: "underline" });
               
                return textStyle;
            }
        ]]>
    </mx:Script>
   
    <mx:TextArea id="sourceText" x="10" y="36" width="280" height="316"/>
    <mx:TextArea id="destinationText" x="298" y="36" width="280" height="316"/>
    <mx:Label x="10" y="10" text="HTML Text"/>
    <mx:Label x="298" y="363" text="Char Limit"/>
    <mx:Label x="298" y="10" text="Truncated HTML Text"/>
    <mx:Button x="452" y="361" label="Truncate" click="truncateText()"/>
    <mx:NumericStepper id="limitStepper" x="368" y="361" stepSize="10" minimum="100" maximum="1000" value="200"/>
   
</mx:Application>



Download: TextTruncation Flex Project File

- Mister

Posted in Flex, Flex 3 | Tagged , , , , | 13 Comments

Using AIR 1.5.1 InovkeReason for friendlier applications

I recently discovered a new feature of AIR 1.5.1 that makes AIR applications more consumer friendly in certain situations. When you have your application set to start on user login using “NativeApplication.nativeApplication.startAtLogin = true”, you may want the application to start as a background process rather than show the entire application at login. In AIR 1.5.1 there is a new property of the InvokeEvent called “reason” that will report if the application was started at login or was started when the users invokes the application by launching the application.

By capturing this event and testing for InovokeReason.LOGIN, you can keep your application invisible and only show the windows docked in the system tray. If a users starts the application it will be InvokeReason.STANDARD and you can then show the application as normal. I find it annoying to have a ton of windows popping up on my screen when I restart my computer.

I usually set up an InvokeEvent when a users clicks on the system tray icon to activate the application and bring it to the forefront. I initially set WindowsApplicatoin visible=”false” so the application is invisible on start. If the the InvokeEventReason is “LOGIN”, I keep the application invisible, in other cases I activate the application and bring it to the forefront.

It should be noted that the InvokeEvent fires on application start up as well as from running the the application from the IDE (so you can’t test it fully unless you install the application and login again). Also, because of timing of events at start up, the application is invisible even when you set it to active, so it will blink if the InvokeEventReason is STANDARD. To avoid this behavior, I use the “windowComplete” event of the WindowedApplication to add the InvokeEvent listeners. So the code might look something like the following:

Read More »

Posted in AIR | Tagged , , | 6 Comments

Adding Mac OS X Close Behavior to AIR 1.5 Application

Just a quick post because I have seen this done on other sites but it no longer is valid in AIR 1.5. Most Mac OS X applications minimize to system tray on closing the application using the “x” close button on the app. To achieve this in AIR 1.5 you will need to do the following on creationComplete event of the main application:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// on application creation complete setup default Mac OS X behavior
private function init():void
{
    if (NativeApplication.supportsMenu) {  // we are on a Mac
         //this.nativeWindow.addEventListener(InvokeEvent.INVOKE, handleAppInvoke);
         NativeApplication.nativeApplication.addEventListener(InvokeEvent.INVOKE, handleAppInvoke);
         this.nativeWindow.addEventListener(Event.CLOSING, handleAppHide);
         NativeApplication.nativeApplication.addEventListener(Event.EXITING, handleAppExit);
         NativeApplication.nativeApplication.autoExit = false;
    } else {  // we are on a Windows machine
       this.nativeWindow.addEventListener(Event.CLOSING, handleAppClosing);
    NativeApplication.nativeApplication.autoExit = true;
    }
}

// active that application
private function handleAppInvoke(event:InvokeEvent):void
{
                this.nativeWindow.activate();
}

// close all open windows
private function handleAppClosing(event:Event):void
{  
       event.preventDefault();
    for (var i:int = NativeApplication.nativeApplication.openedWindows.length - 1; i >= 0; --i) {
        NativeWindow(NativeApplication.nativeApplication.openedWindows[i]).close();
    }
}

// hide the application instead of closing it
private function handleAppHide(event:Event):void
{
    if( this.nativeWindow.visible ) { // if the window is visible the event behaviour is canceld and the window is hidden
        event.preventDefault();
            this.nativeWindow.visible = false;
        }
}

// close all open windows, remove event listener for hiding application and close applicatin
private function handleAppExit(event:Event):void
{
    this.nativeWindow.removeEventListener(Event.CLOSING, handleAppHide);
               
    for (var i:int = NativeApplication.nativeApplication.openedWindows.length - 1; i >= 0; --i) {
        NativeWindow(NativeApplication.nativeApplication.openedWindows[i]).close();
    }
    this.close();
}

- Mister

Posted in AIR | Tagged , | 6 Comments

Garbage Collection and Event Listeners

On a recent AIR project I have become aware and subsequently obsessed with memory. My concerns have centered around garbage collection and event listeners, specifically how you clean up event listeners to allow Garbage Collection (GC) to clean up objects. Most DisplayObjects implement the EventDispatcher class that allows it to broadcast events. Trolling around the googleverse I found some interesting ways to insure that you break strong references to allow clean up or manually remove event listeners on your own to make your application more memory savvy.

When you use addEventListener() method and register a event listener to an object you create reference. By default any object that has a reference to a listener will keep that reference until it is removed using the removeEventListener() method. Strong references will not be cleaned up by Flash player GC and remain in memory until the application is closed or the world ends, whichever comes first. This makes for a horribly leaky AIR applications, especially those applications that may run for days on the user’s system without being restarted.

Use Additional Parameters of Listener

The addEventListener() method does have some additional (and seldom used) parameters to create a weak reference when adding the listener to an object. On of those parameters is “useWeakReference”, the 5th parameter of the method. To utilize this parameter you need to set it from false to true:

1
2
3
4
5
6
7
8
9
10
addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false);
.....

var button:Button = new Button()
button.addEventListener(MouseEvent.click, handleMouseClick, false, 0, true);

private function handleMouseClick(event:MouseEvent):void
{
 // handle the event
}

Read More »

Posted in AIR, Flash, Flex, Flex 3 | Tagged , , , , | 3 Comments

AIR :: NativeMenu issue on Mac

I think the NativeMenu is a great option for AIR applications as you can get a consistent look for the application that matches the operating system. The other advantage is that NativeMenu’s are “outside” of the application and do not get clipped as they would if you just created a popup Canvas with a List control. However, I could not get the native menu to appear as a popup in my tests. In fact, it would often result in crashing the AIR application when i did NativeMenu.display();.

If I call a function that creates a NativeMenu from a button click everything works great. If I call the same function to create the menu from an event (like receiving data from a service call), the menu is not created and the application crashes upon subsequent attempts to create the menu. In addition, trying to create a NativeMenu popup on “createComplete” of the application also causes a crash.

So let’s say you have an application that you want to show a NativeMenu as a popup on startup of the application so you run the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
< ?xml version="1.0" encoding="utf-8"?>
<mx:windowedapplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="init()"  width="540" height="388" >
   
    <mx:style source="styles.css"/>
   
    <mx:script>
        < ![CDATA[
            private function init():void
            {
                var nativeMenu:NativeMenu = new NativeMenu();
                var menuItem1:NativeMenuItem = new NativeMenuItem("Menu Item 1");
                var menuItem2:NativeMenuItem = new NativeMenuItem("Menu Item 2");
                nativeMenu.addItem(menuItem1);
                nativeMenu.addItem(menuItem2);
               
                NativeApplication.nativeApplication.menu.display(this.stage, 100, 100);
               
            }
        ]]>
    </mx:script>
   
   
</mx:windowedapplication>

Read More »

Posted in AIR, Flex | Tagged , , , , | 1 Comment

Placing a ContextMenu on TextArea in AIR 1.5

The TextArea in Flex comes with its own contextual menu, so adding one does absolutely nothing but leave you wondering what the heck you are doing wrong. In the bug report there is a bit of a work around for AIR 1.5 which requires you to access the “textField” property of the TextArea and add your contextMenu to that item. Apparently there are not enough people using custom contextMenu’s to justify fixing the bug. The problem gets a bit trickier as the textField is protected in Flex Builder 3, so you can’t just do:

1
myTextArea.textfield.contextMenu = myMenu;

You need to expose or get at the textField to add the menu. You can do this by using mx_internal (careful, kid, or you’ll put your eye out with that thing!). So you can throw a little hack together to get your context menu on your text area:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private function createMenu():void
{
     var mainMenu:ContextMenu = new ContextMenu();
   
     var menuitem:ContextMenuItem = new ContextMenuItem("hello mom!");
     menuitem.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, handleContextMenuEvent);
     mainMenu.addItem(menuitem);

     textInput.contextMenu = mainMenu;

     var txt:TextField = textInput.mx_internal::getTextField() as TextField;
     txt.contextMenu = mainMenu;
}

private function handleContextMenuEvent(event:ContextMenuEvent):void
{
    var menuitem:ContextMenuItem = event.target as ContextMenuItem;
    trace("context menu event: " + menuitem);
}

<mx:textarea id="textInput"  width="100%" height="100%">

Read More »

Posted in AIR, Cairngorm | Tagged , , , , , , , , | 1 Comment

Dude, where’s my phone (GPS)

I thought I had lost my phone for the past day, I looked every where and called it a few times, no luck. So I remembered I had the InstaMapper GPS software installed. You can send an SMS to the phone with your InstaMapper key to activate the GPS and have it report its location. Worked perfectly, apparently the phone is at my house some place hanging out without me, nice. I think its pretty cool that you can activate applications this way with the G1 phone.

Since iPhones don’t allow applications to run in the background, it wouldn’t have been possible, just another lost phone ringing with nobody to hear its plea to be found. I recommend anyone with a G1 to use InstaMapper. I also created an AIR application that tracks your device with Google Maps.

-Mr

Posted in Flash Remoting | 2 Comments


  • Thanksmister on Twitter



Who's Behind Thanks, Mister!?

I am a professional software developer specializing in creating Rich Internet Applications with Adobe Flex/Flash and desktop applications using Adobe AIR runtime. I provide consulting and contract development work for various companies using Flex, Flash and Adobe AIR runtime.

This blog is my contribution to the community where I share tips, techniques, and development strategies about Adobe Flex and AIR. If you are looking for a Flex or Flash Developer for contract or consulting, please contact me to check my availability: michael.ritchie {a t} gmail

Read more at my about page