Archive for 4月 2008

ExtJSサンプル: KeyMapを使ってGoogle Mapsをマウス無しで操作

4月 26th, 2008 | By yuki | Category: Ext JS

今回は、Ext.KeyMapを利用してGoogle Mapsをマウス無しで操作するサンプルを作ってみました↓

Ext.KeyMapを利用したGoogle Maps操作デモ

もともとGoogle Maps APIには、GKeyboardHandlerというクラスが用意されていて、これを組み込めば一行で簡単にキーボード操作ができるのですが、ちょっと不便なところがあったので、Ext.KeyMapで独自のキーマップを行ってみました。

ソースコードはちょっと長くなりました、以下の通り:


    //名前空間を確保
    Ext.namespace('KeyMap');

    // メイン
    Ext.onReady(function(){
		var cont = Ext.get('gmap');
		var map = new KeyMap(cont.dom);

		map.on('pan',function(ll){ Ext.get('latlng').dom.innerHTML = ll.lat()+'/'+ll.lng();});
		map.on('zoom',function(zoom){ Ext.get('zoom').dom.innerHTML = zoom;});
		map.on('typechange',function(type){Ext.get('maptype').dom.innerHTML = type;});

		Ext.get('latlng').dom.innerHTML = map.cfg.lat+'/'+map.cfg.lng;
		Ext.get('zoom').dom.innerHTML = map.cfg.zoom;
		Ext.get('maptype').dom.innerHTML = 'NORMAL';								

		// キーマップのためのフォーカスが得られない場合があるので、念のため
		setTimeout(function(){ cont.focus(); },500);
    });

    KeyMap = function(cont){
		this.cont = cont;
		this.cfg = arguments[1] || {};
		Ext.applyIf(this.cfg,{lat:35.6833, lng:139.55, zoom:12});

		// イベントの定義 (Ext.util.Observableのextendに必要)
		this.addEvents({
			'pan'		: true,
			'zoom'		: true,
			'typechange': true,
			'beforesearch'	: true,
			'aftersearch'	: true
		});

		// 初期化
		this.initialize();
    }

    // イベントを発生させるためにExt.util.Observableを継承
    Ext.extend(KeyMap, Ext.util.Observable, {
		initialize: function(){
			this.createMap();
			this.createSearchDlg();
			this.bindKeys();

			// 検索窓の開閉時にキーマップを切り替え
			this.on('beforesearch',function(t){t.keymaps.toggle('esc')});
			this.on('aftersearch',function(t){t.keymaps.toggle('reg')});
		},

		// 地図オブジェクトの作成
		createMap: function(){
			var map = new GMap2(this.cont);
			var lat=this.cfg.lat, lng=this.cfg.lng, z=this.cfg.zoom;

			map.enableContinuousZoom();
			map.addControl(new Gmap.ScaleControl());
			map.setCenter(new GLatLng(lat, lng), z);
			map.disableDoubleClickZoom();

			map.setCenterAndPin = function (latlng){
				map.setCenter(latlng);

				// GMarkerを使わずにマーカーを中心に表示して一定時間後にエフェクト付きで消すための操作
				var mark_id = 'pointer_div';
				var coord = map.fromLatLngToContainerPixel(latlng); // setCenter後に呼ぶこと
				var cont = map.getContainer();

		   		if(!Ext.get(mark_id)){
			   		Ext.DomHelper.append(cont,{
   						tag: 'div', id: mark_id, html: '<img src="./data/mano2.png" />'
   					});
		   		}

   				Ext.get(mark_id).setStyle({
   					position: 'absolute',
   					top: (coord.y-34)+'px',
   					left: (coord.x-10)+'px',
   					'z-index': 100000,
   					display: 'none'
   				});

				// Extでもエフェクトのチェーン可能
	   			Ext.get(mark_id).fadeIn('t', {easing: 'easeOut',duration: .5})
			   				   .pause(3.0)
							   .ghost('t', {easing: 'easeOut',duration: .5});
			};   	

			this.map = map;
		},

		// 検索窓の生成
		createSearchDlg: function(){
			var app = this;
			var map = app.map;
			// 検索窓としてExt.Panelを利用
			var pnl = new Ext.Panel({
				collapsed	: true,
				layout		: 'table',
				layoutConfig: {columns:3},
				autoHeight	: true,
				width		: 290,
				// 場所を絶対値で配置するため
				floating	: true,
				style		: {padding:'3px'},
				// itemsのデフォルト設定
				defaults	:{bodyStyle:'margin:3px;padding:1px',border:false},
				items:[{
					html: '住所/場所:'
				},{
					xtype: 'textfield',
					width: 150,
		       		        id: 'address'
		       	        },{
					xtype: 'button',
					text: '検索',
					handler: function(){
						onSearch();
					}
				}]
			});

			var addr = pnl.getComponent('address');
			pnl.render(app.cont);
			pnl.setPosition(20,0);

			// Panelが閉じていてもなぜかFirefoxでは入力を受け付けてしまうためdisbaleし、
			// Enterキーで検索するように設定
			addr.disable().on('specialkey',onSearch);

			// Panelを開くときに「beforesearch」イベントを発生させる
			pnl.begin = function(){
				app.fireEvent('beforesearch',app);
				pnl.expand();
				addr.enable().focus(true, 500);
			};

			// Panelを閉じるときに「aftersearch」イベントを発生させる
			pnl.end = function(){
				pnl.collapse();
				addr.disable();
				app.fireEvent('aftersearch',app);
			};

			this.dlg = pnl;

			function onSearch(){
				if(arguments.length==2 && arguments[1].getKey()!=13){ return; }
				var str = pnl.getComponent('address').getValue();
				if(str.length){
					// Google Maps APIのClientGeocodeを利用
					var geo = new Gmap.ClientGeocoder();
					geo.getLatLng(str,function(loc){
						if(loc){
							pnl.end();
				   			map.setCenterAndPin(new GLatLng(loc.y,loc.x));
				   		}else{
				   			Ext.Msg.alert('「'+str+'」は見つかりませんでした');
				   		}
					});
				}
			};
	},

	// キーマップを作成
    	bindKeys: function(){
			var app = this;
			var map = app.map, dlg = app.dlg;
			var keymaps = {};

			// 検索窓が閉じているときのキーマップ
			keymaps.reg = new Ext.KeyMap(document,[
				{key: 'io',	  fn: zoom},
				{key: 'hjkl', fn: pan},
				{key: 'nsb',  fn: changetype, shift: true},
				{key: Ext.EventObject.UP,  fn: dlg.begin}
			]);

			// 検索窓が開いているときのキーマップ
			keymaps.esc = new Ext.KeyMap(document,[
				{key: Ext.EventObject.ESC,fn: dlg.end}
			]);

			// どちらのキーマップもdocumentに対して行われているため、切り替えを行う必要がある
			// disable→enableの順序で行うこと(disable処理はdocumentからすべてのキーイベントを削除しているため)
			keymaps.toggle = function(km){
				if (km == 'esc' || km == 'reg') {
					keymaps.esc.disable();
					keymaps.reg.disable();
					keymaps[km].enable();
				}
			};

			keymaps.toggle('reg');
			this.keymaps = keymaps;

			// 地図を移動
			function pan(){
				var e = arguments[1];
				var dirs = {
					'72': {x:-1,y: 0}, // h
					'74': {x: 0,y:-1}, // j
					'75': {x: 0,y:+1}, // k
					'76': {x:+1,y: 0} // l
				};

				var d = dirs[e.getKey()]; if(!d){ return; }

				if(!e.hasModifier()){
					// 文字キーのみが押されている場合、指された方向に50%スクロール
					map.panDirection(-(d.x),d.y);
				}else if(e.shiftKey){
					// シフトキーが同時に押されている場合、指された方向に2%スクロール
					var c = map.getCenter();
					var sp = map.getBounds().toSpan();
					var dx = sp.lng()/50, dy = sp.lat()/50;
					map.setCenter(new GLatLng(c.lat()+d.y*dy,c.lng()+d.x*dx));
				}

				// 「pan」イベントを発生
				app.fireEvent('pan',map.getCenter());
			};

			// 地図の種類を変更
			function changetype(){
				var e = arguments[1];
				var types = {
					'66': {code:G_HYBRID_MAP,	name:'HYBRID'}, 	// b hybrid
					'78': {code:G_NORMAL_MAP,	name:'NORMAL'}, 	// n normal
					'83': {code:G_SATELLITE_MAP,name:'SATELLITE'}  	// s satellite
				};
				var type = types[e.getKey()]; if(!type){ return; }
				map.setMapType(type.code);

				// 「typechange」イベントを発生
				app.fireEvent('typechange',type.name);
			};

			// ズームレベルを変更
			function zoom(){
				var e = arguments[1];
				var z = map.getZoom();
				if(e.getKey()==73){ z = z+1<20?z+1:z; } // i
				else if(e.getKey()==79){ z = z-1>=0?z-1:z } // o
				map.setZoom(z);

				// 「zoom」イベントを発生
				app.fireEvent('zoom',z);
			};
    	}
    });

Ext.KeyMap利用上の注意点としては、同一のDOMに対して別々のKeyMapオブジェクトを生成した場合、片方をdisableすると、もう片方も強制的にdisableされてしまいます。

これは、disable()の中において、そのDOMにマップされている「全ての」キーイベントを削除してしまっているからです。なので、片方をdisableした後に、もう片方を必ずenableする必要があります。今回、検索窓の開閉でキーマップを切り替えたかったのですが、ここで少しはまってしまいました。

ONGMAPのVersion2でも、同様のキーマップを取り入れていますので、こっちも試してみてください(若干仕様は異なります&まだどこにも説明書きしてません・・・)



Business Blog & SNS World 08

4月 22nd, 2008 | By yuki | Category: お知らせ

Tokyo2point0で知り合った、esynapseのRobertさんに誘われて、パネルディスカッションに参加することになりました

Business Blog & SNS World 08 〜 「D23 オープンデータやマッシュアップによって、ブログやSNSに付加価値を」

パネリストが、SixApartの関さん、IBMの米持さん、リクルートの川崎さん、と業界では有名な方々に混ざって場違いな感じもしますが気楽にやってきます。



ExtJSサンプル: 2.1で追加されたExt.Slider

4月 22nd, 2008 | By yuki | Category: Ext JS

新しくリリースされた2.1のExt.Sliderのデモを作ってみました。

Ext.Sliderのサンプル

使い方は簡単ですが、背景画像(background-image)がなぜかPNGで指定されているので、IE6だと表示が綺麗じゃありません。

google-code-prettiefyを入れてみたので、ソースをそのまま晒してみます(IEだとうまく動かないので、google-code-prettiefy機能をオフにしてます)


Ext.onReady(function(){
  Ext.get('button').on('click',function(){
    // Sliderを動かしたときに表示するTipのフォーマット
    var tip = new Ext.ux.SliderTip({
      getText: function(slider){
        return String.format("{0}%", parseInt(slider.getValue()/50*100));
      }
    });

    // 縦方向のスライダー(刻み幅10)
    var vslider = new Ext.Slider({
      height: 400,
      value: 50,
      increment: 10,
      minValue: 0,
      maxValue: 100,
      vertical: true,	// デフォルトは横方向なので、これを設定
      plugins: tip
    });

    // 横方向のスライダー(刻み幅設定なし)
    var hslider = new Ext.Slider({
      width: 535,
      value: 50,
      minValue: 0,
      maxValue: 100,
      plugins: tip
    });

    // スライダーの値が変化した場合の動作
    vslider.on('change',function(s,v){
      Ext.get('photo').dom.height = parseInt(400 * v / 50);
    });

    hslider.on('change',function(s,v){
      Ext.get('photo').dom.width = parseInt(533 * v / 50);
    });

    var panel = new Ext.Panel({
      layout: 'table', // TableLayoutで部品を配置
      layoutConfig: {columns: 2},
      items: [{
        border: false,
        items: vslider
      },{
        border: false,
        height: 400,
        width: 535,
        html: '<img src="./data/photo.jpg" id="photo" height="400" width="533" />'
      },{
        border: false
      },{
        border: false,
        items: hslider
      }]
    });

     // パネルをウィンドウに組み込み
    var win = new Ext.Window({
      title: 'Sliderデモ',
      modal: true,
      resizable: false,
      width: 600,
      height: 450,
      layout: 'fit',
      items: panel
    });

    win.show();
  },this);
});


ExtJSサンプル: Excelデータを読み込んでGoogle Chart APIで描画

4月 21st, 2008 | By yuki | Category: Ext JS

前回のXmlReaderデモを応用して、Xml形式で保存されたExcelデータを読み込むデモを作ってみました。

XmlReader + Excelデータ + Ext.grid.EditorGridPanel + Google Chart API

読み込むだけでは面白くないので、Google Chart APIを使ってグラフも描画してます。また、データを表示しているグリッドはExtJS2.0から実装されたEditorGirdPanelを利用しているので、編集可能です。データを編集すると合わせてグラフが再描画されます。
chartapi.jpg

注意点としては:

  • IE6&7ではネームスペース付きのタグ(<ss:Worksheet>のような)が読み取れなかったので、前処理をサーバーで行う必要がありそうです(今回は予め処理したデータを使いました)→やり方ご存じの方教えてください。。。
  • Google Chart APIは初めて触ったのですが、色々とコツがありそうです(参考にしたサイト「Google Chart API入門」)。詳しくは検証していないのですが、桁数が多い数字をそのまま渡すと仕様なのか、HTTP GETの文字数制限に引っかかったのか、うまく描画できませんでした。渡すデータの桁数を減らすなり、別途用意されているエンコーディングを使ってデータを渡す必要がありそうです。他にも軸の最大値・最小値の設定回りなんかでもはまりました


ExtJS HttpProxy+XmlReaderのサンプル

4月 20th, 2008 | By yuki | Category: Ext JS

前回のデモのHttpProxy+XmlReader版デモです↓

HttpProxy+XmlReaderデモ

コードの説明については↑のページのソースをご覧ください。前回と異なるのは、

  • Proxy : ScriptTagProxy → HttpProxy
  • Reader : JsonReader → XmlReader

データを取得するためのProxyと読み込みのためのReaderがそれぞれ変わっています。注意点ですが、

  • JsonReaderとXmlReaderではパラメーター名とパスの指定方法が異なる(レコードを指すパラメータはJsonReaderが「root」なのに対しXmlReaderは「record」、トータルのレコード数は「totalProperty」に対し「totalRecords」といった感じです。以下比較)

XmlReader

reader: new Ext.data.XmlReader({
  record: ‘usedcar’,
  totalRecords: ‘results_available’,
  id: ‘id’,
  fields: [
    {name:’photo’, mapping: ‘photo/main/s’},
    {name:’model’},
    {name:’grade’},
    {name:’brand’, mapping: ‘brand/name’},
    {name:’url’, mapping: ‘urls/pc’},
    {name:’price’}
  ]
})

JsonReader

reader: new Ext.data.JsonReader({
  root: ‘results.usedcar’,
  totalProperty: ‘results.results_available’,
  id: ‘id’,
  fields: [
    {name:’photo’, mapping: ‘photo.main.s’},
    {name:’model’},
    {name:’grade’},
    {name:’brand’, mapping: ‘brand.name’},
    {name:’url’, mapping: ‘urls.pc’},
    {name:’price’}
  ]
})

JsonReaderのrootとtotalRecordsは上記例ではそれぞれ、「results.usedcar」、「results.results_available」と最上位からのパスになっていますが、XmlReaderは最上位のノードは省略して「usedcar」「results_available」となります。パスの表現もJsonReaderは「.」で区切るのに対して、XmlReaderは「/」となります

  • HttpProxyのデフォルトリクエストは「POST」なので、必要に応じてコンストラクターで「GET」に設定

以下、サーバー側のPHPのコードになります:

// Carsendor.NET データ取得スクリプト

// 入力パラメータの処理
$start = ($_GET['start']!==null && is_numeric($_GET['start']))?(integer)$_GET['start']:1;
$count = ($_GET['count']!==null && is_numeric($_GET['count']))?(integer)$_GET['count']:20;

// APIクエリの設定
$url = 'http://webservice.recruit.co.jp/carsensor/usedcar/v1/';
$params = array(
	'key' 	=> 'd66bf4106270fae4', // 自分のAPI-KEYを入れてください
	'person'=> 2, // Carsensor.NET API用必須パラメーターの一つ(乗車定員)これでほぼ全データを取得
	'order' => 1,
	'format'=> 'xml',
	'start'	=> $start,
	'count' => $count
);
$param_str = array();
foreach($params as $key => $value){
	$param_str[] = $key."=".$value;
}
$query = $url.'?'.implode('&',$param_str);

// XMLヘッダーの送信
header("Content-Type: text/xml");

// 余計なデータをサーバー側で削ってクライアントに送るような処理を入れた方がベターかも
echo file_get_contents($query);


ExtJS ScriptTagProxyのサンプル

4月 20th, 2008 | By yuki | Category: Ext JS

ExtJSのScriptTagProxyを使ったデモを作ってみました↓

ScriptTagProxyデモ

コードの説明については↑のページのソースをご覧ください。ScriptTagProxyを使う際に、気を付けないといけないのは、

  • Proxyに渡すurlは、パラメーターを含まないurlを渡すこと。パラメーターはStoreのbaseParams等で設定しないとうまく動かない
  • CallbackはProxyが自動的に作ってくれるので自分で作る必要は、ない

あと、JsonReaderで読み込むときの、rootのパス設定に注意。

ScriptTagProxyはFirebugで追いにくいので、バグっているとちょっと面倒です。



日経BP社「PC Online」、モバイルマップ、商品検索DB

4月 15th, 2008 | By yuki | Category: お知らせ

本日、日経BP社発行の雑誌「日経パソコン」のオンラインサイト「PC Online」がリニューアルされました。それに伴い、弊社が企画・開発を担当したコンテンツ・Webアプリが2つリリースされています:

モバイルマップ

bpmap_small.jpg「モバイルマップ」は国内の主要な無線LANスポット情報(HOTSPOT、BB Mobile、FLETS、Mzone、FreeSpot、FON)を集めた地図アプリで、ONGMAPの無線LAN情報より遙かに多くの情報が網羅されています。

無線LANスポットをこれだけ集約した地図はこれまでありそうでなかったので、モバイラーにとっては便利なコンテンツだと思います(私自身はemobileユーザーでもあるのですが、最近は加入者が増えたせいか当初と比べて遅く感じることがあるので、マクドナルドでBB Mobileを使う率が高まっています)

開発は「あとで行く」でも有名石原淳也さんにご協力いただきました。

製品データベース

pdb_small.jpg「製品データベース」はコネコネットのデータを元に、国内で販売されているデスクトップおよびノートブックパソコンの詳細な検索・比較ができるマッシュアップです。

ONGMAPで利用しているJavaScriptフレームワーク「Ext JS」を商用利用した国内でもまだまだ数少ない例だと思われます。

今回の案件は、昨年末のこのインタビューでご縁を頂き、今年の1月より開発してきましたが、比較的余裕のあるスケジュールで大きなトラブルもなく、無事今日のリリースを迎えることが一安心です。関係者の皆様お疲れ様でした。

また、開発機のご提供だけでなく開発上の様々なアドバイスをいただいたSun Microsystemsの渡辺様・河原様ありがとうございました。この場を借りて御礼申し上げます。

今回の件について、企画・開発の件についてのお問い合わせはこちらまでお願いします。



ホームページリニューアルしました

4月 8th, 2008 | By yuki | Category: お知らせ

昨年10月にあわてて立ち上げて、そのままになっていたホームページをようやくリニューアルしました。

ONGMAPもバージョン2(まだアルファ版ですが)とLITE版をようやくリリースして、会社のロゴもお世話になっている会社の方に作成してもらい、4月から気分一新、企画・開発に邁進していきます。