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);
}