AS3 Implementing a simple Templating System II

Después de algunas investigaciones sobre los motores de plantillas en AS3, me he decidido a “Portar” PureJSTemplate a AS3.

Digo “Portar” porque realmente no es un código portado a AS3, sino una modificación del código original para que funcione incrustandolo directamente desde Flash mediante Javascript Injection.

Codigo de ejemplo: ( asume que los classpath estan bien definidos y tenemos una instancia de textfield en el stage llamada 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} );
}

Descarga de las clases: Purejstemplate

JSProxy Bug and Solution

Desde hace tiempo uso la clase JSProxy de Joan Garnet, pero ahora he visto un error.

Cuando se trabaja con el Código Javascript inyectado, a veces el Compilador de Javascript  produce un error, pero si se cargamos el mismo código directamente desde una dirección URL esto no ocurre.

Entonces, ¿Cúal es el problema? Después de algunas horas de investigación llegue a la conclusión de que algunos caracteres del código original no se codificaban de una manera correcta ( sobre todo regexps etc ) y por eso despues nos producia los errores.

La solución: simple, desde AS3  codificar en el código JS a Inyectar a Base64, y descifrar en JS antes de compilarlo.

Clase JSProxy original: Original Class
Clase JSProxy modificada: 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

El otro dia recorde esta clase tan útil.

Con ella podremos comparar dos objectos, simplemente generando un ByteArray de ellos y comparandolos byte a byte…

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/