lundi 15 octobre 2012

Opening streamed file with flex, without saving it


When we send streamed files from the backend to the flex frontend, we have at least two ways to 'read' it.

The first common way is to save it using a FilReference.But this way need a confirmation flex popup, and sometimes the flex application is too goodlooking that you're disgusted when the archaic 'Save As' window is displayed!! Espacially when we don't really need to save the file and only want to have a fast look into the document before may be saving it.

It's why there are a second way to handle this, at least for PDF and XLS documents. (and may be for other file types but you have to google it). The way is to open the streamed document into an other tab using a java servlet, and let the web browser decide wich software would open it.

Here is a code sample for PDF :
public static function exportPdfFromStream(stream:ByteArray,
                                           fileName:String):void {
    var header:URLRequestHeader =
        new URLRequestHeader ("Content-type","application/octet-stream");
    var myRequest:URLRequest =
        new URLRequest (urlApplication+ 'CreatePDF' +
                       '?name='+fileName+'&method=attachment');
    myRequest.requestHeaders.push (header);
    myRequest.method = URLRequestMethod.POST;
    myRequest.data = stream;
    navigateToURL(myRequest,"_blank");
}

For Csv you just need to change the header content-type :
var header:URLRequestHeader =
    new URLRequestHeader("Content-type", "application/vnd.ms-excel");

Here is the way to declare the java servlet :
<servlet>
      <servlet-name>createPDF</servlet-name>
      <servlet-class>
          com.manpower.socle.tools.CreatePDFServlet
      </servlet-class>
      <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
      <servlet-name>createPDF</servlet-name>
      <url-pattern>/client/createPDF</url-pattern>
</servlet-mapping>



And this is the servlet :
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

public class CreatePDFServlet extends HttpServlet {

      public void doPost(HttpServletRequest req,    
                         HttpServletResponse resp)
             throws ServletException, IOException {
            doGet(req, resp);
      }

      public void doGet(HttpServletRequest req,
                        HttpServletResponse resp)
             throws ServletException, IOException
      {
            int i = 0;
            int k = 0;
            int maxLength = req.getContentLength();
            byte[] bytes = new byte[maxLength];
            String method = req.getParameter("method");
            String name = req.getParameter("name");
            ServletInputStream si = req.getInputStream();
            while (true)
            {
                  k = si.read(bytes,i,maxLength);
                  i += k;
                  if (k <= 0)
                        break;
            }

            if (bytes != null)
            {
                  ServletOutputStream stream =
                        resp.getOutputStream();
                  resp.setContentType("application/pdf");
                  resp.setContentLength(bytes.length);
                  resp.setHeader("Content-Disposition",method +
                                 ";filename=" + name);
                  stream.write(bytes);
                  stream.flush();
                  stream.close();
            }
            else
            {
                  resp.setContentType("text");
                  resp.getWriter().write("bytes is null");
            }
      }
}


Bon courage my friends!

vendredi 5 octobre 2012

Les ViewStack ne rigolent pas avec leurs fils

Je voudrais vous éviter de vous prendre la tête le jour où vos composants imbriqués (par exemple Container, HBox et VBox) ne prennent plus toute la place, même si vous avez pris la peine de passer sur chacune des balises pour mettre des width="100%" et height="100%"

Ce jour là, si vous restez sans solution jusqu'à 19h30h, et si vous devez corriger ce problème pour que le positionnement des composants soient présentable pour la démo du lendemain à 9h devant la big chef et que vous ne voulez pas rater la séance de 20h de la sortie ciné de l'âge de glace 3 parce que depuis le l'âge de glace 2 vous n'avez jamais eu de fous rires en dehors du boulot... J'arrêtes, ce jour là, essayez de vérifier si dans votre vue il n'y pas un vicieux ViewStack qui empêche ses fils de s'épanouir. Et si c'est le cas, essayez de lui donner une bonne leçon en mettant la propriété resizeToContent de ViewStack à true.
Par exemple :
<mx:ViewStack id="viewsStack
              width="100%"
              height="100%"
              resizeToContent="true"
              horizontalCenter="0">

Bon ciné!!

jeudi 4 octobre 2012

Datagrid checkbox header renderer Fix


Here is a post about datagrid item and header CheckBox renderers in flex 3 : http://shemesh.wordpress.com/2008/05/01/generic-datagrid-checkbox-header-renderer/.

It's well done, and acting directly to modify the data of each row when changing the checkboxes, avoiding to handle more events.

However my need was to make all the items checked at the begining (creationComplete). Unfortunatly when all items are selected at the begining, the header checkbox is not.

So here's my little modification of CheckBoxHeaderRenderer.mxml to fix that :
  
private function checkForSelectedItems():void {
    var dp:ArrayCollection = _dataGrid.dataProvider as ArrayCollection;
    if(dp) {
      var n:Number = 0;
      for(var i:int=0 ; i < dp.length ; i++){
            if(dp[i][_dataField])
                n++
      }
      if(n == dp.length){
            _CBselected = true;
      }else{
            _CBselected = false;
      }
   }
}


Flexivement yours!

mercredi 19 septembre 2012

Utiliser les attributs imbriqués dans les DataGrid


Je sais pas si ça vous parle le terme 'attribut imbriqué', mais c'est le terme que j'emploie pour désigner un attribut d'un attribut d'un attribut (etc) d'un objet. En d'autre terme si une classe 'Maison' contient l'attribut cuisine de type 'Cuisine', et que la classe 'Cuisine' contient un attribut four de type 'Four', alors on pourrait dire que four est un attribut imbriqué de cuisine.

Si vous faites partie des personnes intelligentes qui ont tout compris jusque là, sachez que les DataGrid peuvent prendre des noms d'attributs imbriqués dans les 'dataField' des colonnes. En gros on peut bien faire ça :
<mx:DataGridColumn labelFunction="dateFormatFunction"
                     dataField="cuisine.four"
             headerText="Le four de la cuisine"/>                      


Grâce à cette possibilité on a pas besoin du labelFunction juste pour accéder à l'attribut imbriqué dans la colonne.

Par contre si on veut utiliser 'labelFunction', on ne peut plus faire l'habituel item[column.dataField]. Sans oublier que les tris ne marchent plus puisqu'ils arrivent pas accéder à l'attribut  du nom bizarre ('cuisine.four'). J'entends vos cris et vos regrets. Ne vous inquiétez pas, j'ai la solution qu'il vous faut!

J'ai créé d'abord une méthode dans la classe 'Utils.as' par exemple, qui extrait l'attribut imbriqué à partir de l'objet :

Utils.as
/**
 * Récupérer la valeur d'un impriqué à partir de l'objet et du nom de  
 * l'attribut.
 */
public static function getValeurAttributImbrique(item:Object,nomAttributImbrique:String):Object {
     var nomAttributs:Array = nomAttributImbrique.split(".");
     var currentItem:Object = item;
     for each (var attribut:String in nomAttributs) {
              currentItem = currentItem[attribut]
     }
     return currentItem;
}                  



Ensuite je l'utilises dans les méthodes de création de label ('labelFunction') et de tri ('sortCompareFunction'). Je vous donne l'exemple d'un champ de type 'Date' :

Composant.mxml
<mx:DataGridColumn 
dataField="dateDebutValidite"                   
labelFunction="Utils.formaterDateDefault"

sortCompareFunction="{Utils.dateSortCompareFunction('dateDebutValidite')}"
headerText="Date de début de validité"/>           



Et d'ajouter à Utils.as les méthodes que je viens d'utiliser:

Utils.as
/**
 * Formater la date en entrée sous la forme la plus connue JJ/MM/AAAA
 */
public static function formaterDateDefault(item:Object,column:DataGridColumn):String {
       var date:Object = Utils.getValeurAttributImbrique(item, column.dataField);
       if(date){
              if(date is Date){
                     return formater(date as Date);
              }
              if(date is String){
                    return date as String;
              }
       }
       return "";
}
/**
 * @param date
 * @format format de la date, par défault "DD/MM/YYYY"
 * @return La date au format passé en paramètre
 */
public static function formater(date:Date, format:String = "DD/MM/YYYY"):String {
       var result:String = "";
       var dateFormater:DateFormatter = new DateFormatter();
       dateFormater.formatString = format;
       result = dateFormater.format(date);
       return result;
}

/**
 * Utile pour les propriété 'sortCompareFunction' des DataGridColumn
 */
public static function dateSortCompareFunction(field:String):Function {
       return function (obj1:Object, obj2:Object):int {
             var date1:Date = Utils.getValeurAttributImbrique(obj1,field) as Date;
             var date2:Date = Utils.getValeurAttributImbrique(obj2,field) as Date;
             return comparerDates(date1,date2);
       }
}
public static function comparerDates(date1:Date, date2:Date):int {
       var comparaison:int = ObjectUtil.dateCompare(date1, date2);
       return comparaison;
}                         



Apluche.

mardi 11 septembre 2012

Lire les commentaires c'est pas bien des fois


Un jour je voulais trier les éléments d'un tableau suivant une colonne contenant des dates. J'ai cherché un peu et je suis tombé sur la méthode 'dateCompare' de la classe native flex 'ObjectUtil' . La première chose que j'ai fait était de lire le commentaire de l'entête de cette méthode (Je suis bon élève quand je veux :D). Une fois que j'ai compris comment il fallait l'utiliser, j'ai écris mes lignes de code à la vitesse de l'éclair. Ensuite j'ai compilé le code flex de mon application (pas à la vitesse de l'eclair), il n'y avait pas d'erreurs de syntaxe bien sure :D. J'ai lancé l'application. Et !! Qu'est ce que je constates? Je constates que tous mes tris de dates étaient inversés. Je reviens dans mon code, je cherches, je recherches, je modifie des trucs qui n'ont rien avoir en espérant que c'est ça, je recompiles, je testes, RIEN!

Après à peu prés une heure de recherches et de théories farfelues portant sur les limites du compilateurs flex, j'ai eu l'idée de relire l'entête de la méthode et de lire le code de cette méthode. C'est à cet instant que j'ai remarqué que les commentaires expliquaient l'inverse de ce qui était implémenté dans le code de la méthode. Concrètement dans le code si date1<date2 par exemple alors la méthode retourne -1.
if (na < nb)
    return -1 ;

Mais dans le commentaire si date1<date2 alors la méthode retournerait 1.
* 1 if <code>a</code> is <code>null</code>
* or before <code>b</code>;

Si vous voulais voir tout l'exploit, je vous laisse juger par vous même :
    /**
     *  Compares the two Date objects and returns an integer value
     *  indicating if the first Date object is before, equal to,
     *  or after the second item.
     *
     *  @param a Date object.
     *
     *  @param b Date object.
     *
     *  @return 0 if <code>a</code> and <code>b</code>
     *  are <code>null</code> or equal;
     *  1 if <code>a</code> is <code>null</code>
     *  or before <code>b</code>;
     *  -1 if <code>b</code> is <code>null</code>
     *  or before <code>a</code>.
     */
    public static function dateCompare(a:Date, b:Date):int
    {
        if (a == null && b == null)
            return 0;

        if (a == null)
          return 1;

        if (b == null)
           return -1;

        var na:Number = a.getTime();
        var nb:Number = b.getTime();
       
        if (na < nb)
            return -1;

        if (na > nb)
            return 1;

        return 0;
    }

J'ai envie de dire que le stagiaire qui s'est chargé de passer sur toutes les méthodes pour les commenter n'a pas bien lu celle là avant de la commenter :p