Build a realtime jQuery plugin

Build a realtime jQuery plugin

A very simple display for showing Bitcoin prices

by Janeth Kent Date: 27-08-2013 jquery resources tutorials tips plugins real time

Realtime web technologies make it really easy to add live content to previously static web pages.

Live content can bring a page alive, retain users and remove the need for them to refresh the page periodically. Realtime updates are generally achieved by connecting to a source of data, subscribing to the data you want to add to the page and then updating the page as the data arrives.

But why can’t this be achieved through a page to identify what data should be shown and where?

Well, with JQuery maybe it can!

In this tutorial we’ll create a jQuery plugin that makes it really easy to add realtime content to a page in 3 steps:

1. First of all, we’ll cover how to use a service called Pusher to subscribe to realtime data.

2. Then we’ll define a way of marking up an HTML5 document with ‘data-*’ attributes in a way which can then be queried by our realtime jQuery plugin and converted to realtime data subscriptions.

3. We’ll create the jQuery plugin which will use the attributes to subscribe to data and instantly display updates within the page.

How Pusher works

Pusher is a hosted service that makes it easy to add realtime content and interactive experiences to web and mobile apps.

In this article we’re going to show how simply connect, subscribe to some data and then update a page when the data comes in.

To demonstrate this we have to create a file called ‘example.html’ and include the Pusher JavaScript library from the Pusher CDN.

We know we’re going to use jQuery 2.0.0 so we should also include that now:<!DOCTYPE HTML>

<html>
  
<head>  
  
<meta charset="utf-8"> 
  
<title>Creating a realtime jQuery plugin</title>  
  
</head>  
  
<body>  
  
<script src="http://js.pusher.com/2.0/pusher.min.js"> </script> 
   
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"> 
  
</script>  
  
</body> 
  
</html>

How to Connect

Create an additional ‘<script>’ tag beneath the jQuery include:

<!DOCTYPE HTML> 
  
<html>   
  
<head>   
  
<meta charset="utf-8">
     
<title>Creating a realtime jQuery plugin</title>  
   
</head>  
  
 <body>   
  
<script src="http://js.pusher.com/2.0/pusher.min.js"> </script>  
   
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"> </script> 
    
<script> 
    
( function( $ ) {  
      
var pusher = new Pusher( '7b9djshueiy3883ha' );  
      
} )
  
( jQuery ); 
    
</script>   
  
</body>   
  
</html>

Note: For this tutorial we’ll use a casual key number but for your own applications you’ll need to sign up to Pusher to get your own.

You can notice that you’re connected in three different ways:

1. You can do it manually by checking the Pusher Debug Console

2. The Pusher JavaScript library provides a log property that you can assign a function to and then you can manually check to make sure a connection has been established by inspecting the browser’s JavaScript console.

3. You can check the connection programmatically by monitoring the connection state of the Pusher instance.

How to Subscribe

Pusher uses the Publish & Subscribe pattern, so to receive data from Pusher you first need to subscribe to it. Pusher uses the term channels when it comes to subscriptions, so let’s subscribe to a channel called ‘test-channel’.

<script> ( 

function( $ ) {   

var pusher = new Pusher( '7b9djshueiy3883ha' );    

var channel = pusher.subscribe( 'test-channel' );   

} )( jQuery );   

</script>

As with connection state, you can check the status of a subscription in a few ways: using the Pusher Debug Console, by checking the output from ‘Pusher.log’ or by binding to the ‘pusher:subscription_succeeded’ event.

Bind to events

With Pusher you can bind to events to be informed when something updates; For this example, and with the realtime plugin, we’re interested primarily in the latter.

Let’s bind to a ‘test-event’ on the channel:

<script> 
 
( function( $ ) { 
      
var pusher = new Pusher( '7b9djshueiy3883ha' );  
  
var channel = pusher.subscribe( 'test-channel' ); 
   
channel.bind( 'test-event', handleEvent );    
   
function handleEvent( data ) { 
  
 console.log( 'in handleEvent:' ); 
  
 console.log( data );   }   
    
} )( jQuery );  
 
 </script>

How to Display realtime updates

The next thing to consider is displaying the realtime data updates to the user.

For this we’ll need an idea for an application...what do you think about a very simple display for showing Bitcoin prices?

So..let's go on!

Important: We’re going to use some fake data.

First, let’s create some HTML where we’ll display the realtime prices. We can pre-populate the display with prices known at the time the page was loaded:

<h1>Bitcoin Fake Prices</h1>  

<table id="bitcoin_prices">  

<thead>  

<tr>  

<th></th>
  
<th>Last</th>  

<th>Low</th> 

 <th>High</th>  

<th>Volume</th>  

</tr>  

</thead>  

<tbody>  

<tr>  

<td>BTC/USD</td>
  
<td>61.157 USD</td>  

<td>51 USD</td>  

<td>95.713 USD</td>  

<td>66271 BTC / 4734629 USD</td>
  
</tr>  

</tbody>  

</table>

Now we have to update the JavaScript to subscribe to a more appropriately named channel called ‘btc-usd’ and bind to a ‘new-price’ event:

<script>  

( function( $ ) {  

var pusher = new Pusher( '7b9djshueiy3883ha' );
  
var channel = pusher.subscribe( 'btc-usd' );  

channel.bind( 'new-price', handleEvent );  

function handleEvent( data ) {  

}  } )

( jQuery ); 
 
</script>

The ‘data’ sent to the ‘handleEvent’ function should also be in a more appropriate format – here’s the JSON:

{  "last": "last value",  "low": "low value",  "high": "high value",  "volume": "volume value"  }

Now that we know this we can change the ‘handleEvent’ function to update the appropriate cell in the table:

function handleEvent( data ) {  

var cells = $( '#bitcoin_prices tbody tr td' );  
 
cells.eq( 1 ).text( data.last );   

cells.eq( 2 ).text( data.low );   

cells.eq( 3 ).text( data.high );   

cells.eq( 4 ).text( data.volume );   

 }

If you now trigger a ‘new-price’ event on the ‘btc-usd’ channel, using the JSON we defined, the page will update to show the new values.

Let's add a bit of styling to the example.

<style>  

body {   

font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;  }   

#bitcoin_prices {  border-spacing: 0;  }   

#bitcoin_prices thead {  background-color: gray;  }   

#bitcoin_prices tbody tr:nth-child(odd) {  background-color: 
#fff;  }   

#bitcoin_prices tbody tr:nth-child(even) {   
background-color: #ccc;  }   

#bitcoin_prices tbody tr td:nth-child(1) {  
 background-color: yellow;  }   

#bitcoin_prices td {  padding: 4px 10px;  }  
 
</style>

Finally, restructure things so we’re set up for building the plugin.

  1. Create an ‘examples’ directory and within it a ‘bitcoin’ directory.
  2. Move the ‘example.html’ file to ‘examples/bitcoin’, rename it ‘index.html’.
  3. Create a ‘src’ directory at the top-level of the project.

The directory structure should now look as follows:

/
examples/
bitcoin/
index.html
src/

We’re now ready to define our realtime markup and build the realtime jQuery plugin.

Realtime markup

The purpose here is to use jQuery to parse the page and then interpret the markup, resulting in subscriptions, event binding and ultimately live content being added to the page.

For our plugin we’ll use HTML5′s data-* attributes. These attributes don’t directly affect the layout or presentation of the page so they’re a great choice for our realtime markup.

The questions we now need to answer about the markup are:

  • Where do we put the Pusher application key?
  • How do we identify what channels should be subscribed to?
  • How do we identify the events that should be bound to on a channel?
  • How do we know what data to display in the page, and where?

The first one is relatively easy. Since we need to include our plugin JavaScript file we can add a ‘data-rt-key’ attribute to the ‘<script>’ element for the plugin include and make the value our application key.

Channel subscriptions can be identified by a ‘data-rt-channel’ attribute. This will mean that the element that the attribute is on and all children are related to this subscription.

Event binding can be achieved using a ‘data-rt-event’ attribute where the element with the attribute, and all children are related to this event.

The values to be extracted from the event data can be indicated by a ‘data-rt-value’ attribute.

Putting this all together, update the Fake Bitcoin Prices markup to look as follows:

<h1>Bitcoin Fake Prices</h1>  

<table id="bitcoin_prices">  

<thead>  

<tr> 

 <th></th>
  
<th>Last</th>  

<th>Low</th>  

<th>High</th>  

<th>Volume</th>  

</tr>  

</thead>  

<tbody>  

<tr data-rt-channel="btc-usd"  data-rt-event="new-price">  
<td>BTC/USD</td>
  
<td data-rt-value="last">61.157 USD</td>  

<td data-rt-value="low">51 USD</td> 
 
<td data-rt-value="high">95.713 USD</td>  

<td data-rt-value="volume">66271 BTC / 4734629 USD</td>  

</tr> 

 </tbody>  

</table>  

<script src="path/to/jquery.realtime.js"  data-rt-key="7b9djshueiy3883ha"> </script>

So, from the script tag you can see we’re going to connect to Pusher using the key identified by ‘data-rt-key’. We’re going to subscribe to the ‘btc-usd’ channel and bind to the ‘new-price’ event. When an event is received we’re going to update the appropriate table cell based on the value indicated by ‘data-rt-value’; if the value of the attribute is ‘last’ then the value of the ‘last’ property is taken from the received ‘data’ object and displayed in the cell.

Hopefully what we are trying to achieve is now pretty clear. Let’s start looking at how to create a jQuery plugin.

jQuery plugin basics

The jQuery plugin creation docs are pretty good so I won’t go into the details here. We’ll simply concentrate on building the functionality that we need in our plugin.

Before we write any code we should consider how we want to use the plugin. The normal way a plugin functions is that you use jQuery to query the page, and then you execute the plugin functionality against the matched elements.

$( 'a' ).toggle();

The above code would find all ‘<a>’ elements and then execute the ‘toggle()’ functionality on them — probably hiding all anchors, so not the most useful example you’ll ever see.

So, let’s say we would want to use the plugin as follows:

<script>  

$( function() {   

$( '#bitcoin_prices' ).realtime();  } );  
 
</script>

Let’s look at creating the expected functionality.

A realtime plugin

First, create a ‘realtime.jquery.js’ file within the ‘src’ directory. This file will contain the plugin functionality. Then add the following to the file as the starting point of our plugin:

( function( $) {  

$.fn.realtime = function() {  
 
console.log( 'realtime!' );   

console.log( $( this ).html() );  }; 
  
}( jQuery ) );

We can even test this out now. In ‘examples/bitcoin/index.html’ remove the example plugin ‘<script>’ tag and find the one below the jQuery include and replace it with a ‘<script>’ tag which includes the new ‘realtime.jquery.js’ file (don’t forget the ‘data-rt-key’ attribute) and a script block which executes the ‘realtime()’ plugin on the ‘bitcoin_prices’ table:

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"> </script> 
 
<script src="../../jquery.realtime.js"  data-rt-key="7b9djshueiy3883ha"> </script>  
 
<script>  $( function() {  $( '#bitcoin_prices' ).realtime();  } );  </script>

If you refresh the page now you’ll see ‘realtime!’ logged to the JavaScript console along with the HTML from the ‘<table>’ element. This is great as it means the plugin is working; we’re successfully executing our plugin functionality on the table identified by the selector we passed in to jQuery.

jQuery plugins and 3rd party libraries

Our realtime plugin relies on a 3rd party library — the Pusher JavaScript library. For the moment we have it included statically within our HTML, but we don’t want to make that a requirement to use the plugin. So, let’s dynamically load it. jQuery provides a way of easily doing this in the form of the ‘.getScript()’ function.

So, let’s load version 2.0 of the Pusher JavaScript library. We’ll load the HTTPS hosted version so that browsers are happy if our plugin is used on a page served over HTTPS (Chrome already blocks attempts to load HTTP hosted scripts in HTTPS pages and Firefox will do in Firefox 23). I’m going to wrap loading the library in a function as follows:

var libraryLoaded = false;
  
function loadPusher() {  
 
$.getScript( "https://d3dy5gmtp8yhk7.cloudfront.net/2.0/pusher.min.js" ) 
  
.done( pusherLoaded ) 
  
.fail( function( jqxhr, settings, exception ) {
   
console.log( 'oh oh! ' + exception );  } );  
 
}  
 
function pusherLoaded( script, textStatus ) {
   
libraryLoaded = true;  console.log( 'pusher.min.js loaded: ' + textStatus );   
 
}  loadPusher();
 

If you reload the page the ‘pusher.min.js loaded: success’ message will be logged to the console.

As we’re developing it’s always good to have a way of logging information so at this point let’s create a simple ‘log’ function that we can use which just logs to the console. We’ll use this now and also use it for logging Pusher events. The full source of the plugin is now:

( function( $ ) {  
 
function log( msg ) {  
 
console.log( msg );  }  
 
var libraryLoaded = false;  function loadPusher() {  
 
$.getScript( "https://d3dy5gmtp8yhk7.cloudfront.net/2.0/pusher.min.js" )
   
.done( pusherLoaded )
   
.fail( function( jqxhr, settings, exception ) {
   
log( 'oh oh! ' + exception );  } );  
 
}  
 
function pusherLoaded( script, textStatus ) {  
 
libraryLoaded = true;  Pusher.log = log;  
 
log( 'pusher.min.js loaded: ' + textStatus );   
 
}  
 
$.fn.realtime = function() {  log( 'realtime!' );
   
log( $( this ).html() );  };  loadPusher();  }( jQuery )
  
);

You’ll also notice that we’ve assigned the ‘log’ function to the ‘Pusher.log’ property. This means we can see the internal Pusher library logging as well as our own.

When should we connect?

Due to the asynchronous nature of loading the library we can’t guarantee that it will have loaded when our plugin is called into action. Unfortunately this makes things a bit more complex than is ideal but we’ll try to solve it in as simple a way as possible.

We need to check to see if the library has loaded — hence the ‘libraryLoaded’ flag — and act appropriately; if the library has loaded we can connect, if it hasn’t we need to queue the execution until it does. Because of this it makes more sense to only create the Pusher instance when we really need it, which is when we actually want to subscribe to data.

Let’s look at how we can do that:

var pending = [];  

function pusherLoaded( script, textStatus ) {   

libraryLoaded = true;  while( pending.length !== 0 ) { 
  
var els = pending.shift();  subscribe( els );  } 
  
}  function subscribe( els ) {   

}   

$.fn.realtime = function() { 
  
var els = this;  if( libraryLoaded ) {  
 
subscribe( els );  }  else {  pending.push( els );  }  
};

When the plugin is called we check the ‘libraryLoaded’ flag to see if the Pusher JavaScript library has been loaded. If it has we’re good to go and we can subscribe. If it’s still pending then we need to queue up the subscriptions. We do this by pushing the jQuery collection (‘els’) onto a ‘pending’ array.

Now, connect

Now that we know that the Pusher JavaScript library has loaded and that the page wants to subscribe to data we can create our ‘Pusher’ instance. Because we only want one ‘Pusher’ instance per page we’re going to follow the Singleton pattern and have a ‘getPusher()’:

var pusher; 
 
function getPusher() {  
 
if( pusher === undefined ) {  
 
var pluginScriptTag = $("
 script[src$='jquery.realtime.js']");
   
var appKey = pluginScriptTag.attr("data-rt-key");
   
pusher = new Pusher( appKey );  } 
  return pusher;  
 
}

This function grabs the plugin script tag by looking for a tag with a ‘src’ attribute that ends with ‘jquery.realtime.js’, and then gets the value of the ‘data-rt-key’ attribute. It then creates a new ‘Pusher’ instance, passing in the key. As discussed earlier, creating a new ‘Pusher’ instance results in a connection to the source of our data being established.

Subscribe

We can now use the ‘getPusher()’ function anytime we want to access the ‘Pusher’ instance. In our case we want to use it when we parse the elements to determine subscriptions.

Update the placeholder ‘subscribe’ function and add the additional functions shown below:

function subscribe( els ) { 
 
 var channelEls = els.find( "*[data-rt-channel]" );  
 
log( 'found ' + channelEls.size() + ' channels' );  
 
channelEls.each( subscribeChannel );  }  
 
function subscribeChannel( index, el ) {  
 
el = $( el );  var pusher = getPusher();  
 
var channelName = el.attr( 'data-rt-channel' );  
 
var channel = pusher.subscribe( channelName );  }  
 
function find( els, selector ) {  
 
var topLevelEls = els.filter( selector );
   
var childEls = els.find( selector );  
 
return topLevelEls.add( childEls );  
 
}

The ‘find’ function is a utility function to get any elements from an existing collection that match a given selector using ‘.filter()’, along with any descendants of the elements using ‘.find()’. We use this function to find any elements marked up to represent channel subscriptions (‘data-rt-channel’ attribute) and for each we then call ‘subscribeChannel’. This function extracts the name of the channel to be subscribed to and uses the value in calling ‘pusher.subscribe( channelName )’ to actually subscribe to the channel.

Bind

We then need to find any elements marked up to represent events (‘data-rt-event’ attribute) to be bound to:

function subscribeChannel( index, el ) { 
  
el = $( el );  var pusher = getPusher();  
 
var channelName = el.attr( 'data-rt-channel' );  
 
var channel = pusher.subscribe( channelName ); 
 
 var eventEls = find( el, '*[data-rt-event]' );  
 
log( 'found ' + eventEls.size() + ' events' );  
 
eventEls.each( function( i, el) {  bind( el, channel );
   
} );  
 
}  
 
function bind( el, channel ) {  el = $( el ); 
  
var eventName = el.attr( 'data-rt-event' );  
 
channel.bind( eventName, function( data ) {  
 
displayUpdate( el, data );  } );  }  
 
function displayUpdate( el, data ) { 
 
 }

For each event element we find call our own ‘bind’ function which binds to the event on the channel using ‘channel.bind( eventName, eventHandler )’. The event handler function is a small closure which allows us to pass the data update, when received, and the event element to a ‘displayUpdate’ function.

If we run this now we can see from the logging that a connection is being established, we’re finding one channel and subscribing to it, and finding one event to bind to.

Display the update

When the event handler is called we need to find the name of each property on the ‘data’ object (e.g. last, low, high and volume) sent with the update and find any elements that are marked with that name.

function bind( el, channel ) {  

el = $( el );  var eventName = el.attr( 'data-rt-event' );
  
channel.bind( eventName, function( data ) {  

displayUpdate( el, data );  } );  }  

function displayUpdate( el, data ) {  

for( var propName in data ) {  

var value = data[ propName ];  

var updateEls = find( el, '*[data-rt-value="' + propName + '"]' );  

log( 'found ' + updateEls.size() + ' "' + propName + '" elements to update' );  

updateEls.text( value );  

}  }

We loop over the ‘data’ object and get the name of each property. Once we know the property name (‘propName’) we can find the associated elements and update their text value with the new data value. For now we’re not going to support objects with any kind of hierarchy — we just want one level of key and value pairs.

If you now refresh the page and trigger an event from the Pusher Event Creator the new data will be instantly displayed within the page.

 

Credit: original article written bt Phil Leggetter, a Real-Time Web Software and Technology Evangelist and Consultant, coder, blogger and author. You can get him on Twitter via @leggetter.

 
by Janeth Kent Date: 27-08-2013 jquery resources tutorials tips plugins real time hits : 11768  
 
Janeth Kent

Janeth Kent

Licenciada en Bellas Artes y programadora por pasión. Cuando tengo un rato retoco fotos, edito vídeos y diseño cosas. El resto del tiempo escribo en MA-NO WEB DESIGN AND DEVELOPMENT.

 
 
 

Related Posts

Interesting and Helpful Google Search Features You’ll Want to Start Using

Google – THE search engine for many internet users. It has been with us since its launch back in 1998 and thanks to its simplicity of use and genius algorithms,…

How to use your browser as a file browser, in Chrome or Microsoft Edge

We're going to explain how to use the Chrome browser as a file browser, both on Android and on your computer. This is a hidden feature of Chromium that will…

How to watch deleted or private Youtube videos

Today we are going to talk about the technique which you permit to be able to recover videos from Youtube that was deleted, made private or simply blocked by Youtube…

How to ‘leave’ a WhatsApp group without actually leaving

If you want to leave a WhatsApp group but don't want the 'You left the group' message to be displayed, read this article. Some people love WhatsApp groups, and some people…

The 6 Essentials for Creating a Visually Appealing Web Design

Creating a website might seem like a simple thing. After all, what do you really need besides a good hosting provider and relevant content, right? Well, the reality is quite…

How to get notified when one of your WhatsApp contact is online

Privacy is one of the most important aspects among most users of the popular Facebook messaging application. So much so, that many settings have been included in WhatsApp itself to…

How to change font size and format in WhatsApp

Your WhatsApp messages can be much more original and fun by changing the font, size, or format. Here's how. WhatsApp is the leading messaging app for virtually everyone, with permission from…

WhatsApp: How to View Deleted Messages

Words are carried away by the wind, as long as they are not written on WhatsApp. Sending it was like getting a tattoo, something you'll always like, or yet it'll…

How to enable Chrome's new feature that blocks ads that consume your CPU, battery, and Internet connection

Google has announced that it will begin experimenting with a technology that they have been developing for months, in order to improve the battery consumption of our computers and the…

Protect yourself: know which apps have access to your location, microphone and camera

When you install an application on your smartphone, you decide what permissions you give it, but it's not always clear. We tell you how to know if those permissions are…

Building a Home for Your Home Finders

As a real estate agent, one of the most important aspects of your job is outreach. No one will know about your services if you have no way of letting…

Can Easier Returns Mean Better Business?

Any eCommerce business owner knows that returns are a part of the packaged deal. At the end of the day, today’s consumer holds all of the power. They are informed,…

Clicky