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

