MediCoder

Le blog de développeurs de Mediboard. Attention : jargon technique et barbarismes lexicaux probables, yeux sensibles s'abstenir...
Dernière publication 06/05/2012 17h01
(10 Articles | 4967 Visites | Activité=2.00)

Trouver :
Par mytto15483 points  le 06/05/2012 17h01

Voici un beau fragment de code regroupant trois séries d'assignations qui mériteraient d'être refactorées, dans la double optique d'être plus compact et lisible, afin d'en faire ressortir la logique plus clairement.

function updateFormFields() {
  parent::updateFormFields();
 
  $this->codes_ccam = strtoupper($this->codes_ccam);
  if ($this->codes_ccam) {
    $this->_codes_ccam = explode("|", $this->codes_ccam);
  } 
  else {
    $this->_codes_ccam = array();
  }
 
  $this->_hour_op = substr($this->temp_operation, 0, 2));
  $this->_min_op  = substr($this->temp_operation, 3, 2));
 
  if ($this->libelle_sejour) {
    $this->_view = $this->libelle_sejour;
  } 
  elseif ($this->libelle) {
    $this->_view = $this->libelle;
  } 
  else {
    $this->_view = $this->codes_ccam;
  }
}

Opérateur ternaire

Un cas typique d'opérateur ternaire manquant :

$this->codes_ccam = strtoupper($this->codes_ccam);
if ($this->codes_ccam) {
  $this->_codes_ccam = explode("|", $this->codes_ccam);
} 
else {
  $this->_codes_ccam = array();
}

Au delà de la syntaxe qui est plus compacte, cela permet de mettre en évidence que la condition n'a pour but que de faire varier l'affectation, et non de créer une branche cyclomatique nouvelle, avec tout un — possible — tas d'opérations impératives.

$this->_codes_ccam = this->codes_ccam ? 
  explode("|", strtoupper($this->codes_ccam)) : 
  array();

list + explode vs. substr

Ici on veut séparer les composantes HH et MM d'une variable de type ISO TIME HH:MM.

$this->_hour_op = substr($this->temp_operation, 0, 2));
$this->_min_op  = substr($this->temp_operation, 3, 2));

Si la syntaxe de substr() est très puissante et son exécution rapide, utiliser explode() permet de mieux mettre en évidence la séparation des composantes, tout en étant plus compact.

En outre, la syntaxe est beaucoup plus robuste si par malheur les composantes n'ont qu'un chiffre, ce qui arrive potentiellement lorsque la valeur ne vient pas de directement des données de Mediboard, par exemple 8:20 au lieu de 08:20. Ce comportement pad proof est celui de la plupart des systèmes compatibles avec ISO 8601 (external link), comme par exemple MySQL ou PHP

list($this->_hour_op, $this->_min_op) = explode(":", $this->temp_operation);

Valeurs par défaut en série

Enfin, ce dernier fragment montre un cas typique de valeurs par défaut en série, ou valeurs prioritaires, que l'on cherche pour la première d'entre elles qui n'évalue pas à false — cad null ou la chaine vide dans la plupart de cas.

if ($this->libelle_sejour) {
  $this->_view = $this->libelle_sejour;
} 
elseif ($this->libelle) {
  $this->_view = $this->libelle;
} 
else {
  $this->_view = $this->codes_ccam;
}

On aurait déjà pu compacter le code en utilisant un double opérateur ternaire, seulement la lisibilité n'est pas merveilleuse, surtout lorsqu'on a de nombreuses valeurs dans la série. Par ailleurs ce genre de logique est très fréquemment utilisée dans Mediboard, ainsi le framework fournit un opérateur qui permet de retourner la première valeur !false d'une série variatique.

$this->_view = CValue::first($this->libelle_sejour, $this->libelle, $this->codes_ccam);

Au total

Au total nous avons un code plus concis et plus clair donc plus maintenable.

function updateFormFields() {
  parent::updateFormFields();
 
  $this->_codes_ccam = this->codes_ccam ? 
    explode("|", strtoupper($this->codes_ccam)) : 
    array();
 
  list($this->_hour_op, $this->_min_op) = explode(":", $this->temp_operation);
 
  $this->_view = CValue::first($this->libelle_sejour, $this->libelle, $this->codes_ccam);
}

Nous avons vu également qu'il était légèrement plus robuste !

Par mytto15483 points  le 16/04/2012 11h33

Voici un cas typique de code à simplifier. On souhaite ici avoir un affichage qui dépend du $vue, valant classique ou compacte, avant dans les deux cas, une base commune.

Le snippet côté template :

<div class="wrapper_line{{if $vue == 'compacte'}}_compacte{{/if}}">
...
</div>

Le code CSS concerné :

div.wrapper_line {
  position: relative;
  height: 2em;
  margin-bottom: 8px;
}
 
div.wrapper_line_compacte {
  position: relative;
  height: 1.1em;
  margin-bottom: 8px;
}

Plusieurs remarques :

  1. les deux classes CSS sont très proches en contenu
  2. le code n'est pas symétrique, car il n'y a pas de style spécifique pour la vue classique: elle se définie comme "non-compacte"
  3. la condition Smarty qui concatène un mot dans la classe n'est pas très élégante

Traitons les deux premiers point en factorisant les styles avec des classes multiples. Cela évitera de propager des modifications sur les deux modes, ouvrira la voie à d'autre modes et rendra le différentiel plus explicite :

div.wrapper_line {
  position: relative;
  margin-bottom: 8px;
}
 
div.wrapper_line.classique {
  height: 2em;
}
 
div.wrapper_line.compacte {
  height: 1.1em;
}

Le troisième point est traité de facto par cette nouvelle structure :

<div class="wrapper_line {{$vue}}">
...
</div>

Par mytto15483 points  le 29/03/2012 17h36

On a régulièrement besoin d'enregistrer une valeur donnée sur une collection d'objets.

Prenons l'exemple d'une vue qui permet d'annuler des séjours. On construira classiquement un tableau avec un bouton d'annulation à la fin de chaque ligne :

{{foreach from=$sejours item=_sejour}}
... 
 
<form name="Edit-{{$_sejour->_guid}}" action="?" method="post">
 
<input type="hidden" name="m" value="planningOp" />
<input type="hidden" name="dosql" value="do_sejour_aed" />
<input type="hidden" name="sejour_id" value="{{$_sejour->_id}}" />
 
<input type="hidden" name="annule" value="1" />
 
...
 
</form>
 
...
{{/foreach}}

Seulement on aimerait également avoir un bouton pour tout annuler, sans cliquer sur n boutons et encore moins en chainant des requêtes Ajax.

Voici comment transformer un do_aed classique : il suffit d'ajouter un champ dont le nom est celui de la clé avec un s final, et donc la valeur est une sérialisation des identifiants.

<form name="Edit-sejours" action="?" method="post">
 
<input type="hidden" name="m" value="planningOp" />
<input type="hidden" name="dosql" value="do_sejour_aed" />
<input type="hidden" name="sejour_ids" value="{{$sejours|@array_keys|@join:"-"}}" />
 
<input type="hidden" name="annule" value="1" />
 
</form>

On pourra également laissé la valeur en blanc pour la remplir en Javascript, par exemple sur l'état de cases à cocher :

<input type="hidden" name="sejour_ids" value="" />

Le tout, bien entendu, sans avoir à coder de do_multiple_aed.php specifique.

Remarque: Les controlleurs multiples fonctionnent également en suppression.
Par mytto15483 points  le 15/03/2012 00h01

Bon nombres de vues? en mode dialog? de Mediboard sont spécialement destinées à l'impression. Pour des raisons de mise en page, il est assez fréquent de vouloir effectuer des saut de pages explicites. On rencontre alors des fragments de templates de ce type :

{{foreach from=$sejours_rhs item=_rhs}}
  ...
  <br style="page-break-after: always;" />
{{/foreach}}

Le problème est que le framework charge deux templates systématiquement — header.tpl et footer.tpl — correspondant au style? courant. Or pour les styles courants, le header a furieusement tendance à ouvrir un tableau principal et le footer à le refermer. La conséquence direct est que tout le contenu à imprimer se trouve dans une cellule d'un tableau principal.

Or les page-break détestent les tableaux et ne fonctionnent pas dans une cellule. Résultat on se voit obligé de fermer le tableau principal manuellement au début d'un template d'impression, pour le rouvrir à la fin.

Ainsi peut on voir un des vues ayant cette forme pour le moins douteuse :

<!-- Template header -->
    </td>
  </tr>
</table>
 
{{foreach from=$sejours_rhs item=_rhs}}
  ...
  <br style="page-break-after: always;" />
{{/foreach}}
 
<table>
  <tr>
    <td />
<!-- Template footer -->

Au delà de l'aspect inesthétique indiscutable, ce hack ne fonctionne que sur le style standard et quelques dérivés directs mais pas sur les autres.

Heureusement, pour faire les choses plus simplement et de manière plus fiables au regard des différents styles possibles, il existe désormais deux templates palliant ce problème, à utiliser systématiquement :

{{mb_include style=$style template=open_printable}}
 
{{foreach from=$sejours_rhs item=_rhs}}
  ...
  <br style="page-break-after: always;" />
{{/foreach}}
 
{{mb_include style=$style template=close_printable}}

On remarquera que le style est explicitement passé pour faire comprendre à Smarty qu'il s'agit d'un template de style et non de module.

A propager donc...

Par mytto15483 points  le 13/03/2012 23h10

L'immense majorité des options passées dans les requêtes Ajax de Mediboard on pour objectif de définir un callback à exécuter à la fin de la dite requête Ajax.

Les deux exemples les plus typiques sont, la soumission d'un formulaire et la la mise à jour d'une cible :

// Form submission
return onSubmitFormAjax(form, {
   onComplete: Something.refresh)
});
 
// Div update
url.requestUpdate(target, {
   onComplete: Otherthing.refresh.curry(param))
});

On peut désormais — et avec un soulagement certain — ne passer que le callback en paramètres! En effet, si l'unique paramètre est bien une fonction Javascript, l'objet options est créé à la volée :

// Form submission
return onSubmitFormAjax(form, Something.refresh);
 
// Div update
url.requestUpdate(target, Otherthing.refresh.curry(param));

Au royaume de codeurs, le fainéant est roi...

Par mytto15483 points  le 06/03/2012 17h35

Il est très fréquent de vouloir afficher la vue standard d'un objet, avec une infobulle d'information au survol sur celle-ci. Cela mène à ce jour de fragment de code dans les templates :

{{assign var=patient value=$sejour->_ref_patient}}
<span onmouseover="ObjectTooltip.createEx(this, '{{$patient->_guid}}')">
  {{$patient}}
</span>
...

Il est alors possible de simplifier notablement cette syntaxe en utilisant {{mb_value}} sur l'objet concernant, sans spécifier de champ, ie l'attribut field :

{{assign var=patient value=$sejour->_ref_patient}}
{{mb_value object=$patient}}
...

Dans le cas présent, si on ne souhaite même pas utiliser le $patient pour autre chose, ni avoir à pré-charger l'objet, on peut utiliser la syntaxe suivante en prenant le $sejour comme objet pivot :

{{mb_value object=$sejour field=patient_id tooltip=1}}

Par mytto15483 points  le 05/03/2012 15h58

Il est impératif de privilégier {{mb_include}} dans les templates, aux dépends du classique {{include!}} de Smarty.

En effet, {{mb_include}} est plus fiable car il réalise un certain nombre de vérifications (installation de modules, etc.). En outre, il est plus compact et plus lisible dans l'immense majorité des cas.

Si sur ce premier exemple, le gain visuel n'est pas transcendant,

{{include file=inc_form_message.tpl}}
{{mb_include template=inc_form_message}}

... les choses s'éclaircissent pour les inclusions cross-styles ...

{{include file="../../styles/mediboard/templates/message.tpl"}}
{{mb_include style=mediboard template=message}}

... et cross-modules !

{{include file="../../dPpatients/templates/inc_vw_photo_identite.tpl"}}
{{mb_include module=patients template=inc_vw_photo_identite}}

Merci d'avance pour le nettoyage au fil de l'eau.

Remarque: L'oeil averti notera sur le dernier exemple que {{mb_include}} gère désormais l'affreux préfixe dP gracieusement.
Par mytto15483 points  le 04/03/2012 19h20

Toujours dans l'optique de rendre le code de Mediboard le plus concis, c'est-à-dire le plus clair et le plus facile à maintenir, Prototype est une mine d'or de raccourcis très utiles, qui rendent la vie de javascript coder plus facile au quotidien.

Sur un snippet assez classique :

onSubmitAnt = function (form) {
  if (form.rques.value.blank()) {
    return false;
  }
 
  ...
 
  form.rques.value = "";
  form.rques.focus();
 
  return false;
}

On utilisera volontiers les methodes present(), clear(), focus() et le chainage.

onSubmitAnt = function (form) {
  var rques = $(form.rques);
  if (!rques.present()) {
    return false;
  }
 
  ...
 
  rques.clear().focus();
 
  return false;
}

C'est plus court, plus propre, et permet en outre d'accéder à de nombreux autres raccourcis bien utiles comme activate(), serialize(), etc.

Un seul idée à retenir globalement: prototype.js is good for you!

Par mytto15483 points  le 19/04/2010 22h57

Il est très fréquent, dans Mediboard, d'afficher des listes de séjours, de consultations, d'interventions représentant le planning d'une journée.

Dans ce cadre, on utilise logiquement les propriétés? ad hoc au format dateTime comme par exemple

{{$sejour->_sortie}}
{{$operation->_datetime}}
{{$consultation->_datetime}}
..

Résultat, pour un jour donné, on a une répétition du jour de chacun des éléments, puisqu'ils sont — a priori — identiques. On peut alors tenter d'utiliser une version time, quand elle est disponible, de la propriété en question.

{{$sejour->_hour_entree_prevue}}
{{$operation->_hour_op}}
{{$consultation->heure}}
..

Mais ces propriétés sont appelées à disparaître car elle consomme inutilement des ressources lorsqu'elles sont dérivées, et difficiles à manipuler lorsqu'elles sont stockées, notamment pour rechercher de façon croisée avec les dates. On préférera donc systématiquement les dateTime, quitte à les mettre en page à la volée.

{{$sejour->_sortie|date_format:$conf.time}}

Seulement il arrive assez fréquemment, et contrairement au prédicat initial, qu'on n'affiche également d'autres dates. Un cas typique est la main courante des urgences, ou encore les entrées réelles aux admissions d'hospitalisations, qui peuvent être différentes des dates prévues.

On se retrouve alors avec du code un peu compliqué pour afficher un simple date, si elle est différente du contexte fourni :

{{if $_sejour->_entree == $date}}
{{$_sejour->_entree|date_format:$conf.time}}
{{else}}
{{$_sejour->_entree|date_format:$conf.datetime}}
{{/if}}

En résumé, on veut afficher une propriété dateTime dans le contexte d'une date, ce qu'on peut et doit systématiquement implémenter en utilisant la syntaxe suivante :

{{mb_value object=$_sejour field=_entree date=$date}}

Merci pour les repos des yeux qui scrutent le code en quête d'esthétisme ...wink

Par mytto15483 points  le 25/02/2010 22h59

Comme toujours l'éternel message de bienvenue sur un nouveau blog. Espérons que ca ne sera pas le dernier.

L'objectif ici est de parler technique :

  • de critiquer — applaudissements et coups de gueule — le développement de notre si cher Mediboard,
  • d'annoncer des nouveautés dans le framework,
  • de soulever des problématiques de fond et de forme
  • éventuellement d'apporter des solutions
  • de recommander des bonnes pratiques

En bref, c'est un espace public — que l'on espère — simple d'utilisation pour poster publiquement tout ce qui ne peut pas faire l'objet :

  • d'une actualité : trop abscons, trop technique
  • d'une page wiki : trop éphémère, non documentaire
  • d'un sujet sur un forum : pas vraiment un espace de discussion mais plutôt d'annonce/d'humeur, même si on peut commenter
  • d'un mail interne à l'équipe qui laisse peu de trace sur le web et qui exclut de potentiels nouveaux venus

Et si ma foi, ça ne sert à rien, eh bien nous le fermerons...

A vos claviers messieurs !

Sponsors privilégiés

Mediboard project