From the monthly archives:

February 2010

Full JavaScript Access From ActionScript (update)

by josefchutka on February 10, 2010

Have you ever tought about accessing DOM from ActionScript? In fact, you can do it and even far more. You can create and call JavaScript methods and objects, access cookies, change styles… All you need is correct AllowScriptAccess parameter within your flash object. No framework needed here, no hacks, ExternalInterface takes care.

Following application runs JavaScript from textarea:

Application. Line 16 does all the fun :-)

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    applicationComplete="init()">
<mx:Script>
<![CDATA[
    [Bindable]
    private var lastResult:String = "";

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

    private function runScript():void
    {
        ExternalInterface.call("eval", script.text);
    }

    private function universalCallback(... rest):void
    {
        lastResult = String(rest[0]);
    }
]]>
</mx:Script>
<mx:HBox width="100%" height="100%">
    <mx:TextArea id="script" width="100%" height="100%">
        <mx:text>
            <![CDATA[
function myFunction(){
    var flash = document.getElementById("flash");
    var data = document.childNodes[1].innerHTML;
    flash.universalCallback(data);
}

myFunction();
alert(document.getElementsByTagName('title')[0].innerHTML);
            ]]>
        </mx:text>
    </mx:TextArea>
    <mx:TextArea width="100%" height="100%" text="{lastResult}" />
</mx:HBox>

<mx:Button click="runScript()" label="Run"/>
</mx:Application>

HTML template

<!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>Full Javascript Access</title>
    <meta http-equiv="Content-Type" content="application/xml; charset=utf-8" />
    <meta http-equiv="Cache-Control" content="no-cache" />
    <meta http-equiv="expires" content="1" />
    <meta http-equiv="pragma" content="no-cache" /> 

    <meta name="author" content="http://studio.yoz.sk" />
    <meta name="description" content="Full Javascript Access" />
    <meta name="robots" content="all" /> 

    <script type="text/javascript" src="js/swfobject.js"></script>
    <script type="text/javascript">
    <!--
        var flashvars = {}

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

    <style type='text/css'>
    <!--
        body {margin:0px;overflow:hidden;}
        html, body, object, embed {width:100%;height:100%;outline:none;}
    -->
    </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>
</body>
</html>

No additional nothing here just:

  • line 20: allowscriptaccess: “always” (“sameDomain” may also be used)
  • line 24: tip – add name attribute with swfobject so ExternalInterface.objectID will get correct value

Considerations of using eval:

  • Improper use of eval opens up your code for injection attacks
  • Debugging can be more challenging (no line numbers, etc.)
  • eval’d code executes more slowly (no opportunity to compile/cache eval’d code)

Update (Feb 11, 2010): You can use either eval call:

ExternalInterface.call("eval", script.text);

or wrap your script into anonymous function:

ExternalInterface.call("function(){" + script.text + "}");

Personally, I do not see any difference in execution, it seems to be the same, maybe some performance testing would clarify it. What do you think?

Simple chat with P2P NetGroup in FP 10.1

by Tom Krcha on February 9, 2010


onConference – video chat over facebook

by josefchutka on February 9, 2010

onConference is flash application allowing individuals & businesses to meet face-to-face. Keep in touch with your friends, family and business associates via onConference chat or live video chat.

To be more specific, onConference is using RTMFP protocol, so your conferences, even those multiuser are p2p. Yes, any conference can join multiple users. I decided to use simpliest look-n-feel á la facebook and no pageflow, so you get connected immediately… You can find onConference on http://apps.facebook.com/onconference/, please let me know if you like it, any feedback welcomed :-) .

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…)

Aplikace v App Store se neprodávají jen tak

by Robin on February 6, 2010

Aplikace v App Store se neprodávají jen takV rámci vývoje aplikace Cookmate jsem získal mnoho poznatků a informací, které by mě před startem nenapadly. Rád se o své zkušenosti podělím, a proto bych rád odstartoval sérii článků na téma iPhone marketing a App Store.

Nejen protože na českém trhu není mnoho kvalitních zdrojů s praktickými a zajímavými informacemi z této oblasti. Každý den se na celém světě vyvíjí nové aplikace, mnoho začátečníků vývojářů má silně motivující představu o velkých příjmech z App Store aplikace. Až v průběhu zjišťují různá úskalí a často se zdá, že cíl je velmi daleko. Od nápadu na konkrétní aplikaci přes její vývoj a péči o její růst je spousta kroků, které je potřeba poznat a respektovat.

Pokud si myslíte, že nějakou aplikaci vložíte do App Store a za týden si kupujete novou 911, tak jste zatím jen snílek. App Store je naprosto fenomenální projekt a lidem, které tento článek bude zajímat, jej nemá smysl představovat.

Fakta k dnešnímu dni:

  • 140.000+ aplikací, v den spuštění 500 (11. 7. 2008)
  • již přes 3 miliardy stažení
  • uživatel utratí průměrně $5 měsíčně
  • přes 125 miliónů uživatelů, kteří můžou platit
  • cca. 700 nových aplikací denne

Pokud budete mít štěstí a kvalitní aplikaci, tak si uživatelé vaší aplikaci koupí a můžete začít přemýšlet o barvě vaší nové 911. Nicméně už ikonkou a prvním dojmem, který trvá cca. 2-3 sekundy si je buď získáte nebo odradíte.

Po zveřejnění a na začátcích prodeje jste v situaci, kdy o vaší appce nikdo neví a nejste ani na viditelných pozicích. Přeskočím vývoj a popíšu variantu prodeje appky mimo App Store.

Před submitem do App Store

Komunita

Je potřeba si budovat komunitu, která vám bude věřit a bude věřit tomu, že právě vaše aplikace stojí za pozornost. Vložením appky do prodeje bez jakéhokoliv buzzu předtím jí automaticky snižujete možnost úspěchu na minimum. Půjdete si koupit nové kalhoty k renomované značce, kde víte, že barva, střih a materiál jsou zaručenou kvalitou?

Pokud se dostanete do fáze, kdy si vaše aplikace uživatelé kupují jen kvůli tomu, že je od vás, tak máte vyhráno — viz. Pastebot od Tapbots (aplikace se prodává především kvůli tomu, jaký brand za ní stojí).

Nástroje které se nám osvědčily při budovnání komunity v iPhone businessu jsou tyto:

Facebook se v iPhone businesse moc nenosí, TOP vývojáři tam buď ani nejsou nebo mají zanedbatelné množství fans, které by mohli oslovit (Tapbots 150 fans). Každopádně doporučuji fanpage založit a zabrat si username, aby jste v budoucnu s tímto neměli problémy.

PR a vaše image

Vaším hlavním PR by měla být aplikace samotná. Už při jejím vývoji by jste tedy měli dbát na to, aby design dělal někdo, kdo design dělá — může to být velký hřebík do rakve, když sám programátor kreslí ikonky nebo UI samotné. Svěrte design lidem, kteří se designem zabývají pokud se na to necitíte.

Loren Brichter atebits (vývojář Tweetie) je dobrá ukázka toho, že i programátor může kreslit, avšak web a design mu dělá Tim Van Damme. Tweetie je v podstatě ale hodně jednoduché po stránce UI avšak geniálně řešené a inovativní.

Pro fungování PR je důležité vychytat několik důležitých věcí:

  • zajímavý nápad a precizní zpracování
  • dot-com doména — např. www.coolmate.eu už nevypadá moc dobře, dobrá doména na .com je prostě úplný základ

Tip: pokud nemůžete najít volnou doménu pro vaší appku, tak se často používat koncovka “app” za název (např. www.cookmateapp.com)

  • jednoduchý název — nepoužívejte zdvojení písmen, může to způsobovat špatnou komunikaci, vždy je lepší mít jednoduše vyslovitelný label
  • pěkný web — spousta uživatelů bude chodit do App Store právě přes váš web a pokud na první pohled zjistí, že je to lameřina, tak je to v pytli a ani se do App Store nepodívají (pokud neumíte dělat weby a nemáte nikoho kdo by vám trošku cool web udělal, tak můžete použít tuto šablonu od Jonnotie)
  • presskit — na web umístěte presskit volně ke stažení pro novináře (logo, screenshoty, hlavní ikona, wallpapers apod.)
  • screencast — ukázkové video, kde uživateli ukážete jak aplikace reálně funguje je moc dobrý materiál a rozhodně podpoří zájem o aplikaci v mediích

Když máte vše připraveno

Gratuluji! Udělali jste opravdu kus práce a máte šanci vydělat pár babek a možná se vám i vrátí něco víc než náklady investované do vaší appky…… ale pozor! Vhodný čas na zveřejnění aplikace je dalším klíčem k úspěšnému startu. O tomto tématu a dalších však až v dalším článku. Určitě je na co se těšit!

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
}