TextInput wmode opaque/tranpsarent workaround

by josefchutka on February 8, 2010

Many of you have come accross the horrendous bug that happens with text input in flash when the swf is embedded with wmode=transparent/opaque. It has been much discussed (Firefox Bugzilla entry for this bug, Adobe Forum discussion, etc.), but it seems that in all the years that this bug has existed nothing much has been done. A lot of workarounds have been published, many of them based on custom key mapping etc… My workaround uses html element <input type=”text”> that is placed right over flex <mx:TextInput>. Html element is styled transparent (no design) so user will not notice.

Following application uses wmode “opaque”, try inserting characters (čšň) into first text input (mx:TextInput) and than into second (uses html <input type=”text”> over flash)

Main application

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    applicationComplete="init()" viewSourceURL="srcview/index.html">
<mx:Script>
<![CDATA[
    import mx.managers.PopUpManager;
    private function init():void
    {
        var window:FakeInputsWindow = new FakeInputsWindow();
        PopUpManager.addPopUp(window, this);
        PopUpManager.centerPopUp(window);
    }
]]>
</mx:Script>
</mx:Application>

FakeInputsWindow

<?xml version="1.0" encoding="utf-8"?>
<mx:TitleWindow
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:html="sk.yoz.html.*"
    layout="vertical" width="400" height="250"
    creationComplete="init()"
    move="moveHandler()">
<mx:Script>
<![CDATA[
    [Bindable]
    private var text2:String = "";

    private function init():void
    {
        ExternalInterface.addCallback("fakeTextInputChange", fakeTextChange);
    }

    private function moveHandler():void
    {
        fakeTextInput.positionChanged = true;
        fakeTextInput.invalidateProperties();
    }

    private function fakeTextChange(value:String):void
    {
        text2 = value;
    }
]]>
</mx:Script>
<mx:Text text='&lt;mx:TextInput ....'/>
<mx:TextInput id="text1" width="300" height="20"/>
<mx:Label text="{text1.text}" />
<mx:Spacer height="10" />
<mx:Text text='&lt;input type="text" ....'/>
<mx:Canvas width="300" height="20">
    <mx:TextInput width="300" height="20"/>
    <html:IFrame width="100%" height="100%" id="fakeTextInput"
        autoResize="true"/>
</mx:Canvas>
<mx:Label text="{text2}" />
</mx:TitleWindow>

lines:

  • 15: javascript-to-flash callback definition fakeTextInputChange (from javascript) sends value into actionscript fakeTextChange()
  • 18: fakeTextInput is IFrame class, that controls position and size of html input, lets correct position on TitleWindow move…
  • 35-39: I placed IFrame over TextInput. IFrame is in fact transparent html input, so what user see is just flex TextInput graphics

Html template file

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
<head>
    <title>FakeTextInput</title>
    ...
    <script type="text/javascript" src="js/swfobject.js"></script>
    <script type="text/javascript" src="js/flexiframe.js"></script>
    <script type="text/javascript">
    <!--
        var flashvars = {}

        var params = {
            allowscriptaccess: "always",
            wmode: "opaque"
        };
        var attributes = {
            id: "flash"
        };
        swfobject.embedSWF("Tests.swf", "alternative", "100%", "100%", "10.0.0",
            "flash/expressInstall.swf", flashvars, params, attributes);

        function fakeTextInputChange()
        {
            var input = document.getElementById('fakeTextInput');
            var flash = document.getElementById('flash');
            flash.fakeTextInputChange(input.value);
        }
    //-->
    </script> 

    <style type='text/css'>
    <!--
        body {margin:0px;overflow:hidden;}
        html, body, object, embed {width:100%;height:100%;outline:none;}
        #fakeTextInput {
            position:absolute;
            border:none;
            outline:none;
            padding:0;
            margin:0;
            background:transparent url("images/spacer.gif");
            vertical-align:middle;
        }
    -->
    </style>
</head>
<body>
    <div id="alternative">
        <a href="http://www.adobe.com/go/getflashplayer">
            <img src="http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt="Get Adobe Flash player" />
        </a>
    </div>
    <input type="text" id="fakeTextInput" onkeyup="fakeTextInputChange()"/>
</body>
</html>

Notice lines:

  • 07: import flexiframe.js used for positioning html input on correct place
  • 13: allowscriptaccess must be enabled
  • 14: wmode opaque/transparent is what the source of all evil
  • 22: javascript-to-flash communication
  • 35-42: remove all html input text design and makes it transparent
  • 53: input text definition, you may want to define your own handlers (submit, paste, focus…)

URLShorten Class (update)

by josefchutka on February 4, 2010

URL shortening is a technique where a provider makes a web page available under a very short URL in addition to the original address. There are hundreds of provider out there, but only a few of them provides public api and crossdomain.xml. I put toghether some of those that work and URLShorten class was created :-) feel free to use it and enjoy

Update: jdem.cz, tinyurl.com, bit.ly, tr.im, is.gd providers implemented.

Application (view source enabled), please use your own api key for bit.ly

I have just discovered tr.im has not crossdomain.xml, please do not us it while it throws errors. I have just asked them and few more providers to create crossdomain files, will see…

There are two methods you can handle resuls:

// 1. use callback function
URLShorten.jdemcz(long_url.text, jdemczResult);

// ...callback function gets one parameter = short url
private function jdemczResult(url:String):void
{
    jdemcz = url;
}

// 2. addEventDispatcher to have bigger control
var dispatcher:EventDispatcher = URLShorten.tinyurlcom(long_url.text);
dispatcher.addEventListener(URLShortenEvent.COMPLETE, tinyurlcomComplete);

// ...dispatched event.url = short url
private function tinyurlcomComplete(event:URLShortenEvent):void
{
    tinyurl = event.url;
}

URLShorten class

package sk.yoz.net
{
    import com.adobe.serialization.json.JSON;

    import flash.events.Event;
    import flash.net.URLRequest;
    import flash.net.URLVariables;

    import sk.yoz.events.URLShortenEvent;

    public class URLShorten extends Object
    {
        public function URLShorten()
        {
            super();
        }

        private static function prepare(request:URLRequest,
            variables:URLVariables, callback:Function,
            completeHandler:Function=null):URLLoaderDynamic
        {
            var loader:URLLoaderDynamic = new URLLoaderDynamic();
            var handler:Function = completeHandler || complete;

            request.data = variables;
            loader.callback = callback;
            loader.load(request);
            loader.addEventListener(Event.COMPLETE, handler);
            return loader;
        }

        private static function complete(event:Event):void
        {
            var loader:URLLoaderDynamic = URLLoaderDynamic(event.target);
            var url:String = String(loader.data);

            loader.removeEventListener(Event.COMPLETE, complete);
            dispatch(url, loader)
        }

        private static function dispatch(url:String, loader:URLLoaderDynamic)
            :void
        {
            var callback:Function = loader.callback;
            var type:String = URLShortenEvent.COMPLETE;

            if(callback != null)
                callback(url);
            loader.dispatchEvent(new URLShortenEvent(type, false, false, url));
        }

        public static function jdemcz(longUrl:String, callback:Function=null)
            :URLLoaderDynamic
        {
            var apiUrl:String = "http://www.jdem.cz/get";
            var request:URLRequest = new URLRequest(apiUrl);
            var variables:URLVariables = new URLVariables();

            variables.url = longUrl;
            return prepare(request, variables, callback);
        }

        public static function tinyurlcom(longUrl:String,
            callback:Function=null):URLLoaderDynamic
        {
            var apiUrl:String = "http://tinyurl.com/api-create.php";
            var request:URLRequest = new URLRequest(apiUrl);
            var variables:URLVariables = new URLVariables();

            variables.url = longUrl;
            return prepare(request, variables, callback);
        }

        public static function bitly(longUrl:String, login:String,
            apiKey:String, version:String="2.0.1", callback:Function=null)
            :URLLoaderDynamic
        {
            var apiUrl:String = "http://api.bit.ly/shorten";
            var request:URLRequest = new URLRequest(apiUrl);
            var variables:URLVariables = new URLVariables();

            variables.version = version;
            variables.longUrl = longUrl;
            variables.login = login;
            variables.apiKey = apiKey;
            return prepare(request, variables, callback, bitlyComplete);
        }

        private static function bitlyComplete(event:Event):void
        {
            var loader:URLLoaderDynamic = URLLoaderDynamic(event.target);
            var result:String = String(loader.data);
            var data:Object = JSON.decode(result);
            var url:String;

            loader.removeEventListener(Event.COMPLETE, bitlyComplete);
            for each(var resultItem:Object in data.results)
                url = resultItem.shortUrl;
            dispatch(url, loader);
        }

        public static function trim(longUrl:String,
            callback:Function=null):URLLoaderDynamic
        {
            var apiUrl:String = "http://api.tr.im/v1/trim_simple";
            var request:URLRequest = new URLRequest(apiUrl);
            var variables:URLVariables = new URLVariables();

            variables.url = longUrl;
            return prepare(request, variables, callback);
        }

        public static function isgd(longUrl:String,
            callback:Function=null):URLLoaderDynamic
        {
            var apiUrl:String = "http://is.gd/api.php";
            var request:URLRequest = new URLRequest(apiUrl);
            var variables:URLVariables = new URLVariables();

            variables.longurl = longUrl;
            return prepare(request, variables, callback);
        }
    }
}

we need loader to hold callback function reference, URLLoaderDynamic class pretty much fits this needs

package sk.yoz.net
{
    import flash.net.URLRequest;
    import flash.net.URLLoader;

    import sk.yoz.net.URLLoaderDynamic;

    public dynamic class URLLoaderDynamic extends URLLoader
    {
        public function URLLoaderDynamic(request:URLRequest=null)
        {
            super(request);
        }

    }
}

custom URLShortenEvent class is dispatched after url is returned

package sk.yoz.events
{
    import flash.events.Event;

    public class URLShortenEvent extends Event
    {
        public static const COMPLETE:String = "URLShortenEventCOMPLETE";

        private var _url:String;

        public function URLShortenEvent(type:String, bubbles:Boolean=false,
            cancelable:Boolean=false, url:String="")
        {
            super(type, bubbles, cancelable);
            _url = url;
        }

        public function get url():String
        {
            return _url;
        }
    }
}

Quick tip: … (rest) parameter

by josefchutka on February 2, 2010

Here is a quick tip for using … (rest) parameter in your code. It sometimes happens that you want to have your function called multiple times, when different events dispatched or even without any event dispatched. Properly you would create callback/handler functions for all events, that would call the required function. Or you can use … (rest) parameter to bypass arguments casting.

priate function init():void
{
    addEventListener(Event.ENTER_FRAME, doSomething);

    var timer:Timer = new Timer(500);
    timer.addEventListener(TimerEvent.TIMER, doSomething);
    timer.start();

    callLater(doSomething);
    doSomething();
}

private function doSomething(... rest):void
{
// whatever
}

Wordpress SyntaxHighlighter Evolved goes Error

by josefchutka on January 28, 2010

After latest SyntaxHighlighter plugin update (version 2.3.8), there suddenly appears error window while mouse moving over SyntaxHighlighter code:

Error #2044 & text=Error #2036 Load never comple

The problem is in clipboard.swf file loading as icon (copy to clipboard). There are more people complaining on this error behaviour, and there is a simple fix. Replace this file in your wordpress:

wp-content/plugins/syntaxhighlighter/syntaxhighlighter/scripts/clipboard.swf

For this one clipboard.swf. And its working nicely again.

SmoothImage Class (update)

by josefchutka on January 27, 2010

While Flex uses bilinear resizing method when scaling images (event for Bitmap.smoothing() method), you won’t be able to get nice results when downscaling more than 2 times. Inspired by ImageResizer class I have also created SmoothImage Class. This object extends mx.Image and uses ImageResizer.bilinearIterative() class in order to generate smoother resized results.

In following example mx.Image, SmoothImage and sk.yoz.image.SmoothImage used to demonstrate resizing original 1024×679px image.

sk.yoz.image.SmoothImage class:

package sk.yoz.image
{
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.geom.Point;

    import mx.controls.Image;

    import sk.yoz.math.ResizeMath;

    public class SmoothImage extends Image
    {
        protected var _resizeFunction:String = "bilinearIterative";
        protected var _iterationMultiplier:Number = 2;
        protected var resizedBitmap:Bitmap;

        public function SmoothImage()
        {
            super();
        }

        [Bindable(event="resizeFunctionChanged")]
        [Inspectable(category="Other", defaultValue="bilinearIterative",
            enumeration="bilinear,bilinearIterative")]
        public function set resizeFunction(value:String):void
        {
            if(value == resizeFunction)
                return;
            _resizeFunction = value;
            resizeBitmap();
        }

        public function get resizeFunction():String
        {
            return _resizeFunction;
        }

        [Bindable(event="iterationMultiplierChanged")]
        public function set iterationMultiplier(value:Number):void
        {
            if(value == iterationMultiplier)
                return;

            _iterationMultiplier = value;
            resizeBitmap();
        }

        public function get iterationMultiplier():Number
        {
            return _iterationMultiplier;
        }

        override protected function updateDisplayList(unscaledWidth:Number,
            unscaledHeight:Number):void
        {
            super.updateDisplayList(unscaledWidth, unscaledHeight);
            resizeBitmap();
        }

        protected function resizeBitmap(... rest):void
        {
            if(!width || !height || !content || !(content is Bitmap))
                return;

            if(resizedBitmap && resizedBitmap.parent)
                resizedBitmap.parent.removeChild(resizedBitmap);

            resizedBitmap = new Bitmap(resizedBitmapData);
            addChild(resizedBitmap);
            content.visible = false;
        }

        protected function get resizeMethod():String
        {
            return maintainAspectRatio ? ResizeMath.METHOD_LETTERBOX
                : ResizeMath.METHOD_RAW;
        }

        protected function get resizedBitmapData():BitmapData
        {
            var bitmapData:BitmapData = Bitmap(content).bitmapData;
            var dimensions:Point = ResizeMath.newDimensions(
                new Point(bitmapData.width, bitmapData.height),
                new Point(width, height), resizeMethod, true);

            return ImageResizer[resizeFunction](bitmapData, dimensions.x,
                dimensions.y, resizeMethod, true, iterationMultiplier);
        }
    }
}

Update (Jan 27, 2010): in sk.yoz.image.SmoothImage

E-seminar: Drupal and Flex

by Tom Krcha on January 27, 2010


Zajímá vás jak prodat flexové aplikace do enterprise prostředí? Majitel firmy SiteOne, Jan Bezděk vám o tom bude povídat (a máte se nač těšit). Chcete vidět práce soutěžících, co by rádi Adobe software v hodnotě $2100? Každý kdo přijde bude rozhodovat o tom kdo vyhraje. Toto vše se odehraje v …

Flex 4 beginner series in Czech

by Tom Krcha on January 26, 2010


ColdFusion 9 Free Workshop in Prague on February 23

by Tom Krcha on January 25, 2010


Parsing FQL result

by josefchutka on January 25, 2010

Facebook Query Language, or FQL, allows you to use a SQL-style interface to more easily query the same Facebook social data that you can access through other Facebook API methods (assuming your application has access!). Data returned for Facebook ActionScript Api are XML. You can access data via E4X after setting correct namespace. As for now (January 25, 2010), Facebook result defines xmlns=”http://api.facebook.com/1.0/” , but it may change in future, so lets make it dynamic…

Use e.g. extended FacebookLogger and add these lines

public function fql(query:String):void
{
	var call:FacebookCall = facebook.post(new FqlQuery(query));
	call.addEventListener(FacebookEvent.COMPLETE, fqlCallComplete);
}

private function fqlComplete(event:FacebookEvent):void
{
	var data:FacebookData = FacebookData(event.data);
	var xml:XML = XML(data.rawResult);
	var ns:Namespace = xml.namespace();
	default xml namespace = ns;

	var result:Array = [];
	for each(var user:XML in xml.user)
		result.push({uid:user.uid.toString(), name:user.name.toString()});

	default xml namespace = new Namespace("");
	dispatchEvent(new ResultEvent(ResultEvent.RESULT, false, true, result));
}

Notice xml.namespace() line. This method returns Namespace object used in main node (“http://api.facebook.com/1.0/”), so we can define it as default xml namespace. If you do not set empty default namespace after you finish parsing this XML, it may throw some errors later in parsing another XMLs.

this is our fql query:

FB.fql("SELECT uid, name FROM user WHERE uid = XXX")

and data.rawResult contains following XML object

<?xml version="1.0" encoding="UTF-8"?>
<fql_query_response xmlns="http://api.facebook.com/1.0/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" list="true">
  <user>
    <uid>12345678</uid>
    <name>Some John Smith</name>
  </user>
  <user>
    <uid>12345679</uid>
    <name>Another John Smith</name>
  </user>...

Notice xmlns attribute in main node.