Saturday, May 22, 2010

Moving from compile time to run time locales

So you just woke up and realized that users are complaining about slow load times and you need to make you SWF file leaner ... where do you start?

Baby Step # 1: If you have localized your application then begin by removing all the properties files for the various locales and only loading what any given user needs at a given time.

The best place to start such an endeavor is at Adobe's live help: Using Flex 4 > Enhancing usability > Localization

The link above is helpful and critical in many ways but it doesn't talk about developers who use Maven and what they need to start doing differently in their build process before they start worrying about how to load the localized SWF files.

If you are using flexmojos then a part of your pom.xml probably looks something like:
        <configuration>
          ...
          <compiledLocales>
              <locale>en_US</locale>
              <locale>de_DE</locale>
          </compiledLocales>
          <mergeResourceBundle>true</mergeResourceBundle>
          <resourceBundlePath>${basedir}/src/main/flex/locale/{locale}</resourceBundlePath>
        </configuration>

Initially this may have seemed great as it did the job fast but now you need to change this to something like:
        <configuration>
          ...
          <runtimeLocales>
              <locale>en_US</locale>
              <locale>de_DE</locale>
          </runtimeLocales>
          <mergeResourceBundle>false</mergeResourceBundle>
          <resourceBundlePath>${basedir}/src/main/flex/locale/{locale}</resourceBundlePath>
        </configuration>
A few helpful flexmojos references can be found here and here.

Also in the project where you put together your war file, you need to get the runtime locales' SWF files over in addition to the application. So follow the instructions here, with a few more details listed here.

Now if you head over to the adobe site I mentioned earlier and follow their instructions, you may end up with:
Error: Unable to load resource module from ...
This happened when using:
var resourceModuleURL:String = "locales/myApp-version-locale.swf";
var resourceManager:IResourceManager = ResourceManager.getInstance();
var eventDispatcher:IEventDispatcher = resourceManager.loadResourceModule(resourceModuleURL);
Even though it may seem logical to try and reference the resource SWF as if it was a relative file and hope that Flex will fill in the blanks, that is not the case ... so a quick & dirty solution can be to use localhost like so:
var resourceModuleURL:String = "http://localhost:8080/myApp/locales/myApp-version-locale.swf";
var resourceManager:IResourceManager = ResourceManager.getInstance();
var eventDispatcher:IEventDispatcher = resourceManager.loadResourceModule(resourceModuleURL);
And you'll have a working solution ... mind you that I've left out some critical code that Adobe cover in their article which come after the few lines I've outlined here.

If it is unappealing to use localhost, you can look into configuring crossdomail.xml or refer to these great articles for an alternative approach:
1) Flex: Loading Remote Modules Throws the following: Error: Unable to load resource module from…
2) Loading a Remote Module into a Local App

Monday, May 17, 2010

Fluid Layout

Assumptions:
1) The browser represents the container.
2) The resizing of the container's width and height are primarily at the user's mercy and the components inside it must behave like water, which has no choice but to overflow if the beaker that it is being poured into is too small.

I've provided a sample that demonstrates what it would be like to have fluidity on the horizontal plane.

Click here to see the sample in action.

1) Try to resize your browser window and click on the collapsible and expandable right/left side buttons to see how the horizontal scroller behaves as the browser is resized.
2) If both the left and right side buttons stay expanded, then you will see the horizontal scrollbar appear after shrinking the browser window to be smaller than 500 pixels wide.
2) If both the left and right side buttons stay collapsed, then you will see the horizontal scrollbar appear after shrinking the browser window to be smaller than 300 pixels wide.

Here's the sample source.
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               xmlns:mx="library://ns.adobe.com/flex/mx">

    <fx:Script>
        <![CDATA[
            protected function expandCollapseHandler(event:Event):void
            {
                if (event.target.width>100) {
                    event.target.width=100;
                } else {
                    event.target.width=200;
                }
            }
        ]]>
    </fx:Script>

    <s:Scroller left="0" right="0" top="0" bottom="0">
        <s:Group width="100%" height="100%">
            <s:Group width="100%" height="100%">
                <s:Button id="buttonLeft"
                          minWidth="100"
                          width="200"
                          height="100%"
                          label="buttonLeft Height:{buttonLeft.height}, Width:{buttonLeft.width}"
                          click="expandCollapseHandler(event)"/>
                <s:Button id="buttonCenter"
                          left="{buttonLeft.width}"
                          right="{buttonRight.width}"
                          minWidth="100"
                          height="100%"
                          label="buttonCenter Height:{buttonCenter.height}, Width:{buttonCenter.width}" />
                <s:Button id="buttonRight"
                          right="0"
                          minWidth="100"
                          width="200"
                          height="100%"
                          label="buttonRight Height:{buttonRight.height}, Width:{buttonRight.width}"
                          click="expandCollapseHandler(event)"/>
            </s:Group>
        </s:Group>
    </s:Scroller>
</s:Application>
That's all folks.


Thursday, May 13, 2010

Adding SWFs to your blogger posts

1) Find a location to host your swf files.
For example, you can log into Google Sites and host your content by uploading the swf files as attachments:
http://sites.google.com/site/yourUsernameGoesHere/system/app/pages/admin/attachments

2) Make sure to upload the following swf files in addition to your main swf file.
2.1) spark_4.0.0.14159.swf
2.2) textLayout_1.0.0.595.swf
2.3) rpc_4.0.0.14159.swf
2.4) sparkskins_4.0.0.14159.swf
2.5) framework_4.0.0.14159.swf
2.6) playerProductInstall.swf
2.7) osmf_flex.4.0.0.13495.swf
These are dependencies without which you will face various errors when trying to run your main swf file.

a) Error: Error #2030: End of file was encountered.
at flash.net::URLStream/readBytes()
at flash.net::URLLoader/onComplete()

b) Error #2048: Security sandbox violation: http://...-s-sites.googlegroups.com/site/usernam/blah.swf?attachauth=...&attredirects=0 cannot load data from http://sites.google.com/site/username/some_missing_dependency.swf

3) Afterwards, get the link to your swf and to embed it in your blog as outlined here.

4) After embedding the SWFs in your blog, you may feel that your posts load significantly slower that they did before. You can remedy this situation and speed-up the load times if you place a div tag around your embedded tags and have the default mode set to be collapsed. This will give your readers the option of expanding the content and causing the flash player to load the swf files on demand.

Here's an example of all the code that I had to put into this post.
<style type="text/css">
  .commenthidden {display:none}
  .commentshown {display:inline}
</style>
<script type="text/Javascript">
  function togglecomments (postid)
  {
    var whichpost = document.getElementById(postid);
    if (whichpost.className=="commentshown")
    {
      whichpost.className="commenthidden";
    }
    else
    {
      whichpost.className="commentshown";
    }
  }
</script>

<a href="javascript:togglecomments('1')">Expand/Collapse Example</a>
<div class="commenthidden" id="1">

<embed src="http://sites.google.com/site/pulkitsinghal/Bounds1.swf" quality="high" bgcolor="#869ca7" width="650" height="100" name="Main" align="middle" play="true" loop="false" quality="high" allowScriptAccess="sameDomain" type="application/x-shockwave-flash" pluginspage="http://www.adobe.com/go/getflashplayer">
</embed>

</div>

To get the following to work as I described above: Expand/Collapse Example




Wednesday, May 12, 2010

Flex4: Transitions

Why transitions suck?
Because of their workflow:
1) the components are getting included or excluded from the state, also their state-based properties are put into effect immediately
2) the animation/transition is run
3) the state-based properties are put into effect once again

For example:
For a component that can go between the collapsed and expanded state:
a) the body suddenly shows up in the expanded state and then it disappears to go from a fade of 0 to 1 ... very clunky
b) OR, if we try to fix that by saying that alpha.expanded=0 so that this component will come into the expanded state with alpha of 0 and the starting point of the animation and the state-based property will be the same ... then the initial presentation of fade from 0 to 1 goes well but afterwards the state-based alpha of 0 takes effect once again the component becomes invisible .... so bad :(

Flex4: Nuances of a flexible UI (Part 1)

Given:
1) a s:Group with a width of 100
2) a s:Label inside it with left="10", right="10", textAlign="right"
I would hope that flex calculates the width for the label to be 80 and my text stays right aligned within those bounds with the freedom to grow vertically as I haven't set the Height or maxDisplayedLines properties. But that is not the case!
Expand/Collapse Example



Inspecting the properties at runtime via flexspy shows that the width has set to an obscene number like 10000 and if I change the text to be really really long, it simply overflows its boundaries from the right side! Which is a bit of a surprise because it goes against my intuition of having textAlign="right", which I had hoped, would at least make the overflow happen towards the left side. Even setting maxDisplayedLines="1" does not truncate the text because it thinks that it can grow up to a width of 10000 before it has to honor that property.

So apparently the right way to do this is to give up on the idea that width will be calculated based on the gap between left and right.

So if instead, for the s:Label, you specify:
a) right="10", textAlign="right" then you'll have text that can overflow towards the left side
Expand/Collapse Example



b) right="10", textAlign="right" and width="80" then you'll have text that will wrap and grow vertically as it gets longer.
Expand/Collapse Example



c) right="10", textAlign="right", width="80 and maxDisplayedLines="1" then you'll have text that will wrap and grow towards left until it gets too long and will then get truncated.
Expand/Collapse Example



I would say that this is really inflexible as I can never have text that grows within its left and right bounds and gets truncated only when it overflows the dynamically calculated width within those bounds... but oh well, such is life.


Monday, May 10, 2010

Flex4: Templatizing skins

With Flex4 the convention is to leave the appearance up to the skins but you may sometimes find that you are copy pasting the same skin multiple times with only minor tweaks in them. For example: different gradient colors, thicker/thinner borders etc. One of the most common scenarios is having a consistent button appearance across the app. If you follow Adobe's approach you will end up with multiple button skins containing very few changes. The proposed solution is to move commonly changed properties to a generic component class and then reference those properties in the skin.

Example: you have 2 types of buttons in you app - one with gradient fill and one with solid fill.
One way is to create GradientFillButtonSkin and SolidFillButtonSkin. Wouldn't it be nice to have only one skin and just fill it with what you wish to use - gradient or solid? You can!
<templatebutton>
    <fill><gradientcolor.../></fill>
</templatebutton>
To accomplish this we define a custom class TemplateButton:
TemplateButton extends Button {
...
[Bindable]
var buttonFill:IFill;
and in its respective skin class TemplateButtonSkin (which is probably a copy of spark ButtonSkin), you define a host component and a default fill:
[HostComponent("TemplateButton")]
<fx:declarations>
    <s:lineargradient id="defaultButtonFill">
    </s:lineargradient>
</fx:declarations>
...
<s:rect>
    <s:fill>
        {hostComponent.buttonFill!=null ? hostComponent.buttonFill : defaultButtonFill}
    </s:fill>
</s:rect>
Following this approach you can have with all types of fills: Solid, Linear, Radial, etc. and just use as following
<templatebutton skinclass="TemplateButtonSkin">
<fill>
    <solidfill.../>
</fill>
The same approach can be applied to strokes, (up, over, down states for fill and strokes), styles for text in the button and many others.

Saturday, May 8, 2010

Flex4: Show the browser viewport as busy

If you had to place a busy message or icon to indicate a loading state in your flex application, where would you place it? Naturally, centering it along the application's height and width sounds like a good idea. But an application may easily overflow a browser's viewing area at any given time based on how the browser is re-sized ... then what?

A better approach would be to center the modal busy message/icon indicator according to the height and width of the viewing area provided by the browser at any given time.

Theory:
This may be possible in Flex4 if the application is provided a skin which has a s:Scroller defined. The s:Scroller can be pooled for its viewport's x & y coordinates and the busy message can be centered based on those coordinates. This way, even if the user scrolls or resizes the browser ... the busy icon would stay centered and visible to the user.

Solution:
The example given here with its downloadable sample source code ... will pretty much solve the entire problem for you by showing you how to access the viewport properties. After that just take use x="viewport.height/2 - verticalScrollPosition" and y="viewport.width/2 - horizontalScrollPosition" and the task is done.

Pitfalls:
If you try to refactor the scroller code into a skin for the application, the viewport is considered as un-bindable by the compiler and the solution won't work any more. So my original "theory" wasn't so accurate but oh well, lesson learned.

Key Takeaways:
1) The s:Group class hierarchy:
Group extends GroupBase -> GroupBase implements IViewport
shows that a group can directly be handled as a viewport. Therefore, the group immediately inside the s:Scroller for the s:Application is the viewport that you want to deal with. Its easy to reference directly via its id as shown by the example code in the link above.
2) If you have to factor out the s:Scroller into a skin for the application then the group must be named with an id="contentGroup" or nothing will show up when you launch your application. Whereas within the application's main mxml file, you can name the group anything you like.

Monday, May 3, 2010

Flex4: Best Practices for working with states

Flex4 makes it very easy to write custom components:
1) The data & layout logic can be easily split between a Host Component (data-oriented) and a Skin (layout-oriented).
2) The Host Component can expose public Boolean flags to let the Skin indicate which state should be active.
3) Whenever invalidateSkinState() method is called (either by the system or explicitly by you), overriding the getCurrentSkinState() method based on the Boolean flags allows you complete control over the behaviour.

BUT ... just when we are about to become complacent, all kinds of bugs crawl out of the wood works and we are left wondering ... what happened? This is where the best-practices part comes in:
1) The number of Boolean flags should be equal to the number of states that you component has. (+/-)
[SkinState("normal")]
[SkinState("expanded")]
[SkinState("collapsed")]
[SkinState("disabled")]
...
public class CustomComponentView {
...
   private var _normal:Boolean;
   private var _expanded:Boolean;
   private var _collapsed:Boolean;
   private var _disabled:Boolean;
...
}
2) The Boolean flags themselves should be private or protected and you should expose Bindable public getters and setters for them. (+/-)
private var _normal:Boolean;
...
[Bindable]
public function set normal(value:Boolean):void {
  ...
}
public function get normal():Boolean {
   return _normal;
}
3) The setter methods should always be called with a value of true. (+/-)
There is no point in knowing a state that you don't want to be in, it is far better to know the state that you want to goto. If you wish to enforce this, then place an if statement around your code as follows:
[Bindable]
public function set normal(value:Boolean):void {
  if (value) {
     _normal = value;
     ...
  }
}
4) Each setter should toggle-off the Boolean values for all of the other flags. (+/-)
[Bindable]
public function set normal(value:Boolean):void {
  if (value) {
     _normal = value;
     _expanded = !value;
     _collapsed= !value;
     _disabled= !value;
  }
}


Sunday, May 2, 2010

Flex4: Show busy / loading state per component - going beyond the busy cursor

In Flex 4, the easiest way to indicate a busy or loading state for an application is to use the CursorManager's setBusyCursor() and removeBusyCursor() methods.

This is made even easier when all you need to do is set the showBusyCursor property of the SWFLoader, WebService, HttpService, and RemoteObject classes to automatically display the busy cursor.

But what if you need to go beyond that? What if there are multiple service calls that various components in your application need to make for their respective data sets? How can one go about clearly indicating which area of the application is ready for use and which one isn't?

Implementation:
1) BusyStateManager.as (+/-)
package com.xxx.util
{
   
    import flash.events.EventDispatcher;
    import flash.utils.Dictionary;
   
    import mx.collections.ArrayCollection;

    public class BusyStateManager extends EventDispatcher
    {
        /**
         * Keys for some commonly used components/areas
         */
        public static const ALL:String = "all";
        public static const HEADER:String = "header";
        public static const FOOTER:String = "footer";
        public static const DASHBOARD:String = "dashboard";

        /**
         * Use a string key to pull the component that
         * should be shown as busy. This really only
         * needs to happen IF a component wants to show
         * a busy state for a area bigger than itself.
         */
        private var availableComponents:Dictionary = new Dictionary(true);

        /**
         * Sometimes we may want to show a component/area
         * as busy but it might not have been added to stage
         * yet!
         * So the request will wait in this queue until:
         * a) either, the component is registered so that it
         *    may be marked as busy and removed from this queue
         * b) or, the busy state ends before the component is
         *    ever added to stage and thus it will be removed
         *    from the queue.
         */
        private var busyQueue:ArrayCollection = new ArrayCollection();

        [MessageHandler(selector="registerMyPersonalComponent")]
        public function registerComponent(event:BusyEvent):void {
            availableComponents[event.key] = event.component;
            // check if someone is waiting for this component to be marked as busy
            for each (var key:String in busyQueue) {
                if (key == event.key) {
                    //immediately mark the component/area as BUSY
                    showAsBusy(event.key);
                    //remove the key from the busy queue
                    busyQueue.removeItemAt(busyQueue.getItemIndex(key));
                    break;
                }
            }
        }

        public function showAsBusy(key:String):void {
            var value:Object = availableComponents[key];
            if (value != null) {
                //immediately mark the component/area as BUSY
                value.busy = true;
            } else {
                //add the key to the busy queue to be serviced later
                busyQueue.addItem(key);
            }
        }

        public function showAsReady(key:String):void {
            var value:Object = availableComponents[key];
            if (value != null) {
                //immediately mark the component/area as READY
                value.busy = false;
            } else {
                //remove the key from the busy queue
                busyQueue.removeItemAt(busyQueue.getItemIndex(key));
            }
        }
    }
}
2) Overlay for Skin (+/-)
<s:Group height="100%" width="100%" click="group1_clickHandler(event)" visible="{hostComponent.busy}">
        <s:Rect height="100%" width="100%" alpha="0">
            <s:fill>
                <s:SolidColor color="black" />
            </s:fill>
        </s:Rect>
        <ns:Image
            id="searchInProgressIcon"
            source="@Embed(source='/assets/icons/loading_spinner.swf')"
            verticalCenter="0" horizontalCenter="0"
        />
</s:Group>
3) Eat up the events in the skin (+/-)
    <fx:Script>
        <![CDATA[
            protected function group1_clickHandler(event:MouseEvent):void
            {
                event.stopImmediatePropagation();
            }
        ]]>
    </fx:Script>
4) Skin's Host Component should have the flag which toggles the overlay on & off (+/-)
[Bindable]
public var busy:Boolean = false;

How to use the code above:
1) Add a black rectangle with an alpha to indicate an overlay and the busy-spinner to the component’s skin class ... this is shown in the code.
2) Dispatch an Event from all the involved component’s view class to help them register themselves with unique names so that they may be marked as busy/ready as needed.
3) Have a wrapper around the server side action (for which the component should be busy).
4) In the wrapper class, call the BusyStateManager’s showAsBusy and showAsReady methods ... before and after making the service call. You can specifically ask for the busy or ready state to take effect on the desired components by using the name that you register them with.
5) Hopefully, you'll find it easy to plug it in and get it working for yourself as well ... good luck :)

Design:
1) Decide how you want to indicate the busy state to the user. (+/-)
1.1) You can add a s:Rect inside a s:Group at the end of each of your component skins
1.2) Assign the s:Rect an aplha (see-through) value of 0.25 to 0.75
1.3) Give the s:Group a click handler that will stop the propagation of any mouse events.
1.4) The users will feel as if there is a dark overlay or shield of sorts that is preventing them from interacting with the component when its busy.
1.5) You can always throw in some text like busy or loading and some sort of spinning graphic to polish it up a bit more.
2) Decide which service calls are made for individual components on the stage versus the ones that might provide data used by multiple components on the stage.
3) Also you may realize that it would be better to lock the entire application if certain service operations haven't finished, even if they are localized to only one of the components on the stage.
4) Often enough, the service calls might be made even before the components have been added to stage, for example when the application has just started. Even with a slow start, some components may still not have the data they need as their respective service calls might not have yet returned any results so they will need to be marked as busy whenever they are added to stage.
5) Certain service calls may need to mark the entire application or multiple components or individual ones as busy. Therefore, it is necessary for any and all components who participate in this process to register themselves with some sort of a busy state manager which can then toggle their busy state on & off for them.