Em janeiro deste ano, jQuery anunciou um novo registro de plugins , então agora parecia um ótimo momento para escrever um tutorial combinando a construção de um plugin jQuery com minha paixão - tecnologias web em tempo real.

As tecnologias da Web em tempo real tornam realmente fácil adicionar conteúdo ao vivo a páginas da Web anteriormente estáticas. O conteúdo ao vivo pode trazer uma página ativa, reter usuários e remover a necessidade de atualizar a página periodicamente. Geralmente, as atualizações em tempo real são obtidas conectando-se a uma fonte de dados, assinando os dados que você deseja adicionar à página e atualizando a página à medida que os dados chegam. Mas por que isso não pode ser alcançado simplesmente marcando uma página para identificar quais dados devem ser mostrados e onde? Bem, talvez possa!

O slogan do jQuery é escrever menos, fazer mais . O slogan para o plugin do jQuery Realtime que vamos construir neste tutorial será escrever menos, fazer em tempo real.

Neste tutorial, criaremos um plug-in jQuery que facilita a inclusão de conteúdo em tempo real em uma página, simplesmente adicionando uma marcação. Primeiro, vamos cobrir como usar um serviço chamado Empurrador para assinar dados em tempo real. Em seguida, definiremos uma maneira de marcar um documento HTML5 com atributos 'data- *' de uma maneira que possa ser consultada pelo nosso plug-in jQuery em tempo real e convertida em assinaturas de dados em tempo real. Por fim, criaremos o plug-in jQuery, que usará os atributos para assinar os dados e exibir instantaneamente as atualizações na página.

Se você quer mergulhar direto em você, pode ver uma demonstração em ação ou você pode baixe o código e começar a invadir.

Noções básicas empurrador

O Pusher é um serviço hospedado que facilita a inclusão de conteúdo em tempo real e experiências interativas em aplicativos da Web e móveis. Aqui vamos simplesmente nos conectar, assinar alguns dados e depois atualizar uma página quando os dados chegarem.

Para demonstrar isso, crie um arquivo chamado 'example.html' e inclua a biblioteca JavaScript Pusher da CDN Pusher. Sabemos que vamos usar o jQuery 2.0.0, então devemos incluir isso agora:

Creating a realtime jQuery plugin | Webdesigner Depot

Conectar

Uma vez que a biblioteca JavaScript Pusher foi incluída, podemos nos conectar ao Pusher criando uma nova instância 'Pusher' e passando uma chave de aplicação. Crie um adicional '

Note: For the tutorial we’ll use an application key that I’ve provided but for your own applications you’ll need to sign up to Pusher to get your own.

You can check that you’re connected in three different ways. You can do it manually by checking the Pusher Debug Console, if you load the page with the Pusher Debug Console open you’ll see the connection logged. 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. Or you can check the connection programmatically by monitoring the connection state of the Pusher instance.

pusher_001

The Pusher Debug console

Whatever you choose to do, you’ll now be connected.

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’.

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.

pusher_002

Using Pusher.log to log pusher-js library information

Bind to events

Those of you who use jQuery will probably be familiar with the idea of binding to events. jQuery does provide shortcuts for some events (e.g. ‘.onclick( )’) but you can also bind to events using ‘.bind(, )’. Pusher follows this convention and you can bind to events to be informed when something updates; when the connection state changes, when a subscription succeeds or when new application data is received. 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:

When binding to an event you simply identify the event by name and pass in a reference to a function that will be called when that event occurs (is triggered) on the channel.

If you have a Pusher account you can test that the ‘handleEvent’ function is called by using the Pusher Event Creator; enter ‘test-channel’ as the channel name, ‘test-event’ as the event name and some data (‘{ “some” : “data” }’) into the event data text area and click the submit button. You’ll then see the debug information, along with the data you entered, logged to the JavaScript console.

pusher_003 

Triggering an event from the Pusher Event Creator and logging it in the JavaScript console

Since the realtime jQuery plugin that we’re building doesn’t publish (trigger) data (it just consumes it) we won’t cover that here. But if you’re interested in finding out more checkout the Pusher server docs.

Displaying realtime updates

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

For this we’ll need an idea for a simple application; having worked in finance for a few years I’m generally keen to avoid any type of financial example, but Bitcoin has made it interesting and relevant. So, let’s create a very simple display for showing Bitcoin prices.

Note: We’re going to use some fake data. Let’s make sure this doesn’t result in more Bitcoin panic selling!

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:

Bitcoin Fake Prices

LastLowHighVolume
BTC/USD61.157 USD51 USD95.713 USD66271 BTC / 4734629 USD

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

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.

There are ways of both making this code nicer and, as the page grows to show more data, optimise things. But, we’re going to make it so that realtime data will be added to the page simply by applying markup.

Before we progress, let’s first add a bit of styling to the example. In the ‘’ add the following CSS:

As you can undoubtedly tell, I’m no designer. So please feel free to improve on this.

pusher_004

The “styled” Bitcoin Fake Prices application

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 first thing to highlight is that this isn’t a new idea — I worked for a company called Caplin Systems and in 2001 they had a technology known as RTML that let you markup a page so that realtime updates could be applied. 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 ‘

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 ‘’ 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:

Vamos analisar a criação da funcionalidade esperada.

Um plugin em tempo real

Primeiro, crie um arquivo 'realtime.jquery.js' dentro do diretório 'src'. Este arquivo irá conter a funcionalidade do plugin. Em seguida, adicione o seguinte ao arquivo como ponto de partida do nosso plug-in:

( function( $) {$.fn.realtime = function() {console.log( 'realtime!' );console.log( $( this ).html() );}  ;} (jQuery)); 

Podemos até testar isso agora. Em 'examples / bitcoin / index.html', remova o plug-in de exemplo '

Se você atualizar a página agora, verá "tempo real!" logado no console JavaScript junto com o HTML do '

' elemento. Isso é ótimo, pois significa que o plugin está funcionando; Estamos executando com sucesso nossa funcionalidade de plugin na tabela identificada pelo seletor que passamos para o jQuery.

pusher_005

Plugins jQuery e bibliotecas de terceiros

Nosso plugin em tempo real depende de uma biblioteca de terceiros - a biblioteca JavaScript Pusher. No momento, temos isso incluído estaticamente em nosso HTML, mas não queremos que seja um requisito usar o plug-in. Então, vamos dinamicamente carregá-lo. jQuery fornece uma maneira de fazer isso facilmente na forma de '.getScript ()' função.

Então, vamos carregar a versão 2.0 da biblioteca JavaScript Pusher. Carregaremos a versão hospedada em HTTPS para que os navegadores fiquem satisfeitos se nosso plug-in for usado em uma página veiculada por HTTPS (o Chrome já bloqueia as tentativas de carregar scripts hospedados em HTTP em páginas HTTPS e o Firefox fará isso em Firefox 23 ). Vou embalar o carregamento da biblioteca em uma função da seguinte forma:

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 carregado:' + textStatus);} loadPusher (); 

Se você recarregar a página, a mensagem 'pusher.min.js loaded: success' será registrada no console.

Como estamos desenvolvendo, é sempre bom ter uma maneira de registrar informações. Nesse ponto, vamos criar uma função simples de 'log' que possamos usar, apenas registrando no console. Nós vamos usar isso agora e também usá-lo para registrar eventos Pusher. A fonte completa do plugin é agora:

( 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 carregado:' + textStatus);} $. fn.realtime = function () {log (' em tempo real! '); log ($ (this) .html ());}; loadPusher ();} (jQuery)); 

Você também notará que atribuímos a função 'log' à propriedade 'Pusher.log'. Isso significa que podemos ver o registro interno da biblioteca Pusher e também o nosso.

Quando devemos nos conectar?

Devido à natureza assíncrona de carregar a biblioteca, não podemos garantir que ela será carregada quando nosso plug-in for acionado. Infelizmente isso torna as coisas um pouco mais complexas do que é ideal, mas vamos tentar resolvê-lo da maneira mais simples possível.

Precisamos verificar se a biblioteca foi carregada - daí o sinalizador "libraryLoaded" - e agir adequadamente; se a biblioteca foi carregada, podemos nos conectar, se não tivermos que enfileirar a execução até que isso aconteça. Por causa disso, faz mais sentido criar apenas a instância Pusher quando realmente precisamos dela, que é quando realmente queremos nos inscrever nos dados.

Vamos ver como podemos fazer isso:

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 );}};

Quando o plugin é chamado, checamos o flag 'libraryLoaded' para ver se a biblioteca JavaScript do Pusher foi carregada. Se for bom, podemos nos inscrever. Se ainda estiver pendente, precisamos enfileirar as inscrições. Fazemos isso enviando a coleção jQuery ('els') para uma matriz 'pendente'.

Agora conecte

Agora que sabemos que a biblioteca JavaScript Pusher foi carregada e que a página deseja se inscrever nos dados, podemos criar nossa instância 'Pusher'. Porque nós só queremos uma instância 'Pusher' por página, vamos seguir o Padrão singleton e tem um '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;}

Essa função pega a tag de script de plug-in procurando por uma tag com um atributo 'src' que termina com 'jquery.realtime.js' e, em seguida, obtém o valor do atributo 'data-rt-key'. Em seguida, cria uma nova instância 'Pusher', passando a chave. Como discutido anteriormente, a criação de uma nova instância 'Pusher' resulta em uma conexão com a origem de nossos dados sendo estabelecida.

Se inscrever

Agora podemos usar a função 'getPusher ()' sempre que quisermos acessar a instância 'Pusher'. No nosso caso, queremos usá-lo quando analisamos os elementos para determinar as assinaturas.

Atualize a função de assinatura do espaço reservado e adicione as funções adicionais mostradas abaixo:

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 );}

A função 'find' é uma função de utilidade para obter quaisquer elementos de uma coleção existente que correspondam a um determinado seletor usando '.filtro()', junto com quaisquer descendentes dos elementos usando '.encontrar()'. Usamos essa função para encontrar quaisquer elementos marcados para representar as assinaturas de canal (atributo 'data-rt-channel') e para cada um deles chamamos 'subscribeChannel'. Esta função extrai o nome do canal a ser inscrito e usa o valor em chamar 'pusher.subscribe (channelName)' para realmente se inscrever no canal.

Ligar

Em seguida, precisamos encontrar quaisquer elementos marcados para representar eventos (atributo 'data-rt-event') a serem associados a:

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 ) {}

Para cada elemento de evento que encontramos, chamamos nossa própria função de 'bind' que se liga ao evento no canal usando 'channel.bind (eventName, eventHandler)'. A função de manipulador de eventos é um pequeno fechamento que nos permite passar a atualização de dados, quando recebida, e o elemento de evento para uma função 'displayUpdate'.

Se executarmos isso agora, podemos ver no log que uma conexão está sendo estabelecida, estamos localizando um canal e assinando-o, e encontrando um evento para associar.

pusher_006

Marcação em tempo real do jQuery para encontrar assinatura de canal e ligação de evento

Mostrar a atualização

Quando o manipulador de eventos é chamado, precisamos encontrar o nome de cada propriedade no objeto 'data' (por exemplo, last, low, high e volume) enviado com a atualização e encontrar quaisquer elementos marcados com esse nome.

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 );}}

Fazemos um loop sobre o objeto 'data' e obtemos o nome de cada propriedade. Uma vez que sabemos o nome da propriedade ('propName'), podemos encontrar os elementos associados e atualizar seu valor de texto com o novo valor de dados. Por enquanto, não vamos apoiar objetos com qualquer tipo de hierarquia - queremos apenas um nível de pares chave e valor.

Se você atualizar a página e acionar um evento do Criador do Evento Pusher, os novos dados serão exibidos instantaneamente na página.

Você já trabalhou com um serviço de dados ao vivo? Que lições você aprendeu? Deixe-nos saber nos comentários.

Imagem em destaque / miniatura, imagem de dados ao vivo via Shutterstock.