Introducing: Speakeasy.js – It's kind of like ActiveResource

During this Greasemonkey bender I’m on I wanted to get a cleaner interface for working with GM_xmlhttpRequest. I’m using a resourceful style Rails app to serve up my data so I jimmied up this little library to handle my data storage, update, and retrieval needs. This requires jQuery and is designed to be included in a Greasemonkey script. Example to follow.

/*
  Speakeasy.js
  Version: 0.1.0
  It's kind of like ActiveResource
  Copyright (c) 2009, STRd6 (http://strd6.com)
  Liscensed under the MIT License

  Prerequisites:
    Greasemonkey Environment
    jQuery
*/

/**
  Speakeasy abstracts the GM_xmlhttprequest and handles communication with the remote script server.
  It's kind of like ActiveResource
 */
Speakeasy = function($) {
  var baseUrl = 'http://localhost:3000/';
  var apiKey = 0;

  function generateArrayDataTransfer(objectType, callback) {
    return function(responseData) {
      var dataArray = eval('(' + responseData + ')');
      var elements = $.map(dataArray, function(element) {
        return element[objectType];
      });
      callback(elements);
    };
  }

  function generateDataTransfer(objectType, callback) {
    return function(responseData) {
      var data = eval('(' + responseData + ')');
      callback(data[objectType]);
    };
  }

  function loadOptionsData(type, dataObject) {
    var optionsData = {
      api_key: apiKey
    };

    $.each(dataObject, function(field, value) {
      log(field + ': ' + value)
      optionsData[type + '[' + field +']'] = value;
    });
    return optionsData;
  }

  function makeRequest(resource, options) {
    var method = options.method || 'GET';
    var url = baseUrl + resource + '.js';
    var headers = {
      'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey',
      'Accept': 'application/json,application/atom+xml,application/xml,text/xml'
    };
    var data = $.param(options.data || '');
    var onSuccess = options.onSuccess || (function(){});

    if(method == 'POST') {
      headers['Content-type'] = 'application/x-www-form-urlencoded';
    } else if(method == 'GET') {
      if(data) {
        url += '?' + data;
      }
    }

    GM_xmlhttpRequest({
      method: method,
      url: url,
      headers: headers,
      data: data,

      onload: function(responseDetails) {
        if(responseDetails.status == 200) {
          onSuccess(responseDetails.responseText);
        } else {
          console.warn(url + ' - ' + responseDetails.status + ':nn' + responseDetails.responseText);
        }
      }
    });
  }

  function generateResource(type) {
    var pluralType = type + 's';

    var all = function() {
      return function(options, callback) {
        var dataTransfer = generateArrayDataTransfer(type, callback);
        options.onSuccess = dataTransfer;
        makeRequest(pluralType, options);
      };
    }();

    var create = function() {
      return function(dataObject, callback) {
        var options = {
          method: 'POST'
        };

        options.data = loadOptionsData(type, dataObject);
        makeRequest(pluralType, options);
      };
    }();

    var find = function() {
      return function(options, callback) {
        var dataTransfer = generateDataTransfer(type, callback);
        if(typeof(options) == 'number') {
          options.onSuccess = dataTransfer;
          makeRequest(pluralType + '/' + options, dataTransfer);
        } else {
          log("TODO: Non-integer find not currently supported!");
        }
      };
    }();

    var update = function() {
      return function(dataObject, callback) {
        var id = dataObject.id;
        var options = {
          method: 'POST'
        };

        options.data = loadOptionsData(type, dataObject);
        makeRequest(pluralType + '/update/' + id, options);
      };
    }();

    var resource = {
      all: all,
      create: create,
      find: find,
      update: update
    };

    return resource;
  }

  var self = {
    annotation: generateResource('annotation'),
    script: generateResource('script')
  };

  return self;
}(jQuery);

Example uses:

// ==UserScript==
// @name           Speakeasy Demo
// @namespace      http://strd6.com
// @description    Super-simple website annotations shared with all!
// @include        *
//
// @require     http://ajax.googleapis.com/ajax/libs/jquery/1.3.1/jquery.min.js
// @require     http://strd6.com/stuff/jqui/speakeasy.js
// ==/UserScript==

function display(annotation) {
  var id = annotation.id;

  $('
') .text(annotation.text) .addClass('annotation') .css({ top: annotation.top, left: annotation.left }) .bind('drag', function( event ) { $( this ).css({ top: event.offsetY, left: event.offsetX }); }) .bind('dragend', function( event ) { Speakeasy.annotation.update({id: id, top: $(this).css('top'), left: $(this).css('left')}); }) .fadeTo('fast', 0.75) .appendTo('body'); } Speakeasy.annotation.all({data: {url: currentUrl}}, function(annotations) { $.each(annotations, function(index, annotation) { display(annotation); }); });

Enjoy!

Author: Daniel X

Heretic priest of the Machine God. I enjoy crawling around in Jeff Bezo's spaceship, bringing technology to the people, and long walks outside of time and space.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s