Base de données en Javascript avec TaffyDB

Posté par seiyar81 le 6 octobre 2014 | Laisser un commentaire (0)
TaffyDB

Lors de mes développements, une tâche qui revient quasiment à chaque fois est l’implémentation de la gestion de collection en Javascript. Faire une recherche dans une collection, ajouter/supprimer des éléments etc …

Si la gestion de ces opérations n’est pas compliquée il n’en reste pas moins que le plus simple est d’utiliser une librairie dédiée à cette gestion.

C’est alors qu’intervient TaffyDB, une librairie de gestion de base de données en Javascript.
Elle gère les opérations de type CRUD, est facilement extensible et est aussi bien utilisable en front avec jQuery et compagnie qu’en back via NodeJS par exemple.

Sans tarder voici ci dessous un exemple complet de ce qu’il est possible de faire avec la librairie :

	// Déclaration de la table employés
	var employees = new TAFFY([
		{"id":1,"gender":"M","first":"John","last":"Smith","branch":"Seattle, WA","department":1},
		{"id":2,"gender":"F","first":"Kelly","last":"Ruth","branch":"Seattle, TX","department":2},
		{"id":3,"gender":"M","first":"Jeff","last":"Stevenson","branch":"Washington, D.C.","department":1},
		{"id":4,"gender":"F","first":"Jennifer","last":"Gill","branch":"Seattle, WA","department":3}	
	]);
		
	// Déclaration de la table départements
	var departments = TAFFY([
	  {id: 1, name: 'Sales', abbreviation: 'SA'},
	  {id: 2, name: 'Accounting', abbreviation: 'AC'},
	  {id: 3, name: 'Human Resources', abbreviation: 'HR'}
	]);

Vous pouvez donc initialiser vos bases de données en récupérant le contenu d’une requête AJAX.

	
	// Tri sur la colonne last et récupération du premier de la liste
	employees.order("last").first().name;

	// Select sur la colonne branch et count
	employees({branch:"Seattle"}).count();

	// Update de la colonne branch
	employees({id:"1"}).update({ branch: "Washington, D.C." });

	// Delete
	employees({id:"4"}).remove();
	
	// Insert d'un nouveau département
	departments.insert({id: 4, name:"IT Support", abbreviation:"IT"});

Enfin sachez que les jointures sont gérées :


	// Jointure sur la table departments et récupération du nom du premier élément : Sales
	employees().join( departments, [ 'department', 'id' ]).first().name;

Attention si les deux tables jointes ont des noms de colonnes en commun alors le nom de colonne de la table jointe sera préfixé avec right_.

Et voilà il n’y a plus qu’à se servir des multiples fonctions de TaffyDB pour gérer au mieux ses jeux de données.

Catégorie: Développement Web, Javascript | Laisser un commentaire (0)

Java : créer une archive Zip en mémoire avec ZipOutputStream

Posté par seiyar81 le 3 avril 2014 | Laisser un commentaire (0)

Une petite class utilitaire pour générer des archives Zip en mémoire, sans rien écrire sur le disque, le tout aussi bien à partir de fichiers physiques ou de texte brut.

package fr.yriase.tools;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.servlet.ServletOutputStream;

public class ZipFileWriter {

  /**
  * Flux de l'archive zip
  */
  private ZipOutputStream zos;
  
  /**
  * Constructor: creation d'une nouvelle archive directement dans le stream passé en paramètre
  * @param OutputStream out
  * @throws FileNotFoundException
  */
  public ZipFileWriter(OutputStream out) throws FileNotFoundException {
  	this.zos = new ZipOutputStream(out);
  }
  
  public ZipOutputStream getOutputStream()
  {
  	return this.zos;
  }
  
  /**
  * Ajouter un fichier depuis le disque dur au zip
  * @param String fileName
  * @throws FileNotFoundException
  * @throws IOException
  */
  public void addFile(String fileName) throws FileNotFoundException, IOException {
    FileInputStream fis = new FileInputStream(fileName);
    int size = 0;
    byte[] buffer = new byte[1024];
    
    //Ajouter une entree à l'archive zip
    File file = new File(fileName);
    ZipEntry zipEntry = new ZipEntry(file.getName());
    this.zos.putNextEntry(zipEntry);
    
    //copier et compresser les données
    while ((size = fis.read(buffer, 0, buffer.length)) > 0) {
    	this.zos.write(buffer, 0, size);
  	}
  
  	this.zos.closeEntry();
  	fis.close();
  }
  
  /**
  * Ajouter un fichier depuis un array de bytes au zip
	* @param 
  * @param fileName
  * @throws IOException
  */
  public void addFile(byte[] bytes, String fileName) throws IOException {
    //Ajouter une entree à l'archive zip
    ZipEntry zipEntry = new ZipEntry(fileName);
    this.zos.putNextEntry(zipEntry);
    
    //Copier et compresser les données
    this.zos.write(bytes, 0, bytes.length);
    
    this.zos.closeEntry();
  }
  
  /**
  * Fermer le fichier zip
  * @throws IOException
  */
  public void close() throws IOException {
  	this.zos.finish();
  	this.zos.close();
  }
}

A utiliser comme ceci dans une action Struts par exemple :

public class DownloadZipAction extends Action {

	public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
	
		ServletOutputStream out = response.getOutputStream();

		response.setHeader("Content-Type","application/zip");
		response.setHeader("Content-Disposition", "attachment; filename=\"test.zip\"");

		ZipFileWriter zip = new ZipFileWriter(out);

		zip.addFile("<?xml version=\"1.0\" encoding=\"UTF-8\"?><test>var1</test>", "test.xml");
		zip.addFile("C:/test.xml");
		
		zip.close();

	}

}

A garder sous le coude, ça peut toujours servir.

Catégorie: Développement Web, Java | Laisser un commentaire (0)

Développer en ligne avec Koding

Posté par seiyar81 le 30 janvier 2014 | Laisser un commentaire (0)
Koding

Une petite news rapide pour préciser que le site Koding offre à tout nouvel inscrit un espace de 4Go pour développer ses applications en ligne (dans la limite de 100 To pour tout le monde).

Pour information Koding est un service qui offre une VM accessible via une interface Web et avec tout ce qui va avec : terminal, explorateur de fichiers, applications etc …

Idéal lorsqu’on travaille sur plusieurs machines en même temps ou que l’on souhaite tester un bout de code rapidement.

Plus intéressant encore, Koding permet de travailler en collaboration avec d’autres utilisateurs ! Voir la vidéo ci-dessous :

Un outil à tester d’urgence car le tout est plutôt fluide et simple à utiliser !

Catégorie: Développement Web, Geek, Internet | Laisser un commentaire (0)

jQuery UI DatePicker : désactiver certains jours de la semaine

Posté par seiyar81 le 28 janvier 2014 | Laisser un commentaire (3)
jQuery UI DatePicker

jQuery UI propose bon nombre de widgets très utiles lorsqu’on développe des applications Web dynamiques.
Parmis ces derniers le DatePicker, ou calendrier, qui propose plusieurs options de configuration très précises.

Prenons par exemple le cas suivant : le client pour qui vous développez une application de réservation de restaurants vous informe que tous les réservations ne sont pas possibles les lundis et jeudis.

Et bien pas de problèmes, la fonction beforeShowDay permet de pré-filtrer chaque date et de définir si elle est sélectionnable ou pas.
Ce qui donne dans notre cas :


	jQuery(document).ready(function() {
		jQuery("#calendar").datepicker({
			beforeShowDay: function (date) {
				if (date.getDay() == 1 || date.getDay() == 4) { // La semaine commence à 0 = Dimanche
					return [false, ''];
				} else {
					return [true, ''];
				}
			}
		});
	});

Et voilà c’est tout simple.

On peut également utiliser la même méthode pour supprimer certaines semaines ou mois de l’année (vacances par exemple) :


	jQuery(document).ready(function() {
		jQuery("#calendar").datepicker({
			beforeShowDay: function (date) {
				if (date.getMonth() == 6 || date.getMonth() == 7) { // Mois Juillet et Août
					return [false, ''];
				} else {
					return [true, ''];
				}
			}
		});
	});

Si vous souhaitez par contre mettre une date minimum, utilisez plutôt l’option minDate :


	jQuery(document).ready(function() {
		jQuery("#calendar").datepicker({
			minDate: new Date(2014, 1 - 1, 1)
		});
	});

A vos claviers !

Catégorie: Développement Web, Javascript | Laisser un commentaire (3)

Remplir un formulaire avec jQuery

Posté par seiyar81 le 7 janvier 2014 | Laisser un commentaire (0)
jQuery Populate

Lorsque vous developpez une application Web, disons en PHP par exemple et que vous devez assigner vous-même les valeurs aux champs d’un formulaire, le mélange HTML/PHP peut vite s’avérer indigeste.

Si vous disposez de données JSON, ou de la possibilité de formater des données dans ce format, pour remplir votre formulaire, alors le plugin jQuery Populate devrait répondre à tous vos besoins.

Imaginons le formulaire HTML suivant :

	

Vous récupérez un jeu de données JSON via une requête AJAX ou bien directement formaté dans la page du type :

  var data = {

		name: "Yriase",

		type: ["0","2"],

		valider: false,

		comment: "Commentaire plus long"

	}

Grâce à Populate vous n’avez plus qu’à réaliser l’appel suivant :


	$('form["myform"]').populate(data);

Et le tour est joué !

Catégorie: Développement Web, Javascript | Laisser un commentaire (0)

FullCalendar : chargement et mise à jour des évènements dans une base de données

Posté par seiyar81 le 7 août 2013 | Laisser un commentaire (7)

Pour faire suite au précédent article présentant le plugin FullCalendar, nous allons voir comment charger des évènements depuis une base de données pour peupler notre calendrier et ensuite comment les mettre à jour.

Nous allons pour cela mettre en place un calendrier avec affichage simple (mois/semaine/jour) sur notre page web :


<html>
<head>
<link href='fullcalendar/fullcalendar/fullcalendar.css' rel='stylesheet' />
<link href='fullcalendar/fullcalendar/fullcalendar.print.css' rel='stylesheet' media='print' />
<script src='fullcalendar/jquery/jquery.min.js'></script>
<script src='fullcalendar/jquery/jquery-ui.min.js'></script>
<script src='fullcalendar/fullcalendar/fullcalendar.js'></script>

<style>

	body {
		margin-top: 40px;
		text-align: center;
		font-size: 14px;
		font-family: "Lucida Grande",Helvetica,Arial,Verdana,sans-serif;
		}

	#calendar {
		width: 900px;
		margin: 0 auto;
		}

</style>
</head>
<body>
<div id='calendar'></div>
</body>
</html>

Côté serveur, il va nous falloir une base de données, forcément, avec une table pour stocker les évènements pouvant ressembler à ceci :



--
-- Table structure for table `event`
--

CREATE TABLE IF NOT EXISTS `event` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(55) NOT NULL,
  `start` datetime NOT NULL,
  `end` datetime DEFAULT NULL,
  `allDay` tinyint(1) DEFAULT NULL,
  `url` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ;

On va ensuite ajouter le code Javascript pour charger et mettre à jour les évènements :


    $(document).ready(function() {
	
        function updateEvent(event,dayDelta,minuteDelta,allDay)
        {
            $.ajax({ 'url': 'ajax.php?action=update', 'type': 'POST', 
                'data': {'id':event.id, 'dayDelta': dayDelta, 'minuteDelta': minuteDelta, 'allDay': allDay},
                success: function(data) {
                    if(data.error)
                        alert(data.error);
                },
                error: function(data) {
                   alert('Une erreur est survenue.'); 
                }
            });
        }
		
				$('#calendar').fullCalendar({
            header: {
    					left: 'prev,next today',
							center: 'title',
							right: 'month,agendaWeek,agendaDay'
						},
            editable: true,
            eventSources: [
                'ajax.php?action=get'
            ],
            eventDrop: function(event,dayDelta,minuteDelta,allDay,revertFunc) {    
                if (!confirm("Valider la modification ?")) {
                    revertFunc();
                } else {
                    updateEvent(event,dayDelta,minuteDelta,allDay);
                }
            },
            eventResize: function(event,dayDelta,minuteDelta,revertFunc) {
                if (!confirm("Valider la modification ?")) {
                    revertFunc();
                } else {
                    updateEvent(event,dayDelta,minuteDelta,0);
                }
            }
		});
		
	});

On note donc que le paramètre d’initialisation eventSources indique au calendrier une (ou plusieurs) url à interroger pour charger les évènements.
Le résultat attendu étant un tableau au format JSON.
Les callbacks eventDrop et eventResize sont appellés dès qu’un évènement est modifié sur le calendrier.
Si l’utilisateur valide l’action, alors on appelle la fonction updateEvent qui enverra la requête d’update au serveur.
Les paramètres dayDelta, minuteDelta et allDay correspondent aux modifications apportées à l’évènement.

Pour fournir ce tableau c’est très simple.
On va créer une page PHP pour recevoir et traiter les requêtes AJAX :


    $dsn = 'mysql:dbname=fullcalendar;host=127.0.0.1';
    $user = 'root';
    $password = 'root';

    if(isset($_GET['action']))
    {
        switch($_GET['action'])
        {
            case 'get':
                try {
                    $db = new PDO($dsn, $user, $password);
                    
                    $sql = 'SELECT * FROM event';
                    $events = array();
                    
                    foreach ($db->query($sql) as $row) {
                        $events[] = $row;
                    }
                    
                    echo json_encode($events);
                    
                } catch (PDOException $e) {
                    echo json_encode(array('error' => 'Connection failed: ' . $e->getMessage()));
                }                
                break;
            default:
                break;
        }
    }
    

Très simple donc, on contrôle l’action reçue en paramètre, si c’est get alors on renvoit un tableau contenant les évènement stockés dans la base de données.
Un simple json_encode des évènements suffit.

Avec ce code on possède donc un calendrier affichant nos évènements.
Pour maintenant mettre à jour nos évènements en base de données, il faut juste ajouter l’action update côté serveur :


case 'update':
  try {
    $db = new PDO($dsn, $user, $password);
    
    $query = $db->query('SELECT * FROM event WHERE id = ' . $_POST['id']);
    $event = $query->fetch(PDO::FETCH_ASSOC);
    
    // Mise a jour de la date
    $diff = calculateDiff($_POST['dayDelta'], $_POST['minuteDelta']);
    
    $newStart = DateTime::createFromFormat('Y-m-d H:i:s', $event['start']);
    $newStart->modify($diff);
    
    $newEnd = DateTime::createFromFormat('Y-m-d H:i:s', $event['end']);
    $newEnd->modify($diff);
    
    $sql = 'UPDATE event SET start = "'.$newStart->format('Y-m-d H:i:s').'",
    end = "'.$newEnd->format('Y-m-d H:i:s').'",
    allDay = "'.$_POST['allDay'].'"
    WHERE id = ' . $_POST['id'];
    $row = $db->query($sql);
    
    echo json_encode(array('updated' => true));
  
  } catch (PDOException $e) {
    echo json_encode(array('error' => 'Db error : ' . $e->getMessage()));
  } catch (Exception $e) {
    echo json_encode(array('error' => $e->getMessage()));
  }       
break;

Le traitement est assez simple, on utilise la classe DateTime pour calculer les changements de dates avec sa méthode modify et deux fonctions que l’on va utiliser pour transformer les dayDelta et minuteDelta en chaîne compréhensible par DateTime :


	function pluralize($string) 
  {
      preg_match("|^([0-9]+)(.*)|",$string,$matched);
      if($matched[1] > 1) {
          return number_format($matched[1]) . $matched[2] . 's';
      }
      return $string;
  }

  function calculateDiff($dayDelta, $minuteDelta)
  {
      if($dayDelta != 'null' && $dayDelta != '0') {
          if($dayDelta < 0)
              $diff = "-".pluralize(abs(intval($dayDelta))." day");
          else if($dayDelta > 0)
              $diff = "+".pluralize(intval($dayDelta)." day");
      }
      else
          $diff = "";

			if($minuteDelta != 'null' && $minuteDelta != '0') {
          if($minuteDelta < 0)
              $diff .= "-".pluralize(abs(intval($minuteDelta))." minute");
          else
              $diff .= "+".pluralize(intval($minuteDelta)." minute");
      }
        
      return $diff;
  }

Ces fonctions vont par exemple transformer dayDelta = +2 : +30 en la chaîne "+2 days"

Et voilà on a maintenant un calendrier affichant nos propres évènements et les enregistrant en base de données.

Catégorie: Développement Web, Javascript | Laisser un commentaire (7)

DataTables : Personnalisation du template

Posté par seiyar81 le 12 décembre 2012 | Laisser un commentaire (0)

Encore un article sur DataTables, et oui je dois bien me l’avouer, la librairie me plaît elle est très ouverte et flexible c’est donc tout normalement que je me permets de donner des petites astuces sur son utilisation.

Il existe une option de configuration que vous n’avez peut-être pas encore utilisée, car pas nécessairement configurable si on se sert de la version basique de DataTables, mais qui se révèle pourtant très utile.
Chaque tableau possède un header et un footer (balises <thead> et <tfoot>) qui servent à afficher un certain nombre de contrôles : pages, filtre etc …

Et bien le plugin nous offre la possibilité de pleinement configurer tout ça !
C’est donc via l’option de configuration sDom que l’on va choisir quoi afficher, et où.

Un petit exemple tout simple :

    $(document).ready(function() {
        $('#example').dataTable( {
            "sDom": '<"top"lp>rt<"bottom"if>'
        } );
    } );

Cette configuration inverse les contrôles par défaut, ceux du header se retrouvent dans le footer et vice-versa.

Mais plus intéressant cette option nous permet de rajouter des contrôles personnalisés. Imaginons que pour une raison particulière on ai besoin d’ajouter un filtre sur une colonne et que l’on ai envie de mettre ce filtre à la place du champ de recherche.

Rien de plus simple :

    

$(document).ready(function() {

        $.fn.dataTableExt.aoFeatures.push( {
                "fnInit": function( oSettings ) {
                    return $("<select id='filter'>...</select>").get(0); 
                }, 
                "cFeature": "S", 
                "sFeature": "Filtre" 
        } ); 

        $('#example').dataTable( { 
                     "sDom": '<"top"Si>rt<"bottom"lp>' 
              } 
        ); 

} );

Un petit coup de CSS et le tour est joué !

La prochaine fois je parlerai des options de filtrage, triage de DataTables.

Catégorie: Développement Web, Javascript | Laisser un commentaire (0)

PHPUi : créer et manipuler des interfaces utilisateurs en PHP

Posté par seiyar81 le 21 septembre 2012 | Laisser un commentaire (2)

PHPUi Logo

Je présente aujourd’hui un projet commencé pour mon mémoire de fin d’études, puis mis en pause, puis repris, puis complètement réécris …

Il s’agit d’une librairie qui permet de générer des interfaces utilisateurs en écrivant uniquement du PHP (ou presque :p).
Le but est de simplifier la vie aux développeurs qui souhaitent générer dynamiquement du code HTML/CSS ou Javascript côté serveur.
A l’heure actuelle, le support du CSS et du Javascript est en cours d’implémentation/optimisation.

La librairie se veut 100% objet PHP5, utilise les namespaces, est facilement extensible via une abstraction de la plupart de ses fonctionnalités, supporte l’autoload, utilise le pattern Observer etc …
De plus un support particulier des frameworks CSS 960gs et Blueprint a été implémenté (via le système d’abstraction, donc applicable à d’autres frameworks/librairies) afin de réellement faciliter la mise en place d’interfaces aux développeurs.

Si vous souhaitez plus d’infos sur les frameworks cités : 960gs, Blueprint.

Un petit exemple de code :

    $blue = PHPUi::getInstance()->blueprint(array('class' => 'container', 'id' => 'blueprint'));
    $blue->addChild(new Xhtml\Element('h2', array('span' => 24), true, '24 Column Grid - Blueprint'));
    $blue->addChild(new Xhtml\Element('div', array('notice' => true), true, 'This is a div with class notice'));
    $blue->addChild(new Xhtml\Element('div', array('error' => true), true, 'This is a div with class error'));
    $blue->jquery()->click( 
        $blue->jquery()->ajax( 
             array( 
                 'url' => 'ajax.php', 
                 'type' => 'POST', 
                 'dataType' => 'html',
                 'success' => 'function(data) { $("#blueprint").append(data); }' 
             ) 
        ) 
    );

On créé ici un nouvel élément utilisant l’adapter Blueprint, on peut donc directement initialiser des div de type error, notice ou alert en passant le paramètre à true. On applique ensuite un filtre jQuery qui au clic sur la div lancera un appel AJAX avec les paramètres donnés.
La librairie se charge ensuite de générer le code correspondant aux appels effectués.

Sachez qu’il est également possible de charger/décharger un ensemble d’éléments via un système d’adapter.
Exemple avec notre élément $blue de tout à l’heure :

     $dump = Xhtml\Dumper\DumperJson::dump( $blue );
     $loaderJson = new Xhtml\Loader\LoaderJson(array('content' => $dump));
     echo $loaderJson->load();

Ici on dump et on load un élément en JSON mais à terme il sera possible d’exporter/charger via n’importe quel format puisqu’il est possible de définir ses propres classes.

Si vous souhaitez regarder les exemples ou bien le code de la librairie, un dépôt Github existe :
https://github.com/seiyar81/phpui

La librairie est toujours en cours de développement, je suis donc ouvert aux propositions d’améliorations ou aux fonctionnalités à implémenter.

Catégorie: Développement Web, Javascript, PHP | Laisser un commentaire (2)

Réaliser une requête POST en PHP avec cURL

Posté par seiyar81 le 10 mai 2012 | Laisser un commentaire (0)

Voici un snippet d’une fonctionnalité que l’on ne trouve pas facilement dans la documentation de PHP : uploader un fichier avec la librairie cURL.

Cette dernière nous offre en effet de nombreuses possibilités pour gérer nos requêtes et ce qui nous intéresse plus particulièrement ici : les requêtes HTTP à l’instar de wget par exemple.


function curlUploadFile($url, $fileName) {

  $ch = curl_init();

  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_HEADER, 0);
  curl_setopt($ch, CURLOPT_VERBOSE, 0);
  curl_setopt($ch, CURLOPT_USERAGENT, "MY_USER_AGENT");
  curl_setopt($ch, CURLOPT_URL, $url);
  curl_setopt($ch, CURLOPT_POST, true);

  $post = array(
    "upload_file"=>"@".str_replace("\\", "/", $fileName),
  );
  curl_setopt($ch, CURLOPT_POSTFIELDS, $post); 
  $response = curl_exec($ch);

  if(!curl_errno($ch))
  {
    return $response;
  }
  else
  {
    return curl_error($ch);
  }

}

Voilà en espérant que cela dépanne certains !

Catégorie: Développement Web, Internet, PHP | Laisser un commentaire (0)

Firebug en version 1.5 et ses extensions

Posté par seiyar81 le 22 janvier 2010 | Laisser un commentaire (0)

Tout bon développeur Web utilise, ou tout du moins, connaît Firebug ! C’est l’extension Firefox la plus utilisée par les développeurs Web !
Pour ceux qui ne la connaissent pas, elle permet d’analyser la page en temps réel, le CSS, le Javascript/AJAX, le code HTML, DOM etc… réellement indispensable elle vient de passer en version 1.5.
Parmi les changements annoncés, amélioration des panneaux CSS, Javascript et Réseau, une meilleur gestion des points d’arrêt etc, le détails des fonctionnalités mises est disponible ici.

Mais ce qui ajoute encore plus de ‘charme’ à cette extension ce sont ses divers extensions. Voici une petite liste des extensions vraiment indispensables :

  • FireCookie : permet de manager vos cookies directement
  • FirePHP : permet d’analyser sa page PHP en rajoutant seulement un petit bout de code
  • FireQuery : pour débugger les codes jQuery
  • Pixel Perfect : permet de contôler le positionnement an ajoutant une image au dessus de la page
  • FireRainbow : colore le code Javascript dans Firebug
  • YSlow : analyse le contenu de la page et propose des solutions pour améliorer son chargement
  • SenSEO : donne des informations sur l’optimisation du référencement de la page
  • FirePicker : ajoute un color picker pour le CSS
  • Plus d’extensions ici

Voilà avec ça vous êtes parés pour développer sous Firefox si ce n’était pas déjà le cas !

Catégorie: Internet | Laisser un commentaire (0)