ArcGIS API For JavaScript官方文件(四)之事件處理
ArcGIS API For JavaScript是事件驅動的API。事件發生在您與JavaScript應用程式互動時,載入頁面、單擊滑鼠、執行任務和許多其他操作都會觸發事件。您可以通過監聽事件並編寫響應事件的程式碼使應用程式具有互動性,這就是所謂的“處理”事件。
1、為什麼ArcGIS API For JavaScript是事件驅動的API?
事件的使用主要有兩個主要原因:
①JavaScript API 是非同步的,物件上的每個操作都不是立即返回結果。在物件使用之前,可能需要到伺服器獲取附加資訊。
②事件模型允許對每個事件執行多個事件處理程式,您可以根據物件觸發的單個事件執行多個操作
2、新增和刪除事件監聽器
為了處理一個事件,您需要新增監聽事件的程式碼。為事件註冊監聽器會提醒您的應用程式在發生特定事件時需要執行一些操作。特別是,它將呼叫事件處理函式來響應事件。
在ArCGIS API For JavaScript中,監聽事件的推薦方法是使用on關鍵字。也可以使用dojo/aspect或者舊的dojo/_base/connect模組來監聽或響應事件。從3.6版本開始,ArcGIS API中的所有模組都支援on()形式的事件監聽器。
下面的程式碼演示瞭如何使用這兩種方法:
①使用on方法:
var mapExtentChange = map.on("extent-change", changeHandler); function changeHandler(evt){ var extent = evt.extent, zoomed = evt.levelChange; // ... Do something ... // in some cases, you may want to disconnect the event listener mapExtentChange.remove(); }
on方法的引數:
- 監聽的事件名
- 事件發生時呼叫的事件處理函式
②使用dojo/_base/connect(dojo.connect)模組:
require(["dojo/_base/connect", "esri/map"],function(connect, Map) { ... var mapExtentChange_connect = connect.connect(map, "onExtentChange", changeHandler_connect); function changeHandler_connect(extent, delta, levelChange, lod){ // ... Do something ... // in some cases, you may want to disconnect the event listener connect.disconnect(mapExtentChange_connect); } }
connect方法的引數:
- 被監聽的物件或元素
- 監聽的事件型別
- 事件發生時呼叫的事件處理函式
除了不同的方法簽名外,事件處理函式在一個重要的方面上也不同。通過on方法連線的事件監聽器接收單個事件物件作為引數,事件資訊作為事件物件上的屬性進行傳遞。這與connect方法形成對比,connect方法的監聽器被傳遞為位置引數。另一個重要的區別是事件名稱,on方法的事件名稱是小寫的且沒有“on”字首。
作為最佳的實踐,為了避免記憶體洩漏,應該在關閉應用程式時刪除事件監聽器。這是通過新增另一個監聽器來完成的。下面是針對map的onUnload事件的例子:
①使用on方法
var myUnload = map.on("unload", unloadHandler);
②使用connect方法
var myUnload_connect = connect.connect(map, "onUnload", unloadHandler_connect);
或者您可以註冊在視窗unload時要呼叫的unload處理程式:
dojo.addOnUnload(myUnloadHandler);
在"unload"事件處理函式中,您可以在"onUnload"取消任何的事件監聽器
①使用on方法
function unloadHandler(evt){
changeHandler.remove();
myUnload.remove();
}
②使用connect方法
function unloadHandler_connect(map){
connect.disconnect(changeHandler_connect);
connect.disconnect(myUnload_connect);
}
3、on方法和connect方法的比較
使用on方法比使用connect方法更可取:
①首先,Dojo文件宣告connect方法將Dojo2.0中刪除,實際上,connect方法實際是dojo.on的遺留產物
②其次,特別是在AMD風格中,它使您的程式碼更少,並且更類似於其他JavaScript框架中的事件新增語法
③最後,對於Esri元件,我們向所有“synthetic”事件(不是滑鼠或者鍵盤事件)添加了一個target屬性,該屬性指向觸發事件的元件。由於事件處理程式非同步觸發,並且不能保證事件處理程式的全部內容被觸發,但是,evt.Target的值是可靠的。
require([
"dojo/_base/lang","esri/map", "esri/layers/WebTiledLayer", ...
], function(lang, Map, WebTiledLayer, ...) {
var map = new Map("mapDiv"),
layer = new WebTiledLayer(...);
map.on("load", lang.hitch(layer, mapLoaded));
...
function mapLoaded(evt) {
var map = evt.target;
console.log("Initial Map Extent: ", map.extent.toJson());
// this function is executing with a scope "hitch"-ed to layer
console.log("Full Layer Extent: ", this.fullExtent.toJson());
}
});
4、普通事件
本節提供了處理ArcGIS API For JavaScript中常見事件的一些提示
Map 的"load"事件
當您將map新增到頁面時,您不能立即使用它,直到第一個layer被新增到map您才能使用map。向map中新增一個layer將初始化graphics並觸發onLoad事件,此時,您可以與map進行互動。此規則的一個例外是setExtent()方法,您可以在map的建構函式中設定map的初始化extent,或者在新增第一個layer之前呼叫setExtent()方法改變map的extent。
①使用on方法:
require(["esri/map", ...], function(Map, ...) {
var map = new Map("mapDiv"),
mapOnLoad = map.on("load", configNavigation);
map.addLayer(...);
function configNavigation(evt) {
evt.map.disableMapNavigation();
}
});
②使用connect方法:
require(["esri/map", "dojo/_base/connect", ...], function(Map, connect, ...) {
var map = new Map("mapDiv"),
mapOnLoad = connect.connect(map, "onLoad", configNavigation);
map.addLayer(...);
function configNavigation(map) {
map.disableMapNavigation();
}
});
ArcGISDynamicMapServiceLayer的"load"事件和ArcGISTiledMapServiceLayer 的"load"事件
esri.layers.ArcGISDynamicMapServiceLayer和esri.layers.ArcGISTiledMapServiceLayer與ArcGIS Server 的REST服務端點一起工作。當第一次建立該layer時,它需要向ArcGIS Server發出請求以獲取服務資訊,您應該等到該layer的onLoad事件被觸發後再與該layer互動。下面的程式碼使用事件處理程式在onLoad事件觸發之後訪問初始的extent屬性。
①使用on方法:
require(["esri/layers/ArcGISDynamicMapServiceLayer", ... ], function(ArcGISDynamicMapServiceLayer, ... ){
var layer = new ArcGISDynamicMapServiceLayer(...);
layer.on("load", printInitialExtent);
function printInitialExtent(evt) {
alert(evt.layer.initialExtent);
}
});
②使用connect方法:
require([
"dojo/_base/connect","esri/layers/ArcGISDynamicMapServiceLayer", ...
], function(connect, ArcGISDynamicMapServiceLayer, ... ) {
var layer = new ArcGISDynamicMapServiceLayer(...);
connect.connect(layer, "onLoad", printInitialExtent);
function printInitialExtent(layer) {
console.log(layer.initialExtent);
}
});
在瀏覽器中,由於資源快取,一旦layer被構造,onLoad事件就會被觸發。因此,在為"Load"事件註冊事件監聽器之前,您應該檢查該層的loaded屬性是否為true:
①使用on方法:
require(["esri/layers/ArcGISDynamicMapServiceLayer", ...
], function(ArcGISDynamicMapServiceLayer, ... ) {
var layer = new ArcGISDynamicMapServiceLayer(...);
if(layer.loaded){
printInitialExtent({"layer":layer});
} else {
layer.on("load", printInitialExtent);
}
function printInitialExtent(evt) {
console.log(evt.layer.initialExtent);
}
});
然而,on方法有一個輔助函式emit,我們可以使用emit來強制事件觸發,而不是直接呼叫事件處理程式:
①使用on方法:
require(["esri/layers/ArcGISDynamicMapServiceLayer", ... ], function(ArcGISDynamicMapServiceLayer, ... ) {
var layer = new ArcGISDynamicMapServiceLayer(...);
layer.on("load", printInitialExtent);
if(layer.loaded){
layer.emit("load",{
"layer":layer
});
}
function printInitialExtent(evt) {
console.log(evt.layer.initialExtent);
}
});
在map和graphics中的Mouse事件
ArcGIS JavaScript API 的map和graphics提供了大量的mouse事件,使用者可以使用這些事件與這些物件互動。
註冊和監聽map的onClick事件:
①使用on方法:
require(["esri/map", ...], function(Map, ...) {
var map = new Map("mapDiv"),
map.on("click", myClickHandler);
map.addLayer(...);
function myClickHandler(evt) {
...
}
});
②使用connect方法:
require(["dojo/_base/connect", "esri/map", ...], function(connect, Map, ...) {
var map = new Map("mapDiv"),
connect.connect(map, "onClick", myClickHandler);
map.addLayer(...);
function myClickHandler(evt) {
...
}
});
注意,on形式和connect形式的事件處理程式在上面的例項中都有相同的簽名。因為,滑鼠和鍵事件都用單個事件物件觸發,這些事件的處理程式都是相同的,無論使用的是哪種事件繫結方法。
當用戶單擊map時,會生成一個滑鼠事件並呼叫所有已註冊的單擊處理程式,滑鼠事件物件作為引數傳遞給每個事件處理程式。除了瀏覽器提供的所有屬性外,事件的屬性還包括mapPoint和screenPoint,mapPoint表示在map的座標系統中滑鼠單擊的座標,screenPoint表示螢幕座標系統中滑鼠單擊的座標。
function myClickHandler(event) {
alert("User clicked at " +
event.screenPoint.x + ", " + event.screenPoint.y +
" on the screen. The map coordinate at this point is " +
event.mapPoint.x + ", " + event.mapPoint.y
);
}
除了mapPoint和screenPoint屬性之外,返回的事件物件還包括一個graphic屬性,它是接收事件的esri.Graphic物件。下面的程式碼顯示瞭如何處理map的onClick事件,以報告哪個graphic被使用者點選。注意,onClick事件只有在map的onLoad事件觸發後才有效,在這種情況下,監聽器依賴於另一個監聽器。
①使用on方法:
require(["esri/map", ...], function(Map, ...) {
var map = new Map("mapDiv"),
mapOnLoad = map.on("load", function(){
map.graphics.on("click", myGraphicsClickHandler);
});
map.addLayer(...);
function myGraphicsClickHandler(evt) {
alert("User clicked on " + evt.graphic);
}
});
②使用connect方法:
require(["esri/map", "dojo/_base/connect", ...], function(Map, connect, ...) {
var map = new Map("mapDiv"),
mapOnLoad = connect.connect(map, "onLoad", function() {
connect.connect(map.graphics, "onClick", myGraphicsClickHandler);
});
map.addLayer(...);
function myGraphicsClickHandler(evt) {
alert("User clicked on " + evt.graphic);
}
});
由於Map.graphics物件只有在Map.onLoad事件被觸發後才可用,所以您應該等待事件監聽器,直到Map的“Load”事件被觸發。
Map的“滑鼠滾輪”事件
滑鼠滾輪事件已經標準化,可以在所有瀏覽器中使用,一個屬性值被新增到MouseScroll/Wheel 事件(Firefox)或者MouseWheel 事件(Internet Explorer/Safari/Chrome/Opera),其中屬性值為正數表示滑鼠滾輪向上滾動,屬性值為負數表示滑鼠滾輪向下滾動:
①使用on方法:
map.on("mouse-wheel", myMouseWheelHandler);
②使用connect方法:
connect.connect(map, "onMouseWheel", myMouseWheelHandler);
function myMouseWheelHandler(event) {
alert("Mouse wheel value = " + event.value);
}
6、事件傳播(冒泡)
IE事件模型有時會導致同一事件發生在頁面中的多個元素上,如果元素重疊,就會發生這種情況。您可以通過停止該事件來阻止事件冒泡。
假設您有一個應用程式,使用者可以在其中單擊map執行查詢,使用者還可以單擊任意graphic,在單擊的位置重新居中地圖,假設該graphic具有point geometry:
①使用on方法:
require(["dojo/_base/event",
"esri/map", "esri/tasks/Query", "esri/tasks/QueryTask" ...],
function(event, Map, Query, QueryTask, ...) {
var map = new Map("mapDiv"),
mapOnClick = map.on("click", executeQuery),
mapOnLoad = map.on("load", function(){
map.graphics.on("click", recenterMap);
}),
queryTask = new QueryTask(...);
map.addLayer(...);
function executeQuery(event) {
var query = new Query({
geometry: event.mapPoint
});
queryTask.execute(query, ...);
}
function recenterMap(event) {
map.centerAt(event.mapPoint);
}
});
②使用connect方法:
require(["dojo/_base/event",
"esri/map", "esri/tasks/Query", "esri/tasks/QueryTask" ...],
function(event, Map, Query, QueryTask, ...) {
var map = new Map("mapDiv"),
mapOnClick = connect.connect(map, "onClick", executeQuery),
mapOnLoad = connect.connect(map, "onLoad", function(){
connect.connect(map.graphics, "onClick", recenterMap);
}),
queryTask = new QueryTask(...);
map.addLayer(...);
function executeQuery(event) {
var query = new Query({
geometry: event.mapPoint
});
queryTask.execute(query, ...);
}
function recenterMap(event) {
map.centerAt(event.mapPoint);
}
});
在IE中,當用戶單擊map來重新居中map時,上面的程式碼可能會執行不必要的查詢。這是因為click事件不僅在graphics容器上觸發,而且在也會map的div上觸發。為了確保事件不會傳播到map div,您可以通過在recenterMap函式中呼叫事件物件的stop()方法來停止事件:
function recenterMap(evt) {
// event is an alias for dojo/_base/event
event.stop(event);
map.centerAt(evt.mapPoint);
}