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

2016年7月23日土曜日

Sitesの落とし穴再び / Salesforce

Salesforceのことをここに書き込むのは何年ぶりでしょ。

勤務先でSalesforceを導入するかも?ってんで、やらせてもらってます。で、Sitesを使うんですが…初期段階で動いていたものが、いつの間にか毎度おなじみの「認証が必要です」画面が出てしまうように。

当然ApexもVisualforceも登録済です。そして数日後、Apexのコンストラクタを見て気付きました。

public FooClass() {
    Id userId = UserInfo.getUserId();
    ....
}

…そりゃ認証しろって言われますよね・・・これに気づかなかった自分がいや。本来ここでidを取得する必要はまったくないのですが、社内デモ向けにSitesではなく普通のVisialforceとして見せるためにちょこっと追加したコードでした。

あー、でも動いてよかった。

なお、この「認証が必要です」について「salesforce sites 認証が必要です。」でぐぐったら2010年3月にまさにこのわたくしがdeveloper forumで同じ質問をしていることが判明いたしましたorz この時は違う原因だったんですが…。

これコンサル取得する前かなー…いやはや…なんとも…。

2012年6月9日土曜日

Salesforce季節の変わり目にありがちなこと:APIが違う

さてもうすぐSummer '12です。

新規に取得したDev環境はSummer '12になってますね。その上で一部リリースされた新機能を試したりチャットのポップアップがうぜぇwなどと言いつつ作業し、できあがったパッケージを本番環境に移そうとしたのですが…「API違うから駄目っすw」と言われました。

パッケージもForce.com IDEも駄目。同じことをSpring '12の時期にもやっていたような気がする…でも、Dev環境取得する時点でAPI選べないのでどうにもなりません。

で、エラーを細かくチェックしていくと、

  • カスタムオブジェクトやレイアウトに関しては移植可能
  • エラーが出るのはVisualforce, Apex, Triggerなどのメタファイル

という状態でした。

ということでwork around。

  1. 稼働環境用サンドボックスへカスタムオブジェクトやレイアウトをすべて移植
  2. 同サンドボックスへ手動でVisualforce, Apex, Triggerをコピペ
  3. サンドボックスから本番環境へDeployment
まぁ当たり前の手順ですけども、ハマった方のご参考になれば幸いです。

なお、今回ソース数本だったので手動で対応できましたが、本数が多いと依存関係もあって大変です。とりあえずは以下の順番でコピペしてみます。
  1. Trigger
  2. 通常のApex
  3. Visualforce
  4. テストコード
ひっかかったら適宜入れ替えます。また、どうしても依存関係が解消できない場合には、先にクラス名定義だけコピーしておいて、あとから実装部分をコピーするという方法もあります。最後の手段として、ソースの数だけブラウザにタブを開いておいて、依存関係のないところから片っ端からQuick Saveして少しずつ埋めていく、なんてのもあります。

申し遅れましたが、この手の作業はForce.com IDEよりもブラウザ経由の方がレスポンスが良くて作業しやすいような気がします。

2012年1月18日水曜日

Batch Apex / Mailでハマった件

香港にて
お疲れ様です。

Batch Apexに限らないんだけど…arrayに入れたSingleMailMessageをまとめてどーんと送る時、その中に1通でも不達アドレスが含まれていると一通も送られないでエラーになりますね。

昨日はそれでハマりました。

Contactに関連付けたemailアドレスにメールを送って、それが正常に配信されないとそのアドレスには「確認しろよコラ」ってフラグが立ちます。その状態で同じアドレスにメールを送ろうとすると送信前にエラーになる。まぁそれは当たり前。問題は、不達アドレスだと検出されるタイミングが前回送信から数十分〜数時間かかるってところで、いつ判定されるかわかんない。なので大量に送信してたりすると、エラーがたまたま次回の送信Batchにひっかかってコケてました。

報告をするのにエラーの経緯を再現しなきゃいけないんだけど、そんな神業なタイミングをどーしろとw

それにしても、疲れてるとレアケースの推測がダメだわ。

今日は、送信前にそのContact / emailアドレスが不達だと判断する方法を探さなければ…。


2012-01-29追記:
EmailBouncedDate / EmailBouncedReasonがnullじゃなかったら不達ですね…。

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さんがインデントをつぶしてしまったけど、まぁ読めるのでご勘弁

2011年11月6日日曜日

Xcode 4.2 + iOS 5 + Force.com mobile SDK


■今週もまたXcode三昧■

このところ休日はXcode上でobjective-c三昧です。ようやくツールに馴染んできて、昔WebObjectsで散々使っていたクラスたちにも再会し、Google先生頼みですがとりあえず「iOSアプリとはこういうもの」というのが分かってきました。まぁ、過去30年+に及ぶプログラミング経験上、この時期が一番楽しいのですけどね。

過去の経験上、何か新しいツールを始める時には、とりあえず動くものをベースにしてイジり倒し、次にテンプレートなどを使わないで同じものを作ってみる、という段階を踏むと手っ取り早く習得できるように思います。一応本も読むのですが、それは前にも書いたようにそのツールの世界観をつかむためで、習得するためには動くものを少しずつ改造していくのが良です。


■しかしまたハマる:まずXcode 4.1■

さて、とりあえずメシの種にしようと思っているForce.com / Database.com + iOS方面、一応Salesforce mobile SDKのテンプレートから作ったプロジェクトに関しては、re-authentificationやCRUDはひと通り動き、DescriptionなどFlash toolkitでハマった一連の処理も片付いたので、次の段階としてまっさらなプロジェクトを作りその上でSalesforce対応アプリを書いていく練習に移りました。

まぁ真っさらなプロジェクトとはいえ、Xcodeだとテンプレートが基本的な構成作ってくれるんですけどね。考えてみりゃGUIをプログラムで生成してたのはMS-DOS上でcでアプリ書いてた頃ぐらいのもので、そのあとのVisualbasic, VisualC++, Symantec C/C++, Xcode+WOBuilderなど、ずっとGUI Builder頼みの人生だったな(遠い目)。

で、Xcode 4.1にアップデートしたのですが…ここの追記に書きましたが、Xcode 4.1 + iOS5では、Salesforce mobile SDKから生成したプロジェクトがうまく動いてくれませんでした。世間様でもiOS5になって日本語入力できなくなったアプリが何本がありましたが、これもそれと似たような感じでOAuthの認証画面でキーボードが出てこなくてidもpasswordも入力できないという現象です。

4.0.2に戻すのも面倒だし、せっかくなのでダメ元でXcode 4.2にアップデートしました。Salesforce mobile SDKテンプレートからのプロジェクト生成でも一発で動きました。やれやれ。

そういえば上記SDKのドキュメントには「このSDK使う時には Other Linker Flags-Obj_c -all_load と設定しろ」って書いてありますが、ありましたが、これは「-ObjC -all_load」の間違いですね。ええハマりましたともさ。
#修正されていました(2011/11/13)


■次にStoryboardにハマった■

先に書いておきますがStoryboardさんが悪いわけではないです、その特性などをよく調べないままに使って、私が勝手に「動かねー」と泣いていただけです。はい。

経緯は
  1. Xcode 4.2でMaster-DetailテンプレートからStoryboard対応のアプリを生成
  2. Salesforce mobile SDKテンプレートから生成したプロジェクトから必要箇所をコピペ
  3. Table View Cellを従来のnibからカスタマイズして実装 →It just works.
  4. 次に、また新しくMaster-DetailテンプレートからStoryboard対応のアプリを生成
  5. 上記2と同じく必要箇所をコピペ
  6. Table View CellをStoryboard上のTable View Cellに対してカスタマイズ
  7. OAuthが終わり、requestSOQLが返って来て、TableViewをreloadしようとしてSIGABRT
上記1-6については2-3時間でしたが、7は金曜日の夜一晩徹夜しても解決できませんでした。

Master-Detail + Storyboardの真っさらなプロジェクトを作り、手作りのArray of Dictionaryを単純にTable View Cell上に表示するだけなら動くので、問題はmobile SDKが何かdelegateを呼び出していて、それをインプリメントしていない等の原因で落ちているのだろう、と結論付けました。

とりあえずStoryboardにあるTableView下のTableView SectionとCellを削除すれば普通にTable Viewとして動きますし、Cellをカスタマイズする際には以前と同様にnib+implement classで実装できます。Cellをカスタマイズする際にStoryboard上に直接TableViewCellを置くことができればStoryboardの画面遷移が使えて今後何かと便利だと思うのですが、できないっすね。

まぁそのうち、何かwork aroundが出てくるでしょう。

この週末ずっとXcode 4.2をいじって、プロダクツという意味では何も成果なかったんですが、上記7を調べるためにアプリの挙動をずっと追いかけたりググりまくったりしたので、とりあえず、勉強にはなった、はず。

先週は本業でずっとApex+Visualforceを書いていて、週末はXcode三昧、体重は増えたけど、久しぶりに充実した日々でした。今週は…要件聴取が多いなぁ…。

#追記:スクリーンショットが消えるなぁ…PNGじゃなくてJPEGで貼って見ました(11月8日)。
#また消えた…:コピペじゃなくてアップロードしてみた(11/08 19:00)

2011年10月4日火曜日

Developer Editionを入手

申し込む前に注意事項。

Salesforce のIDは一意です。通常、e-mailアドレスを登録します。しかし、Force.com開発者であれば本番環境の 他に開発環境を複数所持して客先デモや 実験などに利用するのがごく一般的ですので、環境の数だけIDを用意する必要があります。メインにつかうe-mailアドレスは本番環境で使う時のために 残しておいて、 dev-email@address.com.123 のような架空のアカウントを使いましょう。


氏名、emailアドレス(これは実際のアドレスを入力してください)、アンケート項目(3つ)、勤務先、前述のユーザ名など(※)を入力し、送信します。

※余談ですが、最後の方にあるセキュリティのためのビットマップ文字、あたしゃ一発でOKになったことがありません…。

2通のメールが届きます。

Salesforce ログインのお知らせ
Force.comへようこそ!


このうち、「Salesforce ログインのお知らせ」を開き、ログイン先へのリンクをクリックします。ブラウザ上にパスワード登録画面が開きますので、新しいパスワードとリマインダを入力し保存します。これでSalesforce Developer環境が使えるようになります。

※記事の内容は発表当時の情報を元にしております。現在は変更されている可能性もありますので、くれぐれもご注意ください。Salesforceはライセンスの名称などを結構頻繁に変更しますので…。

まずは無料ライセンスで

無料で提供されるライセンスは2種類あります。

一つはDeveloper Edition。 開発用の環境としてユーザ数、オブジェクト数などに制限はあるものの、機能的にはほぼEnterprise Editionと同じです。制限の範囲内であればある程度実用になってしまうのですが、あくまでも開発用・テスト用として提供されてい るものですから、これを実運用に用いることはライセンス規約に抵触します。ご注意ください。

もう一つは「30日間無料トライアル」、 これはProfessional Editionの機能を期間限定で使うことができ、そのまま有償ライセンスに移行することができます。導入を前提としてサインアップし無料トライアル期間のうちに本番稼働のためのインプリを進めておく、というのが標準的な使い方です。

ということで今回force.comを始めるにあたってDeveloper Editionを入手します。

なお、「入手」と書いてはいますが、実際になにかパッケージなどが手元に届くわけではありません。オンラインサインアップだけですぐに利用可能になります。

※記事の内容は発表当時の情報を元にしております。現在は変更されている可能性もありますので、くれぐれもご注意ください。Salesforceはライセンスの名称などを結構頻繁に変更しますので…。

Salesforce/Force.com概要

Force.comはSalesforce.com社の提供するサービスです。クラウド・レイヤでいえばIaaS〜PaaS+DaaSあたりまでをカバーするのがForce.com、その上にSaaSとして提供されているCRM/SFAがSalesforceです。

Salesforce.com及びForce.comは基本的にユーザ一人当たり月いくら、という課金制です。

Salesforce(Sales Cloud)には主なライセンスとしてProfessional Edition(7,500円/ユーザ/月), Enterprise Edition(15,000円/ユーザ/月), Unlimited Edition(30,00円/ユーザ/月)があります。

※その下にContact Manager, Groupというライセンスもありますが、これらはユーザ数の上限も提供される機能も少ないので一般的ではありません。

Force.comのライセンスは大きく分けてEnterprise Edition, Unlimitedの2 Editionがあり、EnterpriseとUnlimitedはそれぞれ「One App」と「Multiple Apps」があります。Salesforceにおけるアプリケーションとは一言でいえば「タブのセット」を指しますが、One Appはタブのセットを切り替えることはできず、Multi Appはタブを業務ごとに切り替えることができます。また、One Appでは扱うことのできるオブジェクト数などの制限が厳しくなります。その代わり、月1500円/ユーザで利用することができますので、例えば「営業担当全員とシステム管理担当者にはSales Cloud Enterprise Edition、総務部はForce.com Enterprise Edition Multiple Edition、一般社員はForce.com Enterprise Edition One Application」というような組み合わせを選択することも可能です。

原則としてForce.comライセンスは単独では使用することができず、一つの組織に一つ以上のSalesforceアカウントが必須となっています。また、追加するアカウントは同じeditionでなければいけません。なお、上記One/Multiple Appは混在可能です。

例えば「Force.com Unlimited Edition/Multiple Applications」ライセンスは月9,000円/ユーザでフル機能のMobile(iPhoneなどのスマートフォンに対応)やプレミアムサポートが付随していてとてもお得です。しかし「一つの組織は一つのEdition」のルールにより、その後Sales cloudを導入する際には30,000円のSales cloud Unlimited Editionを選ばなければなりません。組織としてのEditionを選ぶ時にはSalesforceの担当営業さんとしっかり相談して最適なライセンスを選んでください。

余談ですが、お客さんのライセンスを決めるときにはこれで悩むんですよね。「営業担当者はSales Cloud Enterprise Edition+Mobile licenseで、その他の社員はForce.com Unlimited Editionで」なんて組み合わせができたら、どんなにか楽だろう…。

これらの他にChatter FreeとChatter Plusというライセンスがあります。ChatterとはTwitterライクな機能ですが、Chatter Freeライセンスはこの機能のみが使えるライセンスで有償の組織に対し無料で5,000ユーザまで追加することができます。Chatter Plusは1ユーザ1,500円/月、Force.com One Appとほぼ同等のライセンスでChatterの機能と10個までのカスタムオブジェクト、CRMデータの参照、レポートとダッシュボード機能、カレンダーなどを利用することができます。

※記事の内容は発表当時の情報を元にしております。現在は変更されている可能性もありますので、くれぐれもご注意ください。Salesforceはライセンスの名称などを結構頻繁に変更しますので…。