AS3 Implementing a simple Templating System II

After some investigation on as3 templates engines, I write a simple class to manage key value pairs, but for a project I am now developing I have to control more things in the Template layout…

I have find a simple Javascript template System and “Ported” it to work width AS3.

It is not a “Direct Port” because I adapted it to work as a single javascript class that anyone can use from AS3.

Example Code: ( assumes classPaths are correct and have a Textfield in stage named output )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import es.xperiments.utils.PureJSTemplate;
var exampleTemplate:XML =
<code>
<!--[CDATA[
<$=data.welcome$>
<$for(var k=0; k<data.number; k++){
    data.k = k+200;
$>
    <$=data.k$>
<$}$>
]]-->
</code>;

PureJSTemplate.addEventListener( Event.COMPLETE, onJSTemplate );
PureJSTemplate.initialize( );

function onJSTemplate( e:Event ):void
{
    PureJSTemplate.setDelimiters( "<$","$>");
    output.htmlText = PureJSTemplate.parseTemplate( "TestTemplate", exampleTemplate,{welcome:"PureJSTemplate is great",number:30} );
}

Download: Purejstemplate

JSProxy Bug and Solution

For some time I was using the JSProxy class from Joan Garnet but now I have seen a BUG and patched it.

When working with Injected Javascript Code, sometimes the Javascript Compiler trow an error, but if you load the same code directly from a URL it works like a charm.

Then, whats is the problem? After some hours of investigation I managed that sometimes some caracters are not correctly encoded from ExternalInterfaces calls, then the code runs in wrong way.

The solution: Simple, encode in AS3 the Injected JS code to Base64, and decode it in JS before compile it.

Original JSProxy: Original Class
Modified JSProxy Class: Patched JSProxy Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package com.joangarnet.js
{
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IEventDispatcher;
    import flash.external.ExternalInterface;
    import flash.utils.Proxy;
    import flash.utils.flash_proxy;
    import flash.utils.ByteArray;
    import org.phprpc.util.Base64;

    /*
     * Original class: Joan Garnet http://www.joangarnet.com/blog/?p=437
     * Patched by Pedro Casaubon
     * This patch add Base64 encoding/decoding to the Injected Code
     */


    dynamic final public class JavaScript extends Proxy implements IEventDispatcher
    {
        private var eventDispatcher:EventDispatcher;

        /*
         * scope for calling the Javascript methods and properties. Defaults to DOM's window object.
         */

        public static const WINDOW:String = "";
        public static const DOCUMENT:String = "document";
        private var scope:String;

        /*
         * javascript toolkit to do some internal tasks
         */

        private const JSProxyTools:XML =
            <script type="text/javascript"><!--mce:0--></script>;
        /*
         * end javascript toolkit
         */


        private var movieID:String;

        public function JavaScript(movieID:String,url:String=null)
        {
            this.movieID = movieID;
            if( !ExternalInterface.available ){
                throw new Error("ExternalInterface not available");
            }
            eventDispatcher = new EventDispatcher(this);
            ExternalInterface.marshallExceptions = true;

            // compile internal toolkit
            try{
                ExternalInterface.call( "eval", JSProxyTools.toString() );
            }catch(err:Error){
                throw new Error( "com.joangarnet.js.JavaScript::constructor()\ngetStackTrace() "+ err.getStackTrace() );
            }

            // handle the loaded script callback
            ExternalInterface.addCallback( "jsLoadedHandler", jsLoaded );

            if(url)this.url = url;
        }

        private function callToolkit( methodName:String, params_arr:Array ):*
        {
            var res:*;
            try{
                res = ExternalInterface.call( "com.joangarnet.JSProxy.facade", movieID, methodName, params_arr );
            }catch(err:Error){
                throw new Error( "com.joangarnet.js.JavaScript::callToolkit()\ngetStackTrace() "+ err.getStackTrace() );
            }
            return res;
        }

        private function set url( url:String ):void
        {
            callToolkit( "loadScript", [url] );
        }

        public function set js( jsCode:String ):void
        {
            compile( jsCode );
        }

        public function getScope( scope:String=WINDOW ):JavaScript
        {
            this.scope = scope.replace( /^(window\.?)(.*)/i, "$2" );
            return this;
        }

        private function jsLoaded():void
        {
            trace("jsLoaded!");
            dispatchEvent( new Event(Event.COMPLETE) );
        }

        /*
         * Modified method to support Base64 encoding
         * @author Pedro Casaubon (http://www.xperiments.es/blog)
         */

        private function compile( jsCode:String ):void
        {
            // javascript compilation
            try{
                    var ba:ByteArray = new ByteArray();
                    ba.writeUTFBytes( jsCode );
                    ba.position = 0;
                    var baseCode:String = Base64.encode( ba );
                    callToolkit( "compileCode", [baseCode] );

            }catch(err:Error){
                throw new Error( "com.joangarnet.js.JavaScript::compile() javascript compilation error\ngetStackTrace() "+ err.getStackTrace() );
            }
            dispatchEvent( new Event(Event.COMPLETE) );
        }

        private function buildPath( objectMember:String ):String
        {
            return (scope.length&gt;0 ? scope+"."+objectMember : objectMember);
        }

        /*
         * Proxy implementation
         */

        override flash_proxy function callProperty(methodName:*, ... args):*
        {
            var res:*;
            var functionCall:String = buildPath((methodName as QName).localName);
            try{
                args = args!=null ? args : [];
                args.unshift( functionCall );
                res = ExternalInterface.call.apply(null, args);
            }catch(err:Error){
                throw new Error( "com.joangarnet.js.JavaScript::"+functionCall+"()\ngetStackTrace() "+ err.getStackTrace() );
            }
            return res;
        }
        override flash_proxy function getProperty(name:*):*
        {
            return callToolkit( "getProperty", [buildPath((name as QName).localName)] );
        }
        override flash_proxy function setProperty(name:*, value:*):void
        {
            callToolkit( "setProperty", [buildPath((name as QName).localName), value] );
        }

        /*
         * IEventDispatcher implementation
         */

        public function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void
        {
            eventDispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference);
        }

        public function removeEventListener(type:String, listener:Function, useCapture:Boolean=false):void
        {
            eventDispatcher.removeEventListener(type, listener, useCapture);
        }

        public function dispatchEvent(event:Event):Boolean
        {
            return eventDispatcher.dispatchEvent(event);
        }

        public function hasEventListener(type:String):Boolean
        {
            return eventDispatcher.hasEventListener(type);
        }

        public function willTrigger(type:String):Boolean
        {
            return eventDispatcher.willTrigger(type);
        }
    }
}

Comparing Objects using ByteArrays

Other day I was thinking in this class I have seen.

Simple compare 2 objects by generating a ByteArray from themselves and compare with the other.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package {
              import flash.utils.ByteArray;
              public class ObjectTools {
                      public function ObjectTools () {
                      }
                      /**
                       * Compares two objects and checks if they are identical. Does not compare the objects by reference,
                       * but compares to see if the values of the properties are identical.
                       *
                       * @param obj1
                       * @param obj2
                       *
                       * @return
                       */

                      public static function compare (obj1:Object, obj2:Object):Boolean {
                              var b1:ByteArray = new ByteArray();
                              var b2:ByteArray = new ByteArray();
                              b1.writeObject(obj1);
                              b2.writeObject(obj2);

                              // compare the lengths first
                              var size:uint = b1.length;
                              if (b1.length == b2.length) {
                                      b1.position = 0;
                                      b2.position = 0;
                                      // then the bits
                                      while (b1.position &lt; size) {
                                              var v1:int = b1.readByte();
                                              if (v1 != b2.readByte()) {
                                                      return false;
                                              }
                                      }
                              }
                              if (b1.toString() == b2.toString()) {
                                      return true;
                              }
                              return false;
                      }
              }
      }

From: http://blog.joshbuhler.com/2008/02/11/comparing-objects-using-bytearrays/