Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Nodestream initial release (alpha)

  • Loading branch information...
commit 3f9a0d749b44fe64a44bd5e882cf06ec41f019f0 0 parents
Guillermo Rauch authored September 25, 2010
113  README.md
Source Rendered
... ...
@@ -0,0 +1,113 @@
  1
+Nodestream
  2
+==========
  3
+
  4
+Nodestream aims to make the most common use cases for realtime web applications easy to implement, by connecting event listeners to template rendering.
  5
+
  6
+## Example
  7
+
  8
+### Use-case
  9
+
  10
+Consider the following example, written in Express. The following two routes show and save items respectively
  11
+
  12
+	app.get('/', function(req, res){
  13
+		database.getItems(function(items){
  14
+			res.render('my.items.jade', {locals: { items: items }})
  15
+		})
  16
+	})
  17
+	
  18
+	app.post('/save/item', function(req, res){
  19
+		database.saveItem(req.body, function(){
  20
+			res.redirect('/');
  21
+		})
  22
+	})
  23
+
  24
+And the following template displays them
  25
+
  26
+	- each item in items
  27
+		div.item
  28
+			p This is an item titled #{item.title}
  29
+
  30
+When a user loads the page, he'll see all the items that are in the database at that particular moment. When he adds one, he'll be redirected and the page will be refreshed entirely.
  31
+
  32
+### The need for realtime
  33
+
  34
+Even though this model works fine for really simple applications, reloading the page after a save is often undesirable. Developers usually turn to ajax, which means after a save a chunk of html is taken and appended it to the list.
  35
+
  36
+This approach has drawbacks:
  37
+
  38
+- If a user has multiple tabs open with your applicaiton, only one tab will be updated.
  39
+
  40
+- If multiple users are editing concurrently, only your own changes are shown, but not changes from others.
  41
+
  42
+### The nodestream solution
  43
+
  44
+Building on top of the powerful jade template engine filters, nodestream solves the problems listed above by making only two changes to your applications:
  45
+
  46
+1. Firing events
  47
+
  48
+	database.saveItem(item){
  49
+		...
  50
+		nodestream.emit('newitem', item)
  51
+	}
  52
+	
  53
+2. Adding a line of code to your template (`:realtime`)
  54
+
  55
+	:realtime(append: 'newitem', local: 'items', obj: 'item')
  56
+		div.item
  57
+			p This is an item titled #{item.title}
  58
+			
  59
+### How to use
  60
+
  61
+1. Include [`socket.io`](http://github.com/learnboost/socket.io-node) and `nodestream`:
  62
+
  63
+	var io = require('socket.io');
  64
+	require('nodestream');
  65
+	
  66
+2. Attach `socket.io` to your `http.Server` and load nodestream
  67
+
  68
+	var nodestream = io.listen(httpserver).nodestream();
  69
+
  70
+3. In the client side, load `public/nodestream.js` and attach it to your `io.Socket`
  71
+
  72
+	var mySocket = new io.Socket():
  73
+	mySocket.connect();
  74
+	Nodestream(mySocket);
  75
+	
  76
+4. In your code, fire events and edit your templates!
  77
+
  78
+### API
  79
+
  80
+The `:realtime` filter takes the following options
  81
+
  82
+`local`: The local variable passed to your template that your piece of html interacts with
  83
+`obj`: Required for `append`, the name of the local used when the collection is looped through.
  84
+`append`: Append event
  85
+`repaint`: Repaint event name. If it finished on `.`, the `id` property is appended to it for every item on a collection (dynamic event name)
  86
+`remove`: Remove event name. If it finished on `.`, the `id` property is appended to it for every item on a collection (dynamic event name)
  87
+`id`: The name of the key to look for an unique identifier to append to events finished in `.`
  88
+
  89
+### Example
  90
+
  91
+#### Showing how many users are browsing your website
  92
+
  93
+On the server side:
  94
+	
  95
+	var connections = 0;
  96
+	var nodestream = io.listen(app).nodestream()
  97
+	  .on('connect', function(){
  98
+	    connections++;
  99
+	    this.emit('connections', connections);
  100
+	  })
  101
+	  .on('disconnect', function(){
  102
+	    connections--;
  103
+	    this.emit('connections', connections);
  104
+	  });
  105
+
  106
+On the client side:
  107
+
  108
+	:realtime(repaint: 'connections', local: 'connections')
  109
+	  .connections
  110
+	    - if (connections > 1)
  111
+	      p #{connections} people are editing right now
  112
+	    - else
  113
+	      p You're all alone, loser
1  index.js
... ...
@@ -0,0 +1 @@
  1
+module.exports = require('./lib/nodestream');
290  lib/nodestream/index.js
... ...
@@ -0,0 +1,290 @@
  1
+// pending subscriptions maps a hash of the session id, filename and block id
  2
+// to 
  3
+var pendingSubscriptions = {},
  4
+    subscriptions = {},
  5
+    fileBlocks = {};
  6
+
  7
+var sys = require('sys');
  8
+
  9
+// socket.io additions
  10
+
  11
+function subscribe(client, nodeid, ev, type, file, blockIndex, local, sessionId){
  12
+  if (!(ev in subscriptions)) subscriptions[ev] = [];
  13
+  if (!(ev in client._nodestreamCleanup)) client._nodestreamCleanup[ev] = [];
  14
+  client._nodestreamCleanup[ev].push(subscriptions.length);
  15
+  subscriptions[ev].push({
  16
+    nodeId: nodeid
  17
+  , type: type
  18
+  , clientId: client.sessionId
  19
+  , file: file
  20
+  , blockIndex: blockIndex
  21
+  , local: local
  22
+  , sessionId: sessionId
  23
+  });
  24
+};
  25
+
  26
+function paint(type, filename, index, local, obj, sessionId){
  27
+  var locals = {};
  28
+  locals[local] = obj;
  29
+  locals._ = {};
  30
+  locals.attrs = attrs;
  31
+  locals.escape = escape
  32
+  // fake a request
  33
+  var req = new Request();
  34
+  req.sessionId = sessionId;
  35
+  var oldSubscribe = req.subscribe;
  36
+  // do not resubscribe to append
  37
+  req.subscribe = function(file, blockIndex, append, repaint, remove, local){
  38
+    return oldSubscribe.call(req, file, blockIndex, '', repaint, remove, local);
  39
+  };
  40
+  return fileBlocks[filename][index][type].call(req, locals);
  41
+};
  42
+
  43
+var Listener = require('socket.io').Listener;
  44
+
  45
+Listener.prototype.nodestream = function(){
  46
+  if (!this._nodestream){
  47
+    // prevent double-attachment
  48
+    this._nodestream = new Nodestream(this);
  49
+  }
  50
+  return this._nodestream;
  51
+};
  52
+
  53
+var Nodestream = function(socket){
  54
+  this.socket = socket;
  55
+  var self = this;
  56
+  socket.on('connection', function(c){
  57
+    c.on('message', function(msg){
  58
+      if (typeof msg == 'object' && 'nodestream' in msg){
  59
+        self._handle(c, msg);
  60
+        if (!c._nodestream){
  61
+          self._emit('connect', c);
  62
+          c._nodestream = true;
  63
+        }
  64
+      }
  65
+    });
  66
+    c.on('disconnect', function(){
  67
+      if (c._nodestream){
  68
+        self._end(c);
  69
+      }
  70
+    });
  71
+  });
  72
+};
  73
+
  74
+sys.inherits(Nodestream, process.EventEmitter);
  75
+
  76
+Nodestream.prototype._emit = Nodestream.prototype.emit;
  77
+
  78
+Nodestream.prototype._handle = function(client, message){
  79
+  if (!('_nodestreamCleanup' in client)) client._nodestreamCleanup = {};
  80
+  // we receive a hash that confirms the suscription of the client
  81
+  // to the events stored by that hash
  82
+  if (message.subscribe){
  83
+    // lookup pending subscriptions by hash
  84
+    var subscription = pendingSubscriptions[message.subscribe];
  85
+    
  86
+    if (subscription){
  87
+      if (subscription[2] && subscription[2].length > 1)
  88
+        subscribe(client, message.subscribe, subscription[2], 'append', subscription[0], subscription[1], subscription[5], subscription[6]);
  89
+      if (subscription[3] && subscription[3].length > 1)
  90
+        subscribe(client, message.subscribe, subscription[3], 'repaint', subscription[0], subscription[1], subscription[5], subscription[6]);
  91
+      if (subscription[4] && subscription[4].length > 1)
  92
+        subscribe(client, message.subscribe, subscription[4], 'remove', subscription[0], subscription[1], null, subscription[6]);
  93
+
  94
+      delete pendingSubscriptions[message.subscribe];
  95
+    } else {
  96
+      console.error('cant find subscription by encoded id ' + message.subscribe);
  97
+    }
  98
+  }
  99
+};
  100
+
  101
+Nodestream.prototype._end = function(client){
  102
+  for (var ev in client._nodestreamCleanup){
  103
+    (function(ev){
  104
+      client._nodestreamCleanup[ev].forEach(function(i){
  105
+        subscriptions[ev][i] = null;
  106
+      });
  107
+    })(ev);
  108
+  }
  109
+  this._emit('disconnect', client);
  110
+};
  111
+
  112
+Nodestream.prototype.emit = function(ev, obj){
  113
+  // notify suscriptors
  114
+  var socket = this.socket;
  115
+  if (ev in subscriptions){
  116
+    subscriptions[ev].forEach(function(s){
  117
+      if (!s) return;
  118
+      if (socket.clients[s.clientId]){
  119
+        var args = {id: s.nodeId};
  120
+        
  121
+        if (s.type == 'repaint' || s.type == 'append'){
  122
+          args.html = paint(s.type, s.file, s.blockIndex, s.local, obj, s.sessionId);
  123
+        }
  124
+        
  125
+        socket.clients[s.clientId].send({
  126
+          nodestream: 1,
  127
+          type: s.type,
  128
+          args: args
  129
+        })
  130
+      }
  131
+    });
  132
+  }
  133
+};
  134
+
  135
+// express request
  136
+
  137
+var crypto = require('crypto')
  138
+  , Request = require('http').IncomingMessage;
  139
+
  140
+function md5(str) {
  141
+  return crypto.createHash('md5').update(str).digest('hex');
  142
+}
  143
+
  144
+Request.prototype.subscribe = function(file, blockIndex, append, repaint, remove, local){
  145
+  if (!('_pendingSubscriptions' in this)){
  146
+    this._pendingSubscriptions = {};
  147
+  }
  148
+  
  149
+  var hash = md5(file + blockIndex + this.sessionId + append + repaint + remove)
  150
+    , random = md5(hash + Math.random());
  151
+  
  152
+  if (hash in this._pendingSubscriptions){
  153
+    return this._pendingSubscriptions[hash];
  154
+  }
  155
+  
  156
+  pendingSubscriptions[random] = Array.prototype.slice.call(arguments).concat(this.sessionId);
  157
+  this._pendingSubscriptions[hash] = random;
  158
+  return random;
  159
+};
  160
+
  161
+// filters
  162
+var jade = require('jade')
  163
+  , Compiler = jade.Compiler
  164
+  , filters = jade.filters
  165
+  , nodes = jade.nodes;
  166
+
  167
+filters.realtime = function(block, compiler, attrs){
  168
+    var placeholder, filename = compiler.options.filename;
  169
+  
  170
+  if (!(filename in fileBlocks)) fileBlocks[filename] = [];
  171
+  
  172
+  block.forEach(function(node, i){
  173
+    if (node.name == 'placeholder'){
  174
+      placeholder = new nodes.Tag('div', node.block);
  175
+      placeholder.setAttribute('class', '"placeholder"')
  176
+      block.splice(i, 1);
  177
+    }
  178
+  });
  179
+  
  180
+  if (!attrs.local){
  181
+    throw new Error('Please pass the `local` to the :realtime filter options');
  182
+  }
  183
+  
  184
+  if (!attrs.append && !attrs.obj){
  185
+    attrs.obj = attrs.local;
  186
+  }
  187
+  
  188
+  var events = ['undefined', attrs.repaint || 'undefined', attrs.remove || 'undefined'];
  189
+  
  190
+  events.forEach(function(name, i){
  191
+    // append '+ obj.id' to events finishing in a dot
  192
+    if (/\.'$/.test(name)){
  193
+      events[i] = name + (' + ' + (eval(attrs.obj) || 'obj') + '.' + (eval(attrs.id) || 'id'));
  194
+    } 
  195
+  });
  196
+  
  197
+  // wrapper tag
  198
+  // actually: there's no need to wrap! we could inspect the `class` of the main node, or we could make it an option
  199
+  var subscribeString = 'this.subscribe("'+ filename +'", '+ fileBlocks[filename].length + ', ' + events.join(',') + ', '+ (attrs.obj || '"obj"') +')';
  200
+  
  201
+  block[0].setAttribute('class', block[0].getAttribute('class') + ' + "nodestream nodestream_" + ' + subscribeString);
  202
+  block.push(new nodes.Filter('javascript', new nodes.Text('Nodestream.register(\'#{'+ subscribeString +'}\');')));
  203
+  
  204
+  var compiled = {};
  205
+  
  206
+  if (attrs.append){
  207
+    var bl = new nodes.Block()
  208
+      , ifNode = new nodes.Code('if ('+ eval(attrs.local) +'.length)')
  209
+      , forEachBlock = new nodes.Block()
  210
+      , forEach = new nodes.Each(eval(attrs.local), eval(attrs.obj) || 'obj', undefined, block)
  211
+      , elseNode = new nodes.Code('else')
  212
+      , elseBlock = new nodes.Block();
  213
+        
  214
+    forEachBlock.push(forEach);
  215
+    ifNode.block = forEachBlock;
  216
+    
  217
+    if (placeholder){
  218
+      elseBlock.push(placeholder);
  219
+      elseNode.block = elseBlock;
  220
+    }
  221
+    
  222
+    bl.push(ifNode);
  223
+    bl.push(elseNode);
  224
+    
  225
+    var cc = new Compiler(block);
  226
+    compiled['append'] = new Function('locals', 'with (locals) {' + cc.compile() + '}');
  227
+    
  228
+    // create a fake invisible element that serves as an indicator of where the parent container is
  229
+    events = [attrs.append, 'undefined', 'undefined'];
  230
+    subscribeString = 'this.subscribe("'+ filename +'", '+ fileBlocks[filename].length + ', ' + events.join(',') + ', '+ (attrs.obj || '"obj"') +')';
  231
+    var appendPlaceholder = new nodes.Tag('div');
  232
+    appendPlaceholder.setAttribute('class', '"nodestream nodestream_" + ' + subscribeString);
  233
+    bl.push(appendPlaceholder);
  234
+    bl.push(new nodes.Filter('javascript', new nodes.Text('Nodestream.register(\'#{'+ subscribeString +'}\');')));
  235
+  }
  236
+  
  237
+  if (attrs.repaint){
  238
+    var cc = new Compiler(block[0].block);
  239
+    compiled['repaint'] = new Function('locals', 'with (locals) {' + cc.compile() + '}');
  240
+  }
  241
+  
  242
+  fileBlocks[filename].push(compiled);
  243
+  
  244
+  if (attrs.append){
  245
+    compiler.visit(bl);
  246
+  } else {
  247
+    compiler.visit(block);
  248
+  }
  249
+};
  250
+
  251
+function attrs(obj){
  252
+    var buf = [],
  253
+        terse = obj.terse;
  254
+    delete obj.terse;
  255
+    var keys = Object.keys(obj),
  256
+        len = keys.length;
  257
+    if (len) {
  258
+        buf.push('');
  259
+        for (var i = 0; i < len; ++i) {
  260
+            var key = keys[i],
  261
+                val = obj[key];
  262
+            if (typeof val === 'boolean' || val === '' || val == null) {
  263
+                if (val) {
  264
+                    terse
  265
+                        ? buf.push(key)
  266
+                        : buf.push(key + '="' + key + '"');
  267
+                }
  268
+            } else {
  269
+                buf.push(key + '="' + escape(val) + '"');
  270
+            }
  271
+        }
  272
+    }
  273
+    return buf.join(' ');
  274
+}
  275
+
  276
+/**
  277
+ * Escape the given string of `html`.
  278
+ *
  279
+ * @param {String} html
  280
+ * @return {String}
  281
+ * @api private
  282
+ */
  283
+
  284
+function escape(html){
  285
+    return String(html)
  286
+        .replace(/&(?!\w+;)/g, '&amp;')
  287
+        .replace(/</g, '&lt;')
  288
+        .replace(/>/g, '&gt;')
  289
+        .replace(/"/g, '&quot;');
  290
+}
229  public/nodestream.js
... ...
@@ -0,0 +1,229 @@
  1
+(function($){
  2
+  
  3
+  var socket,
  4
+      subscriptions = [];
  5
+  
  6
+  var oldMessage = io.Socket.prototype._onMessage;
  7
+  
  8
+  io.Socket.prototype._onMessage = function(msg){
  9
+    if (typeof msg == 'object' && 'nodestream' in msg){
  10
+      Nodestream.handle(msg.type, msg.args);
  11
+    } else {
  12
+      oldMessage.apply(this, arguments);
  13
+    }
  14
+  };
  15
+  
  16
+  Nodestream = function(s){
  17
+    socket = s;
  18
+    socket.on('connect', function(){
  19
+      $(function(){
  20
+        var name;
  21
+        while(name = subscriptions.shift()){
  22
+          socket.send({
  23
+            nodestream: 1,
  24
+            subscribe: name
  25
+          });
  26
+        }
  27
+      });
  28
+    })
  29
+  };
  30
+  
  31
+  Nodestream.register = function(name){
  32
+    if (socket){
  33
+      socket.send({
  34
+        nodestream: 1,
  35
+        subscribe: name
  36
+      });
  37
+    } else {
  38
+      subscriptions.push(name);
  39
+    }
  40
+  };
  41
+  
  42
+  Nodestream.handle = function(type, args){
  43
+    Nodestream.actions[type].call(this, args);
  44
+  };
  45
+  
  46
+  Nodestream.subscribe = function(name, type){
  47
+    subscriptions.push([name, type]);
  48
+  };
  49
+  
  50
+  Nodestream.actions = {
  51
+    
  52
+    remove: function(args){
  53
+      $('.nodestream_' + args.id).highlightFade({ complete: function(){
  54
+        $('.nodestream_' + args.id).remove();
  55
+      }});
  56
+      var parent = $('.nodestream_' + args.id).parent()
  57
+        , siblings = parent.children();
  58
+      if (siblings.length == 0){
  59
+        parent.find('.placeholder').css('display', 'block');
  60
+      }
  61
+    },
  62
+    
  63
+    repaint: function(args){
  64
+      $('.nodestream_' + args.id).html(args.html).highlightFade();
  65
+    },
  66
+    
  67
+    append: function(args){
  68
+      $('.nodestream_' + args.id).parent().find('.placeholder').css('display', 'none');
  69
+      if (args.bottom){
  70
+        $('.nodestream_' + args.id).parent().append(args.html).children(':last').highlightFade();
  71
+      } else {
  72
+        $('.nodestream_' + args.id).parent().prepend(args.html).children(':first').highlightFade();
  73
+      }
  74
+    }
  75
+    
  76
+  }
  77
+  
  78
+})(jQuery);
  79
+
  80
+/**
  81
+ *  jQuery Plugin highlightFade (jquery.offput.ca/highlightFade)
  82
+ *  (c) 2006 Blair Mitchelmore (offput.ca) blair@offput.ca
  83
+ */
  84
+/**
  85
+ * This is version 0.7 of my highlightFade plugin. It follows the yellow fade technique of Web 2.0 fame
  86
+ * but expands it to allow any starting colour and allows you to specify the end colour as well.
  87
+ *
  88
+ * For the moment, I'm done with this plug-in. Unless I come upon a really cool feature it should have
  89
+ * this plug-in will only receive updates to ensure future compatibility with jQuery.
  90
+ *
  91
+ * As of now (Aug. 16, 2006) the plugin has been written with the 1.0.1 release of jQuery (rev 249) which
  92
+ * is available from http://jquery.com/src/jquery-1.0.1.js
  93
+ *
  94
+ * A note regarding rgb() syntax: I noticed that most browsers implement rgb syntax as either an integer 
  95
+ * (0-255) or percentage (0-100%) value for each field, that is, rgb(i/p,i/p,i/p); however, the W3C 
  96
+ * standard clearly defines it as "either three integer values or three percentage values" [http://www.w3.org/TR/CSS21/syndata.html] 
  97
+ * which I choose to follow despite the error redundancy of the typical behaviour browsers employ.
  98
+ *
  99
+ * Changelog:
  100
+ *
  101
+ *    0.7:
  102
+ *        - Added the awesome custom attribute support written by George Adamson (slightly modified)
  103
+ *        - Removed bgColor plugin dependency seeing as attr is customizable now...
  104
+ *    0.6:
  105
+ *        - Abstracted getBGColor into its own plugin with optional test and data retrieval functions
  106
+ *        - Converted all $ references to jQuery references as John's code seems to be shifting away
  107
+ *          from that and I don't want to have to update this for a long time.
  108
+ *    0.5:
  109
+ *        - Added simple argument syntax for only specifying start colour of event
  110
+ *        - Removed old style argument syntax
  111
+ *        - Added 'interval', 'final, and 'end' properties
  112
+ *        - Renamed 'color' property to 'start'
  113
+ *        - Added second argument to $.highlightFade.getBGColor to bypass the e.highlighting check
  114
+ *    0.4:
  115
+ *        - Added rgb(%,%,%) color syntax
  116
+ *    0.3:
  117
+ *        - Fixed bug when event was called while parent was also running event corrupting the
  118
+ *          the background colour of the child
  119
+ *    0.2:
  120
+ *        - Fixed bug where an unspecified onComplete function made the page throw continuous errors
  121
+ *        - Fixed bug where multiple events on the same element would speed each subsequent event
  122
+ *    0.1:
  123
+ *        - Initial Release
  124
+ * 
  125
+ * @author          Blair Mitchelmore (blair@offput.ca)
  126
+ * @version         0.5
  127
+ */
  128
+jQuery.fn.highlightFade = function(settings) {
  129
+	var o = (settings && settings.constructor == String) ? {start: settings} : settings || {};
  130
+	var d = jQuery.highlightFade.defaults;
  131
+	var i = o['interval'] || d['interval'];
  132
+	var a = o['attr'] || d['attr'];
  133
+	var ts = {
  134
+		'linear': function(s,e,t,c) { return parseInt(s+(c/t)*(e-s)); },
  135
+		'sinusoidal': function(s,e,t,c) { return parseInt(s+Math.sin(((c/t)*90)*(Math.PI/180))*(e-s)); },
  136
+		'exponential': function(s,e,t,c) { return parseInt(s+(Math.pow(c/t,2))*(e-s)); }
  137
+	};
  138
+	var t = (o['iterator'] && o['iterator'].constructor == Function) ? o['iterator'] : ts[o['iterator']] || ts[d['iterator']] || ts['linear'];
  139
+	if (d['iterator'] && d['iterator'].constructor == Function) t = d['iterator'];
  140
+	return this.each(function() {
  141
+		if (!this.highlighting) this.highlighting = {};
  142
+		var e = (this.highlighting[a]) ? this.highlighting[a].end : jQuery.highlightFade.getBaseValue(this,a) || [255,255,255];
  143
+		var c = jQuery.highlightFade.getRGB(o['start'] || o['colour'] || o['color'] || d['start'] || [255,255,128]);
  144
+		var s = jQuery.speed(o['speed'] || d['speed']);
  145
+		var r = o['final'] || (this.highlighting[a] && this.highlighting[a].orig) ? this.highlighting[a].orig : jQuery.curCSS(this,a);
  146
+		if (o['end'] || d['end']) r = jQuery.highlightFade.asRGBString(e = jQuery.highlightFade.getRGB(o['end'] || d['end']));
  147
+		if (typeof o['final'] != 'undefined') r = o['final'];
  148
+		if (this.highlighting[a] && this.highlighting[a].timer) window.clearInterval(this.highlighting[a].timer);
  149
+		this.highlighting[a] = { steps: ((s.duration) / i), interval: i, currentStep: 0, start: c, end: e, orig: r, attr: a };
  150
+		jQuery.highlightFade(this,a,o['complete'],t);
  151
+	});
  152
+};
  153
+
  154
+jQuery.highlightFade = function(e,a,o,t) {
  155
+	e.highlighting[a].timer = window.setInterval(function() { 
  156
+		var newR = t(e.highlighting[a].start[0],e.highlighting[a].end[0],e.highlighting[a].steps,e.highlighting[a].currentStep);
  157
+		var newG = t(e.highlighting[a].start[1],e.highlighting[a].end[1],e.highlighting[a].steps,e.highlighting[a].currentStep);
  158
+		var newB = t(e.highlighting[a].start[2],e.highlighting[a].end[2],e.highlighting[a].steps,e.highlighting[a].currentStep);
  159
+		jQuery(e).css(a,jQuery.highlightFade.asRGBString([newR,newG,newB]));
  160
+		if (e.highlighting[a].currentStep++ >= e.highlighting[a].steps) {
  161
+			jQuery(e).css(a,e.highlighting[a].orig || '');
  162
+			window.clearInterval(e.highlighting[a].timer);
  163
+			e.highlighting[a] = null;
  164
+			if (o && o.constructor == Function) o.call(e);
  165
+		}
  166
+	},e.highlighting[a].interval);
  167
+};
  168
+
  169
+jQuery.highlightFade.defaults = {
  170
+	start: [255,255,128],
  171
+	interval: 50,
  172
+	speed: 400,
  173
+	attr: 'backgroundColor'
  174
+};
  175
+
  176
+jQuery.highlightFade.getRGB = function(c,d) {
  177
+	var result;
  178
+	if (c && c.constructor == Array && c.length == 3) return c;
  179
+	if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(c))
  180
+		return [parseInt(result[1]),parseInt(result[2]),parseInt(result[3])];
  181
+	else if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(c))
  182
+		return [parseFloat(result[1])*2.55,parseFloat(result[2])*2.55,parseFloat(result[3])*2.55];
  183
+	else if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(c))
  184
+		return [parseInt("0x" + result[1]),parseInt("0x" + result[2]),parseInt("0x" + result[3])];
  185
+	else if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(c))
  186
+		return [parseInt("0x"+ result[1] + result[1]),parseInt("0x" + result[2] + result[2]),parseInt("0x" + result[3] + result[3])];
  187
+	else
  188
+		return jQuery.highlightFade.checkColorName(c) || d || null;
  189
+};
  190
+
  191
+jQuery.highlightFade.asRGBString = function(a) {
  192
+	return "rgb(" + a.join(",") + ")";
  193
+};
  194
+
  195
+jQuery.highlightFade.getBaseValue = function(e,a,b) {
  196
+	var s, t;
  197
+	b = b || false;
  198
+	t = a = a || jQuery.highlightFade.defaults['attr'];
  199
+	do {
  200
+		s = jQuery(e).css(t || 'backgroundColor');
  201
+		if ((s  != '' && s != 'transparent') || (e.tagName.toLowerCase() == "body") || (!b && e.highlighting && e.highlighting[a] && e.highlighting[a].end)) break; 
  202
+		t = false;
  203
+	} while (e = e.parentNode);
  204
+	if (!b && e.highlighting && e.highlighting[a] && e.highlighting[a].end) s = e.highlighting[a].end;
  205
+	if (s == undefined || s == '' || s == 'transparent') s = [255,255,255];
  206
+	return jQuery.highlightFade.getRGB(s);
  207
+};
  208
+
  209
+jQuery.highlightFade.checkColorName = function(c) {
  210
+	if (!c) return null;
  211
+	switch(c.replace(/^\s*|\s*$/g,'').toLowerCase()) {
  212
+		case 'aqua': return [0,255,255];
  213
+		case 'black': return [0,0,0];
  214
+		case 'blue': return [0,0,255];
  215
+		case 'fuchsia': return [255,0,255];
  216
+		case 'gray': return [128,128,128];
  217
+		case 'green': return [0,128,0];
  218
+		case 'lime': return [0,255,0];
  219
+		case 'maroon': return [128,0,0];
  220
+		case 'navy': return [0,0,128];
  221
+		case 'olive': return [128,128,0];
  222
+		case 'purple': return [128,0,128];
  223
+		case 'red': return [255,0,0];
  224
+		case 'silver': return [192,192,192];
  225
+		case 'teal': return [0,128,128];
  226
+		case 'white': return [255,255,255];
  227
+		case 'yellow': return [255,255,0];
  228
+	}
  229
+};

0 notes on commit 3f9a0d7

Please sign in to comment.
Something went wrong with that request. Please try again.