rajax.js

// RAjax Namespace
var RAjax = {};

// Basic Logger facility that supports logging to
// an HTMLElement, the Firebug console and alerts.
// If provided with a valid DOM id, it will append
// log messages to this element. If it can't find
// the DOM id you provided, it will try to use
// the Firebug console instead (you can make sure
// that this will be found by installing Firebug Lite
// from http://www.getfirebug.com/lite.html website).
// Finally, if it can't find a console Object to log to,
// and you tell it so by passing true as the
// second parameter to initialize, it will fall back
// to those nasty alerts, that can lead to very unfortunate
// situations dependending on which situations (events)
// you use them!

RAjax.Logger = Class.create({

  initialize: function(stream, performAlerts) {
    this.stream = (s = $(stream)) ? s : console; // maybe null
    this.performAlerts = performAlerts || false;
  },

  log: function(message) {
    if(PERFORM_LOGGING) {
      if(this.stream) {
        // Firebug console or HTMLElement present
        if(this.logsToFirebug()) {
          // Logging to Firebug console
          this.stream.log(message);
        } else {
          // Logging to HTMLElement
          this.stream.insert({bottom: this.formattedMessage(message)});
        }
      } else { // no stream present
        // fallback to annoying alerts
        alert(this.formattedMessage(message));
      }
    }
  },

  clear: function() {
    if(this.logsToPage()) {
      this.stream.update('');
    }
  },

  // override this if you need different formatting
  formattedMessage: function(message) {
    return "
  • ” + message + “
  • “; }, debug: function(message) { this.log(message); }, error: function(message) { this.log(”ERROR: ” + message); }, // utility methods logsToFirebug: function() { return !!this.stream.log; }, logsToPage: function() { return this.stream && !this.firebugPresent(); } }); // additional supported callbacks RAjax.EXTENDED_CALLBACKS = [ “onPOSTSuccess” , “onPOSTFailure”, “onGETSuccess” , “onGETFailure”, “onPUTSuccess” , “onPUTFailure”, “onDELETESuccess”, “onDELETEFailure” ];

    // Basic RAjax.Request class
    RAjax.Request = Class.create({
    
      initialize: function(url, options) {
    
        // URL and Ajax.Request options
        this.url = url;
        this.options = options;
    
        // remember desired http method
        this.options._method = this.options.method;
    
        // workaround prototype Ajax.Request JSON handling
        // such that it is possible to perform all HTTP operations
        // with JSON data in both directions
    
        if(this.needsJsonParameterAdjustment()) {
          this.adjustJsonParameters();
          this.adjustJsonUrlParameters();
        }
    
        // remember callbacks to be able to call the originals
        this.onSuccess = this.options.onSuccess;
        this.onFailure = this.options.onFailure;
    
        // mix extended callback dispatcher into Ajax.Request options
        Object.extend(this.options, this.callbackDispatcher());
      },
    
      fire: function() {
        new Ajax.Request(this.url, this.options);
      },
    
      // ------------------------------------------------------------------
      // ------------------------------------------------------------------
      // ------------------------------------------------------------------
    
      // 'real' request method
      method: function() {
        return this.options.method || 'get';
      },
    
      // piggybacked rails '_method'
      _method: function() {
        return this.options._method || null;
      },
    
      isJsonRequest: function() {
        // defaults to true if not explicitly specified different
        return this.options.json ? this.options.json : true;
      },
    
      needsAuthenticityToken: function() {
        // if something is about to be changed on the server
        return this.method() != 'get';
      },
    
      needsSpecialMethodParameter: function() {
        // if it's neither GET nor POST (rails '_method' parameter)
        return this._method() == 'put' || this._method() == 'delete'
      },
    
      needsJsonParameterAdjustment: function() {
        // if it's a json request and something will be changed on the server
        return this.isJsonRequest() && this.needsAuthenticityToken();
      },
    
      needsExtendedCallbacks: function() {
        // if this.options include any of the extended callbacks
        var keys = Object.keys(this.options);
        return RAjax.EXTENDED_CALLBACKS.any(function(c) {
          return keys.include(c);
        });
      },
    
      // by convention, hidden input containing authenticity_token
      // must have id="auth-token" (this applies only to Rails 2.0)
      authenticityToken: function() {
        if(t = $('auth-token')) return "authenticity_token=" + t.getValue();
        else return "";
      },
    
      adjustJsonUrlParameters: function() {
        var needsAuthentication = false;
        // add authenticity token
        if(this.needsAuthenticityToken()) {
          this.url += ("?" + this.authenticityToken());
          needsAuthentication = true;
        }
        // add special method parameter
        if(this.needsSpecialMethodParameter()) {
          if(needsAuthentication) { // authenticity_token already there
            this.url += ("&_method=" + this._method());
          } else {
            this.url += ("?_method=" + this._method());
          }
        }
      },
    
      adjustJsonParameters: function() {
        this.options.method = 'post';
        this.options.contentType = "application/json";
        this.options.evalScripts = false;
        if(this.options.parameters) {
          this.options.postBody = Object.toJSON(this.options.parameters);
        }
      },
    
      callbackDispatcher: function(successCallback, failureCallback) {
        var self = this;
        return {
          onSuccess: function(transport) {
    
            var json = transport.responseText.evalJSON(true);
    
            // original callback
            if(self.onSuccess) self.onSuccess(transport, json);
    
            if(self.needsExtendedCallbacks()) {
              // detailed
              self.onSuccessDispatcher(transport, json);
            }
          },
    
          onFailure: function(transport) {
    
            var json = transport.responseText.evalJSON(true);
    
            // original callback
            if(self.onFailure) self.onFailure(transport, json);
    
            if(self.needsExtendedCallbacks()) {
              // detailed callbacks
              self.onFailureDispatcher(transport, json);
            }
          }
        };
      },
    
      onSuccessDispatcher: function(transport, json) {
    
        if(this._method() == "post" && this.options.onPOSTSuccess)
          this.options.onPOSTSuccess(transport, json);
        if(this._method() == "get" && this.options.onGETSuccess)
          this.options.onGETSuccess(transport, json);
        if(this._method() == "put" && this.options.onPUTSuccess)
          this.options.onPUTSuccess(transport, json);
        if(this._method() == "delete" && this.options.onDELETESuccess)
          this.options.onDELETESuccess(transport, json);
    
      },
    
      onFailureDispatcher: function(transport, json) {
    
        if(this._method() == "post" && this.options.onPOSTFailure)
          this.options.onPOSTFailure(transport, json);
        if(this._method() == "get" && this.options.onGETFailure)
          this.options.onGETFailure(transport, json);
        if(this._method() == "put" && this.options.onPUTFailure)
          this.options.onPUTFailure(transport, json);
        if(this._method() == "delete" && this.options.onDELETEFailure)
          this.options.onDELETEFailure(transport, json);
    
      }
    
    });
    
    RAjax.AUTH_TOKEN_ID = "auth-token";
    
    RAjax.POST = "POST";
    RAjax.GET = "GET";
    RAjax.PUT = "PUT";
    RAjax.DELETE = "DELETE";
    
    RAjax.Options       = { evalScripts: false };
    
    RAjax.POSTOptions   = Object.extend({ method: 'post'   }, RAjax.Options);
    RAjax.GETOptions    = Object.extend({ method: 'get'    }, RAjax.Options);
    RAjax.PUTOptions    = Object.extend({ method: 'put'    }, RAjax.Options);
    RAjax.DELETEOptions = Object.extend({ method: 'delete' }, RAjax.Options);
    
    RAjax.Request.POST = function(url, options) {
      new RAjax.Request(url, Object.extend(options, RAjax.POSTOptions)).fire();
    }
    
    RAjax.Request.GET = function(url, options) {
      new RAjax.Request(url, Object.extend(options, RAjax.GETOptions)).fire();
    }
    
    RAjax.Request.PUT = function(url, options) {
      new RAjax.Request(url, Object.extend(options, RAjax.PUTOptions)).fire();
    }
    
    RAjax.Request.DELETE = function(url, options) {
      new RAjax.Request(url, Object.extend(options, RAjax.DELETEOptions)).fire();
    }
    
    RAjax.DISPATCH_TABLE = {
      POST: RAjax.Request.POST,
      GET: RAjax.Request.GET,
      PUT: RAjax.Request.PUT,
      DELETE: RAjax.Request.DELETE
    };
    
    RAjax.ResourceProxy = Class.create({
    
      initialize: function(element, resource) {
        this.element = element;
        this.resource = resource || this.resourceName();
        this.resourceId = this.resourceId();
        this.newRecord = this.isNewRecord();
        this.authToken = (t = $(this.authTokenId())) ? t.getValue() : null;
      },
    
      // dispatch to appropriate operation
      fire: function(action, parameters, callbacks) {
        logger.debug("RAjax.ResourceProxy#fire " + action);
        var params = Object.extend(
          { parameters: parameters || {} },
          this.prepareCallbacks(action, callbacks)
        );
        RAjax.DISPATCH_TABLE[action](this.url(action), params);
      },
    
      // bundle action relevant callbacks
      prepareCallbacks: function(action, callbacks) {
    
        var self = this;
    
        // handle original callbacks (setting 'this' to proper self)
        var cs =  {
          onSuccess: function(transport, json) {
            self.onSuccess.call(self, transport, json);
          },
          onFailure: function(transport, json) {
            self.onFailure.call(self, transport, json);
          }
        };
    
        cs["on" + action + "Success"] = this.mergedCallback(callbacks.onSuccess);
        cs["on" + action + "Failure"] = this.mergedCallback(callbacks.onFailure);
    
        return cs;
      },
    
      // callbackObject can either be a Function or any Object
      definesMultipleCallbacks: function(callback) {
        // TODO investigate why (callback instanceof Object) always returns true
        // maybe this has something to do with passing params by reference?
        return typeof(callback) != "function";
      },
    
      // if callbackObject is an Object, merge all callbacks into one function
      // if callbackObject is a Function, return it
      mergedCallback: function(callbackObject) {
        var self = this; // workaround bug with 'this' in inner functions
        if(this.definesMultipleCallbacks(callbackObject)) {
          return function(transport, json) {
            for(callback in callbackObject) {
              // provide self for correct access to 'this' in callback actions
              callbackObject[callback].call(self, transport, json);
            }
          };
        } else {
          // provide self for correct access to 'this' in callback actions
          return function(transport, json) {
            callbackObject.call(self, transport, json);
          }
        }
      },
    
      // -------------------------------------------------------------
      // -------------------- HELPER METHODS -------------------------
      // -------------------------------------------------------------
    
      setElement: function(element) {
        this.element = element;
        this.resource = this.resourceName();
        // TODO better pluralization
        this.resourceCollection = this.resource + "s";
      },
    
      // override this to deal with different naming conventions
      resourceName: function() {
        return this.element.name.split('[')[0];
      },
    
      // override this to deal with different naming conventions
      resourceId: function() {
        return this.element.name.split('[')[1].gsub(']', '');
      },
    
      // override this to deal with different naming conventions
      isNewRecord: function() {
        return !!this.resourceId().match(/new/);
      },
    
      // change value of RAjax.AUTH_TOKEN_ID or
      // override this to return the DOM id of your authenticity_token
      authTokenId: function() {
        return RAjax.AUTH_TOKEN_ID;
      },
    
      // override this to return the value of your input field
      value: function() { return null; },
    
      // override this to determine if this resource should be deleted
      deleted: function() { return false; },
    
      resourceUrl: function() {
        return "/"+ this.resourceCollection +"/"+ this.resourceId();
      },
    
      resourceCollectionUrl: function() {
        return "/" + this.resourceCollection;
      },
    
      url: function(action) {
        switch(action) {
          case RAjax.POST: return this.resourceCollectionUrl();
          case RAjax.GET: return this.resourceUrl();
          case RAjax.PUT: return this.resourceUrl();
          case RAjax.DELETE: return this.resourceUrl();
        }
      }
    
    });
    
    RAjax.AutoSave = Behavior.create({
    
      initialize: function(proxy) {
        this.proxy = new proxy(this.element);
      },
    
      onchange: function() {
        // dispatch to appropriate event
        this.proxy[this.event()]();
      },
    
      event: function() {
        if(this.proxy.isNewRecord()) {
          return RAjax.POST
        } else {
          return this.proxy.deleted() ? RAjax.DELETE : RAjax.PUT
        }
      }
    
    });
    

    Leave a Reply

    Formatting: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>