Friday, August 7, 2009

Counting Words, StringWordValidator

I was recently required to limit the number of words in a TextArea. It's easy to limit the number of characters by using the TextArea.maxChars property, but how do you restrict the number of words?

The way I solved this problem was to make a new class that extends mx.validators.StringValidator and adds the following properties:
  • maxWords - the maximum number of words allowed
  • minWords - the minimum number of words allowed
  • tooManyWordsError - the error string to be displayed when too many words are entered
  • tooFewWordsError - the error string when not enough words are entered
  • trim - trims the input string before the validation occurs

Here is the utility function I wrote for counting how many words are in a string:
    /**
     * Splits the text into words and returns the number of words found.
     * If the text is null or blank (trimmed) then 0 is returned.
     * The regular expression used is /\s+/g which splits the text into
     * words based on whitespace.
     */
    public static function countWords(txt:String):uint {
        var count:uint = 0;
        if (txt != null) {
            txt = StringUtil.trim(txt);
            if (txt.length > 0) {
                count = txt.split(/\s+/g).length;
            }
        }
        return count;
    }

And here is it in action (view source). The validator will run when the TextArea loses focus (that is the default behavior), so try typing in one word and then pressing tab. You can also adjust the minimum and maximum number of words allowed and the validation will be updated immediately.

  • StringWordValidator source
  • ErrorTipManager source (see previous blog post on Always showing error tips)

  • I also included the ResizableTextArea class too just for fun.

    Thursday, August 6, 2009

    Always showing error tips (validators)


    Many people have complained about how Flex 3's error validation messages are not shown unless you hover your mouse over the input field that is invalid. See the image on the right and you'll probably know what I'm talking about. If not, then when you use a mx.validators.Validator on an input field such as a TextInput or TextArea and the user enters invalid data then the input control gets a red border indicating an error. But the error message (e.g. "This field is required") is ONLY displayed when the user hovers the mouse over the input field.

    After reading a few other posts on the subject, including a bug report that is in the Adobe Bug Tracker I decided to see if I could come up with a nice easy solution.

    I made a new class called ErrorTipManager that has a bunch of public static functions that lets you easily attach your validators (or your own validation functions). The code is documented, so view the sample below and right click view source to get the source code. The error tips should stay in the correct position even when the input moves (from a window resize or layout).



    A lot of credit goes to Aral Balkin's blog entry on Better form validation in Flex. I also got help from this blog on how to show error tooltips.

    Note that if you use the ErrorTipManager as described above and you set the validator.enabled = false; then the error tip will still be displayed. The validator does not fire an event when the enabled value changes, so it isn't possible to automatically update the visibility of the error tip. Instead you can do it manually by calling ErrorTipManager.hideErrorTip(validator.source, true). See the source code of the example above - I've added in a CheckBox that controls the second validator's enabled state.

    August 26th, 2009 update
    Fixed a problem where the error tip wasn't being position properly.

    November 2nd, 2009 update
    Added two new functions to ErrorTipManager for handling error tips in popup windows:
    • registerValidatorOnPopUp(validator, popUp, hideExistingErrorTips) - registers the validator and adds move and resize listeners on the popUp. It can also hide any existing error tips (this is a good idea because the existing error tips appear on top of the popUp window which doesn't look good).
    • unregisterPopUpValidators(popUp, validateExistingErrorTips) - unregisters all the validators associated with the popUp and removes the move and resize listeners that were added to the popUp. It can also validate all the remaining validators which will cause the error tips to re-appear now that the popUp has closed.

    March 24th, 2010 update
    Fixed a problem where the error tip wasn't being positioned properly when the parent container was scrolled. It will also hide the error tip if the source component gets scrolled out of the view. I also now move the error tip in front of other error tips when it gets positioned.

    July 14th, 2010 update
    Added two more event listeners in the ErrorTipManager to listen for when the input is hidden (visible="false") and when it gets removed from its parent (input.parent.removeChild(input)).
    This still does not solve the problem where the error tip remains visible when the parent is hidden or removed (e.g. if the input is inside a TabNavigator and the tab changes). In this case I'd suggest manually hiding or removing the error tip. Listen for the appropriate event like "change" and then call ErrorTipManager.hideErrorTip(input).

    March 2011 update
    I've posted an alternate approach to displaying validation error messages that doesn't involve the rather hacky approach above. You can view the blog post here.

    Wednesday, July 29, 2009

    DataGridColumn header tooltips

    I really thought that something as simple as putting a tooltip on a DataGridColumn header would be easy to do in Flex.

    If you want to make your own headerRenderer, then it is obviously very simple to just set the toolTip property in your renderer. But I was curious if it could easily be done with out a custom header renderer. My solution was to create a new class which extended DataGridColumn and adds three new properties: headerToolTip, headerToolTipPosition, and headerTextTruncate (which adds the "..." if the headerText is too long to fit).

    Here is the simplified version of the MXML code:
    <mx:DataGrid x="10" y="10" width="500" height="130" dataProvider="{employees}">
      <mx:columns>
        <ui:DataGridToolTipColumn headerText="Phone" dataField="phone" width="90"
          headerToolTip="Phone Number (tooltip above)" headerToolTipPosition="above"/>
      </mx:columns>
    </mx:DataGrid>

    The headerToolTipPosition property can have the following values:
  • above - positions the tooltip above the header
  • below - positions the tooltip below the header
  • left - positions the tooltip to the left of the header
  • right - positions the tooltip to the right of the header
  • inline - positions the tooltip on top of the header
  • default - positions the tooltip slightly below and to the right of the header (this is the default value if headerToolTipPosition isn't specified.

  • And here is the example (right-click View Source for the full source code):

    Feel free to use this code or modify it to your own needs.

    ** Updated on September 16th 2009
  • Fixed it so that setting headerWordWrap="true" now works as expected.
  • When the headerToolTip isn't defined then no tooltip is shown.
  • Added a new property: headerTextTruncate which will truncate the headerText and add the ellipsis ("...") if the headerText doesn't fit inside the renderer. Note that this property doesn't work if the headerWordWrap property is set to true.


  • Tuesday, June 30, 2009

    Loading Remote SWFs and parameters

    Here is another example of one SWF loading another SWF and how parameters can be passed through the url.

    The main application (called Loader1) has a panel which displays information about the current application, parameters, url etc.

    The main application loads a remote application SWF (called Loader2) which also has the same panel displaying information about the application, parameters, url, etc. The differences are highlighted in bold.

    Here is the code for loading a remote swf - notice how parameters are passed into the url:
    private function loadRemoteSWF():void {
        var remoteSwfUrl:String = 
            "http://keg.cs.uvic.ca/flexdevtips/loaders/Loader2.swf?loaderurl=hello";
        var loader:SWFLoader = new SWFLoader();
        loader.addEventListener(Event.COMPLETE, remoteSWFLoaded);
        loader.load(remoteSwfUrl);
    }

    private function remoteSWFLoaded(event:Event):void {
        var loader:SWFLoader = event.currentTarget as SWFLoader;
        ui.addChild(loader.content); // ui is a UIComponent
    }

    In the remote application you can access the parameters that were passed in from the main application through the SWFLoader by the parameters property, but you MUST be inside the remote application MXML (e.g. in this case in Loader2.mxml), otherwise if you use Application.application.parameters this will refer to the parameters from the main application and not the ones that were passed in using the SWFLoader.


    Source Code:

    Monday, June 22, 2009

    Default stylesheet in an SWC (Flex Library Project), and embedded font rotation

    For ages now I've been trying to figure out how I can use a StyleSheet from inside my Flex Library Project in ActionScript. I kept reading that it is very resource intensive to be calling UIComponent.setStyle(...) at runtime, so I wanted to set all my styles using a StyleSheet. The LiveDocs on the subject seem to say that there are two ways to load a StyleSheet:
    1. From inside MXML in your Application using the <Style source="assets/styles.css"/> tag
    2. By loading an external stylesheet SWF file (compiled from a CSS file) using the
      StyleManager. loadStyleDeclarations(url)

    Then I finally found the solution in this article (right near the bottom of the page in the comment by Henk). And that pointed me to this LiveDoc - Applying styles to your custom component which solved the problem. Scroll most of the way down until you get to the part called "Applying styles from a defaults.css file".

    If you don't want to read the articles above, here is my brief summary.

    Using a StyleSheet in your FlexLibrary Project:
    1. create a StyleSheet in the src directory of your project, and call it defaults.css
    2. open the project Properties > Flex Library Build Path and check the src/defaults.css file under the Assets tab
    3. define your styles inside the defaults.css StyleSheet
    Restrictions:
    • You can include only a single style sheet in the SWC file
    • The file must be named defaults.css
    • The file must be in the top-most directory of the SWC file
    *Note: it appears that having a defaults.css file in your Flex Application project (swf) also works without having to specify the stylesheet in the mxml (e.g. <mx:Style source="defaults.css"/> is not needed).


    Using Embedded Fonts inside Flex Library Project/SWC:
    If you want to use an embedded font inside your library project you have two ways of doing this:
    1. Embed the font inside an ActionScript file like this (the font is inside the project src/assets directory):
      [Embed(source='/assets/verdana.ttf'fontName='localVerdana'
             mimeType='application/x-font')]
      public var verdanaFont:Class;
      ...
      setStyle("fontFamily""localVerdana");
    2. Or put your embedded ttf font in your project's src directory and embed the fond using the defaults.css file (the font must be in the src directory for this to work - see this Adobe Bug for more details):
      defaults.css:
      @font-face {
        font-family: localVerdana;
        font-weight: normal;
        src: url("verdana.ttf");
      }
      .label {
        /* must use embedded font for label rotation to work */
        font-family: localVerdana;
        /* must specifically set the font weight */
        font-weight: normal;
      }
    Note that if you want to set the DisplayObject.rotation property then you have to use an embedded font otherwise it doesn't work.

    Here is a simple example of font/label rotation (right click "View Source" for the code).
    In this example I actually included three fonts - Verdana plain, Verdana bold, and Comic Sans.

    * Note that fonts can greatly increase the size of your SWF. The three fonts that I used above are all about 150 KB each. One way to reduce the size of your SWF is to restrict the unicode character range (meaning that only part of the font set is included in your SWF). Here is the LiveDoc on Using Fonts and Setting Character Ranges.

    Ben Stucki's blog helped me figure out how to embed a bold and plain font in CSS.