Thursday, November 25, 2010

YouTube Videos in Flex

Here is an example of how to get YouTube videos to be shown inside your Flex Application.

The first thing to do is add an SWFLoader to your mxml file. The source property of the SWFLoader gets set to the url of the video that you want to show inside Flex.
But you can't just use any YouTube url like this one:
http://www.youtube.com/watch?v=owGykVbfgUE
Instead you use a url in this form:
http://www.youtube.com/v/VIDEO_ID?version=3

The id of the video you want to play (e.g. owGykVbfgUE) must replace the VIDEO_ID portion of the url. This url actually redirects to an SWF file somewhere.

The version=3 parameter is very important. This tells YouTube to load the ActionScript 3.0 version of the SWF file. Without this parameter it defaults to the AS2 version which doesn't not play nicely with AS3. I found that I could watch one video, but after that no other videos could be played (I found this blog post that explains why if you're interested). I also found this YouTube reference which is where I learned about using the version=3 parameter.

There are a few other parameters that you can use like:
  • autoplay=1 - starts the video immediately without the user having to press play
  • fs=1 - allows full screen
  • rel=1 - shows related content after the video has finished playing
For a full list of the parameters go here

And finally, when you want to play another video, make sure you call the swfLoader.unloadAndStop() function otherwise you can get multiple videos playing at the same time, very confusing.

Keep in mind that when you run this locally you will see a lot of SecurityExceptions in your console, this is expected and normal.

And one last thing, some videos you will not be able to play inside Flex due to copyright restrictions. The owners of youtube videos must be able to specify whether the video can be played from a different website using an embedded player.

Here is the example in action (right click to view-source):

Wednesday, October 27, 2010

Displaying Html text in Labels

Displaying html content inside Flex labels is not as straight forward as it was in Flex 3 by simply setting the htmlText property. Here are the 3 Spark controls used for displaying text:
  • Label - lightweight plain text only
  • RichText - as the name says it provides support for rich text.
    Also allows html content to be imported (anchor tags <a> don't work)
  • RichEditableText - provides the same rich text support as the RichText control.
    Allows editing and anchor html tags work properly
Here is a list of the supported HTML tags: <div>, <p>, <a>, <span>, <img>, <br>.

As noted above, if you want to use anchor <a> tags, you must use a RichEditableText control. In this case you'd most likely want to also set the editable="false" property.
The anchor href property can be set to a relative path like
<a href="index.html">index</a>
Or an absolute one like
<a href="http://google.com" target="_blank">google</a>
Note that you can set the target property to control whether the link is opened in a new window or not.
If you want to listen for when the user clicks on an anchor it is possible, but involves a lot more work. Basically you import the html string into a TextFlow object. Then you iterate through all the child elements until you find the LinkElement (which represents the anchor tag), and then add a FlowElementMouseEvent.CLICK event listener.

Here is some sample code that I use to achieve this:
/**
 * Converts the html string (from the resources) into a TextFlow object
 * using the TextConverter class. Then it iterates through all the 
 * elements in the TextFlow until it finds a LinkElement, and adds a 
 * FlowElementMouseEvent.CLICK event handler to that Link Element.
 */
public static function addLinkClickHandler(html:String, 
        linkClickedHandler:Function):TextFlow {
  var textFlow:TextFlow = TextConverter.importToFlow(html, 
    TextConverter.TEXT_FIELD_HTML_FORMAT);
  var link:LinkElement = findLinkElement(textFlow);
  if (link != null) {
    link.addEventListener(FlowElementMouseEvent.CLICK, 
          linkClickedHandler, false0true);
  else {
    trace("Warning - couldn't find link tag in: " + html);
  }
  return textFlow;
}

/**
 * Finds the first LinkElement recursively and returns it.
 */
private static function findLinkElement(group:FlowGroupElement):LinkElement {
  var childGroups:Array = [];
  // First check all the child elements of the current group,
  // Also save any children that are FlowGroupElement
  for (var i:int = 0; i < group.numChildren; i++) {
    var element:FlowElement = group.getChildAt(i);
    if (element is LinkElement) {
      return (element as LinkElement);
    else if (element is FlowGroupElement) {
      childGroups.push(element);
    }
  }
  // Recursively check the child FlowGroupElements now
  for (i = 0; i < childGroups.length; i++) {
    var childGroup:FlowGroupElement = childGroups[i];
    var link:LinkElement = findLinkElement(childGroup);
    if (link != null) {
      return link;
    }
  }
  return null;
}

A simple control that I've created and use frequently is called HtmlLabel which extends the RichEditableText class to provide simple support for html text:
<?xml version="1.0" encoding="utf-8"?>
<s:RichEditableText xmlns:fx="http://ns.adobe.com/mxml/2009"
  xmlns:s="library://ns.adobe.com/flex/spark"
  xmlns:mx="library://ns.adobe.com/flex/mx"
  focusEnabled="false"
  selectable="false"
  editable="false">

  <fx:Script>
    <![CDATA[
      import flashx.textLayout.conversion.TextConverter;

      override public function set text(value:String):void {
        super.textFlow = TextConverter.importToFlow(value, 
          TextConverter.TEXT_FIELD_HTML_FORMAT);
      }
    ]]>
  </fx:Script>

</s:RichEditableText>

Spell Checking

Here is a short example of how to get the Adobe Squiggly spell checking tool installed. Squiggly can be downloaded here.

Squiggly files:
  • AdobeSpellingConfig.xml - put this file in your project's src folder. It defines the locations of the dictionaries and which language(s) to use.
  • en_US.aff, en_US.dic - these are the dictionary files. You'll need different ones for different languages of course. By default they go in the src/dictionaries/en_US folder.
  • AdobeSpellingEngine.swc - the main engine library - required, goes in project lib folder.
  • AdobeSpellingUI.swc - the ui library for Flex 3/MX components (e.g. <mx:TextArea>)
  • AdobeSpellingUIEx.swc - the ui library for Flex 4/Spark components (e.g. <s:TextArea>)
** Only use one of AdobeSpellingUI.swc or AdobeSpellingUIEx.swc.

To integrate Spell Checking into a Spark TextArea (id="textArea"), all you have to do is add one line:

SpellUI.enableSpelling(textArea, "en_US");

I did notice one problem with using Squiggly with Spark controls. The default Cut/Copy/Paste/Select All context menu items were not available anymore. I found a workaround for this by getting the contextMenu (which gets set on the RichEditableText control, not the TextArea) and setting clipboardMenu = true. See the example below for more details (right click to View Source).

Squiggly is still in prerelease, so a few bugs are expected. There is a forum here for discussions on Squiggly, and to report problems.

Thursday, September 9, 2010

Flex 4 Tree Lines

I've updated my previous blog post on Tree Lines to include a Flex 4/Spark version which uses the MXTreeItemRenderer class.

For more documentation on how to use it see the blog post linked above.


I've also updated the AdvancedDataGrid example to show tree lines in the tree:

Friday, August 27, 2010

Detecting browser height

Here is an example of one way to find out the height of the browser window from inside Flex. It is very easy to find this out if your have your Flex application set to height="100%". But if you use a fixed height like height="300" then stage.stageHeight and app.height both return 300.

This example uses the ExternalInterface to determine the height of the browser by calling the eval javascript function (it is a built-in function).

E.g.
var browserHeight:Number = ExternalInterface.call("eval", "window.innerHeight");

Different browsers (NS, FF, Chrome, IE, Safari) obviously don't all support this call. So far the window.innerHeight property works on all of them except IE. For IE you can use this one (IE 7 and above I think?):
var browserHeight:Number = ExternalInterface.call("eval", "document.documentElement.clientHeight");

And if your browser is older you can try this one:
var browserHeight:Number = ExternalInterface.call("eval", "document.getElementsByTagName('body')[0].clientHeight");

Browsers will also handle errors differently, so if you try to use one of those JavaScript functions in one browser you might get undefined return, and in another browser you might get an error dialog box.

Here is an example of this in action, it opens in a new browser window to show it properly.
If it was embedded on this page using an <iframe> tag then the size returned is that of the iframe, not the browser.

- Browser Height Example -
Click this image to open the real application


Obviously if you were interested in the browser width then you could do exactly the same thing but replace innerHeight with innerWidth and clientHeight with clientWidth.

Comments welcome.