Date:
11.03.2010

La pétition en ligne helpwiels.org est un travail que nous avons réalisé en quelques jours et dont nous avons documenté la progression ici. Un des objectifs de ce blog est d’entrer dans les détails des travaux, de partager nos recherches et nos ressources. C’est précisément le but de cet article, où il sera question du code derrière l’affichage de la liste des signataires du site helpwiels.org. Le principe de la structure graphique que l’on retrouve sur le site en arrière plan est d’afficher progressivement les signatures (enregistrées via le formulaire dans une base de données), dans des cases dont la teinte varie selon l’heure de la souscription. En observant la structure, on distingue clairement les jours d’affluence (au début de l’action, tout en bas de la structure, il y a de longs dégradés) des autres (quand les teintes varient brutalement, en haut de la structure). Le principal danger dans le développement du système était de saturer la base de données avec des requêtes trop lourdes (demander à la base de données 2000 signatures en une fois prendrait trop de temps et trop de mémoire) ou trop fréquentes (demander toutes les milisecondes une nouvelle signature créerait trop de connexions). L’idée pour récupérer ces signatures était donc de trouver un équilibre entre le nombre de signatures à récupérer à chaque requête et la fréquence de ces requêtes. En dehors de ce problème de performance, il fallait que l’affichage progressif des signatures soit suffisamment rapide.

La structure du développement:

– une classe Listing chargée des requêtes à la base de données
– une classe Box chargée de créer et de contenir les éléments html div graphiques à partir de chaque signature
– un tableau displayList destiné à se remplir progressivement de ces éléments graphiques à afficher
– une variable buffer de type Listing
– un timer chargé de l’affichage du contenu de displayList
– un élément html div « structure » chargé de recevoir les éléments html div contenus dans les éléments Box.
Et le processus:
– à l’affichage de la page, on lance le timer (sa fréquence est déterminée par le nombre de signatures à charger en une requête par le buffer — 100 — et le délai d’apparition d’une signature sur le site — 200 ms —) et le buffering des premières signatures.
– le buffer récupère les signatures via le fichier nodelist.json.php qui renvoie progressivement les signatures sous cette forme:

{"length":10,"listing":[{"id":"1","name":"Josianne Dupont","time":"2010-03-11 12:52:32"},{"id":"2","name":"Emile","Dumont":"2010-03-11 12:16:41"}]}

– le buffer, après avoir récupéré les signatures, remplit un tableau d’éléments Box (un élément par signature). Et ajoute ce tableau à la fin de displayList.
– le timer récupère toutes les 200 ms le premier élément de displayList et l’affiche.

Ce qui donne en pratique et en javascript avec la librairie mootools:

classes.js

var Listing = new Class({
	Implements: [Options, Events],
	options: {
		orderField:false,
		order:'ASC',
		dataPath:'json/nodelist.json.php'

	},
	initialize: function(options) {
		this.setOptions(options);
		this.state = 'READY';

	},
	load:function(begin, end){
		this.state = 'LOADING';
		var params = {'begin':begin, 'end':end, 'order':this.options.order};
		if(this.options.orderField)
		params.orderField =  this.options.orderField;

		var request = new  Request({
			url:this.options.dataPath,
			method:'get',
			data: params,
			onSuccess:this.fillElements.bind(this)
		}).send();
	},
	fillElements:function(obj){
		jsonObj = JSON.decode(obj);
		this.nbElems = jsonObj.length;

		if(this.nbElems == 0) this.state = 'DONE';
		else this.state =  'READY';

			this.elements = new Array();
			jsonObj.listing.each(function(item){
				var box = new  Box({'id':item.id, 'name':item.name, 'time':item.time});
				this.elements.push(box.boxElem);

			}.bind(this));

			this.fireEvent('loaded');
		}
});

var  Box = new Class({
	Implements: [Options, Events],
	options:  {
		id:'',
		name:'',
		time:''
	},
	initialize: function(options) {
		this.setOptions(options);
		this.getOpacity();
		this.makeElem();
	},
	getOpacity:function(){
		var hour =  this.options.time.substring(11,13);
		this.opacity =  (hour/24).round(2);

	},
	makeElem:function(){
		var background = new Element('div', {
			'class':'background',
			'opacity':this.opacity,
			'text':this.options.name
		});
		var textElem =  new Element('div', {
			'class':'textElem',
			'text':this.options.name

		});

		this.boxElem = new Element('div',{'class':'box'});

		textElem.inject(this.boxElem);
		background.inject(this.boxElem);

	}
});

init.js

var displayList;
var displayY;

var dataLength = 100;
var  contenerWidth = window.getSize().x-150;

var nbBoxLine = 6;
var  fadeInTime = 200;

var buffer = new  Listing({onLoaded:addToList});
var displayTimer;
var  displayThreads = 0;

function addToList(){
	displayList =  displayList.concat(buffer.elements);
	dataBegin += dataLength;
	if(buffer.state == 'READY'){

		startBuffering.delay(dataLength/nbBoxLine*fadeInTime)*10;
	}
}

function  startBuffering(){
	buffer.load(dataBegin, dataLength);
}

function  display(){
	if(displayList.length == 0){
		if(buffer.state == 'DONE'){
			$clear(displayTimer);
		}
		return;
	}
	if(displayThreads > 2)  return;

	displayThreads++;
	if(displayList.length  < nbBoxLine) var lineLength = displayList.length;
	else var  lineLength = nbBoxLine;
	var lineWidth = 0;
	for(var i = 0;  i<lineLength; i++){
		displayList[i].inject($('structure'));
		if(lineWidth +  displayList[i].getElement('.textElem').getDimensions().x >  contenerWidth){
			lineLength = i;
			break;
		}
		lineWidth +=  displayList[i].getElement('.textElem').getDimensions().x;

	}
	var displayX = (contenerWidth-lineWidth)/2;
	var tweens  = new Array();
	var previousElem = false;
	var firstElem =  displayList[0];
	var lineHeight =  firstElem.getElement('.textElem').getDimensions().y;
	var line =  new Element('div', {'class':'line'});

	line.setStyles({'left':displayX, 'top':displayY, 'width':lineWidth,  'height':lineHeight});
	line.inject($('structure'));
	var  lastElem = displayList[lineLength-1];
	for(var i = 0; i <  lineLength; i++){
		var elem = displayList.shift();
		elem.setStyles({'left':displayX, 'top':displayY});

		tweens.push(elem.get('tween', {property: 'opacity', duration:  fadeInTime}));
		elem.getElements('div').each(function(el){el.setStyle('display',  'block');});
		if(Browser.Engine.trident){
			elem.getElement('.background').setStyle('display', 'none');
		}
		elem.setStyle('opacity', 0);
		displayX +=  elem.getElement('.textElem').getDimensions().x;
		if(previousElem != false){
			previousElem.store('nextElem',  elem);
			var tween = previousElem.get('tween', {property:  'opacity', duration: fadeInTime});

			tween.addEvent('onComplete', function(){
				if(Browser.Engine.trident){
					this.element.retrieve( 'nextElem' ).getElement( '.background' ).setStyle( 'display' ,  'block');
				}
				this.element.retrieve('nextElem').get('tween').start(1);

			});
		}
		previousElem = elem;

	}
	firstElem.get('tween').start(1);
	lastElem.get('tween').addEvent('onComplete', function(){

		displayThreads--;

	});

	if(Browser.Engine.trident){
		firstElem.getElement('.background').setStyle('display', 'block');
	}

	displayY += lineHeight;

}

function  launch(){
	displayList = new Array()
	dataBegin = 0;
	displayY = 10
	$('structure').empty();
	$clear(displayTimer);
	startBuffering();
	displayTimer =  display.periodical(fadeInTime*nbBoxLine);
}

window.addEvent('domready',  function(){
	showSubs();
	launch();

});