Flexの最近のブログ記事

AirアプリにロードしたhtmlのJavaScriptを実行する

AdobeAirで作成したアプリケーション上にロードしたhtml内のJavaScriptを
Air側から実行する方法が分からず、割とハマったので早速覚書です。

今回作成したサンプルは、外部のhtmlを読み込みアプリケーション内のボタンをクリックすると
ロードしたhtml内のJavaScriptのコードが実行されるAirアプリケーションです

J-110622-1.jpg

<Airアプリ内にロードしたhtml(flexアプリ)>
J-110622-2.jpg
html側で"callbackFlexFunc()"というメソッドを定義しており、
実行されるとFlexアプリケーションに 値をコールバックします。

<sampleFlex.mxml>

<?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/mx" width="300" height="100"
			   creationComplete="application1_creationCompleteHandler(event)">
	<fx:Script>
		<![CDATA[
			import mx.events.FlexEvent;

			protected function application1_creationCompleteHandler(event:FlexEvent):void
			{
				ExternalInterface.addCallback("callBackFunc",
					function(args:String):void
					{
						txi.text = args;
					});
			}

		]]>
	</fx:Script>
	
	<fx:Declarations>
		<!-- 非ビジュアルエレメント (サービス、値オブジェクトなど) をここに配置 -->
	</fx:Declarations>
	
	
	<s:Panel x="0" y="0" width="100%" height="100%" title="SampleWebApp">
		<s:Label x="10" y="10" text="JavaScriptからの戻り値"/>
		<s:TextInput id="txi" editable="false" x="10" y="30" width="278" />
	</s:Panel>
</s:Application>


<JavaScript ※index.template.html>

function getHtmlString()
{
  ${application}.callBackFunc(swfVersionStr);
}
 

<Airアプリケーション>

J-110622-3.jpg

<SampleAir.mxml>

<!--?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" width="310" height="200">

	<fx:Script>
		<![CDATA[
			protected function executeBtn_clickHandler(event:MouseEvent):void
			{
				// ロードしたhtmlのJavaScriptを実行する
				myHtml.domWindow.callbackFlexFunc();
			}
		]]>
	</fx:Script>
	
	<fx:Declarations>
		<!-- 非ビジュアルエレメント (サービス、値オブジェクトなど) をここに配置 -->
	</fx:Declarations>
	
	<mx:HTML id="myHtml" width="100%" height="150" 
			 location="htmlのパス"/>
	<s:Button id="executeBtn" x="119" y="154" label="実行" click="executeBtn_clickHandler(event)"/>
	
</s:WindowedApplication>

<mx:HTML>の"domWindow"プロパティにアクセスする事で、
ロードしたhtml内に定義されるJavaScriptにアクセスする事が可能となります。

変数ビューで確認すると、"callbackFlexFunc"が定義されている事が分かります。

J-110622-5.jpg

JavaScriptへのアクセスというと"ExternalInterface"がおなじみですが、 どうやらAirでは対応していないようです。
ExternalInterface非対応という事で、JavaScriptへのアクセスを諦めようかと思いましたが、こんなに素晴らしい事が出来るとは思いませんでした。
いえ、、勉強不足ですね。精進しようと思います。

イベントフェーズについて その1 では、3段階あるイベントフェーズの中で「ターゲット段階」・「バブリング段階」 をとりあげ、
イベントフェーズについて その2 では、残りのイベントフェーズ 「キャプチャ段階」をとりあげました。

今回は、イベントの伝播を止める方法について見ていきたいと思います。
早速 Adobe Flex3 API を調べてみると、イベントの伝播を停止する方法として次の2つのメソッドが用意されていることがわかります。
(今回のサンプルでは stopPropagation を使用しています)


----- APIに記載されている説明の引用 -----

stopImmediatePropagation()
イベントフローの現在のノードおよび後続するノードで、イベントリスナーが処理されないようにします。このメソッドはすぐに有効になり、現在のノードのイベントリスナーに影響します。これに対し、stopPropagation() メソッドは、現在のノードのイベントリスナーの処理がすべて終了するまで有効になりません。
stopPropagation()
イベントフローの現在のノードに後続するノードで、イベントリスナーが処理されないようにします。このメソッドは、現在のノード(currentTarget)のイベントリスナーには影響しません。 これに対し、stopImmediatePropagation() メソッドは、現在のノードとそれ以降のノードで、イベントリスナーが処理されないようにします。このメソッドを繰り返し呼び出しても影響はありません。このメソッドは、イベントフローの任意の段階で呼び出すことができます。

 

さっそくイベント伝播を停めるメソッドを使用したサンプルを見ていきましょう。

今回用意したサンプルは、予め用意されたBoxコンポーネント(生成時にマウスクリックのイベントリスナーをはっている)が8層の入れ子構造になっており、各Box上でマウスがクリックされるとイベントの伝播によってターゲットよりも外側のBoxのボーダライン色が青⇔赤に切り替わる様になっています。ただし、今回のポイントは一番外側のBoxに関してはイベント伝播の影響を受けず、自身のBoxがクリックされない限りボーダライン色は切り替わらない仕組みになっています。

実際に実行した際の初期画面はこんな感じです。

init.PNG

まずはBoxコンポーネント側のサンプルソースからみていきます。BoxコンポーネントのcreationCompleteイベントでマウスクリックイベントのリスナーを作成し、そのハンドラーでボーダーラインの色を切り替えています。

<?xml version="1.0" encoding="utf-8"?>
<mx:Box xmlns:mx="http://www.adobe.com/2006/mxml"
     horizontalAlign="center" verticalAlign="middle"
     borderStyle="solid" borderColor="#0099FF" borderThickness="3"
     creationComplete="onCC()" >
<mx:Script>
    <![CDATA[
   
        private var _redBorder:Boolean = false;
       
        public function set redBorder(isRed:Boolean):void{
            _redBorder = isRed;
        }

        private function onCC():void{
            this.addEventListener(MouseEvent.CLICK, onClickHandler);
        }
       
        private function onClickHandler(e:MouseEvent):void{
           
            this._redBorder = !_redBorder;
           
            if(_redBorder){
                e.currentTarget.setStyle("borderColor", "#ff4400");
            }else{
                e.currentTarget.setStyle("borderColor", "#0099FF");
            }
        }
               
    ]]>
</mx:Script>
</mx:Box>

次に実行ファイル側のサンプルソースを見ていきます。

layerBox0 ~ layerBox7までのBoxコンポーネントが入れ子構造になっており、application のcreationCompleteイベントで layerBox2 に対してマウスクリックイベントのリスナーを作成し、そのハンドラーで stopPropagation を実行しています。こうすることで イベントの伝播を layerBox2 で停止し、これ以上外側(layerBox1, layerBox0 )へ伝播しない様に設定しています。

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:Comp="Comp.*"
    layout="vertical" backgroundColor="#FFFFFF"
    backgroundGradientAlphas="[1.0, 1.0]" backgroundGradientColors="[#FFFFFF, #FFFFFF]"
    horizontalAlign="center" verticalAlign="middle"
    creationComplete="onCC()">
<mx:Script>
    <![CDATA[
       
        private function onCC():void{
            layerBox2.addEventListener(MouseEvent.CLICK, onclickHandler);
        }
       
        private function onclickHandler(e:Event):void{
            e.stopPropagation();
        }
       
    ]]>
</mx:Script>
    <Comp:Square id="layerBox0" width="400" height="300" >
      <Comp:Square id="layerBox1" width="360" height="270">
       <Comp:Square id="layerBox2" width="320" height="240">
        <Comp:Square id="layerBox3" width="280" height="210">
        <Comp:Square id="layerBox4" width="240" height="180">
        <Comp:Square id="layerBox5" width="200" height="150">
         <Comp:Square id="layerBox6" width="160" height="120">
          <Comp:Square id="layerBox7" width="120" height="90" />
           </Comp:Square>
          </Comp:Square>
         </Comp:Square>       
        </Comp:Square>
       </Comp:Square>
      </Comp:Square>
     </Comp:Square>
    </Comp:Square>
</mx:Application>

例えば、一番内側のlayerBox7をクリックした際の結果は以下のようになります。

stopPropagation.PNG

一番外側のBox枠線だけ色が変わらず、残りの枠線は赤色に切り替わりました。
イベントフェーズの順番に処理を追っていくと・・・・

1.  「キャプチャ段階」 では イベントリスナーを設定していない為、何も処理は行われない
2.  「ターゲット段階」で クリックされた layerBox7 の枠線の色を赤色にセットします
3.  「バブリング段階」で layerBox6 から外側に向かって順に枠線の色を赤色にセットします
4.  layerBox2 までバブリングしてきた後、layerBox2 で stopPropagation() が実行されます
5.  stopPropagation() の実行により、伝播は中止されlayerBox1及びlayerBox0 では枠線の色が変わらない

という流れになります。

ちなみにですが、stopPropagation() はイベントの伝播を停めるメソッドですので、layerBox0 をクリックしたときには 「ターゲット段階」の処理として layerBox0 の枠線の色は赤色に切り替わります。この場合は、イベントフェーズ対象に layerBox2 が登場しないのであたりまえといえばあたりまえですね。 (私はサンプル作成中に少しだけ混乱しました。ほんの少しだけ)

 

全3回に渡ってイベントフェーズとイベント伝播の停止を見てきました。サンプルソースが単純なので、その便利さがいまいち伝わらなかったかもしれませんが、イベント送信 + イベント伝播 + イベント停止 の使い方を覚えると画面の作り込みが楽しくなってきます。 是非応用して楽しい画面をつくってみてください。

イベントフェーズとは、イベントがトリガされた時にイベントリスナーが存在するかを確認する順番(段階)のことで、次の3段階構成になっています。

[ キャプチャ段階 ] → [ ターゲット段階 ] → [ バブリング段階 ]

 ●キャプチャ段階
  イベントが発行されたターゲットの祖先の表示リストを親側から順に検査し、リスナーが登録されているかを確認します。

 ●ターゲット段階
  イベントが発行されたターゲットのリスナーが呼び出されます。

 ●バブリング段階
  イベントが発行されたターゲットの祖先の表示リストをターゲット側から順に検査し、リスナーが登録されているかを確認します。

今回の その1 ではターゲット段階とバブリング段階に着目し、サンプルソースを通してイベントフェーズを確認していきます。

以下のサンプルソースは、入れ子構造の3つのVBox (外側から outerBox - middleBox - innerBox という id を付加) を用意し、ApplicationのcreationCompleteイベント処理で各々のVBoxにクリックイベントのリスナーを発行します。
サンプルソースを実行し何れかのVBoxをクリックすると、クリックイベントの処理として
 「 "リスナー処理対象VBoxのid":"処理連番"["イベントフェーズ段階"] 」
が右側にリスト表示されます。 わかりやすい様に文字色はVBoxの背景色と同色にセットしています。

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="horizontal" horizontalAlign="center" backgroundColor="#FFFFFF"
     creationComplete="onCreationComplete()" >
     
    <mx:Script>
        <![CDATA[
            import mx.controls.Label;
       
            private var counter:int = 1;
           
            private function onCreationComplete():void{   
                innerBox.addEventListener(MouseEvent.CLICK, onCatchDispatcher);
                middleBox.addEventListener(MouseEvent.CLICK, onCatchDispatcher);
                outerBox.addEventListener(MouseEvent.CLICK, onCatchDispatcher);
            }
           
            private function onCatchDispatcher(e:MouseEvent):void{
                var lbl:Label = new Label();
                var phase:String = getPhase(e.eventPhase);
                lbl.text = counter + " : " + e.currentTarget.id + " [" + phase + "]";
                setLabelColor(lbl, e.currentTarget.id);
                eventList.addChild(lbl);
                counter++;
            }
           
            private function getPhase(eventPhase:int):String{   
                var phase:String;
                //EventPhase 1=キャプチャ段階, 2=ターゲット段階, 3=バブリング段階
                switch(eventPhase){
                    case 1:
                        phase = "キャプチャ段階";
                        break;
                    case 2:
                        phase = "ターゲット段階";
                        break;
                    case 3:
                        phase = "バブリング段階";
                        break;
                }
               
                return phase;
            }
           
            private function setLabelColor(lbl:Label, targetBox:String):void{       
                if(targetBox == "innerBox"){
                    lbl.setStyle("color", "#CC0000");
                }else if(targetBox == "middleBox"){
                    lbl.setStyle("color", "#00CC00");
                }else if(targetBox == "outerBox"){
                    lbl.setStyle("color", "#0000CC");
                }
            }

        ]]>
    </mx:Script>
    <mx:HBox>
        <mx:VBox id="outerBox" minHeight="150" minWidth="150" horizontalAlign="center" verticalAlign="middle" backgroundColor="#0000CC">
            <mx:VBox id="middleBox" minHeight="100" minWidth="100" horizontalAlign="center" verticalAlign="middle" backgroundColor="#00CC00">
                <mx:VBox id="innerBox" minHeight="50" minWidth="50" horizontalAlign="center" verticalAlign="middle" backgroundColor="#CC0000" />
            </mx:VBox>
        </mx:VBox>
        <mx:VBox id="eventList" fontWeight="bold" />
    </mx:HBox>

</mx:Application>

 

【サンプルソースの実行】

① 一番内側の innerBox (赤色) を1回クリック
② 次に内側の middleBox (緑色) を1回クリック
③ 一番外側の outerBox (青色) を1回クリック

上記①~③の手順で実行した際の結果はこうなりました。

click_result.PNG

手順①では一番内側のコンポーネントがクリックされたことにより、ターゲット段階として innerBox がリスナーが実行されます。その後、親コンポーネントへの伝播が働くことにより、バブリング段階として middleBox・outerBox の順にリスナーが実行されます。

手順②では middleBox がクリックされたので ターゲット段階として middleBoxのリスナーが実行され、バブリング段階として 親コンポーネントである outerBox のリスナーが実行されます。

手順③では、一番外側のコンポーネントのouterBox がクリックされたので、バブリング段階対象のコンポーネントは存在せず、ターゲット段階の outerBox のリスナーのみ実行されます。

つまり、イベント発行したコンポーネントはターゲット段階としてリスナーを実行し、イベント発行したコンポーネントよりも親のコンポーネントはバブリング段階としてリスナーを実行しています。
考え方はシンプルですが、1つのアクション(イベント)で複数個所のリスナーを動かせる非常に強力なしくみですよね。

次回は、キャプチャ段階についてサンプルソースを見ながら確認したいと思います。

前回の「itemRendererで編集したデータをdataProviderに反映させる」では、
itemRendererにtextInputを使用しました。

今回はcheckBoxを使用してみます。
前回同様、2つのデータグリッド間でうまく連携できるでしょうか?

サンプルコードは次の通りです。

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
	<mx:Script>
		<![CDATA[
			import mx.collections.ArrayCollection;
			
            [Bindable]
            private var initDG:ArrayCollection = new ArrayCollection([
                {Album:true},
                {Album:true}
            ]);
 		]]>
	</mx:Script>
	<mx:VBox>
		<mx:DataGrid id="dg1" dataProvider="{initDG}" width="300" editable="true">
			<mx:columns>
				<mx:DataGridColumn headerText="Album" dataField="Album" rendererIsEditor="true">
					<mx:itemRenderer>
						<mx:Component>
							<mx:CheckBox/>
						</mx:Component>
					</mx:itemRenderer>
				</mx:DataGridColumn>
			</mx:columns>
		</mx:DataGrid>
		<mx:DataGrid id="dg2" dataProvider="{initDG}" width="300" editable="true">
			<mx:columns>
				<mx:DataGridColumn headerText="Album" dataField="Album" rendererIsEditor="true">
					<mx:itemRenderer>
						<mx:Component>
							<mx:CheckBox/>
						</mx:Component>
					</mx:itemRenderer>
				</mx:DataGridColumn>
			</mx:columns>
		</mx:DataGrid>
	</mx:VBox>
</mx:Application>



前回からの変更点は以下4点。
 ・itemRendererをテキストインプットからチェックボックスに変更
 ・DataGridColumnのプロパティに「rendererIsEditor=true」を追加
  ※アイテムレンダラーをアイテムエディタとして使用するため
 ・初期値(initDG)を文字列から「true」に変更
  ※チェックボックスに変更したため
 ・確認ボタンと、押下時の処理を削除

 

それでは起動してみます。

flex_itemrenderer2_01.jpg

 

1行目のチェックボックスの選択を解除してフォーカスアウトしてみます。

flex_itemrenderer2_02.jpg

 

そうすると次のエラーが発生しました。
ReferenceError: Error #1069: itemRendererSample_inlineComponent1 にプロパティ text が見つからず、デフォルト値もありません。

これは、DataGridColumnのeditorDataField プロパティのデフォルト値が 「text」なためです。
アイテムレンダラーで使用しているチェックボックスはtextプロパティを持っていません。

これを解消するにはDataGridColumnのediterDataFieldプロパティにcheckBoxのプロパティである「selected」を指定します。
実際のコードは以下のようになります。

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
	<mx:Script>
		<![CDATA[
			import mx.collections.ArrayCollection;
			
            [Bindable]
            private var initDG:ArrayCollection = new ArrayCollection([
                {Album:true},
                {Album:true}
            ]);
 		]]>
	</mx:Script>
	<mx:VBox>
		<mx:DataGrid id="dg1" dataProvider="{initDG}" width="300" editable="true">
			<mx:columns>
				<mx:DataGridColumn headerText="Album" dataField="Album" rendererIsEditor="true" editorDataField="selected">
					<mx:itemRenderer>
						<mx:Component>
							<mx:CheckBox/>
						</mx:Component>
					</mx:itemRenderer>
				</mx:DataGridColumn>
			</mx:columns>
		</mx:DataGrid>
		<mx:DataGrid id="dg2" dataProvider="{initDG}" width="300" editable="true">
			<mx:columns>
				<mx:DataGridColumn headerText="Album" dataField="Album" rendererIsEditor="true" editorDataField="selected">
					<mx:itemRenderer>
						<mx:Component>
							<mx:CheckBox/>
						</mx:Component>
					</mx:itemRenderer>
				</mx:DataGridColumn>
			</mx:columns>
		</mx:DataGrid>
	</mx:VBox>
</mx:Application>


 

早速起動して、先程と同じように1行目のチェックボックスの選択を解除してフォーカスアウトします。

flex_itemrenderer2_03.jpg

 

すると、今度はエラーも発生せず、データグリッド間の連携もうまくいきました。

意外とはまりそうなので、覚え書きしておきました…

データグリッドなどのリストコントロールで、アイテムレンダラーに編集可能なコンポーネントを使用して
変更したデータ内容をデータプロバイダに戻す方法をご紹介します。

早速、検証用アプリで動作確認を行っていきましょう。
サンプルソースは以下の通りです。

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
	<mx:Script>
		<![CDATA[
			import mx.controls.Alert;
			import mx.collections.ArrayCollection;
			
            [Bindable]
            private var initDG:ArrayCollection = new ArrayCollection([
                {Album:'Slanted and Enchanted'},
                {Album:'Brighten the Corners'}
            ]);
            
            //inidDGの内容を表示します
            private function checkBtnHandler():void {
            	Alert.show(
            		"initDGの内容確認" + "\n\n" +
            		"(0)=" + Object(initDG.getItemAt(0))["Album"].toString() + "\n" +
            		"(1)=" + Object(initDG.getItemAt(1))["Album"].toString()
            	);
            }
 		]]>
	</mx:Script>
	<mx:VBox>
		<mx:DataGrid id="dg1" dataProvider="{initDG}" width="300">
			<mx:columns>
				<mx:DataGridColumn headerText="Album" dataField="Album">
					<mx:itemRenderer>
						<mx:Component>
							<mx:TextInput/>
						</mx:Component>
					</mx:itemRenderer>
				</mx:DataGridColumn>
			</mx:columns>
		</mx:DataGrid>
				
		<mx:Button label="check!!" id="checkBtn" click="checkBtnHandler()"/>
	</mx:VBox>
</mx:Application>


 

起動直後の画面です。

flex_itemrenderer1_01.jpg

 

「check!!」ボタンを押下し、データプロバイダにバインディングしている変数(initDG)の内容を確認しておきます。

flex_itemrenderer1_02.jpg

 

次に1行目のデータを変更します。

flex_itemrenderer1_03.jpg

 

再度「check!!」ボタンで変数(initDG)の内容を確認してみます。

flex_itemrenderer1_04.jpg

 

結果は、先程と変わっていません。
つまり、データプロバイダに反映されていないことになります。

 

では、サンプルコードを修正してみましょう。
データグリッドのeditableプロパティに「true」を設定してみます。

		<mx:DataGrid id="dg1" dataProvider="{initDG}" width="300" editable="true">
			<mx:columns>
				<mx:DataGridColumn headerText="Album" dataField="Album">
					<mx:itemRenderer>
						<mx:Component>
							<mx:TextInput/>
						</mx:Component>
					</mx:itemRenderer>
				</mx:DataGridColumn>
			</mx:columns>
		</mx:DataGrid>


起動後、先程と同様に1行目のデータを変更して「check!!」ボタンを押下します。

flex_itemrenderer1_05.jpg

 

データプロバイダに反映されていることが確認できました。
editableを「true」にすることで、データプロバイダ内のアイテムを編集することが可能となります。

これを拡張して、2つのデータグリッド間でデータの同期を取るように作り変えてみましょう。
サンプルソースは以下になります。

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
	<mx:Script>
		<![CDATA[
			import mx.controls.Alert;
			import mx.collections.ArrayCollection;
			
            [Bindable]
            private var initDG:ArrayCollection = new ArrayCollection([
                {Album:'Slanted and Enchanted'},
                {Album:'Brighten the Corners'}
            ]);
            
            //inidDGの内容を表示します
            private function checkBtnHandler():void {
            	Alert.show(
            		"initDGの内容確認" + "\n\n" +
            		"(0)=" + Object(initDG.getItemAt(0))["Album"].toString() + "\n" +
            		"(1)=" + Object(initDG.getItemAt(1))["Album"].toString()
            	);
            }
 		]]>
	</mx:Script>
	<mx:VBox>
		<mx:DataGrid id="dg1" dataProvider="{initDG}" width="300" editable="true">
			<mx:columns>
				<mx:DataGridColumn headerText="Album" dataField="Album">
					<mx:itemRenderer>
						<mx:Component>
							<mx:TextInput/>
						</mx:Component>
					</mx:itemRenderer>
				</mx:DataGridColumn>
			</mx:columns>
		</mx:DataGrid>
		<mx:DataGrid id="dg2" dataProvider="{initDG}" width="300" editable="true">
			<mx:columns>
				<mx:DataGridColumn headerText="Album" dataField="Album">
					<mx:itemRenderer>
						<mx:Component>
							<mx:TextInput/>
						</mx:Component>
					</mx:itemRenderer>
				</mx:DataGridColumn>
			</mx:columns>
		</mx:DataGrid>
						
		<mx:Button label="check!!" id="checkBtn" click="checkBtnHandler()"/>
	</mx:VBox>
</mx:Application>


 

起動直後の画面です。

flex_itemrenderer1_06.jpg

 

1つ目のデータグリッドの1行目を変更します。

flex_itemrenderer1_07.jpg

 

2つ目のデータグリッドも変更されました。
データプロバイダの参照先を同じ変数(initDG)にしているため、このようなことが可能となります。

応用次第では、何か面白いことが出来そうです。

次回はアイテムレンダラーにチェックボックスを使ってみます。

12345

このブログについて

このブログは吉祥寺にあるブレインチャイルド株式会社の社員で投稿しています。
業務ではまってしまったことや発見したこと。
自分達で新たに学習してみようと思って勉強し始めたことなどを綴っています。
こんな社員が働いているブレインチャイルドに興味がわいててきたなら、是非お問い合わせください。
会社案内
求人案内
先輩のコメント