]> gitweb.factorcode.org Git - factor.git/commitdiff
fjsc: initial version of factor to javascript compiler
authorchris.double <chris.double@double.co.nz>
Mon, 11 Dec 2006 11:14:58 +0000 (11:14 +0000)
committerchris.double <chris.double@double.co.nz>
Mon, 11 Dec 2006 11:14:58 +0000 (11:14 +0000)
apps/fjsc-responder/fjsc-responder.factor [new file with mode: 0644]
apps/fjsc-responder/load.factor [new file with mode: 0644]
apps/fjsc-responder/resources/bootstrap.js [new file with mode: 0644]
apps/fjsc-responder/resources/yahoo/connection.js [new file with mode: 0644]
apps/fjsc-responder/resources/yahoo/event.js [new file with mode: 0644]
apps/fjsc-responder/resources/yahoo/yahoo.js [new file with mode: 0644]
libs/fjsc/fjsc.factor [new file with mode: 0644]
libs/fjsc/load.factor [new file with mode: 0644]
libs/fjsc/tests.factor [new file with mode: 0644]

diff --git a/apps/fjsc-responder/fjsc-responder.factor b/apps/fjsc-responder/fjsc-responder.factor
new file mode 100644 (file)
index 0000000..32844b8
--- /dev/null
@@ -0,0 +1,49 @@
+! Copyright (C) 2006 Chris Double. All Rights Reserved.
+! See http://factorcode.org/license.txt for BSD license.
+!
+IN: fjsc-responder
+USING: kernel lazy-lists parser-combinators fjsc cont-responder html io namespaces file-responder httpd hashtables ;
+
+USE: prettyprint
+
+: fjsc-eval-page ( -- )
+  [ "response" get . ] string-out log-message
+  "code" "response" get hash dup log-message
+  serving-text
+  'expression' parse car 
+  parse-result-parsed compile write flush ;
+
+: fjsc-page ( -- )
+  [
+    <html>
+      <head> 
+        <script "text/javascript" =type "/responder/fjsc-resources/yahoo/yahoo.js" =src script> </script>
+        <script "text/javascript" =type "/responder/fjsc-resources/yahoo/event.js" =src script> </script>
+        <script "text/javascript" =type "/responder/fjsc-resources/yahoo/connection.js" =src script> </script>
+        <script "text/javascript" =type "/responder/fjsc-resources/bootstrap.js" =src script> </script>
+      </head>
+      <body>   
+       <form "toeval" =id "fjsc_eval(document.getElementById(\"toeval\"));return false;" =onsubmit "post" =method form>
+         <textarea "code" =name "code" =id textarea>
+         </textarea>
+         <input "submit" =type input/>
+       </form>
+       <div "compiled" =id div>
+       </div>
+       <div "stack" =id div>
+       </div>
+      </body>     
+    </html>
+  ] show ;
+  
+
+"fjsc" [ fjsc-page ] install-cont-responder
+"fjsceval" [ fjsc-eval-page ] add-simple-responder
+"fjsc-resources" [
+  [
+    "apps/fjsc-responder/resources/" resource-path "doc-root" set
+    file-responder
+  ] with-scope
+] add-simple-responder
+
+
diff --git a/apps/fjsc-responder/load.factor b/apps/fjsc-responder/load.factor
new file mode 100644 (file)
index 0000000..83b2878
--- /dev/null
@@ -0,0 +1,18 @@
+! Copyright (C) 2006 Chris Double. All Rights Reserved.
+! See http://factorcode.org/license.txt for BSD license.
+!
+REQUIRES: libs/httpd libs/fjsc ;
+
+PROVIDE: apps/fjsc-responder 
+{ 
+  +files+ { 
+  "fjsc-responder.factor"
+  } 
+} {
+  +tests+ { 
+  }
+} { 
+  +help+ 
+  { 
+  } 
+} ;
diff --git a/apps/fjsc-responder/resources/bootstrap.js b/apps/fjsc-responder/resources/bootstrap.js
new file mode 100644 (file)
index 0000000..3885606
--- /dev/null
@@ -0,0 +1,42 @@
+function fjsc_eval(form) {
+   var callback = {
+      success: function(o) {
+        var v = o.responseText;
+        eval(v)
+        display_datastack();
+        document.getElementById('compiled').innerHTML="<pre>" + v + "</pre>";
+        document.getElementById('code').value="";
+
+      }
+   };
+   YAHOO.util.Connect.setForm(form);
+   YAHOO.util.Connect.asyncRequest('POST', "/responder/fjsceval/", callback);
+}
+
+var data_stack = [ ] 
+
+function fjsc_dup() {
+   var v = data_stack.pop();
+   data_stack.push(v);
+   data_stack.push(v);
+}
+
+function fjsc_drop() {
+   data_stack.pop();
+}
+
+function fjsc_alert() {
+   alert(data_stack.pop())
+}
+
+function display_datastack() {
+   var html=[];
+   html.push("<table border='1'>")
+   for(var i = 0; i < data_stack.length; ++i) {
+      html.push("<tr><td>")
+      html.push(data_stack[i])
+      html.push("</td></tr>")
+   }
+   html.push("</table>")
+   document.getElementById('stack').innerHTML=html.join("");
+}
diff --git a/apps/fjsc-responder/resources/yahoo/connection.js b/apps/fjsc-responder/resources/yahoo/connection.js
new file mode 100644 (file)
index 0000000..098a3ab
--- /dev/null
@@ -0,0 +1,586 @@
+/*
+Copyright (c) 2006, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+*/
+
+/**
+ * The Connection Manager provides a simplified interface to the XMLHttpRequest
+ * object.  It handles cross-browser instantiantion of XMLHttpRequest, negotiates the
+ * interactive states and server response, returning the results to a pre-defined
+ * callback you create.
+ * @ class
+ */
+YAHOO.util.Connect =
+{
+  /**
+   * Array of MSFT ActiveX ids for XMLHttpRequest.
+   * @private
+   * @type array
+   */
+       _msxml_progid:[
+               'MSXML2.XMLHTTP.5.0',
+               'MSXML2.XMLHTTP.4.0',
+               'MSXML2.XMLHTTP.3.0',
+               'MSXML2.XMLHTTP',
+               'Microsoft.XMLHTTP'
+               ],
+
+  /**
+   * Array of HTTP header(s)
+   * @private
+   * @type array
+   */
+       _http_header:{},
+
+  /**
+   * Determines if HTTP headers are set.
+   * @private
+   * @type boolean
+   */
+       _has_http_headers:false,
+
+ /**
+  * Property modified by setForm() to determine if the data
+  * should be submitted as an HTML form.
+  * @private
+  * @type boolean
+  */
+       _isFormSubmit:false,
+
+ /**
+  * Property modified by setForm() to set the HTML form data
+  * for each transaction.
+  * @private
+  * @type string
+  */
+       _sFormData:null,
+
+ /**
+  * Collection of polling references to the polling mechanism in handleReadyState.
+  * @private
+  * @type string
+  */
+       _poll:[],
+
+  /**
+   * The polling frequency, in milliseconds, for HandleReadyState.
+   * when attempting to determine a transaction's XHR  readyState.
+   * The default is 50 milliseconds.
+   * @private
+   * @type int
+   */
+       _polling_interval:50,
+
+  /**
+   * A transaction counter that increments the transaction id for each transaction.
+   * @private
+   * @type int
+   */
+       _transaction_id:0,
+
+  /**
+   * Member to add an ActiveX id to the existing xml_progid array.
+   * In the event(unlikely) a new ActiveX id is introduced, it can be added
+   * without internal code modifications.
+   * @public
+   * @param string id The ActiveX id to be added to initialize the XHR object.
+   * @return void
+   */
+       setProgId:function(id)
+       {
+               this.msxml_progid.unshift(id);
+       },
+
+  /**
+   * Member to modify the default polling interval.
+   * @public
+   * @param {int} i The polling interval in milliseconds.
+   * @return void
+   */
+       setPollingInterval:function(i)
+       {
+               if(typeof i == 'number' && isFinite(i)){
+                       this._polling_interval = i;
+               }
+       },
+
+  /**
+   * Instantiates a XMLHttpRequest object and returns an object with two properties:
+   * the XMLHttpRequest instance and the transaction id.
+   * @private
+   * @param {int} transactionId Property containing the transaction id for this transaction.
+   * @return connection object
+   */
+       createXhrObject:function(transactionId)
+       {
+               var obj,http;
+               try
+               {
+                       // Instantiates XMLHttpRequest in non-IE browsers and assigns to http.
+                       http = new XMLHttpRequest();
+                       //  Object literal with http and tId properties
+                       obj = { conn:http, tId:transactionId };
+               }
+               catch(e)
+               {
+                       for(var i=0; i<this._msxml_progid.length; ++i){
+                               try
+                               {
+                                       // Instantiates XMLHttpRequest for IE and assign to http.
+                                       http = new ActiveXObject(this._msxml_progid[i]);
+                                       if(http){
+                                       //  Object literal with http and tId properties
+                                               obj = { conn:http, tId:transactionId };
+                                               break;
+                                       }
+                               }
+                               catch(e){}
+                       }
+               }
+               finally
+               {
+                       return obj;
+               }
+       },
+
+  /**
+   * This method is called by asyncRequest to create a
+   * valid connection object for the transaction.  It also passes a
+   * transaction id and increments the transaction id counter.
+   * @private
+   * @return object
+   */
+       getConnectionObject:function()
+       {
+               var o;
+               var tId = this._transaction_id;
+
+               try
+               {
+                       o = this.createXhrObject(tId);
+                       if(o){
+                               this._transaction_id++;
+                       }
+               }
+               catch(e){}
+               finally
+               {
+                       return o;
+               }
+       },
+
+  /**
+   * Method for initiating an asynchronous request via the XHR object.
+   * @public
+   * @param {string} method HTTP transaction method
+   * @param {string} uri Fully qualified path of resource
+   * @param callback User-defined callback function or object
+   * @param {string} postData POST body
+   * @return {object} Returns the connection object
+   */
+       asyncRequest:function(method, uri, callback, postData)
+       {
+               var o = this.getConnectionObject();
+
+               if(!o){
+                       return null;
+               }
+               else{
+                       if(this._isFormSubmit){
+                               //If the specified HTTP method is GET, setForm() will return an
+                               //encoded string that is concatenated to the uri to
+                               //create a querystring.
+                               if(method == 'GET'){
+                                       uri += "?" +  this._sFormData;
+                               }
+                               else if(method == 'POST'){
+                                       postData =  this._sFormData;
+                               }
+                               this._sFormData = '';
+                               this._isFormSubmit = false;
+                       }
+
+                       o.conn.open(method, uri, true);
+
+                       if(postData){
+                               this.initHeader('Content-Type','application/x-www-form-urlencoded');
+                       }
+
+                       //Verify whether the transaction has any user-defined HTTP headers
+                       //and set them.
+                       if(this._has_http_headers){
+                               this.setHeader(o);
+                       }
+
+                       this.handleReadyState(o, callback);
+                       postData?o.conn.send(postData):o.conn.send(null);
+
+                       return o;
+               }
+       },
+
+  /**
+   * This method serves as a timer that polls the XHR object's readyState
+   * property during a transaction, instead of binding a callback to the
+   * onreadystatechange event.  Upon readyState 4, handleTransactionResponse
+   * will process the response, and the timer will be cleared.
+   *
+   * @private
+   * @param {object} o The connection object
+   * @param callback User-defined callback object
+   * @return void
+   */
+       handleReadyState:function(o, callback)
+       {
+               var oConn = this;
+               try
+               {
+                       this._poll[o.tId] = window.setInterval(
+                               function(){
+                                       if(o.conn && o.conn.readyState == 4){
+                                               window.clearInterval(oConn._poll[o.tId]);
+                                               oConn._poll.splice(o.tId);
+                                               oConn.handleTransactionResponse(o, callback);
+                                       }
+                               }
+                       ,this._polling_interval);
+               }
+               catch(e)
+               {
+                       window.clearInterval(oConn._poll[o.tId]);
+                       oConn._poll.splice(o.tId);
+                       oConn.handleTransactionResponse(o, callback);
+               }
+       },
+
+  /**
+   * This method attempts to interpret the server response and
+   * determine whether the transaction was successful, or if an error or
+   * exception was encountered.
+   *
+   * @private
+   * @param {object} o The connection object
+   * @param {function} callback - User-defined callback object
+   * @return void
+   */
+       handleTransactionResponse:function(o, callback)
+       {
+               // If no valid callback is provided, then do not process any callback handling.
+               if(!callback){
+                       this.releaseObject(o);
+                       return;
+               }
+
+               var httpStatus;
+               var responseObject;
+
+               try
+               {
+                       httpStatus = o.conn.status;
+               }
+               catch(e){
+                       // 13030 is the custom code to indicate the condition -- in Mozilla/FF --
+                       // when the o object's status and statusText properties are
+                       // unavailable, and a query attempt throws an exception.
+                       httpStatus = 13030;
+               }
+
+               if(httpStatus >= 200 && httpStatus < 300){
+                       responseObject = this.createResponseObject(o, callback.argument);
+                       if(callback.success){
+                               if(!callback.scope){
+                                       callback.success(responseObject);
+                               }
+                               else{
+                                       // If a scope property is defined, the callback will be fired from
+                                       // the context of the object.
+                                       callback.success.apply(callback.scope, [responseObject]);
+                               }
+                       }
+               }
+               else{
+                       switch(httpStatus){
+                               // The following case labels are wininet.dll error codes that may be encountered.
+                               // Server timeout
+                               case 12002:
+                               // 12029 to 12031 correspond to dropped connections.
+                               case 12029:
+                               case 12030:
+                               case 12031:
+                               // Connection closed by server.
+                               case 12152:
+                               // See above comments for variable status.
+                               case 13030:
+                                       responseObject = this.createExceptionObject(o, callback.argument);
+                                       if(callback.failure){
+                                               if(!callback.scope){
+                                                       callback.failure(responseObject);
+                                               }
+                                               else{
+                                                       callback.failure.apply(callback.scope,[responseObject]);
+                                               }
+                                       }
+                                       break;
+                               default:
+                                       responseObject = this.createResponseObject(o, callback.argument);
+                                       if(callback.failure){
+                                               if(!callback.scope){
+                                                       callback.failure(responseObject);
+                                               }
+                                               else{
+                                                       callback.failure.apply(callback.scope,[responseObject]);
+                                               }
+                                       }
+                       }
+               }
+
+               this.releaseObject(o);
+       },
+
+  /**
+   * This method evaluates the server response, creates and returns the results via
+   * its properties.  Success and failure cases will differ in the response
+   * object's property values.
+   * @private
+   * @param {object} o The connection object
+   * @param {} callbackArg User-defined argument or arguments to be passed to the callback
+   * @return object
+   */
+       createResponseObject:function(o, callbackArg)
+       {
+               var obj = {};
+               var headerObj = {};
+
+               try
+               {
+                       var headerStr = o.conn.getAllResponseHeaders();
+                       var header = headerStr.split("\n");
+                       for(var i=0; i < header.length; i++){
+                               var delimitPos = header[i].indexOf(':');
+                               if(delimitPos != -1){
+                                       headerObj[header[i].substring(0,delimitPos)] = header[i].substring(delimitPos+1);
+                               }
+                       }
+
+                       obj.tId = o.tId;
+                       obj.status = o.conn.status;
+                       obj.statusText = o.conn.statusText;
+                       obj.getResponseHeader = headerObj;
+                       obj.getAllResponseHeaders = headerStr;
+                       obj.responseText = o.conn.responseText;
+                       obj.responseXML = o.conn.responseXML;
+                       if(typeof callbackArg !== undefined){
+                               obj.argument = callbackArg;
+                       }
+               }
+               catch(e){}
+               finally
+               {
+                       return obj;
+               }
+       },
+
+  /**
+   * If a transaction cannot be completed due to dropped or closed connections,
+   * there may be not be enough information to build a full response object.
+   * The failure callback will be fired and this specific condition can be identified
+   * by a status property value of 0.
+   * @private
+   * @param {int} tId Transaction Id
+   * @param callbackArg The user-defined arguments
+   * @return object
+   */
+       createExceptionObject:function(tId, callbackArg)
+       {
+               var COMM_CODE = 0;
+               var COMM_ERROR = 'communication failure';
+
+               var obj = {};
+
+               obj.tId = tId;
+               obj.status = COMM_CODE;
+               obj.statusText = COMM_ERROR;
+               if(callbackArg){
+                       obj.argument = callbackArg;
+               }
+
+               return obj;
+       },
+
+  /**
+   * Public method that stores the custom HTTP headers for each transaction.
+   * @public
+   * @param {string} label The HTTP header label
+   * @param {string} value The HTTP header value
+   * @return void
+   */
+       initHeader:function(label,value)
+       {
+               if(this._http_header[label] === undefined){
+                       this._http_header[label] = value;
+               }
+               else{
+                       this._http_header[label] =  value + "," + this._http_header[label];
+               }
+
+               this._has_http_headers = true;
+       },
+
+  /**
+   * Accessor that sets the HTTP headers for each transaction.
+   * @private
+   * @param {object} o The connection object for the transaction.
+   * @return void
+   */
+       setHeader:function(o)
+       {
+               for(var prop in this._http_header){
+                       o.conn.setRequestHeader(prop, this._http_header[prop]);
+               }
+               delete this._http_header;
+
+               this._http_header = {};
+               this._has_http_headers = false;
+       },
+
+  /**
+   * This method assembles the form label and value pairs and
+   * constructs an encoded string.
+   * asyncRequest() will automatically initialize the
+   * transaction with a HTTP header Content-Type of
+   * application/x-www-form-urlencoded.
+   * @public
+   * @param {string || object} form id or name attribute, or form object.
+   * @return void
+   */
+       setForm:function(formId)
+       {
+               this._sFormData = '';
+               if(typeof formId == 'string'){
+                       // Determine if the argument is a form id or a form name.
+                       // Note form name usage is deprecated by supported
+                       // here for legacy reasons.
+                       var oForm = (document.getElementById(formId) || document.forms[formId] );
+               }
+               else if(typeof formId == 'object'){
+                       var oForm = formId;
+               }
+               else{
+                       return;
+               }
+               var oElement, oName, oValue, oDisabled;
+               var hasSubmit = false;
+
+               // Iterate over the form elements collection to construct the
+               // label-value pairs.
+               for (var i=0; i<oForm.elements.length; i++){
+                       oDisabled = oForm.elements[i].disabled;
+                       // If the name attribute is not populated, the form field's
+                       // value will not be submitted.
+                       if(oForm.elements[i].name != ""){
+                               oElement = oForm.elements[i];
+                               oName = oForm.elements[i].name;
+                               oValue = oForm.elements[i].value;
+                       }
+
+                       // Do not submit fields that are disabled.
+                       if(!oDisabled)
+                       {
+                               switch (oElement.type)
+                               {
+                                       case 'select-one':
+                                       case 'select-multiple':
+                                               for(var j=0; j<oElement.options.length; j++){
+                                                       if(oElement.options[j].selected){
+                                                               this._sFormData += encodeURIComponent(oName) + '=' + encodeURIComponent(oElement.options[j].value || oElement.options[j].text) + '&';
+                                                       }
+                                               }
+                                               break;
+                                       case 'radio':
+                                       case 'checkbox':
+                                               if(oElement.checked){
+                                                       this._sFormData += encodeURIComponent(oName) + '=' + encodeURIComponent(oValue) + '&';
+                                               }
+                                               break;
+                                       case 'file':
+                                       //      this._sFormData += encodeURIComponent(oName) + '=' + encodeURIComponent(oValue) + '&';
+                                       // stub case as XMLHttpRequest will only send the file path as a string.
+                                       case undefined:
+                                       // stub case for fieldset element which returns undefined.
+                                       case 'reset':
+                                       // stub case for input type reset button.
+                                       case 'button':
+                                       // stub case for input type button elements.
+                                               break;
+                                       case 'submit':
+                                               if(hasSubmit == false){
+                                                       this._sFormData += encodeURIComponent(oName) + '=' + encodeURIComponent(oValue) + '&';
+                                                       hasSubmit = true;
+                                               }
+                                               break;
+                                       default:
+                                               this._sFormData += encodeURIComponent(oName) + '=' + encodeURIComponent(oValue) + '&';
+                                               break;
+                               }
+                       }
+               }
+
+               this._isFormSubmit = true;
+               this._sFormData = this._sFormData.substr(0, this._sFormData.length - 1);
+       },
+
+  /**
+   * Public method to terminate a transaction, if it has not reached readyState 4.
+   * @public
+   * @param {object} o The connection object returned by asyncRequest.
+   * @return void
+   */
+       abort:function(o)
+       {
+               if(this.isCallInProgress(o)){
+                       window.clearInterval(this._poll[o.tId]);
+                       this._poll.splice(o.tId);
+                       o.conn.abort();
+                       this.releaseObject(o);
+
+                       return true;
+               }
+               else{
+                       return false;
+               }
+       },
+
+  /**
+   * Public method to check if the transaction is still being processed.
+   * @public
+   * @param {object} o The connection object returned by asyncRequest
+   * @return boolean
+   */
+       isCallInProgress:function(o)
+       {
+               // if the XHR object assigned to the transaction has not been dereferenced,
+               // then check its readyState status.  Otherwise, return false.
+               if(o.conn){
+                       return o.conn.readyState != 4 && o.conn.readyState != 0;
+               }
+               else{
+                       //The XHR object has been destroyed.
+                       return false;
+               }
+       },
+
+  /**
+   * Dereference the XHR instance and the connection object after the transaction is completed.
+   * @private
+   * @param {object} o The connection object
+   * @return void
+   */
+       releaseObject:function(o)
+       {
+               //dereference the XHR instance.
+               o.conn = null;
+               //dereference the connection object.
+               o = null;
+       }
+};
diff --git a/apps/fjsc-responder/resources/yahoo/event.js b/apps/fjsc-responder/resources/yahoo/event.js
new file mode 100644 (file)
index 0000000..cc2a087
--- /dev/null
@@ -0,0 +1,1096 @@
+/*                                                                                                                                                      \r
+Copyright (c) 2006, Yahoo! Inc. All rights reserved.                                                                                                    \r
+Code licensed under the BSD License:                                                                                                                    \r
+http://developer.yahoo.net/yui/license.txt                                                                                                              \r
+version: 0.10.0                                                                                                                                         \r
+*/ \r
+\r
+/**\r
+ * The CustomEvent class lets you define events for your application\r
+ * that can be subscribed to by one or more independent component.\r
+ *\r
+ * @param {String} type The type of event, which is passed to the callback\r
+ *                 when the event fires\r
+ * @param {Object} oScope The context the event will fire from.  "this" will\r
+ *                 refer to this object in the callback.  Default value: \r
+ *                 the window object.  The listener can override this.\r
+ * @constructor\r
+ */\r
+YAHOO.util.CustomEvent = function(type, oScope) {\r
+    /**\r
+     * The type of event, returned to subscribers when the event fires\r
+     * @type string\r
+     */\r
+    this.type = type;\r
+\r
+    /**\r
+     * The scope the the event will fire from by default.  Defaults to the window \r
+     * obj\r
+     * @type object\r
+     */\r
+    this.scope = oScope || window;\r
+\r
+    /**\r
+     * The subscribers to this event\r
+     * @type Subscriber[]\r
+     */\r
+    this.subscribers = [];\r
+\r
+    // Register with the event utility for automatic cleanup.  Made optional\r
+    // so that CustomEvent can be used independently of pe.event\r
+    if (YAHOO.util.Event) { \r
+        YAHOO.util.Event.regCE(this);\r
+    }\r
+};\r
+\r
+YAHOO.util.CustomEvent.prototype = {\r
+    /**\r
+     * Subscribes the caller to this event\r
+     * @param {Function} fn       The function to execute\r
+     * @param {Object}   obj      An object to be passed along when the event fires\r
+     * @param {boolean}  bOverride If true, the obj passed in becomes the execution\r
+     *                            scope of the listener\r
+     */\r
+    subscribe: function(fn, obj, bOverride) {\r
+        this.subscribers.push( new YAHOO.util.Subscriber(fn, obj, bOverride) );\r
+    },\r
+\r
+    /**\r
+     * Unsubscribes the caller from this event\r
+     * @param {Function} fn  The function to execute\r
+     * @param {Object}   obj An object to be passed along when the event fires\r
+     * @return {boolean} True if the subscriber was found and detached.\r
+     */\r
+    unsubscribe: function(fn, obj) {\r
+        var found = false;\r
+        for (var i=0, len=this.subscribers.length; i<len; ++i) {\r
+            var s = this.subscribers[i];\r
+            if (s && s.contains(fn, obj)) {\r
+                this._delete(i);\r
+                found = true;\r
+            }\r
+        }\r
+\r
+        return found;\r
+    },\r
+\r
+    /**\r
+     * Notifies the subscribers.  The callback functions will be executed\r
+     * from the scope specified when the event was created, and with the following\r
+     * parameters:\r
+     *   <pre>\r
+     *   - The type of event\r
+     *   - All of the arguments fire() was executed with as an array\r
+     *   - The custom object (if any) that was passed into the subscribe() method\r
+     *   </pre>\r
+     *   \r
+     * @param {Array} an arbitrary set of parameters to pass to the handler\r
+     */\r
+    fire: function() {\r
+        for (var i=0, len=this.subscribers.length; i<len; ++i) {\r
+            var s = this.subscribers[i];\r
+            if (s) {\r
+                var scope = (s.override) ? s.obj : this.scope;\r
+                s.fn.call(scope, this.type, arguments, s.obj);\r
+            }\r
+        }\r
+    },\r
+\r
+    /**\r
+     * Removes all listeners\r
+     */\r
+    unsubscribeAll: function() {\r
+        for (var i=0, len=this.subscribers.length; i<len; ++i) {\r
+            this._delete(i);\r
+        }\r
+    },\r
+\r
+    /**\r
+     * @private\r
+     */\r
+    _delete: function(index) {\r
+        var s = this.subscribers[index];\r
+        if (s) {\r
+            delete s.fn;\r
+            delete s.obj;\r
+        }\r
+\r
+        delete this.subscribers[index];\r
+    }\r
+};\r
+\r
+/////////////////////////////////////////////////////////////////////\r
+\r
+/**\r
+ * @class Stores the subscriber information to be used when the event fires.\r
+ * @param {Function} fn       The function to execute\r
+ * @param {Object}   obj      An object to be passed along when the event fires\r
+ * @param {boolean}  bOverride If true, the obj passed in becomes the execution\r
+ *                            scope of the listener\r
+ * @constructor\r
+ */\r
+YAHOO.util.Subscriber = function(fn, obj, bOverride) {\r
+    /**\r
+     * The callback that will be execute when the event fires\r
+     * @type function\r
+     */\r
+    this.fn = fn;\r
+\r
+    /**\r
+     * An optional custom object that will passed to the callback when\r
+     * the event fires\r
+     * @type object\r
+     */\r
+    this.obj = obj || null;\r
+\r
+    /**\r
+     * The default execution scope for the event listener is defined when the\r
+     * event is created (usually the object which contains the event).\r
+     * By setting override to true, the execution scope becomes the custom\r
+     * object passed in by the subscriber\r
+     * @type boolean\r
+     */\r
+    this.override = (bOverride);\r
+};\r
+\r
+/**\r
+ * Returns true if the fn and obj match this objects properties.\r
+ * Used by the unsubscribe method to match the right subscriber.\r
+ *\r
+ * @param {Function} fn the function to execute\r
+ * @param {Object} obj an object to be passed along when the event fires\r
+ * @return {boolean} true if the supplied arguments match this \r
+ *                   subscriber's signature.\r
+ */\r
+YAHOO.util.Subscriber.prototype.contains = function(fn, obj) {\r
+    return (this.fn == fn && this.obj == obj);\r
+};\r
+\r
+/* Copyright (c) 2006 Yahoo! Inc. All rights reserved. */\r
+\r
+// Only load this library once.  If it is loaded a second time, existing\r
+// events cannot be detached.\r
+if (!YAHOO.util.Event) {\r
+\r
+/**\r
+ * @class\r
+ * The event utility provides functions to add and remove event listeners,\r
+ * event cleansing.  It also tries to automatically remove listeners it\r
+ * registers during the unload event.\r
+ * @constructor\r
+ */\r
+    YAHOO.util.Event = function() {\r
+\r
+        /**\r
+         * True after the onload event has fired\r
+         * @type boolean\r
+         * @private\r
+         */\r
+        var loadComplete =  false;\r
+\r
+        /**\r
+         * Cache of wrapped listeners\r
+         * @type array\r
+         * @private\r
+         */\r
+        var listeners = [];\r
+\r
+        /**\r
+         * Listeners that will be attached during the onload event\r
+         * @type array\r
+         * @private\r
+         */\r
+        var delayedListeners = [];\r
+\r
+        /**\r
+         * User-defined unload function that will be fired before all events\r
+         * are detached\r
+         * @type array\r
+         * @private\r
+         */\r
+        var unloadListeners = [];\r
+\r
+        /**\r
+         * Cache of the custom events that have been defined.  Used for\r
+         * automatic cleanup\r
+         * @type array\r
+         * @private\r
+         */\r
+        var customEvents = [];\r
+\r
+        /**\r
+         * Cache of DOM0 event handlers to work around issues with DOM2 events\r
+         * in Safari\r
+         * @private\r
+         */\r
+        var legacyEvents = [];\r
+\r
+        /**\r
+         * Listener stack for DOM0 events\r
+         * @private\r
+         */\r
+        var legacyHandlers = [];\r
+\r
+        /**\r
+         * The number of times to poll after window.onload.  This number is\r
+         * increased if additional late-bound handlers are requested after\r
+         * the page load.\r
+         * @private\r
+         */\r
+        var retryCount = 0;\r
+\r
+        /**\r
+         * onAvailable listeners\r
+         * @private\r
+         */\r
+        var onAvailStack = [];\r
+\r
+        /**\r
+         * Lookup table for legacy events\r
+         * @private\r
+         */\r
+        var legacyMap = [];\r
+\r
+        /**\r
+         * Counter for auto id generation\r
+         * @private\r
+         */\r
+        var counter = 0;\r
+\r
+        return { // PREPROCESS\r
+\r
+            /**\r
+             * The number of times we should look for elements that are not\r
+             * in the DOM at the time the event is requested after the document\r
+             * has been loaded.  The default is 200@50 ms, so it will poll\r
+             * for 10 seconds or until all outstanding handlers are bound\r
+             * (whichever comes first).\r
+             * @type int\r
+             */\r
+            POLL_RETRYS: 200,\r
+\r
+            /**\r
+             * The poll interval in milliseconds\r
+             * @type int\r
+             */\r
+            POLL_INTERVAL: 50,\r
+\r
+            /**\r
+             * Element to bind, int constant\r
+             * @type int\r
+             */\r
+            EL: 0,\r
+\r
+            /**\r
+             * Type of event, int constant\r
+             * @type int\r
+             */\r
+            TYPE: 1,\r
+\r
+            /**\r
+             * Function to execute, int constant\r
+             * @type int\r
+             */\r
+            FN: 2,\r
+\r
+            /**\r
+             * Function wrapped for scope correction and cleanup, int constant\r
+             * @type int\r
+             */\r
+            WFN: 3,\r
+\r
+            /**\r
+             * Object passed in by the user that will be returned as a \r
+             * parameter to the callback, int constant\r
+             * @type int\r
+             */\r
+            SCOPE: 3,\r
+\r
+            /**\r
+             * Adjusted scope, either the element we are registering the event\r
+             * on or the custom object passed in by the listener, int constant\r
+             * @type int\r
+             */\r
+            ADJ_SCOPE: 4,\r
+\r
+            /**\r
+             * Safari detection is necessary to work around the preventDefault\r
+             * bug that makes it so you can't cancel a href click from the \r
+             * handler.  There is not a capabilities check we can use here.\r
+             * @private\r
+             */\r
+            isSafari: (/Safari|Konqueror|KHTML/gi).test(navigator.userAgent),\r
+\r
+            /**\r
+             * IE detection needed to properly calculate pageX and pageY.  \r
+             * capabilities checking didn't seem to work because another \r
+             * browser that does not provide the properties have the values \r
+             * calculated in a different manner than IE.\r
+             * @private\r
+             */\r
+            isIE: (!this.isSafari && !navigator.userAgent.match(/opera/gi) && \r
+                    navigator.userAgent.match(/msie/gi)),\r
+\r
+            /**\r
+             * @private\r
+             */\r
+            addDelayedListener: function(el, sType, fn, oScope, bOverride) {\r
+                delayedListeners[delayedListeners.length] =\r
+                    [el, sType, fn, oScope, bOverride];\r
+\r
+                // If this happens after the inital page load, we need to\r
+                // reset the poll counter so that we continue to search for\r
+                // the element for a fixed period of time.\r
+                if (loadComplete) {\r
+                    retryCount = this.POLL_RETRYS;\r
+                    this.startTimeout(0);\r
+                    // this._tryPreloadAttach();\r
+                }\r
+            },\r
+\r
+            /**\r
+             * @private\r
+             */\r
+            startTimeout: function(interval) {\r
+                var i = (interval || interval === 0) ? interval : this.POLL_INTERVAL;\r
+                var self = this;\r
+                var callback = function() { self._tryPreloadAttach(); };\r
+                this.timeout = setTimeout(callback, i);\r
+            },\r
+\r
+            /**\r
+             * Executes the supplied callback when the item with the supplied\r
+             * id is found.  This is meant to be used to execute behavior as\r
+             * soon as possible as the page loads.  If you use this after the\r
+             * initial page load it will poll for a fixed time for the element.\r
+             * The number of times it will poll and the frequency are\r
+             * configurable.  By default it will poll for 10 seconds.\r
+             * @param {string} p_id the id of the element to look for.\r
+             * @param {function} p_fn what to execute when the element is found.\r
+             * @param {object} p_obj an optional object to be passed back as\r
+             * a parameter to p_fn.\r
+             * @param {boolean} p_override If set to true, p_fn will execute\r
+             * in the scope of p_obj\r
+             *\r
+             */\r
+            onAvailable: function(p_id, p_fn, p_obj, p_override) {\r
+                onAvailStack.push( { id:       p_id, \r
+                                     fn:       p_fn, \r
+                                     obj:      p_obj, \r
+                                     override: p_override } );\r
+\r
+                retryCount = this.POLL_RETRYS;\r
+                this.startTimeout(0);\r
+                // this._tryPreloadAttach();\r
+            },\r
+\r
+            /**\r
+             * Appends an event handler\r
+             *\r
+             * @param {Object}   el        The html element to assign the \r
+             *                             event to\r
+             * @param {String}   sType     The type of event to append\r
+             * @param {Function} fn        The method the event invokes\r
+             * @param {Object}   oScope    An arbitrary object that will be \r
+             *                             passed as a parameter to the handler\r
+             * @param {boolean}  bOverride If true, the obj passed in becomes\r
+             *                             the execution scope of the listener\r
+             * @return {boolean} True if the action was successful or defered,\r
+             *                        false if one or more of the elements \r
+             *                        could not have the event bound to it.\r
+             */\r
+            addListener: function(el, sType, fn, oScope, bOverride) {\r
+\r
+                if (!fn || !fn.call) {\r
+                    return false;\r
+                }\r
+\r
+                // The el argument can be an array of elements or element ids.\r
+                if ( this._isValidCollection(el)) {\r
+                    var ok = true;\r
+                    for (var i=0,len=el.length; i<len; ++i) {\r
+                        ok = ( this.on(el[i], \r
+                                       sType, \r
+                                       fn, \r
+                                       oScope, \r
+                                       bOverride) && ok );\r
+                    }\r
+                    return ok;\r
+\r
+                } else if (typeof el == "string") {\r
+                    var oEl = this.getEl(el);\r
+                    // If the el argument is a string, we assume it is \r
+                    // actually the id of the element.  If the page is loaded\r
+                    // we convert el to the actual element, otherwise we \r
+                    // defer attaching the event until onload event fires\r
+\r
+                    // check to see if we need to delay hooking up the event \r
+                    // until after the page loads.\r
+                    if (loadComplete && oEl) {\r
+                        el = oEl;\r
+                    } else {\r
+                        // defer adding the event until onload fires\r
+                        this.addDelayedListener(el, \r
+                                                sType, \r
+                                                fn, \r
+                                                oScope, \r
+                                                bOverride);\r
+\r
+                        return true;\r
+                    }\r
+                }\r
+\r
+                // Element should be an html element or an array if we get \r
+                // here.\r
+                if (!el) {\r
+                    return false;\r
+                }\r
+\r
+                // we need to make sure we fire registered unload events \r
+                // prior to automatically unhooking them.  So we hang on to \r
+                // these instead of attaching them to the window and fire the\r
+                // handles explicitly during our one unload event.\r
+                if ("unload" == sType && oScope !== this) {\r
+                    unloadListeners[unloadListeners.length] =\r
+                            [el, sType, fn, oScope, bOverride];\r
+                    return true;\r
+                }\r
+\r
+\r
+                // if the user chooses to override the scope, we use the custom\r
+                // object passed in, otherwise the executing scope will be the\r
+                // HTML element that the event is registered on\r
+                var scope = (bOverride) ? oScope : el;\r
+\r
+                // wrap the function so we can return the oScope object when\r
+                // the event fires;\r
+                var wrappedFn = function(e) {\r
+                        return fn.call(scope, YAHOO.util.Event.getEvent(e), \r
+                                oScope);\r
+                    };\r
+\r
+                var li = [el, sType, fn, wrappedFn, scope];\r
+                var index = listeners.length;\r
+                // cache the listener so we can try to automatically unload\r
+                listeners[index] = li;\r
+\r
+                if (this.useLegacyEvent(el, sType)) {\r
+                    var legacyIndex = this.getLegacyIndex(el, sType);\r
+                    if (legacyIndex == -1) {\r
+\r
+                        legacyIndex = legacyEvents.length;\r
+                        legacyMap[el.id + sType] = legacyIndex;\r
+\r
+                        // cache the signature for the DOM0 event, and \r
+                        // include the existing handler for the event, if any\r
+                        legacyEvents[legacyIndex] = \r
+                            [el, sType, el["on" + sType]];\r
+                        legacyHandlers[legacyIndex] = [];\r
+\r
+                        el["on" + sType] = \r
+                            function(e) {\r
+                                YAHOO.util.Event.fireLegacyEvent(\r
+                                    YAHOO.util.Event.getEvent(e), legacyIndex);\r
+                            };\r
+                    }\r
+\r
+                    // add a reference to the wrapped listener to our custom\r
+                    // stack of events\r
+                    legacyHandlers[legacyIndex].push(index);\r
+\r
+                // DOM2 Event model\r
+                } else if (el.addEventListener) {\r
+                    el.addEventListener(sType, wrappedFn, false);\r
+                // Internet Explorer abstraction\r
+                } else if (el.attachEvent) {\r
+                    el.attachEvent("on" + sType, wrappedFn);\r
+                }\r
+\r
+                return true;\r
+                \r
+            },\r
+\r
+            /**\r
+             * Shorthand for YAHOO.util.Event.addListener\r
+             * @type function\r
+             */\r
+            // on: this.addListener,\r
+\r
+            /**\r
+             * When using legacy events, the handler is routed to this object\r
+             * so we can fire our custom listener stack.\r
+             * @private\r
+             */\r
+            fireLegacyEvent: function(e, legacyIndex) {\r
+                var ok = true;\r
+\r
+                var le = legacyHandlers[legacyIndex];\r
+                for (var i=0,len=le.length; i<len; ++i) {\r
+                    var index = le[i];\r
+                    if (index) {\r
+                        var li = listeners[index];\r
+                        if ( li && li[this.WFN] ) {\r
+                            var scope = li[this.ADJ_SCOPE];\r
+                            var ret = li[this.WFN].call(scope, e);\r
+                            ok = (ok && ret);\r
+                        } else {\r
+                            // This listener was removed, so delete it from\r
+                            // the array\r
+                            delete le[i];\r
+                        }\r
+                    }\r
+                }\r
+\r
+                return ok;\r
+            },\r
+\r
+            /**\r
+             * Returns the legacy event index that matches the supplied \r
+             * signature\r
+             * @private\r
+             */\r
+            getLegacyIndex: function(el, sType) {\r
+                /*\r
+                for (var i=0,len=legacyEvents.length; i<len; ++i) {\r
+                    var le = legacyEvents[i];\r
+                    if (le && le[0] === el && le[1] === sType) {\r
+                        return i;\r
+                    }\r
+                }\r
+                return -1;\r
+                */\r
+\r
+                var key = this.generateId(el) + sType;\r
+                if (typeof legacyMap[key] == "undefined") { \r
+                    return -1;\r
+                } else {\r
+                    return legacyMap[key];\r
+                }\r
+\r
+            },\r
+\r
+            /**\r
+             * Logic that determines when we should automatically use legacy\r
+             * events instead of DOM2 events.\r
+             * @private\r
+             */\r
+            useLegacyEvent: function(el, sType) {\r
+\r
+                if (!el.addEventListener && !el.attachEvent) {\r
+                    return true;\r
+                } else if (this.isSafari) {\r
+                    if ("click" == sType || "dblclick" == sType) {\r
+                        return true;\r
+                    }\r
+                }\r
+\r
+                return false;\r
+            },\r
+                    \r
+            /**\r
+             * Removes an event handler\r
+             *\r
+             * @param {Object} el the html element or the id of the element to \r
+             * assign the event to.\r
+             * @param {String} sType the type of event to remove\r
+             * @param {Function} fn the method the event invokes\r
+             * @return {boolean} true if the unbind was successful, false \r
+             * otherwise\r
+             */\r
+            removeListener: function(el, sType, fn, index) {\r
+\r
+                if (!fn || !fn.call) {\r
+                    return false;\r
+                }\r
+\r
+                // The el argument can be a string\r
+                if (typeof el == "string") {\r
+                    el = this.getEl(el);\r
+                // The el argument can be an array of elements or element ids.\r
+                } else if ( this._isValidCollection(el)) {\r
+                    var ok = true;\r
+                    for (var i=0,len=el.length; i<len; ++i) {\r
+                        ok = ( this.removeListener(el[i], sType, fn) && ok );\r
+                    }\r
+                    return ok;\r
+                }\r
+\r
+                if ("unload" == sType) {\r
+\r
+                    for (i=0, len=unloadListeners.length; i<len; i++) {\r
+                        var li = unloadListeners[i];\r
+                        if (li && \r
+                            li[0] == el && \r
+                            li[1] == sType && \r
+                            li[2] == fn) {\r
+                                delete unloadListeners[i];\r
+                                return true;\r
+                        }\r
+                    }\r
+\r
+                    return false;\r
+                }\r
+\r
+                var cacheItem = null;\r
+  \r
+                if ("undefined" == typeof index) {\r
+                    index = this._getCacheIndex(el, sType, fn);\r
+                }\r
+\r
+                if (index >= 0) {\r
+                    cacheItem = listeners[index];\r
+                }\r
+\r
+                if (!el || !cacheItem) {\r
+                    return false;\r
+                }\r
+\r
+\r
+                if (el.removeEventListener) {\r
+                    el.removeEventListener(sType, cacheItem[this.WFN], false);\r
+                } else if (el.detachEvent) {\r
+                    el.detachEvent("on" + sType, cacheItem[this.WFN]);\r
+                }\r
+\r
+                // removed the wrapped handler\r
+                delete listeners[index][this.WFN];\r
+                delete listeners[index][this.FN];\r
+                delete listeners[index];\r
+\r
+                return true;\r
+\r
+            },\r
+\r
+            /**\r
+             * Returns the event's target element\r
+             * @param {Event} ev the event\r
+             * @param {boolean} resolveTextNode when set to true the target's\r
+             *                  parent will be returned if the target is a \r
+             *                  text node\r
+             * @return {HTMLElement} the event's target\r
+             */\r
+            getTarget: function(ev, resolveTextNode) {\r
+                var t = ev.target || ev.srcElement;\r
+\r
+                if (resolveTextNode && t && "#text" == t.nodeName) {\r
+                    return t.parentNode;\r
+                } else {\r
+                    return t;\r
+                }\r
+            },\r
+\r
+            /**\r
+             * Returns the event's pageX\r
+             * @param {Event} ev the event\r
+             * @return {int} the event's pageX\r
+             */\r
+            getPageX: function(ev) {\r
+                var x = ev.pageX;\r
+                if (!x && 0 !== x) {\r
+                    x = ev.clientX || 0;\r
+\r
+                    if ( this.isIE ) {\r
+                        x += this._getScrollLeft();\r
+                    }\r
+                }\r
+\r
+                return x;\r
+            },\r
+\r
+            /**\r
+             * Returns the event's pageY\r
+             * @param {Event} ev the event\r
+             * @return {int} the event's pageY\r
+             */\r
+            getPageY: function(ev) {\r
+                var y = ev.pageY;\r
+                if (!y && 0 !== y) {\r
+                    y = ev.clientY || 0;\r
+\r
+                    if ( this.isIE ) {\r
+                        y += this._getScrollTop();\r
+                    }\r
+                }\r
+\r
+                return y;\r
+            },\r
+\r
+            /**\r
+             * Returns the pageX and pageY properties as an indexed array.\r
+             * @type int[]\r
+             */\r
+            getXY: function(ev) {\r
+                return [this.getPageX(ev), this.getPageY(ev)];\r
+            },\r
+\r
+            /**\r
+             * Returns the event's related target \r
+             * @param {Event} ev the event\r
+             * @return {HTMLElement} the event's relatedTarget\r
+             */\r
+            getRelatedTarget: function(ev) {\r
+                var t = ev.relatedTarget;\r
+                if (!t) {\r
+                    if (ev.type == "mouseout") {\r
+                        t = ev.toElement;\r
+                    } else if (ev.type == "mouseover") {\r
+                        t = ev.fromElement;\r
+                    }\r
+                }\r
+\r
+                return t;\r
+            },\r
+\r
+            /**\r
+             * Returns the time of the event.  If the time is not included, the\r
+             * event is modified using the current time.\r
+             * @param {Event} ev the event\r
+             * @return {Date} the time of the event\r
+             */\r
+            getTime: function(ev) {\r
+                if (!ev.time) {\r
+                    var t = new Date().getTime();\r
+                    try {\r
+                        ev.time = t;\r
+                    } catch(e) { \r
+                        // can't set the time property  \r
+                        return t;\r
+                    }\r
+                }\r
+\r
+                return ev.time;\r
+            },\r
+\r
+            /**\r
+             * Convenience method for stopPropagation + preventDefault\r
+             * @param {Event} ev the event\r
+             */\r
+            stopEvent: function(ev) {\r
+                this.stopPropagation(ev);\r
+                this.preventDefault(ev);\r
+            },\r
+\r
+            /**\r
+             * Stops event propagation\r
+             * @param {Event} ev the event\r
+             */\r
+            stopPropagation: function(ev) {\r
+                if (ev.stopPropagation) {\r
+                    ev.stopPropagation();\r
+                } else {\r
+                    ev.cancelBubble = true;\r
+                }\r
+            },\r
+\r
+            /**\r
+             * Prevents the default behavior of the event\r
+             * @param {Event} ev the event\r
+             */\r
+            preventDefault: function(ev) {\r
+                if (ev.preventDefault) {\r
+                    ev.preventDefault();\r
+                } else {\r
+                    ev.returnValue = false;\r
+                }\r
+            },\r
+             \r
+            /**\r
+             * Finds the event in the window object, the caller's arguments, or\r
+             * in the arguments of another method in the callstack.  This is\r
+             * executed automatically for events registered through the event\r
+             * manager, so the implementer should not normally need to execute\r
+             * this function at all.\r
+             * @param {Event} the event parameter from the handler\r
+             * @return {Event} the event \r
+             */\r
+            getEvent: function(e) {\r
+                var ev = e || window.event;\r
+\r
+                if (!ev) {\r
+                    var c = this.getEvent.caller;\r
+                    while (c) {\r
+                        ev = c.arguments[0];\r
+                        if (ev && Event == ev.constructor) {\r
+                            break;\r
+                        }\r
+                        c = c.caller;\r
+                    }\r
+                }\r
+\r
+                return ev;\r
+            },\r
+\r
+            /**\r
+             * Returns the charcode for an event\r
+             * @param {Event} ev the event\r
+             * @return {int} the event's charCode\r
+             */\r
+            getCharCode: function(ev) {\r
+                return ev.charCode || ((ev.type == "keypress") ? ev.keyCode : 0);\r
+            },\r
+\r
+            /**\r
+             * @private\r
+             * Locating the saved event handler data by function ref\r
+             */\r
+            _getCacheIndex: function(el, sType, fn) {\r
+                for (var i=0,len=listeners.length; i<len; ++i) {\r
+                    var li = listeners[i];\r
+                    if ( li                 && \r
+                         li[this.FN] == fn  && \r
+                         li[this.EL] == el  && \r
+                         li[this.TYPE] == sType ) {\r
+                        return i;\r
+                    }\r
+                }\r
+\r
+                return -1;\r
+            },\r
+\r
+            /**\r
+             * Generates an unique ID for the element if it does not already \r
+             * have one.\r
+             * @param el the element\r
+             * @return {string} the id of the element\r
+             */\r
+            generateId: function(el) {\r
+                var id = el.id;\r
+\r
+                if (!id) {\r
+                    id = "yuievtautoid-" + (counter++);\r
+                    el.id = id;\r
+                }\r
+\r
+                return id;\r
+            },\r
+\r
+            /**\r
+             * We want to be able to use getElementsByTagName as a collection\r
+             * to attach a group of events to.  Unfortunately, different \r
+             * browsers return different types of collections.  This function\r
+             * tests to determine if the object is array-like.  It will also \r
+             * fail if the object is an array, but is empty.\r
+             * @param o the object to test\r
+             * @return {boolean} true if the object is array-like and populated\r
+             * @private\r
+             */\r
+            _isValidCollection: function(o) {\r
+\r
+                return ( o                    && // o is something\r
+                         o.length             && // o is indexed\r
+                         typeof o != "string" && // o is not a string\r
+                         !o.tagName           && // o is not an HTML element\r
+                         !o.alert             && // o is not a window\r
+                         typeof o[0] != "undefined" );\r
+\r
+            },\r
+\r
+            /**\r
+             * @private\r
+             * DOM element cache\r
+             */\r
+            elCache: {},\r
+\r
+            /**\r
+             * We cache elements bound by id because when the unload event \r
+             * fires, we can no longer use document.getElementById\r
+             * @private\r
+             */\r
+            getEl: function(id) {\r
+                return document.getElementById(id);\r
+            },\r
+\r
+            /**\r
+             * Clears the element cache\r
+             * @deprecated\r
+             * @private\r
+             */\r
+            clearCache: function() { },\r
+\r
+            /**\r
+             * Called by CustomEvent instances to provide a handle to the \r
+             * event * that can be removed later on.  Should be package \r
+             * protected.\r
+             * @private\r
+             */\r
+            regCE: function(ce) {\r
+                customEvents.push(ce);\r
+            },\r
+\r
+            /**\r
+             * @private\r
+             * hook up any deferred listeners\r
+             */\r
+            _load: function(e) {\r
+                loadComplete = true;\r
+            },\r
+\r
+            /**\r
+             * Polling function that runs before the onload event fires, \r
+             * attempting * to attach to DOM Nodes as soon as they are \r
+             * available\r
+             * @private\r
+             */\r
+            _tryPreloadAttach: function() {\r
+\r
+                if (this.locked) {\r
+                    return false;\r
+                }\r
+\r
+                this.locked = true;\r
+\r
+\r
+                // keep trying until after the page is loaded.  We need to \r
+                // check the page load state prior to trying to bind the \r
+                // elements so that we can be certain all elements have been \r
+                // tested appropriately\r
+                var tryAgain = !loadComplete;\r
+                if (!tryAgain) {\r
+                    tryAgain = (retryCount > 0);\r
+                }\r
+\r
+                // Delayed listeners\r
+                var stillDelayed = [];\r
+\r
+                for (var i=0,len=delayedListeners.length; i<len; ++i) {\r
+                    var d = delayedListeners[i];\r
+                    // There may be a race condition here, so we need to \r
+                    // verify the array element is usable.\r
+                    if (d) {\r
+\r
+                        // el will be null if document.getElementById did not\r
+                        // work\r
+                        var el = this.getEl(d[this.EL]);\r
+\r
+                        if (el) {\r
+                            this.on(el, d[this.TYPE], d[this.FN], \r
+                                    d[this.SCOPE], d[this.ADJ_SCOPE]);\r
+                            delete delayedListeners[i];\r
+                        } else {\r
+                            stillDelayed.push(d);\r
+                        }\r
+                    }\r
+                }\r
+\r
+                delayedListeners = stillDelayed;\r
+\r
+                // onAvailable\r
+                notAvail = [];\r
+                for (i=0,len=onAvailStack.length; i<len ; ++i) {\r
+                    var item = onAvailStack[i];\r
+                    if (item) {\r
+                        el = this.getEl(item.id);\r
+\r
+                        if (el) {\r
+                            var scope = (item.override) ? item.obj : el;\r
+                            item.fn.call(scope, item.obj);\r
+                            delete onAvailStack[i];\r
+                        } else {\r
+                            notAvail.push(item);\r
+                        }\r
+                    }\r
+                }\r
+\r
+                retryCount = (stillDelayed.length === 0 && \r
+                                    notAvail.length === 0) ? 0 : retryCount - 1;\r
+\r
+                if (tryAgain) {\r
+                    this.startTimeout();\r
+                }\r
+\r
+                this.locked = false;\r
+\r
+            },\r
+\r
+            /**\r
+             * Removes all listeners registered by pe.event.  Called \r
+             * automatically during the unload event.\r
+             * @private\r
+             */\r
+            _unload: function(e, me) {\r
+                for (var i=0,len=unloadListeners.length; i<len; ++i) {\r
+                    var l = unloadListeners[i];\r
+                    if (l) {\r
+                        var scope = (l[this.ADJ_SCOPE]) ? l[this.SCOPE]: window;\r
+                        l[this.FN].call(scope, this.getEvent(e), l[this.SCOPE] );\r
+                    }\r
+                }\r
+\r
+                if (listeners && listeners.length > 0) {\r
+                    for (i=0,len=listeners.length; i<len ; ++i) {\r
+                        l = listeners[i];\r
+                        if (l) {\r
+                            this.removeListener(l[this.EL], l[this.TYPE], \r
+                                    l[this.FN], i);\r
+                        }\r
+                    }\r
+\r
+                    this.clearCache();\r
+                }\r
+\r
+                for (i=0,len=customEvents.length; i<len; ++i) {\r
+                    customEvents[i].unsubscribeAll();\r
+                    delete customEvents[i];\r
+                }\r
+\r
+                for (i=0,len=legacyEvents.length; i<len; ++i) {\r
+                    // dereference the element\r
+                    delete legacyEvents[i][0];\r
+                    // delete the array item\r
+                    delete legacyEvents[i];\r
+                }\r
+            },\r
+\r
+            /**\r
+             * Returns scrollLeft\r
+             * @private\r
+             */\r
+            _getScrollLeft: function() {\r
+                return this._getScroll()[1];\r
+            },\r
+\r
+            /**\r
+             * Returns scrollTop\r
+             * @private\r
+             */\r
+            _getScrollTop: function() {\r
+                return this._getScroll()[0];\r
+            },\r
+\r
+            /**\r
+             * Returns the scrollTop and scrollLeft.  Used to calculate the \r
+             * pageX and pageY in Internet Explorer\r
+             * @private\r
+             */\r
+            _getScroll: function() {\r
+                var dd = document.documentElement; db = document.body;\r
+                if (dd && dd.scrollTop) {\r
+                    return [dd.scrollTop, dd.scrollLeft];\r
+                } else if (db) {\r
+                    return [db.scrollTop, db.scrollLeft];\r
+                } else {\r
+                    return [0, 0];\r
+                }\r
+            }\r
+        };\r
+    } ();\r
+\r
+    /**\r
+     * @private\r
+     */\r
+    YAHOO.util.Event.on = YAHOO.util.Event.addListener;\r
+\r
+    if (document && document.body) {\r
+        YAHOO.util.Event._load();\r
+    } else {\r
+        YAHOO.util.Event.on(window, "load", YAHOO.util.Event._load, \r
+                YAHOO.util.Event, true);\r
+    }\r
+\r
+    YAHOO.util.Event.on(window, "unload", YAHOO.util.Event._unload, \r
+                YAHOO.util.Event, true);\r
+\r
+    YAHOO.util.Event._tryPreloadAttach();\r
+\r
+}\r
+\r
diff --git a/apps/fjsc-responder/resources/yahoo/yahoo.js b/apps/fjsc-responder/resources/yahoo/yahoo.js
new file mode 100644 (file)
index 0000000..55cdc78
--- /dev/null
@@ -0,0 +1,61 @@
+/*                                                                                                                                                      \r
+Copyright (c) 2006, Yahoo! Inc. All rights reserved.                                                                                                    \r
+Code licensed under the BSD License:                                                                                                                    \r
+http://developer.yahoo.net/yui/license.txt                                                                                                              \r
+version: 0.10.0                                                                                                                                         \r
+*/ \r
+\r
+/* Copyright (c) 2006 Yahoo! Inc. All rights reserved. */\r
+\r
+/**\r
+ * The Yahoo global namespace\r
+ * @constructor\r
+ */\r
+var YAHOO = window.YAHOO || {};\r
+\r
+/**\r
+ * Returns the namespace specified and creates it if it doesn't exist\r
+ *\r
+ * YAHOO.namespace("property.package");\r
+ * YAHOO.namespace("YAHOO.property.package");\r
+ *\r
+ * Either of the above would create YAHOO.property, then\r
+ * YAHOO.property.package\r
+ *\r
+ * @param  {String} sNameSpace String representation of the desired \r
+ *                             namespace\r
+ * @return {Object}            A reference to the namespace object\r
+ */\r
+YAHOO.namespace = function( sNameSpace ) {\r
+\r
+    if (!sNameSpace || !sNameSpace.length) {\r
+        return null;\r
+    }\r
+\r
+    var levels = sNameSpace.split(".");\r
+\r
+    var currentNS = YAHOO;\r
+\r
+    // YAHOO is implied, so it is ignored if it is included\r
+    for (var i=(levels[0] == "YAHOO") ? 1 : 0; i<levels.length; ++i) {\r
+        currentNS[levels[i]] = currentNS[levels[i]] || {};\r
+        currentNS = currentNS[levels[i]];\r
+    }\r
+\r
+    return currentNS;\r
+};\r
+\r
+/**\r
+ * Global log method.\r
+ */\r
+YAHOO.log = function(sMsg,sCategory) {\r
+    if(YAHOO.widget.Logger) {\r
+        YAHOO.widget.Logger.log(null, sMsg, sCategory);\r
+    } else {\r
+        return false;\r
+    }\r
+};\r
+\r
+YAHOO.namespace("util");\r
+YAHOO.namespace("widget");\r
+YAHOO.namespace("example");\r
diff --git a/libs/fjsc/fjsc.factor b/libs/fjsc/fjsc.factor
new file mode 100644 (file)
index 0000000..527f54e
--- /dev/null
@@ -0,0 +1,66 @@
+! Copyright (C) 2006 Chris Double. All Rights Reserved.
+! See http://factorcode.org/license.txt for BSD license.
+!
+IN: fjsc
+USING: kernel lazy-lists parser-combinators strings math sequences namespaces io ;
+
+TUPLE: ast-number value ;
+TUPLE: ast-identifier value ;
+TUPLE: ast-string value ;
+TUPLE: ast-expression values ;
+
+LAZY: 'digit' ( -- parser )
+  [ digit? ] satisfy [ digit> ] <@ ;
+
+LAZY: 'number' ( -- parser )
+  'digit' <+> [ 0 [ swap 10 * + ] reduce <ast-number> ] <@ ;
+
+LAZY: 'quote' ( -- parser )
+  [ CHAR: " = ] satisfy ;
+
+LAZY: 'string' ( -- parser )
+  'quote' sp [
+    CHAR: " = not
+  ] satisfy <+> [ >string <ast-string> ] <@ &> 'quote' <& ;
+  
+LAZY: 'identifier' ( -- parser )
+  [ 
+    [ blank? not ] keep 
+    [ digit? not ] keep 
+    [ CHAR: : = not ] keep 
+    [ CHAR: " = not ] keep 
+    CHAR: ; = not 
+    and and and and
+  ] satisfy <+> [ >string <ast-identifier> ] <@ ;
+
+LAZY: 'atom' ( -- parser )
+  'number' 'identifier' <|> 'string' <|> ;
+
+LAZY: 'expression' ( -- parser )
+  'atom' sp <*> [ <ast-expression> ] <@ ;
+
+GENERIC: (compile) ( ast -- )
+
+M: ast-number (compile) 
+  "data_stack.push(" ,
+  ast-number-value number>string , 
+  ")" , ;
+
+M: ast-string (compile) 
+  "data_stack.push('" ,
+  ast-string-value , 
+  "')" , ;
+
+M: ast-identifier (compile) 
+  "fjsc_" , ast-identifier-value , "()" ,  ;
+
+M: ast-expression (compile)
+  ast-expression-values [
+    (compile) "; " ,
+  ] each ;
+
+: compile ( ast -- string )
+  [
+    [ (compile) ] { } make [ write ] each
+  ] string-out ;
+  
diff --git a/libs/fjsc/load.factor b/libs/fjsc/load.factor
new file mode 100644 (file)
index 0000000..9f44876
--- /dev/null
@@ -0,0 +1,19 @@
+! Copyright (C) 2006 Chris Double. All Rights Reserved.
+! See http://factorcode.org/license.txt for BSD license.
+!
+REQUIRES: libs/lazy-lists libs/parser-combinators ;
+
+PROVIDE: libs/fjsc 
+{ 
+  +files+ { 
+  "fjsc.factor" 
+  } 
+} {
+  +tests+ { 
+  "tests.factor"
+  }
+} { 
+  +help+ 
+  { 
+  } 
+} ;
diff --git a/libs/fjsc/tests.factor b/libs/fjsc/tests.factor
new file mode 100644 (file)
index 0000000..e9f7fb9
--- /dev/null
@@ -0,0 +1,22 @@
+! Copyright (C) 2006 Chris Double. All Rights Reserved.
+! See http://factorcode.org/license.txt for BSD license.
+!
+USING: kernel test parser-combinators lazy-lists fjsc ;
+IN: temporary
+
+{ "data_stack.push(123)" } [
+  "123" 'number' parse car parse-result-parsed compile 
+] unit-test
+
+{ "fjsc_alert()" } [
+  "alert" 'identifier' parse car parse-result-parsed compile 
+] unit-test
+
+{ "data_stack.push(123); fjsc_alert(); " } [
+  "123 alert" 'expression' parse car parse-result-parsed compile 
+] unit-test
+
+{ "data_stack.push(123); data_stack.push('hello'); fjsc_alert(); " } [
+  "123 \"hello\" alert" 'expression' parse car parse-result-parsed compile 
+] unit-test
\ No newline at end of file