Flexを使ったApexアプリケーション開発(3)

by Shinichi Tomita on 6月 29, 2007 at 06:29 午後

ドラッグ&ドロップ機能の追加

前回、Salesforceのデータベース内にあるレコードを検索して一覧表示するアプリケーションを作成しました。今回はFlexによるApexアプリケーション構築の最終回として、前回のアプリケーションをもう少し高度なアプリケーションに発展させてみましょう。

Ws000007

このアプリケーションは、取引先の一覧から任意の取引先を選んで、訪問予定を自分のカレンダーの行動リストに追加することができるアプリケーションです。画面を左右2つに分けて、左側にユーザの行動予定(スケジュール)情報、右側に取引先の検索画面を表示しています。

左側にはカレンダーを表示するDateChooserコンポーネントと、垂直にリスト表示を行うためのListコンポーネントを新たに配置しています。

右側の取引先データの検索および一覧表示には前回に作成したものをほぼそのまま用いていますが、前回とは異なりグリッドに表示されている取引先レコード(行)をマウスで掴んでドラッグできるようになっています。

Flexでのドラッグ&ドロップ操作の追加は非常に簡単です。アイテムのドラッグを有効にしたいコンポーネントのdragEnabled属性をtrueに設定して、アイテムのドロップを受け付けるコンポーネントにはdropEnabled属性をtrueに設定しておけばOKです。後はドロップしたときのイベントを処理する関数を独自に定義しておくことで、カスタムのロジックを動かすことが可能です。

DataGridコンポーネントのドラッグを有効化
<mx:DataGrid id="accountTable" width="100%" height="100%" 
             dataProvider="{accounts}" dragEnabled="true">
...
Listコンポーネントへのドロップを有効化
<mx:List id="eventList" width="100%" height="100%" dataProvider="{events}" 
         dropEnabled="true" dragDrop="handleDropOnEventList(event)"
         labelFunction="formatEventLabel" />
....

ドロップイベントの処理関数では、ドロップされた取引先レコードに関連付けて、新たに行動予定(Event)レコードをSalesforceデータベース内に作成します。ここでは処理の記述を簡単にするため、終日の行動のみに固定しています。レコード作成が成功すると、行動予定の情報を格納しているevents変数に作成したレコードを追加します。

ドロップイベントを処理する関数
/**
* 行動予定リストへのドロップイベントを処理する
* ドロップした取引先情報に関連付けて、新規の行動予定を作成する
*/
private function handleDropOnEventList(e:DragEvent):void {
  // デフォルトのイベント処理を上書き
  e.preventDefault();
 
  var account:SObject = accountTable.selectedItem as SObject;
  var event:SObject = new SObject('Event');
  event.WhatId = account.Id;
  event.Subject = account.Name+"様訪問";
  event.IsAllDayEvent = true;
  event.ActivityDate = Util.dateToString(calendar.selectedDate);
  conn.create([event], new AsyncResponder(function(results:Array):void {
    if (results[0].success) {
      // 行動予定リストに追加
      event.Id = results[0].id;
      events.addItem(event);
    }
  }));
}

すでに登録済みの行動予定の情報も参照できるようにするために、アプリケーションロード時に自動的にデータベースに登録されている行動情報を検索して取得するようにしています。取得した行動情報は一括でevents変数に格納されますが、行動予定リストにカレンダーで選択された日付の予定のみが表示されるようにフィルタ関数を追加指定しています。

行動情報をSalesforceから検索する関数
/**
* ユーザの行動予定情報を取得
*/
private function queryEvents():void {
  // ログインユーザの情報を取得
  conn.getUserInfo(new AsyncResponder(function(userInfo:Object):void {

    // 前後30日間の行動予定情報を一括で取得
    var soql:String =
      "SELECT Id, Subject, IsAllDayEvent, ActivityDate, ActivityDateTime, DurationInMinutes FROM Event"+
      " WHERE OwnerId = '"+userInfo.userId+"'"+
      " AND (ActivityDate = LAST_N_DAYS:30 OR ActivityDate = NEXT_N_DAYS:30 "+
      "      OR ActivityDateTime = LAST_N_DAYS:30 OR ActivityDateTime = NEXT_N_DAYS:30)";
    conn.query(soql, new AsyncResponder(function(result:QueryResult):void {
      if (result.size>0) {
        events = result.records;
        // カレンダーで選択した日のみをリスト表示するようにフィルタリング
        events.filterFunction = function(event:Object):Boolean {
          var dateStr:String = Util.dateToString(calendar.selectedDate);
          return String(event.ActivityDate || event.ActivityDateTime || "").indexOf(dateStr)==0;
        }
        events.refresh();
      } else {
        events = new ArrayCollection();
      }
    })); // end of conn.query()
   
  })); // end of conn.getUserInfo()

}

以下は完成したFlexアプリケーションのソースコード(SalesScheduler.mxml)です。

SalesScheduler.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
                xmlns:salesforce="com.salesforce.*"
                layout="vertical"
                applicationComplete="login()" >
  <mx:Script>
    <![CDATA[
import mx.collections.ArrayCollection;
import mx.events.DateChooserEvent;
import mx.events.DragEvent;
import mx.formatters.DateFormatter;
import com.salesforce.*;
import com.salesforce.events.*;
import com.salesforce.objects.*;
import com.salesforce.results.*;

/**
* 取引先情報レコードを保管する変数
* Bindable メタデータを記述して、コンポーネントにデータを関連付けられるように設定
*/
[Bindable]
private var accounts:ArrayCollection = new ArrayCollection();

/**
* 行動予定(Event)レコードを保管する変数
*/
[Bindable]
private var events:ArrayCollection = new ArrayCollection();


/**
* 「種別」フィールド値の選択リストデータを保管する変数
*/
[Bindable]
private var typeDefs:ArrayCollection = new ArrayCollection([
  { label : '--------', data : 0 }
]);

/**
* 「評価」フィールド値の選択リストデータを保管する変数
*/
[Bindable]
private var ratingDefs:ArrayCollection = new ArrayCollection([
  { label : '--------', data : 0 }
]);


/**
* Salesforceにログインして、セッションを構築する
* アプリケーションのロード完了時に呼び出される
*/
private function login():void {
  var lr:LoginRequest = new LoginRequest({
    server_url : Application.application.parameters.server_url,
    session_id : Application.application.parameters.session_id,
    // username : '', // <-- put your username here!
    // password : '', // <-- put your password here!
    callback : new AsyncResponder(function(result:Object):void {
      setupForm();
      calendar.selectedDate = new Date();
      queryEvents();
    })
  });
  conn.login(lr);
}


/**
* 検索絞込みフォームのセットアップ
* 選択リストの定義をdescribeSObjectメソッドで取得
*/
private function setupForm():void {
  conn.describeSObject("Account", new AsyncResponder(function(result:DescribeSObjectResult):void{
    // 「種別(Type)」項目の選択リスト値を取得
    for each (var p:PickListEntry in result.fields['Type'].picklistValues) {
      // アクティブな選択リストのラベルと値のペアを保存
      if (p.active) typeDefs.addItem({ label : p.label, data : p.value });
    }
    // 「評価(Rating)」項目の選択リスト値を取得
    for each (var q:PickListEntry in result.fields['Rating'].picklistValues) {
      // アクティブな選択リストのラベルと値のペアを保存
      if (q.active) ratingDefs.addItem({ label : q.label, data : q.value });
    }
    // 検索ボタンを有効化する
    queryButton.enabled = true;
  }));
}

/**
* 取引先レコード情報の取得
* 検索条件に合致するレコードを上限まで取得する
*/
private function queryAccounts():void {
  var soql:String = "SELECT Id, Name, Type, Rating, BillingState, Phone FROM Account";
  var conditions:Array = [];
  if (ratingPicklist.value) {
    conditions.push(" Rating = '"+ratingPicklist.value+"' ");
  }
  if (typePicklist.value) {
    conditions.push(" Type = '"+typePicklist.value+"' ");
  }
  if (conditions.length>0) { // 条件指定があればWHERE句としてSOQLに追加
    soql += ' WHERE '+conditions.join(' AND ');
  }
  conn.query(soql, new AsyncResponder(function(result:QueryResult):void {
    accounts = result.records;
  }));
}


/**
* ユーザの行動予定情報を取得
*/
private function queryEvents():void {
  // ログインユーザの情報を取得
  conn.getUserInfo(new AsyncResponder(function(userInfo:Object):void {

    // 前後30日間の行動予定情報を一括で取得
    var soql:String =
      "SELECT Id, Subject, IsAllDayEvent, ActivityDate, ActivityDateTime, DurationInMinutes FROM Event"+
      " WHERE OwnerId = '"+userInfo.userId+"'"+
      " AND (ActivityDate = LAST_N_DAYS:30 OR ActivityDate = NEXT_N_DAYS:30 "+
      "      OR ActivityDateTime = LAST_N_DAYS:30 OR ActivityDateTime = NEXT_N_DAYS:30)";
    conn.query(soql, new AsyncResponder(function(result:QueryResult):void {
      if (result.size>0) {
        events = result.records;
        // カレンダーで選択した日のみをリスト表示するようにフィルタリング
        events.filterFunction = function(event:Object):Boolean {
            var dateStr:String = Util.dateToString(calendar.selectedDate);
            return String(event.ActivityDate || event.ActivityDateTime || "").indexOf(dateStr)==0;
        }
        events.refresh();
      } else {
        events = new ArrayCollection();
      }
    })); // end of conn.query()
   
  })); // end of conn.getUserInfo()

}

/**
* 行動予定リストへのドロップイベントを処理する
* ドロップした取引先情報に関連付けて、新規の行動予定を作成する
*/
private function handleDropOnEventList(e:DragEvent):void {
  // デフォルトのイベント処理を上書き
  e.preventDefault();
 
  var account:SObject = accountTable.selectedItem as SObject;
  var event:SObject = new SObject('Event');
  event.WhatId = account.Id;
  event.Subject = account.Name+"様訪問";
  event.IsAllDayEvent = true;
  event.ActivityDate = Util.dateToString(calendar.selectedDate);
  conn.create([event], new AsyncResponder(function(results:Array):void {
    if (results[0].success) {
      // 行動予定リストに追加
      event.Id = results[0].id;
      events.addItem(event);
    }
  }));
}


/**
* 行動予定リストのラベルの表示を定義するメソッド
*/
private function formatEventLabel(event:SObject):String {
  var time:String;
  if (event.IsAllDayEvent) {
    time = "終日";
  } else {
    var df:DateFormatter = new DateFormatter();
    df.formatString = "H:NN";
    var startTime:Date = Util.stringToDateTime(event.ActivityDateTime);
    var endTime:Date = new Date(startTime.getTime()+event.DurationInMinutes*1000*60);
    time = df.format(startTime)+" - "+df.format(endTime);
  }
  return time + " " + event.Subject;
}
    ]]>
  </mx:Script>

  <!-- Salesforce Connection オブジェクト -->
  <salesforce:Connection id="conn" />

 
  <!-- 左右に分割 -->
  <mx:HDividedBox width="100%" height="100%">

    <!-- 左ペイン -->
    <mx:Panel width="200" height="100%" layout="vertical" title="行動予定">

      <!-- カレンダー -->
      <mx:DateChooser id="calendar" width="100%" change="events.refresh()"/>
      <!-- 行動予定のリスト -->
      <mx:List id="eventList" width="100%" height="100%" dataProvider="{events}"
               dropEnabled="true" dragDrop="handleDropOnEventList(event)"
               labelFunction="formatEventLabel"
               dropShadowEnabled="true" />
    </mx:Panel>


    <!-- 右ペイン -->
    <mx:Panel width="100%" height="100%" layout="vertical" title="取引先" horizontalAlign="center">

      <!-- 取引先検索フォーム -->
      <mx:HBox>
        <mx:FormHeading label="種別" />
        <mx:ComboBox id="typePicklist" dataProvider="{typeDefs}" />
        <mx:FormHeading label="評価" />
        <mx:ComboBox id="ratingPicklist" dataProvider="{ratingDefs}" />
        <mx:Button id="queryButton" click="queryAccounts()" label="検索" enabled="false" />
      </mx:HBox>

      <!-- 取引先検索結果表示 -->
      <mx:DataGrid id="accountTable" width="100%" height="100%" dataProvider="{accounts}" dragEnabled="true">
        <mx:columns>
          <mx:DataGridColumn headerText="ID" dataField="Id" editable="false"/>
          <mx:DataGridColumn headerText="取引先名" dataField="Name" />
          <mx:DataGridColumn headerText="種別" dataField="Type" />
          <mx:DataGridColumn headerText="評価" dataField="Rating" />
          <mx:DataGridColumn headerText="都道府県(請求先)" dataField="BillingState" />
          <mx:DataGridColumn headerText="電話番号" dataField="Phone"/>
        </mx:columns>
      </mx:DataGrid>

    </mx:Panel>

  </mx:HDividedBox>

</mx:Application>

トラックバック

このページのトラックバックURL: http://www.typepad.jp/t/trackback/7240/7032144

このページへのトラックバック一覧 Flexを使ったApexアプリケーション開発(3):

コメント

Posted by なべ on 7月 17, 2008 02:58 午後:

色々と勉強させて頂きながら当ブログを拝見しているものです。

前回・今回のFLEXを使ったアプリの実相を試してみたのですが、
ソースコードの75行目と80行目の変数が重複しているようで、
ビルド時にエラーが出ます。

75行目>var p:PickListEntry in result.fields['Type'].picklistValues

80行目>var p:PickListEntry in result.fields['Rating'].picklistValues

80行目の変数名を仮に【q】に変更して、
82行目の参照変数も【q】に変えたら、うまく動くようですが、
何か他に影響ありますでしょうか?

Posted by 管理者 on 8月 22, 2008 03:01 午後:

ありがとうございます。
記事を修正させていただきました。

コメントを投稿

コメントは記事の投稿者が承認するまで表示されません。