アシアルブログ

アシアルの中の人が技術と想いのたけをつづるブログです

【Flex】FlexからJavascriptを実行してみる

こんにちは、橋本です。

今日は、FlexからJavascript関数へアクセスする方法についてお話したいと思います。

FlexからJavascriptへアクセスするためには、ExternalInterface APIを使います。

使い方は非常に簡単です。call()メソッドを使ってラッパーのJavascriptを呼び出すだけです。
Javascriptの関数に引数を渡したり、Javascript側から戻り値を受け取ることも可能です。

簡単なコードを書いて、実際に使ってみましょう。

Flexコード


<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" minWidth="955" minHeight="600">
	<mx:Script>
		<![CDATA[
			import flash.external.*;
			
			import mx.controls.Alert;
			
			public function setWrapperTitle(title:String):void
			{
				var ret:String;
				
				if (ExternalInterface.available)
				{
					ret = ExternalInterface.call("setTitle",title);
				}
				else
				{
					ret = "Fault";
				}
				
				Alert.show(ret);
			}
	
			protected function execBtn_clickHandler(event:MouseEvent):void
			{
				this.setWrapperTitle(this.titleInput.text);
			}
		]]>
	</mx:Script>
	<mx:Panel id="hoge"
			  width="280"
			  height="150"
			  x="{this.width / 2 - hoge.width / 2}"
			  y="{this.height / 2 - hoge.height / 2}"
			  title="タイトル変更"
			  >
		<mx:Form>
			<mx:FormItem label="タイトル:">
				<mx:TextInput id="titleInput"/>
			</mx:FormItem>
		</mx:Form>
		<mx:ControlBar width="100">
			<mx:Spacer width="100%"/>
			<mx:Button id="execBtn"
					   label="変更"
					   click="execBtn_clickHandler(event)"
					   />
		</mx:ControlBar>
	</mx:Panel>
	
</mx:Application>


ラッパーにScriptタグを追加して、Flexで呼び出す関数を設定します。



<script language="JavaScript" type="text/javascript">
	function setTitle(title)
	{
		window.document.title = title;
		
		return "success";
	}
</script>


実行結果がこちらです。







また、オブジェクトや配列をそのまま渡すことも可能です。
先程のソースを少し修正します。



<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" minWidth="955" minHeight="600">
	<mx:Script>
		<![CDATA[
			import flash.external.*;
			
			import mx.controls.Alert;
			
			public function setWrapperTitle(obj:Object):void
			{
				var ret:String;
				
				if (ExternalInterface.available)
				{
					ret = ExternalInterface.call("setTitle",obj);
				}
				else
				{
					ret = "Fault";
				}
				
				Alert.show(ret);
			}
	
			protected function execBtn_clickHandler(event:MouseEvent):void
			{
				var obj:Object = 
				{
					title1: this.titleInput1.text,
					title2: this.titleInput2.text
				};
				
				this.setWrapperTitle(obj);
			}
		]]>
	</mx:Script>
	<mx:Panel id="hoge"
			  width="300"
			  height="180"
			  x="{this.width / 2 - hoge.width / 2}"
			  y="{this.height / 2 - hoge.height / 2}"
			  title="タイトル変更"
			  >
		<mx:Form>
			<mx:FormItem label="タイトル1:">
				<mx:TextInput id="titleInput1"/>
			</mx:FormItem>
			<mx:FormItem label="タイトル2:">
				<mx:TextInput id="titleInput2"/>
			</mx:FormItem>
		</mx:Form>
		<mx:ControlBar width="100">
			<mx:Spacer width="100%"/>
			<mx:Button id="execBtn"
					   label="変更"
					   click="execBtn_clickHandler(event)"
					   />
		</mx:ControlBar>
	</mx:Panel>
	
</mx:Application>


javascriptの方も、オブジェクトを受け取るように変更


<script language="JavaScript" type="text/javascript">
	function setTitle(obj)
	{
		window.document.title = obj.title1 + obj.title2;
		
		return "success";
	}
</script>


実行結果







また、無名関数をFlex内で直接記述して実行することも可能です。

さきほどのコードを、無名関数を使うように変更します。


	public function setWrapperTitle(obj:Object):void
	{
		var ret:String;
				
		if (ExternalInterface.available)
		{
			ret = ExternalInterface.call(
				"function(obj)" +
				"{" +
					"window.document.title = obj.title1 + obj.title2;" +
					"return 'success';" +
				"}"
			, obj);
		}
		else
		{
			ret = "Fault";
		}
		
		Alert.show(ret);
	}
	


実行結果は先程の例と同様になります。

無名関数を使うことで、適用の幅が広がると思います。
非常に簡単ですので、試してみてください。

【AS3】「ActionScript3の、こんなときどうするの??」

こんにちは、橋本です。
今日は、自分が普段AS3を書いていて「こんなときどうするの??」と思ったところを中心にいくつか書いみました。
思いついたものから書いていったので、内容はバラバラですが、お役に立てる内容が一つでもあれば幸いです。

では、どうぞー。


Q. イベントリスナーに引数を渡したい。

A. 関数を返すイベントハンドラを指定することで、引数を渡すことができます。

サンプル.


// イベントリスナを登録するときに、引数を渡す
addEventListener(MouseEvent.CLICK, clickHandler("hoge"));

private function clickHandler(str:String):Function
{
	return function (event:MouseEvent):void {trace(str)};
}


Q. イベントの進行を止めたい。

A. event.stopPropagation()を使います。現在処理されている対象で、イベントの伝達をストップすることができます。

サンプル.


public class test extends Sprite
{
	public function test()
	{
		var obj:Object = {hoge: "hoge"};
		
		var sprite:Sprite = new Sprite();
		sprite.name = "parent";
		this.addChild(sprite);
		sprite.graphics.beginFill(0xFFFF00);
		sprite.graphics.drawRect(0, 0, 100, 100);
		
		sprite.addEventListener(MouseEvent.CLICK, clickHandler1);
		
		var sprite2:Sprite = new Sprite();
		sprite2.name = "child";
		sprite.addChild(sprite2);
		sprite2.graphics.beginFill(0x00FF00);
		sprite2.graphics.drawRect(0, 0, 50, 50);
		
		sprite2.addEventListener(MouseEvent.CLICK, clickHandler2);
	}
	
	private function clickHandler1(event:MouseEvent):void
	{
		trace("parent");
	}
		
	private function clickHandler2(event:MouseEvent):void
	{
		trace("child")
		
		event.stopPropagation();
	}
}


Q. イベントの、currentTargetとtargetの違いが、実はよくわかりません。

A. 大丈夫です。僕は最初はよくわかっていませんでしたw
currentTargetは、イベントを受け取ったオブジェクトで、targetはイベントを発生させたオブジェクトです。
以下のサンプルで、親のボックスをクリックしたときには、両方とも"parent"と表示されますが、子のボックスをクリックしたときには、targetが"child"となり、currentTargetが"parent"となります。これは、子のボックスをクリックしたことで発生したイベントを、親のボックスで受け取っているからです。

サンプル.


public class test extends Sprite
{
	public function test()
	{
		var obj:Object = {hoge: "hoge"};
		
		var sprite:Sprite = new Sprite();
		sprite.name = "parent";
		this.addChild(sprite);
		sprite.graphics.beginFill(0xFFFF00);
		sprite.graphics.drawRect(0, 0, 100, 100);
		
		sprite.addEventListener(MouseEvent.CLICK, clickHandler1);
		
		var sprite2:Sprite = new Sprite();
		sprite2.name = "child";
		sprite.addChild(sprite2);
		sprite2.graphics.beginFill(0x00FF00);
		sprite2.graphics.drawRect(0, 0, 50, 50);
	}
	
	private function clickHandler1(event:MouseEvent):void
	{
		trace("currentTarget:" + event.currentTarget.name + ", target:" + event.target.name);
	}
}


Q. オブジェクトのクラス名を知りたい。

A. flash.utils.getQualifiedClassNameメソッドを使うと、フルパスのクラス名を取得できます。純粋にクラス名だけを取りたいときには正規表現を使ったりしてみてくだしい。

サンプル.


var sprite:Sprite = new Sprite();

trace(getQualifiedClassName(sprite)); // flash.display::Sprite
trace(getQualifiedClassName(sprite).match(/::(.*)/)[1]); // Sprite


Q. 配列にオブジェクトが含まれているか知りたい(PHPでいうところの、in_array)。

A. indexOfメソッドを使います。配列に含まれている場合には、インデックスを返し、無い場合には"-1"を返してくれるので、そこで判定します。

サンプル.


public static function inArray(item:*, arr:Array):Boolean
{
	return arr.indexOf(item) != -1;
}


Q. 配列のコピーが欲しい。

A. Arrayの中身をfor文で回して…というのは嘘で、Array.concat()もしくは、Array.slice()を引数無しで呼び出すことで作成可能です。

サンプル.


var arr:Array = [1, 2, 3];

var arr2:Array = arr.concat();

for each (var num:Number in arr2)
{
	trace(num);
}


Q. オブジェクトディープコピーが欲しい。

A. ByteArrayクラスのインスタンスを作成することでディープコピーが可能になるようです。(参考:http://help.adobe.com/ja_JP/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7ee7.html)リンク先にもありますが、一応cloneメソッドのサンプルを載せておきます。

サンプル.


import flash.utils.ByteArray; 
 
function clone(source:Object):* 
{ 
    var myBA:ByteArray = new ByteArray(); 
    myBA.writeObject(source); 
    myBA.position = 0; 
    return(myBA.readObject()); 
}


どうでしょうか。
お役に立てることがひとつでもあれば幸いです。

E4Xのまとめ-その1-

Flex大好き。橋本です。

さて、今日はFlexXMLデータを操作するために使用する「E4X」の使用方法についてまとめていきたいと思います。
(個人的な備忘目的だったりします。。)

書いてるうちに長くなってしまったため、2回に分けていきたいと思います。

まず、E4Xとは何かと言いますと、ECMAScript3 のXML データを扱うための拡張仕様のことです。

E4Xでは、XMLデータは、AS3のネイティブデータ型である、XML型とXMLList型、それぞれのクラスであるXMLクラスとXMLListクラスで表されます。
XMLインスタンスは、次の5種のXMLの内容のいずれかを表します。

・エレメント
・属性
・テキストノード
・コメント
・処理命令

XMLListは、一つ、または複数のXMLインスタンスの集まりです。
XMLエレメントが子エレメントをや子テキストノードをもつ場合には、子はその親のXMLインスタンスによって、XMLList内に包含されます。

XMLデータの初期化方法
1.リテラル形式でXMLデータを記述する


var xml:XML = <sample attr="hogehoge">
	<hoge>hogehogehoge</hoge>
	<fuga>fugafugafuga</fuga>
</sample>;


インスタンス作成時には、文字列で指定することも可能です。


var str:String = '<sample attr="hogehoge"><hoge>hogehogehoge</hoge><fuga>fugafugafuga</fuga></sample>';
var xml = new XML(str);


ダイナミックにエレメントの内容を指定することも可能です。


var rootName:String = "sample";
var rootAttrName:String = "attr";
var rootAttrVal:String = "hogehoge";
var elementNames:Array = ["hoge", "fuga"];
var elementVals:Array = ["hogehogehoge", "fugafugafuga"];

var xml:XML = <{rootName} {rootAttrName}={rootAttrVal}>
	<{elementNames[0]}>{elementVals[0]}</{elementNames[0]}>
	<{elementNames[1]}>{elementVals[1]}</{elementNames[1]}>
</{rootName}>


XMLデータへのアクセス方法
以下の二種類の方法があります。
1.メソッドの利用


attribute()
attributes()
child()
children()
comments()
descendants()
elements()
parent()
processingInstructions()
text()
等


2.変数スタイルのアクセス方法の利用


ドット演算子(.)
属性演算子(@)
子孫演算子(..)


・具体的なアクセス方法


var xml:XML = <nodes attr="hogehoge">
	<hoge>hogehogehoge</hoge>
	<fuga>fugafugafuga</fuga>
</nodes>;

//ルートへのアクセス
hogeFunc(xml);

//(hgoeFunc(xml.nodes)は間違い。<nodes>エレメントは、xmlノードの子ではありません)

//子ノードへのアクセス
xml.children(); //<nodes>の子ノードを表すXMLListを返す

xml.* //プロパティワイルドカードを使用することも可能

//特定の子へのアクセス
xml.children()[0]

xml.*[0]

//最初の子へのアクセス
xml.children()[0]

//最後の子へのアクセス
xml.children()[xml.children().length() - 1]

//名前でアクセス
xml.child("hoge") //"hoge"という名前の<nodes>ノードの子エレメントを全て返す

xml.hoge


同じ名前のノードが二つ以上ある場合には、2つのXMLエレメントを持ったXMLListエレメントが返ってきます


var xml:XML = <nodes>
	<node>hoge</node>
	<node>fuga</node>
</nodes>;

xml.node[0] // "hoge"
xml.node[1] // "fuga"


結果が一つしか無い場合は、XMLエレメントを一つだけ含んだXMLListが返ってくるのですが、この場合には、XMLListをXMLオブジェクトのように扱うことができます。


var xml:XML = <nodes>
	<node>hoge</node>
</nodes>;
xml.node // "hoge"


・テキストノードへのアクセス


var xml:XML = <nodes>
	<node1>hoge</node1>
	<node2>fuga</node2>
</nodes>;

//XMLインスタンスとして参照する場合
var hoge:XML = xml.node1.children()[0]
var hoge:XML = xml.node1.*[0]

//Stringとしてアクセスしたい場合
var hoge:String = xml.node1.toString()
(xml.node1[0].toString())

//toStringメソッドは省略可能です。
var hoge:String = xml.node1

//まとめてテキストノードだけを取得することもできます
xml.*.text()

xml.*.text()[0] // "hoge"
xml.*.text()[1] // "fuga"


・親ノードへのアクセス


var xml:XML = <nodes>
	<node1>hoge</node1>
	<node2>fuga</node2>
</nodes>;

var hoge:XML = xml.node1[0]
hoge.parent() // <nodes>へのアクセス


parent()を続けて使用することも可能です。


var xml:XML = <nodes>
	<node1>
		<node2>fuga</node2>
	</node1>
</nodes>;

var node2 = xml.node1.node2[0]
node2.parent().parent() // <nodes>エレメント


属性を表すXMLインスタンス上で呼び出されると、その属性が定義されているエレメントを返します


var xml:XML = <nodes>
	<node1 attr="fuga">hoge</node1>
</nodes>;

xml.@attr.parent // <node1>エレメント


・属性へのアクセス


var xml:XML = <nodes attr="fuga">
	<node1>hoge</node1>
</nodes>;

xml.attributes() //<nodes>の全ての属性を表すXMLListを返す

// 属性ワイルドカードを使用することも可能です
xml.@*

// 特定の属性へのアクセス
xml.attribute("attr")

// 変数シンタックス
xml.@attr
// (本当は、xml.@attr[0]。一つしかない場合は、[0]を省略できる)

// 属性の値へアクセスするとき
var hoge:String = xml.@attr.toString()

// toString()は省略出来ます
var hoge:String = xml.@attr


・コメントへのアクセス
デフォルト設定では、E4Xパーサはコメントを無視します。
コメントにアクセスするようにするには、XML.ignoreCommentsをfalseにする必要があります。
XML.ignoreCommentsはstaticな変数なので、クラスに対して設定する必要があります。



XML.ignoreComments = false;

var xml:XML = <nodes attr="fuga">
	<!-- これはコメントです -->
	<node1>hoge</node1>
</nodes>;

trace(xml.comments()[0]) // "これはコメントです"


イテレーションによるアクセス


var xml:XML = <nodes>
	<node1>hoge</node1>
	<node2>fuga</node2>
	<node3>foo</node3>
</nodes>;

for each(var a:XML in xml) {
	trace(a); // "hoge", "fuga", "foo"
}


というわけで、今日は基本的な部分についてまとめてみました。
次回は、子孫ノードへのアクセス、条件判定などについて、突っ込んで行こうと思います。

FlexでObjectのvar_dump()を行う「Debug.dump」を試してみた

こんにちは。松田です。
最近ずっとFlexばかりいじってるわけですが、PHPではおなじみのvar_dumpをFlexで実装した、「Debug.dump」があったので紹介します。
Objectインスタンスは中身を確認しづらいので、PHPに慣れた人にとってはとても使いやすそうです。

ダウンロードは以下のURLから
http://www.flexer.info/2008/06/25/dump-debug-method-like-var_dump-function-in-php-and-debug-class/

ページ中段の 「var_dump_sources」 をクリックするとソースのzipファイルがダウンロードできます。
それを解凍するとcomディレクトリが出てくるので、それを読み込み可能な位置に配置します。
そしてscript内からcom.flexer.Debugをimportすれば準備完了ですっ。


サンプルのコードは以下のとおりです。



<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" 
	layout="absolute" width="500" height="400"
	creationComplete="start()">
	<mx:TextArea id="debugTA" x="10" y="10" 
		width="480" height="380"/>
	<mx:Script>
		<![CDATA[
			import mx.controls.Alert;
			import com.flexer.Debug;
			
			private var testdata:Object;
			
			private function start():void
			{
				// even though the object is created like here
				// the order does not reflect the order of
				// creation - the order is somehow made internally 
				testdata = new Object();
				testdata["row1"] = {
					"col1":"a",
					"col2":{
						"12":"12",
						"13":[1,2,3,4]
					},
					"col3":"c",
					"col4":"d"
				};
				testdata["row2"] = new XML();
				testdata["row2"] = <test><tag1 att="att">val</tag1></test>;
				testdata["row3"] = {"col1":"a","col2":"b","col3":"c","col4":"d"};
				testdata["row4"] = {"col1":"a","col2":"b","col3":"c","col4":"d"};
				testdata["row5"] = new Array();
				testdata["row5"] = ["a","b","c","d"];
				testdata["row6"] = {"col1":"a","col2":"b","col3":"c","col4":"d"};


				// calling debug.dump
				debugTA.text = Debug.dump(testdata,3,5.5);
			}
		]]>
	</mx:Script>
</mx:Application>



これを実行した結果が下のようになります。



Object (6) {
    [row2] => 
    XML(<test>
  <tag1 att="att">val</tag1>
</test>)
    [row3] => 
    Object (4) {
        [col3] => 
        String (1) = "c"
        [col4] => 
        String (1) = "d"
        [col1] => 
        String (1) = "a"
        [col2] => 
        String (1) = "b"
    }
    [row5] => 
    Array (4) {
        [0] => 
        String (1) = "a"
        [1] => 
        String (1) = "b"
        [2] => 
        String (1) = "c"
        [3] => 
        String (1) = "d"
    }
    [row1] => 
    Object (4) {
        [col3] => 
        String (1) = "c"
        [col4] => 
        String (1) = "d"
        [col1] => 
        String (1) = "a"
        [col2] => 
        Object (2) {
            [12] => 
            String (2) = "12"
            [13] => 
            Array (4) {
                [0] => 
                int(1)
                [1] => 
                int(2)
                [2] => 
                int(3)
                [3] => 
                int(4)
            }
        }
    }
    [row4] => 
    Object (4) {
        [col3] => 
        String (1) = "c"
        [col4] => 
        String (1) = "d"
        [col1] => 
        String (1) = "a"
        [col2] => 
        String (1) = "b"
    }
    [row6] => 
    Object (4) {
        [col3] => 
        String (1) = "c"
        [col4] => 
        String (1) = "d"
        [col1] => 
        String (1) = "a"
        [col2] => 
        String (1) = "b"
    }
}
int(3)
Number(5.5)



PHPに慣れた人にとってはとっても見やすいフォーマットですね。
このdumpメソッドですが、サンプルコードにもあるようにObjectだけでなくXMLインスタンスもvar_dumpしてくれるようです。
通常Objectの中身を見るにはfor each等を駆使するしかなく、多重階層の場合は非常に苦労していたので、これはデバッグには欠かせないツールになりそうです。

続・Flex4とsymfonyを連携させて遊んでみた。

屋内の寒さと、屋外の暑さの差で完全に夏バテ気味の橋本です、こんにちは。

早く秋がきてほしいですね。
秋いいよ、秋…。

はい。というわけで、みなさんも体調管理には気をつけてくださいませ。

さて、今回は前回に引き続き、Flex4 × Symfonyのお話です。

前回はsfAmfPluginの使い方と、データの保存処理について書きました。今回は引き続きデータの取得について書いていこうと思います。

(ちなみに、sfAmfPluginの2009/07/22現在の最新版はver.1.4.1です。前回使っていたver.1.3.0のバグがいくつか修正されているようなので、皆様こちらを使用しましょう。)

データの取得についても、前回と同様に、まずsymfony側でサービスのメソッドの作成をします。

とりあえず、前回の記事で作成した保存用のメソッドに加え、取得用のメソッドを作成します。

サービス


<?php

// lib/services/frontend/user/RegisterService.class.php

class RegisterService extends sfAmfService {
  // ユーザデータを保存する
  public function saveUserData($user_data) {
    $user = new User();
    $user->fromArray($user_data);
    $user->save();

    return 'OK';
  }

  // ユーザデータを取得する
  /**
     * @AmfReturnType("ArrayCollection")
     */
  public function getUserData() {
    $user_list = Doctrine::getTable('User')->getUserData();
    return $user_list;
  }
}


モデル


<?php

// lib/model/doctrine/UserTable.class.php

class UserTable extends Doctrine_Table
{
  public function getUserData() {
    $ret = array();

    $rec = $this->createQuery('u')
      ->execute();

    foreach ($rec as $user) {
      $ret[] = array($user->id, $user->name, $user->email, $user->phone);
    }
    return $ret;
  }
}


RegisterService.class.phpの保存用メソッド、getUserData()のコメント。
このコメントはアノテーションになっており、Flex側で受け取った際の戻り値の型を指定することができます。
今回は、戻り値をDataGridの中で使用するため、AllayCollection型で返します。

次にFlex側です。
ソースはこんな感じ。(またまた適当ですみません。)



<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
	           xmlns:s="library://ns.adobe.com/flex/spark"
	           xmlns:mx="library://ns.adobe.com/flex/halo"
	           minWidth="1024"
	           minHeight="768"
	           creationComplete="loadUserData()">
<fx:Script>
<![CDATA[
	import mx.rpc.AsyncResponder;
	import mx.rpc.AsyncToken;
	import mx.collections.ArrayCollection;
	import mx.automation.IAutomationObject;
	import mx.controls.Alert;
	import mx.rpc.remoting.RemoteObject;
	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;
   	
   	[Bindable]
	private var userDataList:ArrayCollection;
	
   	private function loadUserData():void {
   		// サービスの呼び出し
   		var token:AsyncToken = remote.getUserData();
   		// ResultEventとFaultEventの処理を登録
        token.addResponder(new AsyncResponder(successLoadFunc, faultLoadFunc));
   	}
   	
   	private function saveUserData():void {
   		// フォームで取得したデータを入れる配列
   		var data:Array = new Array();
   		
   		data['name'] = nameForm.text;
   		data['email'] = emailForm.text;
   		data['phone'] = phoneForm.text;
   		
   		// サービスの呼び出し
   		var token:AsyncToken = remote.saveUserData(data);
   		// ResultEventとFaultEventの処理を登録
   		token.addResponder(new AsyncResponder(successSaveFunc, faultSaveFunc));
   	}
   	
   	private function successLoadFunc(e:ResultEvent,obj:Object = null):void {
		// 明示的にキャストしないとエラーが出て、FlashBuilderさんがコンパイルしてくれません…
		userDataList = ArrayCollection(e.result);
	}
	
	private function faultLoadFunc(e:FaultEvent,obj:Object=null):void{
		Alert.show(e.toString(), "Error");
	}
	
	private function successSaveFunc(e:ResultEvent,obj:Object = null):void {
		Alert.show(e.result.toString(), "Registerd");
		// フォームをリセットして、データを再読み込みします。
		resetUserData();
		loadUserData();
	}
	
	private function faultSaveFunc(e:FaultEvent,obj:Object=null):void{
		Alert.show(e.toString(), "Error");
	}
	
	private function resetUserData():void {
		nameForm.text = "";
		emailForm.text = "";
		phoneForm.text = "";
	}

]]>
</fx:Script>

<fx:Declarations>
<mx:RemoteObject 
	id="remote"
	endpoint="http://amf_sample/amfgateway/amf"
	source="frontend.user.RegisterService"
	destination="hoge" 
	showBusyCursor="true"
/>	
</fx:Declarations>
	<mx:TabNavigator x="80" y="35" width="485" height="430">
		<mx:Canvas label="save" width="100%" height="100%">
			<s:Button x="80" y="270" label="Save" id="btnSaveUserData" click="saveUserData()"/>
			<s:Button x="200" y="270" label="reset" id="btnResetUserData" click="resetUserData()"/>
			<mx:Label x="50" y="40" text="Name"/>
			<mx:Label x="50" y="120" text="Email"/>
			<mx:Label x="50" y="200" text="Phone"/>
			<s:TextInput x="150" y="35" id="nameForm" width="240"/>
			<s:TextInput x="150" y="120" id="emailForm" width="240"/>
			<s:TextInput x="150" y="200" id="phoneForm" width="240"/>
		</mx:Canvas>
		<mx:Canvas label="Load" width="100%" height="100%">
			<mx:DataGrid x="20" y="20" height="340" width="450" dataProvider="{userDataList}">
				<mx:columns>
					<mx:DataGridColumn headerText="ID" dataField="0"/>
					<mx:DataGridColumn headerText="Name" dataField="1"/>
					<mx:DataGridColumn headerText="Email" dataField="2"/>
					<mx:DataGridColumn headerText="Phone" dataField="3"/>
				</mx:columns>
			</mx:DataGrid>
		</mx:Canvas>
	</mx:TabNavigator>
</s:Application>


タブナビゲータを使って、入力画面と表示画面を分けてあります。

Flex側のポイントはAsyncTokenクラスです。
RemoteObjectを使ってサーバのサービスを呼び出すと、ResultEventやFaultEventにレスポンスが返ってきます。そのレスポンスを受けるのがAsyncTokenクラスになります。(なんだか説明がわかりにくくてすみません。)
また、AsyncTokenのインスタンスには、addResponderメソッドを使ってレスポンダを登録することができます。レスポンダ(AsyncResponderクラス)には、レスポンスがresultの場合の処理と、faultの場合のメソッドをそれぞれ登録することができます。

サービスからの戻り値は、ResultEvent、FaultEventのresultというプロパティに格納されています。

その他は簡単です。サービスから取得したデータをDataGridのdataProviderに指定した変数(今回はArrayCollection型。Array型でも大丈夫です。)に入れてるだけ。

では、早速動かしてみます。

まずは、前回作った登録側から。

最初はデータがありません。



データを登録します。



保存成功。



データを確認。更新されています。やったね。




というわけで、二回にわたって書いてきたFlex4 x Symfony。よく分からないエラーに悩まされた日々もありましたが、結果的にデータの登録と取得が問題なく出来るようになりました。ここまで出来れば、あとは応用でいろいろと面白いことができそうです。

意外と簡単ですので、皆さんも是非やってみてください。


※ちなみに、前回の作成例とコードが違うのは、間違って前回のデータを消してしまったためです。泣きました。皆さんも気をつけてください…。

Flex4とsymfonyを連携させて遊んでみた。

最近仕事で毎日PHP4と格闘している橋本です、こんにちは。

さて、4繋がりということで、今日は先日β版がリリースされたばかりのFlashBuilder4を使って、
Flex4とsymfonyを連携させて遊んでみました。

Asialブログの購読者の方々の中に「symfonyって何??」って方はいらっしゃらないと思いますが、「Flexって何??」って方は中にはいらっしゃるかもしれないので、軽く説明。

Flexとは、Adobe社の提供するRIA(Rich Interface Application)開発のフレームワークです。
Adobeの得意分野であるFlashの技術をベースとしています。
インターフェースの作成には、MXMLというXMLを拡張した言語を用いて行います。
実行時には、MXMLファイルがswfファイルに変換され、クライアントのFlashPlayer上で実行されます。
Flexの開発キットのFlexSDKはAdobe社から配布されており無償でDL可能です。
(最新版:Adobe Flex4 SDK
また、有償ですが、FlashBuilder4(旧称 FlexBuilder)というEclipseベースのIDEを利用することも可能です。
(ちなみに、一個前のバージョンのFlexBuilder3は、学生の方ならば無料で利用することができます。こちら。学生さん、うらやましすぎです。)

今回はこのFlashBuilder4を使ってアプリ作成を進めていきたいと思います。


まず、FlashBuilder4をDL&インストールしましょう。

30日間は無料で使えます。

こちら

ちなみに、FlexBuilder3のライセンスを持っている場合は、β期間中は30日を超えても利用可能みたいです。)

次にsymfonyのインストールです。

と、言いたいところですが、既にsymfonyをお使いになっておられる方が多いかと思いますので、割愛。

今回はsymfony-1.2.8-DEVを使って進めていきます。


始める前に、そもそもどうやってFlashPHPでデータをやりとりするのかという話になるかと思いますので、軽く説明。

FlashPHPでデータのやりとりをする際には、AMF(ActionScript Messaging Format)というファイル形式を利用します。他にもXMLだったり、JSONだったりでやりとりすることも可能ですが、AMFはバイナリ形式でやりとりが可能なため、他の二つの方法よりも高速にデータのやりとりを行うことができます。

(少し古いデータですが、三つのデータ形式を利用した結果が載ってるページがありました。こちら

PHPでAMFを利用するためのライブラリとして主に、amfphp、WebOrb、SabreAMFの三つがありますが、今回はSabreAMFを利用します。

SabreAMFを使うのは、SabreAMFを利用したsymfonyのpluginが公開されていた、という単純な理由からです。

sfAmfPlugin

今回はこのpluginを使ってすすめていきたいと思います。

では、まず、symfonyプロジェクトを作成します。



symfony generate:project amf_sample
symfony generate:app frontend


次に、sfAmfPluginをインストールします。キャッシュのクリアもお忘れなく。



symfony plugin:install sfAmfPlugin
symfony cc


プラグインを使って、AMF-Serviceを作成します。



symfony amf:create-service --package=frontend.user Register
symfony cc


引数としてパッケージ名を渡していますが、これは渡しても渡さなくても大丈夫です。
パッケージ名を渡さなかった場合には、lib/services以下に、パッケージ名を渡した場合は、lib/services/パッケージ名にAMF-Serviceクラスのファイルが作成されます。

上記の例では、lib/services/frontend/user/RegisterServices.class.phpが作成されます。

次に、このサービスの中継役をするモジュールを作成します。

symfonyの場合は、モジュールとアクションを作ってやればOKです。



symfony generate:module amfgateway
symfony cc


apps/frontend/modules/amfgateway/actions/actions.class.php




class amfgatewayActions extends sfActions
{
  public function executeAmf(sfWebRequest $request) {
    $this->setLayout(false);

    $gateway = new sfAmfGateway();
    $response = sfContext::GetInstance()->getResponse();
    $response->setContent($gateway->service());
    return sfView::NONE;
  }
}



次に、サービスの中身を作っていきます。
といっても今日は簡単なことしかしません。
Flex側で取得した値をDoctrineに渡して保存するのみ。

DBはこんな感じの簡単なものを用意しました。



User:
  tableName: user
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    name: string(255)
    email: string(255)
    phone: string(255)
    created_at: timestamp(25)
    updated_at: timestamp(25)
  actAs:
    Timestampable: { }


記述の仕方は普通にDoctrineを使用する場合と同じです。

lib/services/frontend/user/RegisterService.class.php



class RegisterService extends sfAmfService {
  // ユーザデータを保存する
  public function createUserData($user_data) {
    $user = new User();
    $user->fromArray($user_data);
    $user->save();

    return 'OK';
  }
}


次にFlexを使ってクライアント側をつくります。
外観はデザインモードにして、適当に作成。
スクリプト部分をソースモードにして、これまた適当に作成します。
デザインモードで適当に作ったため、座標の値が中途半端なのですが、ご愛嬌ってことで見逃してください。(す、すみません。)

ソースはこんな感じ。


<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/halo" minWidth="1024" minHeight="768">
	<fx:Script>
		<![CDATA[
			import mx.collections.ArrayCollection;
			import mx.controls.Alert;
			import mx.events.CloseEvent;
			import mx.rpc.remoting.RemoteObject;
			import mx.rpc.events.FaultEvent;
			import mx.rpc.events.ResultEvent;
				
			private function createUserData():void {
				var userData:Array = new Array();
				
				userData['name'] = userName.text;
				userData['email'] = email.text;
				userData['phone'] = phone.text;

                remote.addEventListener("result", function (e:ResultEvent):void {
                 	Alert.show(e.result.toString(), "Registrated!", null, null, function (e:CloseEvent):void {
						clearInputText();
                 	});
                });
                 
                remote.addEventListener("fault", function(event:FaultEvent):void {
                 	Alert.show(event.fault.toString(), "Error");
                });
                
                remote.createUserData(userData);
			}
			
			private function clearInputText():void {
				userName.text = "";
                email.text = "";
                phone.text = "";
   			}
		]]>
	</fx:Script>
	<fx:Declarations>
		<mx:RemoteObject 
        id="remote"
        endpoint="http://amf_sample/amfgateway/amf"
        source="frontend.user.RegisterService"
        destination="hoge" 
        showBusyCursor="true"
    />	
	</fx:Declarations>
	
	<mx:TabNavigator x="90.7" y="16.35" width="419" height="261" cornerRadius="0">
		<mx:Canvas label="register member" width="100%" height="100%" cornerRadius="0" alpha="1.0" borderStyle="none">
			<mx:Label x="50" y="27" text="Your name:"/>
			<s:TextInput x="149.25" y="25.25" id="userName"/>
			<mx:Label x="79.000015" y="67.65" text="email:"/>
			<s:TextInput x="149.25" y="64.65" id="email"/>
			<mx:Label x="75.000015" y="108.65" text="phone:"/>
			<s:TextInput x="149.25" y="111.65" id="phone"/>
			<s:Button x="231.3" y="170.65" label="clear" id="clear" click="clearInputText()"/>
			<s:Button x="118.3" y="170.65" label="create"  id="create" click="createUserData()"/>
		</mx:Canvas>
		<mx:Canvas label="show members" width="100%" height="100%">
			<mx:DataGrid x="15" y="10" height="265" width="453">
				<mx:columns>
					<mx:DataGridColumn headerText="ID" dataField="userId" width="50"/>
					<mx:DataGridColumn headerText="Name" dataField="userName" />
					<mx:DataGridColumn headerText="Email" dataField="email"/>
					<mx:DataGridColumn headerText="phone" dataField="phone"/>
				</mx:columns>
			</mx:DataGrid>
		</mx:Canvas>
	</mx:TabNavigator>
	
</s:Application>



RemoteObjectがサーバとのやりとりを行うオブジェクトです。
この中でも特に重要なのが、endpointとsourceです。
endpointは、さきほど作った中継役のモジュールのアクションを指定します。
sourceは、サービスをパッケージ名を含んだ形で指定します。

ようやく準備が整ったので、さっそく実行してみましょう。
実行するクライアントは、4つながりでsafari4を使用します。

実行時


登録完了時


FlashBuilderを使ってさくっと作っただけでしたが、一応ちゃんと動くものができました。

今回は登録だけでしたが、次回はDBからデータを持ってきて表示するところまでやってみたいと思います。

(本当は、今回でデータの取得と表示まで出来るようにしたかったのですが、Doctrineを使ったデータの取得部分ではまってしまったため延期です。すみません…。)

Box2DFlashAS3を使ってゲームっぽいものを作ってみる

こんばんは。松田です。
今回は以前ちょっとだけいじってみたActionScript用2D物理エンジンBox2DFlashAS3を使って簡単なゲームを作ってみたいと思います。

前回のエントリーはこちら

今回は前回いじったものをちょっとずつ直しながら、ゲームっぽく仕上げてみたいと思います。

ゲームセンターにあるなつかしのコインゲームをつくってみます。
コインを入れると上からコインがカタカタと落ちて来て、下のほうにある枠に入ります。
横一列に並ぶといっぱいコインがもらえ、同じマスに2つコインが入ると全部ボッシューとなります。
高校時代に近くのゲーセンに行くと、最初にこのゲームをやって所持コインを増やしてから他のゲームに回ってました。なついです。
参考URLでも出せれば説明しやすいのですが、正式名称がわからないのでググれませんでした。あれってなんてゲームなんですかねー。


さて、今回作るものは以下のような感じです。


1.コインの初期方向を決める左右に揺れて動く棒
2.パチンコのピンのような障害物となるちっちゃいbox
3.最終的にコインが収まる枠(枠の中にコインが2枚たまると床部分が一瞬消えるようにする)



2は単純にboxをたくさん配置するだけなので特に説明不要です。
3も単純ですが、枠の床部分を消すために、 world.DestoryBody(b2Body) を使用して床のboxを削除する必要があります。(この削除方法がわかるまで苦労しました・・・)

1のようなオブジェクトを作るには、自動的に動くモーター機能のついた、回転ジョイント(Revolute Joint)を使う必要があります。
回転ジョイントのおおまかな使い方は以下の通りです。


・動くboxを作成
・動きの支点となるちっちゃいboxを作成
・ジョイントの設定をするb2RevoluteJointDefを作成し、モーターの速度、ジョイントに使用する上記二つのオブジェクトを指定する
・world.CreateJoint(def)でジョイントを設置


他のコインが2枚重なった判定やその他のもろもろの処理はものすっごく汚い判定になってしまったので説明は省きます。
ソースコードはここ

別画面で開く
「投入」ボタンを押すとコインを落とします。
下に9つの枠がありますが、コインが隣同士に並ぶと払い戻し金が増えます。
2枚並ぶと4枚払い戻し、3枚並ぶと8枚、4枚並ぶと16枚・・・。
ただし、同じ枠に2枚コインが入ってしまうと全て没収されます。
そろそろ危ないな・・・と思ったタイミングで「払戻」ボタンを押しましょう。

いろいろと不具合が残りまくりですがそろそろ眠気がピークに達してきたので続きはそのうち・・・。

Box2Dはマニュアルがほとんど見当たらないので、目的の処理を行う方法を探すまでがなかなか大変ですね。