ラベル Flash の投稿を表示しています。 すべての投稿を表示
ラベル Flash の投稿を表示しています。 すべての投稿を表示

2013年2月23日土曜日

Flashの画像を差し替える

ソースリストなど一切失われてしまって手元にあるのはSWFだけ。その上のビットマップを張り替える必要が生じて、いろいろ試行錯誤しました。

  • swfmill
  • swfmill on Moutain Lion
  • 画像差し替えのために
  • どうやってPNGをエンコードするか
  • Mountain Lion上で動かして見る



■ swfmill ■

というオープンソースのツールがあります。これはSWF<->XMLを相互に変換できるという素晴らしいものです。コマンドラインツールでソースとMac版 /  Windows版が配布されています。Windows版はdylibも含んでいて、インストーラを動かせばすぐ使えるようになるはずです(私の環境ではdylibをインストールする時にアンチウィルスさんに怒られる)。

Macでは以下のような作業が必要になります。
  1. サイトからMac版バイナリをダウンロード&解凍
  2. ターミナル上で必要なdylibを用意して、swfmillを実行
…と書くと簡単なんですが。

■ swfmill on Mountain Lion■

配布されているのはコマンドラインツールのバイナリです。前述の通りlibfreetype6などのdylibが/usr/local/libにインストールされている必要があります。いろいろ試行錯誤しましたが、結果としてこんな感じ
  1. http://xquartz.macosforge.org/landing/からXQuartzをダウンロード&インストール
  2. MacPortsをインストール
  3. ターミナル上で準備
    1. MacPortsを最新版にアップグレード
      • sudo port self update
    2. 必要なdylibを読み込むためにportを実行
      • sudo port libfreetype +universal
      • sudo port libpng +universal
    3. portが読み込みディレクトリとswfmillが想定するディレクトリが異なるのでシンボリックリンクを置く
      • sudo ln -s /usr/X11/lib/libfreetype.6.dylib /usr/local/lib/
      • sudo ln -s /usr/X11/lib/libpng12.0.dylib /usr/local/lib/
    4. swfmill起動してdylibが足りないというエラーが出たら、2のようにdylib名称の尻尾を取ってportし、3のようにしてシンボリックリンクを置く
これで使えるようになるはずです。

swfmillの使い方は、またターミナル上での作業ですが、
  • SWFからXMLへの変換
    • swfmill swf2xml source.swf result.xml
  • XMLからSWFへの変換
    • swfmill xml2swf source.xml result.swf
と実行するだけです。他にも沢山機能があるようですが、今回使ったのはこれだけ。

蛇足ですがswfmillをダウンロード&解凍したディレクトリの中で作業するのであれば、
  • ./swfmill
とする必要があります。

変換処理を確認するためにSWFからXMLに変換したものをそのままSWFに変換しなおして動作確認してみてください。私の場合は多少SWFのサイズが変わったものの、動作が変わらなかったので一応OKとしました。

■画像差し替えのために■

XMLをエディタなどで開きます。PNG/GIF画像はDefineBitsLossless2のタグ内にbase64でエンコードされています。私の場合、対象画像のwidthから一発で目的の画像を探し当てることが出来たのですが、そういうヒントがない場合はバイナリのサイズなどから虱潰しに試してみるしかないかもしれないですね…みなさんのご健闘を祈ります。さて、あとは、差し替える画像をテキストに変換するだけです。

■どうやってPNG画像をエンコードするか■

ここは悩みました…。JPEGは比較的カンタンで、単純にBase64に変換してその文字列を<data></data>タグで挟めば良いらしいです。でも、GIF / PNGに関しては、技術的な解説記事が出ているし、Rubyでのライブラリもあるんですが…「ドラッグ&ドロップとは言わないが、せめてコマンドライン一発で変換するツール」みたいなのは見当たりません。

SFWMILLの解説ページにXMLに新しい画像ファイルを取り込む方法は書いてあるのですが、いろいろタグの書き方を変えてみたもののうまくいきません。楽をするためならどんな苦労をすることも厭わないので、帰りの電車で眉間に縦ジワ寄せつつ考えていたら…ひらめいた。画像を入れたSWFを作って、それをswfmillで変換すれば良いということにw

  1. 元の画像をswfmillでXMLに変換しておく
    • swfmill swf2xml Source.swf Source.xml
  2. FlashBuilderを起動、新しいFlashプロジェクトを作る
  3. 画像ファイルをプロジェクトに追加
  4. デザインビューでImageを貼り、さっきの画像ファイルをEmbed
  5. あとは何もいじらないで、リリースビルド
  6. bin-releaseの中の <project-name>.swfをXMLに変換
    • swfmill swf2xml image.swf image.xml
  7. 変換したXML(image.xml)をエディタで開き<data>タグを探す。
  8. 新しい<data><data>....</data></data>を古いものの上にコピペ
  9. swfmillでXMLをSWFに変換
    • swfmill xml2swf Source.xml Result.swf

■Mountain Lion上で動かして見る■

そういえばコントロールパネルからapacheを起動できなくなったんですね。これまたターミナルから、

で起動させて、ブラウザ上で
    http://localhost/
とタイプし「It works!」が表示されれば起動は成功。あとはさっき出来上がったswfを
    sudo cp test.swf /Library/WebServer/Documents/
などとコピーして、ブラウザでURLを入力します。
    http://localhost/test.swf
これで思った通りに画像が差し替わっていれば出来上がりです。

それにしても…オレもいろんなことやるなぁ…。

2011年12月17日土曜日

FlashでPDF・続き

■続き、っていうか本番■

前回はFetchしてデータを一覧表示するところまででした。

FlashでPDF

「そもそもなんでFlashなのか?」という話が抜けていました。
  1. クライアントサイドの有り余るリソース(メモリ, CPU)を使える
  2. きめ細かく罫線などを制御できる
  3. Force.comでは実現不可能なグラフも作れる
  4. Rich GUIで余計な苦労を負わないで済む
  5. PDF添付メールやファイル出力の制約が少ない
PDF生成そのものに関しては「セルその他の描画を細かく制御できるので、基本的にどんな帳票でもおk」ってのがFlash / purePDFでPDFの長所の一つです。クライアントサイドでは数GBのメモリも当たり前なので、ガバナーの制約を気にしないで好きなことできるのもメリットです。「数万件のデータをドーンと読み込んできてループまわしながら集計し、役員会に出せるような美しい罫線の入ったPDFを作る」ようなケースで使えます。

あとグラフ。Flashのグラフは制約が少ないので複雑なグラフを作りたい場合にはForce.comよりもラクできます。

また最近はWebアプリケーションであってもrichなGUIが求められる案件が増えてきました。Force.comでも売掛入金の消し込み画面で設定した条件が間違っていたらボタンをdisableしたり、jQueryで関連するデータをアニメーション表示したりお客様の多彩なご要望に苦労する日々でございます。

でもFlashはrich GUIの本家です。

先日もあるコンサルタント会社から「100近い検索条件から案件を抽出する検索画面」という依頼を受けましたが、設定した条件が埋もれてしまわないようわかりやすく表示するのもFlashなら自由自在です(簡単です、と書かないところに私の誠実さを感じてください)

 そうそう、それにFlashなら開発の途中でオブジェクトの項目を変更する必要が生じても「それAPEX/VFで使ってるから変更できねぇよw」とForce.comから怒られることもありません(笑)。

■purePDFで■

FlashでPDFを生成するためのライブラリでメジャーなのはAlivePDFとPurePDF。今回はPurePDFを使います。PurePDFはJavaのPDFライブラリiTextをFlashに移植したもので、私はiTextには散々痛い目にあったお世話になったことがあるので、まぁ学習コストがかからないだろう、と。

使ってみたら日本語が文字化けたりしてまた散々痛い目にあったのですが、そういう経緯は全部スキップします。ここからライブラリをダウンロードします。

Player10.0 でも purePDF で日本語PDFを作る

FlashBuilder上のプロジェクトのlibsフォルダには前回force-air.swcを追加しましたが、今回ダウンロードしたpurePDF.swcとpurePDFont.swcも追加します。

APIなどに関してはFlashとJavaとで実装上の違いはありますが、iTextの方がサンプルも多く用意されていて便利です。


■解説■

ソースは最後に貼ります。基本的な処理の流れとしては

  1. サイズと出力先(バッファ先)を指定してDocumentを生成する
  2. ベースフォントを作る
  3. ベースフォントにサイズなどを指定してフォントを生成する
  4. テキスト、イメージ、別のテーブルなどをセットしたセルを作る
  5. セルをテーブルにセットする
  6. テーブルをドキュメントにセットする
  7. 必要ならページ送り(document.newPage)

では、実際の処理を見ていきます。

>queryHandler
前回見たようにarrayDataにfetchしたデータを溜めていきます。result.doneになったらfetch完了ですので、dataGridにarrayDataをセットし、pdfの生成を始めます。

PdfWriterでバイトストリームをセットしつつ用紙サイズを設定します。ここではPageSize.A4でA4縦を指定していますが、PageSize.A4.rotate(90)と書くとA4横になります。

>initPdfFont
ベースフォントを生成します。いろいろなフォントを試してみたのですが、結局これ平成明朝しか使えませんでした。ゴシックも指定できるのですが、日本語フォント部分は同じで英数字部分が変わるのみでした。

>createPdf
実際に使用するサイズのフォントを生成してからPDFを生成していきます。vecという配列を用意していますが、これはTableのrowをどういう比率で生成するかを指定するためのものです。値が合計いくつになってもかまわないので、私はお客さんからもらってきたサンプル帳票に物差しあててその数字を使ったりしていますw

あとは文字列を元にしてセルを作ってテーブルにせっせと詰め込んでいくだけです。終わったらnewPageでページを閉じて、最後に document.close(); でドキュメントを閉じます。

>cellFromStringFontAndAlignment
セルを生成しています。関数名がやたらと長いのは私が長年MacOS系プログラマだったからです。生成したcellオブジェクトにはデフォルトで罫線がついてます。top, border, right, leftごとに太さ、色などを制御することができます。逆にいえば、セルを作る時点で罫線のことを考えないと意図した表になりません。まぁこれがメリットになるかデメリットになるかは状況次第っすね。

>savePdf
保存ダイアログを表示して、ファイル名や出力先を指定し、ファイルを保存します。

>formatDateMeasure
ここがFlashの困ったちゃんで、DataGridにDateやNumberを表示する際の書式指定でいちいちコールバック関数などを書かないといけないのです。VisualBASICが懐かしいぜ。

>convertToLocaltime
RESTで帰ってくる日付はGMTなのでこれを日本時間に変換してやります。60000はtimezoneOffsetで取ってくる時差が分単位なのでこれをミリセカンド単位に換算するためです。

>formatDecimal
3桁ごとのカンマ区切り、もっと簡単な方法あったら教えてください^^;


■注意■

今回サンプルとして掲載したソースにはID / Passwordが文字列定数として埋め込まれていますが、実際にFlashで作る場合この方法は避けてください。ダンプしてソースをリバースエンジニアリングすることが可能ですので。

FlashとしてForce.com環境上で使うのであればセッションを引き継いで認証できるのでID / Passwordは不要ですし、AIRでデスクトップアプリとして配布する場合はGUIからID / Passwordを入力してもらいpreferenceとしてPC上に保存すればOKです。


■Flash業界のみなさんへ■

データベースにお悩みではありませんか?

force.com / database.comの世界にどうぞ。database.comなら最高にセキュアでスケーラブルなRESTデータベースが3アカウント, 10万レコード, 月5万トランザクションまで無料で使えます。サーバのセットアップもバックアップも不要です。

お願い:ソースでお気づきの通り私はFlash初心者です。いろいろツッコミどころ満載だと思いますので、ご指導ご鞭撻のほどよろしくお願いいたしますm(_ _)m。


■おまけ■

個人事業としてiPhoneアプリを書いてます。

現職でForce.comに携わる前はWebObjectsというフレームワークで仕事をしていましたが、WO屋から見るとApexはイマイチ美しくありません(もちろんマルチテナントとしての安全性安定性などを確保するための現実的な実装という面もあると思いますが)

そんな私にとってForce/Database/Chatter.comをRESTでアクセスするiOSアプリはほぼ理想郷です。


■最後に■
このブログはForce.com Advent Calendarに参加しています。


■ソース■

というわけでソースです。

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
        xmlns:s="library://ns.adobe.com/flex/spark" 
        xmlns:mx="library://ns.adobe.com/flex/mx"
        xmlns:salesforce="http://www.salesforce.com/"
        applicationComplete="init()" height="560" width="800">
 <fx:Declarations>
  <salesforce:AIRConnection id="force"/>
 </fx:Declarations>

 <fx:Script>
  <![CDATA[
   import com.salesforce.AsyncResponder;
   import com.salesforce.objects.LoginRequest;
   import com.salesforce.results.Fault;
   import com.salesforce.results.LoginResult;
   import com.salesforce.results.QueryResult;
   
   import mx.collections.ArrayCollection;
   import mx.formatters.DateFormatter;
   
   import org.purepdf.Font;
   import org.purepdf.colors.RGBColor;
   import org.purepdf.elements.Element;
   import org.purepdf.elements.Phrase;
   import org.purepdf.elements.RectangleElement;
   import org.purepdf.pdf.PageSize;
   import org.purepdf.pdf.PdfDocument;
   import org.purepdf.pdf.PdfPCell;
   import org.purepdf.pdf.PdfPTable;
   import org.purepdf.pdf.PdfWriter;
   import org.purepdf.pdf.fonts.BaseFont;
   import org.purepdf.pdf.fonts.cmaps.CJKFontResourceFactory;
   import org.purepdf.pdf.fonts.cmaps.CMapResourceFactory;
   import org.purepdf.resources.BuiltinCJKFonts;
   import org.purepdf.resources.CMap;
   import org.purepdf.resources.ICMap;
   import org.purepdf.utils.IProperties;
   import org.purepdf.utils.Properties;

   
   
   [Bindable]
   private var arrayData:ArrayCollection = null;
   private var dateTimeFormatter:DateFormatter = null;
   
   private const MaginLeft:int    = 20;
   private const MarginRight:int  = 20;
   private const MarginTop:int    = 30;
   private const MarginBottom:int = 30;
   
   private function init():void 
   {
    dateTimeFormatter = new DateFormatter();
    dateTimeFormatter.formatString = "YYYY-MM-DD HH:NN:SS";

    var lr:LoginRequest = new LoginRequest();
    status = "login by id/password";
    
    lr.username = "youraccount@your.domain";
    lr.password = "password"+"securitytoken";
    
    lr.callback = new AsyncResponder(loginHandler, faultHandler);
    force.login(lr);
   }  
   
   private function loginHandler(result:LoginResult):void 
   {
    if (result.userInfo != null) {
     status = "login success, query";
     
     force.autoSyncEnabled = false;
     force.doCache = false;
     
     var strQuery:String = "Select DateMeasure__c, High__c, Low__c, Beat__c From BloodPressure__c order by DateMeasure__c desc";
     
     force.query(strQuery, new AsyncResponder(queryHandler, faultHandler));
    }
   }
   
   
   
   private function queryHandler(result:QueryResult):void
   {
    if (result != null && result.records != null && result.records.length > 0) 
    {
     if (arrayData == null)
      arrayData = new ArrayCollection();
      arrayData.addAll(result.records);
    }
    
    if (!result.done)
    {
     trace("query again");
     force.queryMore(result.queryLocator, new AsyncResponder(queryHandler));
    }
    else
    {
     status = "query result";
     force.syncConnection
     
     dataGrid.dataProvider = arrayData;
     
     var byteArray:ByteArray = new ByteArray();
     
     var pdfWriter:PdfWriter =  PdfWriter.create(byteArray, PageSize.A4);
     var document:PdfDocument  =  createPdf(pdfWriter, initPdfFont());
     savePdf(byteArray, "血圧報告.PDF");
     
     document  = null;
     pdfWriter = null;
     byteArray = null;
    }
   }
   
   private function faultHandler(result:Fault):void
   {
    trace(typeof(result));
    trace(result.faultcode);
    trace(result.faultstring);
   }
   
   
   private function initPdfFont() : BaseFont
   {
    var map: ICMap = new CMap( new CMap.UniJIS_UCS2_H() );
    CMapResourceFactory.getInstance().registerCMap( BaseFont.UniJIS_UCS2_H, 
                                                    map );
    
    var prop: IProperties = new Properties();
    prop.load( new BuiltinCJKFonts.HeiseiMin_W3() );
    
    CJKFontResourceFactory.getInstance()
                          .registerProperty(
        BuiltinCJKFonts.getFontName( BuiltinCJKFonts.HeiseiMin_W3 ), prop);
    
    var bf: BaseFont = BaseFont.createFont( 
        BuiltinCJKFonts.getFontName( BuiltinCJKFonts.HeiseiMin_W3 ), 
        BaseFont.UniJIS_UCS2_H, BaseFont.NOT_EMBEDDED, true );
    
    return bf;
   }
   
   private function createPdf(inWriter:PdfWriter, inBf:BaseFont) : PdfDocument 
   {
    var fontHeader:org.purepdf.Font 
        = new org.purepdf.Font( org.purepdf.Font.BOLD, 12, -1, null, inBf );
    var font:org.purepdf.Font       
        = new org.purepdf.Font( -1.   -1,              10, -1, null, inBf );

    var document:PdfDocument = inWriter.pdfDocument;
    document.open();
    document.setMargins(MaginLeft, MarginRight, MarginTop, MarginBottom);
    
    var table:PdfPTable = null;
    var cell:PdfPCell = null;
    
    var lc:int = 99;
    var lpp:int = 40;
    var i:int = 0;
    var n:int = arrayData.length;
    var monthBreak:int = 0;
    
    var vec:Vector.<Number> = new Vector.<Number>;
    vec.push(50);
    vec.push(25);
    vec.push(25);
    vec.push(25);
    
    while (i < n) {
     // 行を取り出し
     var line:Object = arrayData[i++];
     var dateMeasure:Date = convertToLocaltime(line.DateMeasure__c);
     
     // 改ページ
     if (lc >= lpp) {
      if (table != null) {
       document.add(table);
       document.newPage();
       table = null;
      }
      lc = 0;
     }
     
     //ヘッダ
     if (table == null) { 
      table = new PdfPTable(vec);
      table.addCell(cellFromStringFontAndAlignment("測定日時", fontHeader));
      table.addCell(cellFromStringFontAndAlignment("最高血圧", fontHeader));
      table.addCell(cellFromStringFontAndAlignment("最低血圧", fontHeader));
      table.addCell(cellFromStringFontAndAlignment("心拍",     fontHeader));
     }
     
     table.addCell(cellFromStringFontAndAlignment(
                       dateTimeFormatter.format(dateMeasure), font));
     table.addCell(cellFromStringFontAndAlignment(
                       formatDecimal(line.High__c), font, Element.ALIGN_RIGHT));
     table.addCell(cellFromStringFontAndAlignment(
                       formatDecimal(line.Low__c),  font, Element.ALIGN_RIGHT));
     table.addCell(cellFromStringFontAndAlignment(
                       formatDecimal(line.Beat__c), font, Element.ALIGN_RIGHT));
     lc++;
    }
    
    if (table != null) {
     document.add(table);
     table = null;
     
     document.newPage();
    }
    
    document.close();
    
    return document;
   }
   
   private function cellFromStringFontAndAlignment(
                                        inStr:String, 
                                        inFont:org.purepdf.Font, 
                                        inAlignment:int = Element.ALIGN_CENTER)
                                      : PdfPCell {
    var str:String = (inStr == null) ? "" : inStr;
    
    var cell:PdfPCell = PdfPCell.fromPhrase(new Phrase(str, inFont));
    cell.horizontalAlignment = inAlignment; 
    
    return cell;
   }
   
   private function savePdf(inByteArray:ByteArray, inFileName:String) : void
   {
    var fr:FileReference = new FileReference();
    fr.save(inByteArray, inFileName);
   }

   private function formatDateMeasure(inObj:Object, 
                                      inDataGridColumn:DataGridColumn)
                                    : String
   {
    return (inObj != null && inObj.DateMeasure__c != null) 
           ? dateTimeFormatter.format(convertToLocaltime(inObj.DateMeasure__c))
           : "";
   }
   
   private function convertToLocaltime(inStr:String):Date {
    var date:Date = DateFormatter.parseDateString(inStr);
    var time:Number = date.getTime() - date.timezoneOffset * 60000;
    date.setTime(time);
    
    return date;
   }
   
   private function formatDecimal(inDecimal:Number):String
   {
    if (isNaN(inDecimal))
     return "";
    else {
     var pattern:RegExp = /(\d)(?=(\d{3})+(?!\d))/g;
     return String(inDecimal).replace(pattern, "$1,");
    }
   }

  ]]>
 </fx:Script>

 <mx:DataGrid id="dataGrid" top="10" left="23" right="23" bottom="10" 
              editable="false" enabled="false">
  <mx:columns>
   <mx:DataGridColumn headerText="測定日時" dataField="DateMeasure__c" 
                      labelFunction="formatDateMeasure" textAlign="center"/>
   <mx:DataGridColumn headerText="最高血圧" dataField="High__c" 
                      textAlign="center"/>
   <mx:DataGridColumn headerText="最低"  dataField="Low__c"  textAlign="center"/>
   <mx:DataGridColumn headerText="心拍"  dataField="Beat__c" textAlign="center"/>
  </mx:columns>
 </mx:DataGrid>



</s:WindowedApplication>

2011年12月13日火曜日

FlashでPDF

■別に専門というわけではないけれど■

最近は変わりつつありますが、Webアプリにとって「カチッ」とした帳票というのはやや鬼門で、皆さんかなり苦労を強いられてきました。

私自身を振り返ってみてもPDF関連の実装が割と大きな比重を占めています。前職のWebObjectsでもiTextというJavaのライブラリを使っていましたし、現職Force.com屋でもほぼすべての案件にPDF生成が含まれている状況です。

これまでForce.com上で部門別部課別集計、罫線の各種アレンジ、複数ページ請求書などいろいろな帳票を作ってきましたが、日本ビジネス特有のきめ細かい帳票要求については、以下の2点:

  • 日本語フォントが1種類しか使えない
  • ひらがなカタカナの拗促音がbaselineに揃わない

を除けば、特に不自由なく生成できるので、ネタとしては面白くありません。


■FlashでPDF■

さて、Flash toolkit for Force.comつかっている人います? 日本のBBSを見てもほとんど話題が出ていないですし、海外の掲示板でもFlashBuilder for Force.comが中心になってしまったようで、大変寂しい思いをしています。toolkitの開発もすっかり止まってしまいましたし、このままfade outしちゃうんですかね…。

というわけで、この機会に少しでも開発者が増えることを願って、Flash+Force.comでのPDF生成について書きます。今回はFlash+Force.comについて、次回はFlash上でのPDF生成について書きます。


■Flashでの開発■

Force.com Toolkit for Adobe AIR and Flex

何よりFlashがないと話しになりません。とりあえず30日間無料でお試しができますので、Adobeから「FlashBuilder 4.6 Standard Edition」をダウンロードしてきてください。

余談ですが、4.5からはiPhone/Androidアプリも書くことができます。試してみたらホントに15分くらいでForce.com対応iPhoneアプリを書けて大笑いしたんですが、GUIがネイティブと異なりすぎることとメモリをバカ食いするので、今のところ本気では使っていません。

さて、お次は、FlashからForce.comをアクセスするためのライブラリが必要です。これは、このページの「download toolkit」から落としてきます。

では、FlashBuilderを起動してください。ユーザ登録の手続きなどは端折ります。起動遅いっす。私の嫌いなEclipse様がベースなので諦めてください。

ファイル>新規プロジェクト>Flexプロジェクト

適当な場所に適当な名前でプロジェクトを作ります。とりあえず「Advent」にしました。アプリケーションの種類をデスクトップにする以外はデフォルトのままでOKです。

できたプロジェクトにForce.comのライブラリを追加します。さっきダウンロードしてきたライブラリの中から「force-air.swc」をプロジェクトのlibsにドラッグ&ドロップしてください。

パッケージエクスプローラの中から

Adbent>src>(default package)>Advent.mxml

を開きます。ソースの先頭にあるs:WindowedApplicationとfx:Declarationsを以下のソースと置き換えてください。

<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
 xmlns:s="library://ns.adobe.com/flex/spark" 
 xmlns:mx="library://ns.adobe.com/flex/mx"
 xmlns:salesforce="http://www.salesforce.com/"
 applicationComplete="init()" height="560" width="800">
 
 <fx:Declarations>
  <salesforce:AIRConnection id="force"/>
 </fx:Declarations>


その後にはコード部分のタグが来て

 <fx:Script>
  <![CDATA[


   
  ]]>
 </fx:Script>

最後にデータを表示するためのグリッドが来て最初のタグを閉じます。

 <mx:DataGrid id="dataGrid" 
   top="10" left="23" right="23" bottom="10" 
   editable="false" enabled="false">
  <mx:columns>
      <mx:DataGridColumn headerText="id"  dataField="Id"/>
      <mx:DataGridColumn headerText="氏名" dataField="Name"/>
      <mx:DataGridColumn headerText="電子メール" dataField="Email"/>
  </mx:columns>
 </mx:DataGrid>

</s:WindowedApplication>

以上が基本的なソース構造です。で、「CDATA[」の次の行からコードを書いていきます。

■まずはインポートとグローバル変数■

 import com.salesforce.AsyncResponder;
 import com.salesforce.objects.LoginRequest;
 import com.salesforce.results.Fault;
 import com.salesforce.results.LoginResult;
 import com.salesforce.results.QueryResult;
   
 import mx.collections.ArrayCollection;

 [Bindable]
 private var arrayUser:ArrayCollection = null;


■初期化/ログイン■

冒頭部分にあるapplicationCompleteから呼び出される初期化ルーチン、ここでログイン処理をします。昔ながらの処理でOAuthなどはありませんorz。なお、これはデスクトップアプリなのでIDとPasswordでログイン処理を行いますが、FlashとしてVisualforceに埋め込む場合にはセッションidなどからそのまま認証を引き継ぐことができるようになっています。


 private function init():void 
 {
  var lr:LoginRequest = new LoginRequest();
  status = "login by id/password";
  
  lr.username = "your.account@your.domain";
  lr.password = "password" + "token";
  
  lr.callback = new AsyncResponder(loginHandler);
  force.login(lr);
 }  


■ログイン成功■

ログインが成功すると、この関数が呼び出されます。ここではすぐに馴染み深いSOQLを呼び出していますが、検索処理などを行う場合には、ここでGUIの「検索ボタン」をenableにする、などの処理を書きます。

 private function loginHandler(result:LoginResult):void 
 {
  if (result.userInfo != null) {
   var strQuery:String = "Select Id, Name, Email From User";
     
   force.query(strQuery, 
        new AsyncResponder(queryHandler, 
             faultHandler));
  }
 }


■Query成功■

Query成功時にこのコードが呼び出されます。ここは1回に最大2000レコードずつ渡されます。つまり逆にいえば、数万件のレコードがヒットするようなQueryを発行しても、ここでarrayUserにひとまとめにすることができる、ということです。まぁ遅いですけどね。それにFlashは基本的にシングルタスクなので何も配慮しないで数万件のPDF帳票を出力しようとするとUIが一切操作できなくなります。不憫な奴。

で、最後にdataGridに読み込んだデータを一括してセットすれば一丁上がり。

 private function queryHandler(result:QueryResult):void
 {
  if (result != null && result.records != null 
   && result.records.length > 0) 
  {
   if (arrayUser == null)
    arrayUser = new ArrayCollection();
   arrayUser.addAll(result.records);
  }
    
  if (!result.done)
  {
   trace("query again");
   force.queryMore(result.queryLocator, 
     new AsyncResponder(queryHandler));
  }
  else
  {
   status = "検索または実行開始をどうぞ";
   force.syncConnection
     
   dataGrid.dataProvider = arrayUser;
  }
 }


■エラー処理■

SOQLに誤りがあった場合などにこの関数が呼び出されます。

 private function faultHandler(result:Fault):void
 {
  trace(typeof(result));
  trace(result.faultcode);
  trace(result.faultstring);
 }

■基本編終了■

以上でForce.com + Flexの基本はおしまいです。次回はPDF生成編です。それまでよろしかったらSOQLを書き換えたりDataGridのdataFieldをいじったりして遊んでみてください。なお、"Select Id, Name, CurrentStatus From User"などと書いてもAPIが古いのでこのライブラリでは対応していません。あしからず。

--

この記事はForce.com Advent Calendarに参加しています。

Force.com Advent Calendar 2011 : ATND

PhoneGapかiOS SDKについて書くと予告していたのにFlashになったのは準備していたネタが土壇場になって不調だったからさ。以前書いた記事もあるし。

PhoneGap 1.2.0 + Salesforce SDK(hyblid)

以上、50歳と2日のくらはしがお送りしました。

#Bloggerさんがインデントをつぶしてしまったけど、まぁ読めるのでご勘弁