[Widget] sfWidgetFormTimeAndClear

Après trois semaines de vacances au soleil et quelques semaines limitées sur le plan du dev, me voici de retour pour votre plus grand plaisir :o

Encore une fois, je vous propose un petit widget légèrement personnalisé : sfWidgetFormTimeAndClear.

On continue dans la lancée du sfWidgetFormDoctrineJQueryAutocompleterAndClear avec le même petit bouton permettant de vider le contenu des listes déroulantes du champ d’heure.

Il faut bien sûr intégrer jQuery pour que cela fonctionne mais vous l’aviez sans doute compris :)

Voici le code :

class sfWidgetFormTimeAndClear extends sfWidgetFormTime
{
  public function render($name, $value = null, $attributes = array(), $errors = array())
  {
    return parent::render($name, $value, $attributes, $errors).
           ' <img src="data:image/png;base64,'.$this->getImage().'" alt="" id="'.$this->getDeleteId($name).'" />'.
           sprintf(<<<EOF
<script type="text/javascript">
  $("#%s").bind("mouseover", function(e){
    $("#%s")[0].style.cursor = "pointer";
  });
  $("#%s").bind("click", function(e){
    $("#%s")[0].value = "";
    $("#%s")[0].value = "";
    %s
    return false;
  });

</script>
EOF
      ,
      $this->getDeleteId($name),
      $this->getDeleteId($name),
      $this->getDeleteId($name),
      $this->getHourId($name),
      $this->getMinuteId($name),
      $this->getSecondId($name)
    );
  }

  private function getHourId($value)
  {
    return $this->generateId($value."_hour");
  }

  private function getMinuteId($value)
  {
    return $this->generateId($value."_minute");
  }

  private function getSecondId($value)
  {
    if ($this->getOption('with_seconds'))
    {
      return '$("#'.$this->generateId($value."_second").'")[0].value = "";';
    }
    return '';
  }

  private function getDeleteId($value)
  {
    return $this->generateId("delete_hour_value_".$value);
  }

  private function getImage()
  {
    return "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJdSURBVDjLpZP7S1NhGMf9W7YfogSJboSEUVCY8zJ31trcps6zTI9bLGJpjp1hmkGNxVz4Q6ildtXKXzJNbJRaRmrXoeWx8tJOTWptnrNryre5YCYuI3rh+8vL+/m8PA/PkwIg5X+y5mJWrxfOUBXm91QZM6UluUmthntHqplxUml2lciF6wrmdHriI0Wx3xw2hAediLwZRWRkCPzdDswaSvGqkGCfq8VEUsEyPF1O8Qu3O7A09RbRvjuIttsRbT6HHzebsDjcB4/JgFFlNv9MnkmsEszodIIY7Oaut2OJcSF68Qx8dgv8tmqEL1gQaaARtp5A+N4NzB0lMXxon/uxbI8gIYjB9HytGYuusfiPIQcN71kjgnW6VeFOkgh3XcHLvAwMSDPohOADdYQJdF1FtLMZPmslvhZJk2ahkgRvq4HHUoWHRDqTEDDl2mDkfheiDgt8pw340/EocuClCuFvboQzb0cwIZgki4KhzlaE6w0InipbVzBfqoK/qRH94i0rgokSFeO11iBkp8EdV8cfJo0yD75aE2ZNRvSJ0lZKcBXLaUYmQrCzDT6tDN5SyRqYlWeDLZAg0H4JQ+Jt6M3atNLE10VSwQsN4Z6r0CBwqzXesHmV+BeoyAUri8EyMfi2FowXS5dhd7doo2DVII0V5BAjigP89GEVAtda8b2ehodU4rNaAW+dGfzlFkyo89GTlcrHYCLpKD+V7yeeHNzLjkp24Uu1Ed6G8/F8qjqGRzlbl2H2dzjpMg1KdwsHxOlmJ7GTeZC/nesXbeZ6c9OYnuxUc3fmBuFft/Ff8xMd0s65SXIb/gAAAABJRU5ErkJggg==";
  }
}

Et voici à quoi cela ressemble :

Je vais sans doute proposer également les widgets date et datetime sous ce format dans les jours à venir.

[Widget] sfWidgetFormDoctrineJQueryAutocompleterAndClear

Pour faire suite à mon billet sur le widget sfWidgetFormDoctrineJQueryAutocompleter, je vous propose mon petit widget sfWidgetFormDoctrineJQueryAutocompleterAndClear.

Il propose un bouton permettant de vider le contenu du champ texte mais également du champ caché d’autocompletion.

Il fonctionne exactement de la même manière que le widget original, il ne faut rien de plus et rien de moins (l’icône est intégrée en base64).

Voici le code :

class sfWidgetFormDoctrineJQueryAutocompleterAndClear extends sfWidgetFormDoctrineJQueryAutocompleter
{
  public function render($name, $value = null, $attributes = array(), $errors = array())
  {
    return parent::render($name, $value, $attributes, $errors).
           ' <img src="data:image/png;base64,'.$this->getImage().'" alt="" id="'.$this->getDeleteId($name).'" />'.
           sprintf(<<<EOF
<script type="text/javascript">
  $("#%s").bind("mouseover", function(e){
    $("#%s")[0].style.cursor = "pointer";
  });
  $("#%s").bind("click", function(e){
    $("#%s")[0].value = "";
    $("#%s")[0].value = "";
    return false;
  });

</script>
EOF
      ,
      $this->getDeleteId($name),
      $this->getDeleteId($name),
      $this->getDeleteId($name),
      $this->generateId('autocomplete_'.$name),
      $this->generateId($name)
    );
  }

  private function getDeleteId($value)
  {
    return $this->generateId("delete_autocomplete_value_".$value);
  }

  private function getImage()
  {
    return "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJdSURBVDjLpZP7S1NhGMf9W7YfogSJboSEUVCY8zJ31trcps6zTI9bLGJpjp1hmkGNxVz4Q6ildtXKXzJNbJRaRmrXoeWx8tJOTWptnrNryre5YCYuI3rh+8vL+/m8PA/PkwIg5X+y5mJWrxfOUBXm91QZM6UluUmthntHqplxUml2lciF6wrmdHriI0Wx3xw2hAediLwZRWRkCPzdDswaSvGqkGCfq8VEUsEyPF1O8Qu3O7A09RbRvjuIttsRbT6HHzebsDjcB4/JgFFlNv9MnkmsEszodIIY7Oaut2OJcSF68Qx8dgv8tmqEL1gQaaARtp5A+N4NzB0lMXxon/uxbI8gIYjB9HytGYuusfiPIQcN71kjgnW6VeFOkgh3XcHLvAwMSDPohOADdYQJdF1FtLMZPmslvhZJk2ahkgRvq4HHUoWHRDqTEDDl2mDkfheiDgt8pw340/EocuClCuFvboQzb0cwIZgki4KhzlaE6w0InipbVzBfqoK/qRH94i0rgokSFeO11iBkp8EdV8cfJo0yD75aE2ZNRvSJ0lZKcBXLaUYmQrCzDT6tDN5SyRqYlWeDLZAg0H4JQ+Jt6M3atNLE10VSwQsN4Z6r0CBwqzXesHmV+BeoyAUri8EyMfi2FowXS5dhd7doo2DVII0V5BAjigP89GEVAtda8b2ehodU4rNaAW+dGfzlFkyo89GTlcrHYCLpKD+V7yeeHNzLjkp24Uu1Ed6G8/F8qjqGRzlbl2H2dzjpMg1KdwsHxOlmJ7GTeZC/nesXbeZ6c9OYnuxUc3fmBuFft/Ff8xMd0s65SXIb/gAAAABJRU5ErkJggg==";
  }
}

Et à quoi cela ressemble au final :

sfWidgetFormTextareaTinyMCEWithLang

Je souhaitais vous faire partager mon petit widget TinyMCE basé sur celui intégré au plugin sfFormExtraPlugin.

Pourquoi réécrire un widget existant ? Parce que celui-ci n’acceptait pas le paramètre de langue !

Quelque part, il s’agit plus d’un correctif du widget mais bon, il s’avère que j’en ai souvent besoin donc je le partage ici :

class sfWidgetFormTextareaTinyMCEWithLang extends sfWidgetFormTextarea
{
  protected function configure($options = array(), $attributes = array())
  {
    $this->addOption('theme', 'advanced');
    $this->addOption('language', 'en');
    $this->addOption('width');
    $this->addOption('height');
    $this->addOption('config', '');
  }

  public function render($name, $value = null, $attributes = array(), $errors = array())
  {
    $textarea = parent::render($name, $value, $attributes, $errors);

    $js = sprintf(<<<EOF
<script type="text/javascript">
  tinyMCE.init({
    mode:                              "exact",
    elements:                          "%s",
    language:                          "%s",
    theme:                             "%s",
    %s
    %s
    theme_advanced_toolbar_location:   "top",
    theme_advanced_toolbar_align:      "left",
    theme_advanced_statusbar_location: "bottom",
    theme_advanced_resizing:           true
    %s
  });
</script>
EOF
    ,
      $this->generateId($name),
      $this->getOption('language'),
      $this->getOption('theme'),
      $this->getOption('width')  ? sprintf('width:                             "%spx",', $this->getOption('width')) : '',
      $this->getOption('height') ? sprintf('height:                            "%spx",', $this->getOption('height')) : '',
      $this->getOption('config') ? ",\n".$this->getOption('config') : ''
    );

    return $textarea.$js;
  }
}

Et pour l’utiliser :

  • dans votre classe de formulaire
$this->widgetSchema['widget'] = new sfWidgetFormTextareaTinyMCEWithLang(sfConfig::get('app_tinymce_config'));
  • dans le fichier app.yml
all:
  tinymce:
    width:  550
    height: 125
    language: fr
    config: |
      plugins: "paste",
      paste_auto_cleanup_on_paste : true,
      theme_advanced_buttons1: "bullist,pasteword,bold,italic,underline,fontselect,fontsizeselect",
      theme_advanced_buttons2: "",
      theme_advanced_buttons3: "",
      theme_advanced_buttons4: ""
    theme: <?php echo sfConfig::get('app_tinymce_theme','advanced') ?>

Edit : un billet sur le même sujet existe en anglais (merci à Tomasz Ducin)

[Admin generator] Paramètre ‘table method’

Une petite astuce que j’utilise depuis longtemps mais que je n’avais jamais pensé à partager : optimiser le nombre de requêtes Doctrine et par la même occasion, un module d’admin generator.

J’ai un module d’admin generator article lié au modèle Article.

  • schema.yml
Article:
  connection: doctrine
  actAs:
    Timestampable: ~
    Taggable: ~
    Sluggable:
      fields: [titre]
      name: slug
      type: string
      length: 255
      unique: true
      canUpdate: true
  columns:
    titre:
      type: string(255)
      notnull: true
    # et d'autres champs
    id_auteur:
      type: integer
      notnull: true
  relations:
    Auteur:  { class: vjGuardUserProfile, onDelete: SET NULL, onUpdate: CASCADE, local: id_auteur, foreign: id }
  • generator.yml
 list:
 title: Liste des articles
 display: [titre, date_publication, Auteur]
 max_per_page: 5
 table_method: retrieveBackend
  • ArticleTable.class.php
public function retrieveBackend(Doctrine_Query $q)
{
  $rootAlias = $q->getRootAlias($q);
  $q->leftJoin($rootAlias.'.Auteur au');
  return $q;
}

Dans cet exemple, je passe de 6 requêtes Doctrine pour l’affichage de 5 lignes à 3 requêtes.

Ce n’est pas énorme mais sur 20 lignes et 3 relations, on passe de 63 requêtes à

3 requêtes !

Sisi !

Alors, abusez-en !

symfony 1.3.6 et 1.4.6 publiées (security fix inside)

Deux nouvelles mises à jour de symfony ont été publiées ce soir. Estampillées 1.3.6 et 1.4.6, elles corrigent une faille dans la gestion du cache des templates.

Après la dernière mise à jour, c’est une nouvelle fois dans l’urgence que sont corrigées des failles de sécurité !

Il est temps de mettre à jour !

Source

Symfony 2 n’existe pas ! Symfony2 oui !

Votre mission si vous l’acceptée sera de ne plus parler de Symfony 2 mais de Symfony2 !

[Tuto] Bien utiliser le widget sfWidgetFormDoctrineJQueryAutocomplete

Enfin un tutoriel ! Cela faisait bien longtemps que je n’en avais pas publié (par faute de temps …) !

Sujet du jour : sfWidgetFormDoctrineJQueryAutocomplete

Ce widget propose un champ input de type text qui, lorsque vous tapez des données, effectue une recherche en ajax dans la base de données (ici, nous travaillons avec Doctrine) et dans le cas de résultats, qui vous les retranscrit dans une liste placée sous le champ input.

L’idée étant de rechercher rapidement et simplement des données dans une liste plus ou moins longue. Qui plus est, cela permet de charger plus rapidement la page et par la même occasion, d’éviter de créer des listes très longues.

Voici une représentation du widget en fonction :

Alors, pour le mettre en œuvre, il faut au préalable installer le plugin sfFormExtraPlugin qui l’intègre (l’installation est standard).

Pour bien comprendre la technique, je vais vous décrire chaque étape importante et vous fournir la structuration des données.

  • le schéma de données (schema.yml)
options:
  collate: utf8_general_ci
  charset: utf8
Maitre:
  connection: doctrine
  columns:
    titre:
      type: varchar(255)
      notnull: true
Eleve:
  connection: doctrine
  columns:
    titre:
      type: varchar(255)
      notnull: true
    id_maitre:
      type: integer
      notnull: true
  relations:
    Maitre: { onDelete: cascade, onUpdate: cascade, local: id_maitre, foreign: id }

J’ai ensuite généré ma base de données et deux modules d’admin generator avec Doctrine.
Je me retrouve donc avec un module de gestion des Maîtres et un autre des Elèves. Du fait du schéma, lorsqu’on rajoute un élève, on doit choisir son maître parmi la liste déroulante de maîtres. Nous placerons donc ici notre autocomplete.

Avant de l’intégrer, voici comment se présente le formulaire d’ajout d’élève :

La première chose qu’on travaille généralement dans ce cas est l’affichage d’un texte à la place des identifiants :

  • lib/model/doctrine/Maitre.class.php
class Maitre extends BaseMaitre
{
  public function __toString()
  {
    return $this->titre;
  }
}

Ce qui nous donne :

Jusque là, rien de particulier mais la liste paraît assez longue tout de même (899 entrées !) et en environnement dev, cache vidé, cette simple page met plus de 0,8s à apparaître. C’est trop.

Remplaçons cette liste déroulante par un champ autocomplete !

  • lib/form/doctrine/EleveForm.class.php
  public function configure()
  {
    $this->widgetSchema['id_maitre']->setOption('renderer_class', 'sfWidgetFormDoctrineJQueryAutocompleter');
    $this->widgetSchema['id_maitre'] = new sfWidgetFormDoctrineJQueryAutocompleter(
      array(
        'model' => "Maitre",
        'url'   => url_for("@ajax_maitre"),
        'config' => '{ max: 50}'
      )
    );
  }

On remarque qu’on remplace bien le widget d’origine (sfWidgetFormDoctrineChoice) par l’autocomplete.

On remarque également qu’on lui passe en option le modèle avec lequel travaillé et une route symfony mais qu’on peut également lui passer le nombre de lignes à afficher (ici 50).

Utilisant le helper Url, il faut penser à le charger ici :

  public function configure()
  {
    sfProjectConfiguration::getActive()->loadHelpers('Url');
    // ....
  }

Il faut donc maintenant créer cette route

  • apps/your_app/config/routing.yml
ajax_maitre:
  url: /ajax-maitre
  param: { module: maitre, action: ajaxMaitre }

Cette route est simple à comprendre : on charge la méthode executeAjaxMaitre du fichiers actions.class.php du module maitre.

Créons donc cette méthode alors !

  • apps/your_app/modules/maitre/actions/actions.class.php
  public function executeAjaxMaitre(sfWebRequest $request)
  {
    // si ce n'est pas une requête ajax, on renvoit vers du 404
    $this->forward404unless($request->isXmlHttpRequest());
    // c'est de l'ajax, on retourne donc du json
    $this->getResponse()->setContentType('application/json');
    $choices = array();
    // on récupère les données de la base de données
    $maitres = Doctrine::getTable('Maitre')->getMaitreAutocompletion($request->getParameter('q'), $request->getParameter('limit'));
    // on boucle sur les données et on charge les données dans un nouveau tableau, celui qu'on va retourner
    foreach($maitres->getData() as $m)
    {
      $choices[$m->id] = $m->titre;
    }
    // s'il y a des données, on retourne le tableau encodé en json
    if($choices != array())
    {
      return $this->renderText(json_encode($choices));
    }
  }

Il nous reste à créer la méthode getMaitreAutocompletion du modèle Maitre

  • lib/model/doctrine/MaitreTable.class.php
  public function getMaitreAutocompletion($q, $limit){
    $dq = Doctrine_Query::create()
            ->select("m.id, m.titre")
            ->from("Maitre m")
            ->where("m.titre LIKE ?","%".$q."%")
            ->limit($limit)
            ->orderBy('m.titre ASC');
    return $dq->execute();
  }

Dernière chose importante avant de tester, il faudrait peut-être intégrer jQuery non ? Commençons par télécharger la dernière version minifiée sur le site officiel, copions là dans web/js/jquery.min.js puis

  • apps/your_app/config/view.yml
  javascripts:    [jquery.min.js]

That’s all folks !

Regardons alors le résultat :

Quoi de plus simple, je vous le demande !

Ainsi s’achève ce petit tutoriel qui, je l’espère, vous sera bien utile !

A bientôt pour de nouvelles aventures sur symfony !

EDIT : Merci à Jonathan T pour l’astuce concernant le nombre de lignes affichées lors de l’autocompletion !

symfony 1.3.5 et 1.4.5 publiées (security fix inside)

Deux nouvelles mises à jour de symfony ont été publiées en milieu de soirée : 1.3.5 et 1.4.5.

Elles corrigent un certain nombre de tickets mais intègre surtout un fix de sécurité pour Doctrine et Propel !

Il est temps de mettre à jour !

Source

Conférence symfony : « The State of Symfony 2″

Petit billet pour vous informer de la tenue prochaine d’une nouvelle conférence Symfony. Petite nouveauté car celle-ci se tiendra online.

Il y aura 2 sessions :

  • le 22 juin à 10h00
  • le 23 juin à 17h00

Il faut compter environ 3h de conférence (30 minutes par intervention).

Au programme :

  • Tests fonctionnels et unitaires
  • Propel 1.5 pour Symfony 2.0
  • Le nouveau framework de formulaire
  • Améliorations diverses
  • Intégration de Doctrine 2 à Symfony 2
  • Nouveauté « The Symfony 2 killer feature »

Que du bon en fait !

L’inscription, c’est par ici et le site ici.

Livre symfony : « A Gentle Introduction to symfony 1.4″

Allez, un peu de lecture pour commencer cette semaine !

En effet, Fabien Potentier a annoncé hier soir sur le blog de symfony la publication d’un nouveau livre : « A Gentle Introduction to symfony 1.4″.

Un nouveau livre, oui et non. Il s’agit plus d’une mise à jour du « Definitive Guide to Symfony » pour symfony 1.4 qu’autre chose. Globalement, ce livre, travaillant la théorie, est plutôt destiné aux débutants et s’accompagne du « Practical symfony« , orienté cas pratique.

Vous pouvez directement lire ce livre ou le commander depuis le site de sensio.