• dmsLiveList: Lista de itens em Tempo Real com AJAX

    by  • December 12, 2006 • ajax, PHP, RSS, Web 2.0, XML

    No meu local de trabalho temos um Portal de notícias e uma intranet. Na intranet apresentamos noticias do Portal, como ela esta sendo reformulada, decidi me livrar do iframe e buscar alguma forma de manter uma lista de notícias atualizadas sem que isto ocorra junto a um refresh da página.

    Como a necessidade é a mãe de toda invenção, surgiu a idéia de usar AJAX de forma a satisfazer esta minha necessidade. Comecei então este script, simples inspirado na seção “DiggSpy” do Digg.com. Veja como podemos implementar este script no resto do post.

    Objetivos do Script

    Buscar, em intervalos determinados de tempo, novos itens de uma fonte qualquer de dados. De forma que apenas seja atualizada a lista se houver um item novo.

    Fluxo de atividade

    Fluxo de dados - dmsLiveList

    De uma forma simples esta é a ideia do que o script deve realizar para cumprir o objetivo citado acima.

    Para explicar melhor o funcionamento do script vou dividi-lo em X partes, que podem refletir os passos que testei antes de consolidar um script completo. São estas:

    1. Inicialização/Resgate de dados
    2. Obtenção da lista/Retorno de dados (Backend)
    3. Atualização/População dos dados

    1 – Inicialização/Resgate dos Dados

    Com experiências passadas em AJAX a questão do resgate dos dados era bem simples, porém, a inicialização, após analisar o projeto como um todo, trouxe alguns problemas que precisava contornar.
    Ao iniciar o script deveria buscar um numero determinado de itens, de forma a popular a lista preenchendo todas as “vagas”, na outra mão durante a execução devemos receber apenas um novo item de cada vez.

    this.listLength		= 5; //número de elementos a apresentar
    this.iniLoad		= this.listLength; //Número de itens a serem carregados no primeiro load
    
    this.init = function(){
    		me.showLoading();
    		me.getNewItems();
    		if (me._firstLoad) me._firstLoad = 0; //indica primeiro load
    		me.updateList();
    }

    Além disso temos outro problema. Como a requisição XMLHttpRequest é executada de forma paralela, corre-se o risco de o script de atualização se adiantar à primeira chamada e ocasionar um erro de javascript, ou uma longa espera (tempo entre atualizações) até o conteúdo aparecer. Com isso foi necessário adicionar ao script um loop que verificasse o estado do request, e repetisse a chamada do update em intervalos consideravelmente menores. Fora estas ações, verifiquei a necessidade de apresentar um div de carregamento, que indicasse que o conteúdo estava sendo obtido.

    //Continuar repetindo a chamada de atualização até o conteudo chegar
    if (!me.resultReady){
    			me._timer = setTimeout( function(){ me.updateList(); }, 500);
    			return false;
    		}
    
    //Carregar código de loading e indicar que esta mostrando loading
    	this.showLoading = function (){
    		me._target.innerHTML = "<div class='loading'><img src=load.gif/></div>";
    		me._loadingState = 1;
    	}

    Para fazer a requisição utilizei os comandos crus, criando um request e enviando uma simples requisição GET, e apontando para uma função da parte 3, o tratamento dos dados de retorno.

    me.ajaxReq.open("POST", me.ajaxTarget, true);
    me.ajaxReq.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    me.ajaxReq.onreadystatechange = me.insertNewItems;
    if (me._firstLoad){
    	params = "iniLoad="+this.iniLoad;
    }else{
    	params = null;
    }
    me.ajaxReq.send(params);

    2 – Obtenção da lista/Retorno de dados (Backend)

    Esta parte do script foi desenvolvida em PHP, e como exemplo deste script criei dois retornos diferentes. Um script busca dados de um RSS da internet, e o outro retorna frases randômicas sempre que é chamado, simulando uma situação de uma lista com alta freqüência de atualização.

    Em comum estes dois scripts precisam de um flag que permita retornar o numero necessário de itens na primeira chamada, e retornar os dados finais em um formato padrão, neste caso XML. A forma como produzir estes dados e suas fontes é a variação entre eles.

    $limit = ($_POST[iniLoad] > 0)? $_POST[iniLoad]:"1";

    Buscando dados de um RSS

    Aqui tivemos um desafio, determinar quais itens eram realmente novos no RSS. Para isso tivemos de utilizar variáveis de sessão, já que as outras formas sempre falhavam em um ou outro RSS.

    if (!in_array($insItem->guid,$_SESSION[dmsSpy_read]) || $_POST[iniLoad]){

    Frases Randômicas

    Este script é mais um “tampa buraco” para poder mostrar dados mais pertos da realidade. Tudo é feito de forma simples, montando um array de palavras, escolhendo uma seqüência aleatória e retornando ela.

    function randomSentence($length){
    	$ostring = "texto longo, origem das palavras (obtido no gerador de Lero-Lero)";
    	$available = array_unique(explode(" ",$ostring));
    	$nwords = count($available);
    	for($i = 0; $i< $length; $i++){
    		$chamada .= $available[rand(0,$nwords)]." ";
    	}
    	return trim(strtolower($chamada));
    }

    3- Atualização ou População dos dados

    O recebimento dos dados é feito de forma simples, adicionando divs no começo da lista e removendo o ultimo da lista. Para isso foi utilizado um array que armazena os IDs dos DIVS apresentados, combinado com funções de PUSH e SHIFT para adicionar e remover os itens do array.

    var it = me._new.shift();
    
    id = "spy_"+it['id'];
    newdiv = document.createElement('div');
    newdiv.id = id;
    newdiv.className = 'spy';
    newdiv.innerHTML = "<div>"+it['value']+"";
    newdiv.style.display = 'none';
    
    me._target.insertBefore(newdiv,me._olddiv);
    me._olddiv = newdiv;
    
    me._shown.push(id);
    

    Para criar o efeito como o do Spy que foi minha inspiração, decidi usar a biblioteca Scriptaculous, que deve estar presente. Porém, como nem todos desejam usar esta biblioteca ou até ter efeitos especiais, implementei uma configuração simples que desabilita os efeitos, deixando o script mais “seco” mas mantendo sua funcionalidade.

    this.effects = 1; //Usar ou não Script.aculo.us

    Espero que este script seja útil, sintam-se a vontade para alterá-lo e utilizar em suas soluções, mas peço que por favor deixem alguma referencia para este blog.

    Veja uma demostração do script
    Faça o download

    About

    Rafael Dohms is a PHP Evangelist, Speaker and contributor. He is a very active member of the PHP Community, having helped create and manage two PHP User Groups in Brazil. He shared the lead of PHPSP for 3 wonderful years making a positive mark on the local market. Developer, gamer and lover of code he also hosts Brazil’s first PHP Podcast: PHPSPCast, as well as contributing to well known projects. He moved to the Netherlands in search of new challenges and is now part of the team at WEBclusive, sharing his passion for quality code and working on new awesome ideas with the team. You can always find him at the nearest Community events, speaking, sharing, talking or just learning from the rest.

    http://doh.ms