OpenLayers源码学习笔记---OpenLayers中事件相关实现和使用
任何框架中数据,信息交互的处理问题都占有很重要的地位,OpenLayers作为一web前端框架,通过事件流的方式完成,支持数据、信息的传递和交互,其内部设计还是相当不错的。这里整理下关于events这块的相关知识,自我巩固下
在开始分析之前我们先提出两个重要问题:
- 各个浏览器之间Dom事件的兼容性处理
- 提供自己的事件,并且模拟事件流
解决问题1:传统的,我们提供公共的方法来封装对浏览器DOM事件的调用,根据不同浏览器不同处理。 问题2 :通过把对象上监听的回调函数记录到数组中,然后发生变化的时候遍历数组,挨个传递数据信息并调用回调函数。简单的模拟也就差不多了。
OpenLayer是也基本按照这样的方案,具体内容下面介绍。
一、针对跨浏览器事件的解决方案
OpenLayers针对浏览器Dom事件的兼容性问题,提出了一个OpenLayers.Event类来辅助实现,提供了公共的方法observe,stopObservingElement,stopObserving等来提供对事件的监听和注销操作。Event内部对象observers记录事件信息,为每个监听事件的dom元素创建一个数组,数组中记录该元素的事件监听信息,这样方便添加、移除和流程管理。
查看代码分析:
observe 添加事件监听
observe: function (elementParam, name, observer, useCapture) { //通过ID或者直接传入对象,获取事件的监听和相应dom元素对象 var element = OpenLayers.Util.getElement(elementParam); useCapture = useCapture || false ; if (name == 'keypress' && (navigator.appVersion.match(/Konqueror|Safari|KHTML/) || element.attachEvent)) { name = 'keydown' ; } //初始化,开始监听事件,所有与浏览器Dom事件相关的监听处理都记录在observers{}中,方便后期查找和清除处理 if (! this .observers) { this .observers = {}; } //给元素赋以一个唯一标识 if (!element._eventCacheID) { var idPrefix = "eventCacheID_" ; if (element.id) { idPrefix = element.id + "_" + idPrefix; } element._eventCacheID = OpenLayers.Util.createUniqueID(idPrefix); } var cacheID = element._eventCacheID; //以元素作为key值,元素上的事件都记录在obsevers[element]对应的数组中 if (! this .observers[cacheID]) { this .observers[cacheID] = []; } //添加事件,首先记录到数组中,然后通过跨浏览器的方法实现事件监听 this .observers[cacheID].push({ 'element' : element, 'name' : name, 'observer' : observer, 'useCapture' : useCapture }); //跨浏览器的写法 if (element.addEventListener) { element.addEventListener(name, observer, useCapture); } else if (element.attachEvent) { element.attachEvent( 'on' + name, observer); } }, |
stopObserving,移除监听事件
stopObserving: function (elementParam, name, observer, useCapture) { useCapture = useCapture || false ; var element = OpenLayers.Util.getElement(elementParam); var cacheID = element._eventCacheID; if (name == 'keypress' ) { if ( navigator.appVersion.match(/Konqueror|Safari|KHTML/) || element.detachEvent) { name = 'keydown' ; } } // 查找元素,根据元素ID查找记录在observes对象中的事件信息 var foundEntry = false ; var elementObservers = OpenLayers.Event.observers[cacheID]; if (elementObservers) { // 遍历列表,判断当前传入的和记录在案的是否一致,一致则移除记录的事件信息,当元素上没有事件监听的时候从observers中移除元素本身 var i=0; while (!foundEntry && i < elementObservers.length) { var cacheEntry = elementObservers[i]; if ((cacheEntry.name == name) && (cacheEntry.observer == observer) && (cacheEntry.useCapture == useCapture)) { elementObservers.splice(i, 1); if (elementObservers.length == 0) { delete OpenLayers.Event.observers[cacheID]; } foundEntry = true ; break ; } i++; } } //确定存在,移除事件监听(最终的实际处理代码) if (foundEntry) { if (element.removeEventListener) { element.removeEventListener(name, observer, useCapture); } else if (element && element.detachEvent) { element.detachEvent( 'on' + name, observer); } } return foundEntry; }, |
stopObservingElement 这个则是遍历元素上所有的事,通过Observer{}中记录的信息,挨个调用stopObserving方法来移除监听。
OpenLayers.Event总结:
1、标记特殊按键的值信息,singletouch等
2、关键的跨浏览器的事件处理方法,比如获取事件源element();
代码如下:
element: function(event) {
return event.target || event.srcElement;
}
同类的代码比较多,可以供直接调用。
3、事件的监听和取消方法。。
4、所有的Event使用同一个observers
使用:
到此为我们提供了监听事件的一套方法,如果我们有自己的dom元素,则可以通过Openlayers.Event来实现监听而不用担心跨浏览器的问题。下面代码监听div上的点击事件
function init(){ var div = document.createElement( "div" ); document.body.appendChild(div); div.innerText = "点击测试" ; //将处理函数和调用对象绑定,函数最终调用里面的this对象即指向第二个参数指定的object对象 var observeFun = OpenLayers.Function.bindAsEventListener(clickHandler, div); //监听div的click事件,处理函数为observeFun,即最终的clickHandler OpenLayers.Event.observe(div, "click" ,observeFun); } function clickHandler(event){ //this对象指向绑定的object,即DIV本身 alert( this .innerText); } |
这里要补充说明的就是OpenLayers.Funciton.BindAsEventListener(handler,div),这个方法可以帮助我们在最终调用事件处理的时候,确保函数中的this对象指针指向我们传入的第二参数(即div),其内部实现比较简单,请自行查看源码。
二、关于自定义事件模拟的解决方案
OpenLayers.Events,内部事件流实现的关键环节。Events是一个可实例化的对象,有一个Event_Type数组,记录当前events对象支持的事件类型,内部通过事件listeners[event_type],记录监听type类别的事件监听,和Event中有些类似,包含了注册,监听的方法(un,on,regisrer,unregister)等。因为提供自定义事件的处理,所以这里多了一个事件触发的方法triggerEvent();基本流程如此,我们依旧来看代码说明
构造函数:
initialize: function (object, element, eventTypes, fallThrough, options) { OpenLayers.Util.extend( this , options); this .object = object; this .fallThrough = fallThrough; //初始化listeners this .listeners = {}; //构造内部监听函数调用,这个函数最终帮助我们做事件的封装和自定义事件的触发。 this .eventHandler = OpenLayers.Function.bindAsEventListener( this .handleBrowserEvent, this ); // 移除监听 this .clearMouseListener = OpenLayers.Function.bind( this .clearMouseCache, this ); //关键环节,针对事件添加以事件名为key值的数组(初始化,结合un,on,unregister,register)等来查看其具体使用 this .eventTypes = []; if (eventTypes != null ) { for ( var i=0, len=eventTypes.length; i<len; i++) { this .addEventType(eventTypes[i]); } } //关键环节二,为当前要素添加浏览器事件监听,(代码在attachToElement内部实现,这部分作为事件触发的源头。 if (element != null ) { this .attachToElement(element); } }, |
下面是关于监听浏览器事件的方法和监听后的处理代码,仔细观察会发现,每个浏览器事件都被在listeners中注册,然后处理函数中所做的无非是事件信息的封转和转发(最后的triggerEvent)调用。设想一下,用户要是使用un,on等方式来监听浏览器事件,其实只是保存在listener中,并没有实际添加到element对象上,但是元素本身对浏览器事件监听后的封装和触发调用,最终会导致我们遍历listener中的事件来响应。
attachToElement: function (element) { if ( this .element) { OpenLayers.Event.stopObservingElement( this .element); } this .element = element; for ( var i=0, len= this .BROWSER_EVENTS.length; i<len; i++) { var eventType = this .BROWSER_EVENTS[i]; // every browser event has a corresponding application event // (whether it's listened for or not). this .addEventType(eventType); // use Prototype to register the event cross-browser OpenLayers.Event.observe(element, eventType, this .eventHandler); } // disable dragstart in IE so that mousedown/move/up works normally OpenLayers.Event.observe(element, "dragstart" , OpenLayers.Event.stop); }, handleBrowserEvent: function (evt) { var type = evt.type, listeners = this .listeners[type]; if (!listeners || listeners.length == 0) { // noone's listening, bail out return ; } // add clientX & clientY to all events - corresponds to average x, y var touches = evt.touches; if (touches && touches[0]) { var x = 0; var y = 0; var num = touches.length; var touch; for ( var i=0; i<num; ++i) { touch = touches[i]; x += touch.clientX; y += touch.clientY; } evt.clientX = x / num; evt.clientY = y / num; } if ( this .includeXY) { evt.xy = this .getMousePosition(evt); } this .triggerEvent(type, evt); }, |
关于on,un,unRegister,Register等方法很简单,这里不列出来说明。注册事件的优先级我们可以通过设置事件调用在数组中的顺序来实现,这个也比较简单。
下面我们参考map来归纳下在OpenLayers中的事件监听,自定义事件流程的的顺序和正确使用方法。
1、map维持一个events实例,监听浏览器事件
2、我们使用on,un,unRegister,Register等方法,记录我们所编写的浏览器事件和自定义事件
3、我们在某些处理中调用map.event.triggerEvent来触发事件。
4、Events内部触发函数遍历listeners,查找事件监听并调用
控件部分通过handler来从map获取最初的时间信息,根据需求选择不同的事件予以信息封装,判断和响应等等。关于handler中的事件注册等…以及在事件在控件中的信息传递都是依赖于此,可以自行查看来感受其中的美妙设计!!!所以,最终的浏览器事件都是在map.div对象上监听的。Dom元素事件处理经过我们的handBrowserEvent变成供我们流程使用的事件,在我们的数组中遍历被使用。
用户自定义事件的方法最好按照Map的实现方法来:
1、构建属于Dom元素的events对象(独立的事件管理类),注册需要添加的Dom事件,用户自定义事件类型信息
2、通过events上的un,on,unregister,register等方法来实现对自定义事件的操作
3、通过Dom事件,或者内部代码处理来触发自动以事件
4、实现用户自定义事件响应并处理
最后给出一个编写自己自定义事件的案例
var div; function init(){ div = document.createElement( "div" ); div.innerText = "点击测试" ; document.body.appendChild(div); //初始化一个events。定义事件的监听器和自定义事件名称 var events = new OpenLayers.Events(div, div, [ "userselect" ]); //将事件对象保存到div对象上 div.events = events; //监听自定义事件,注册处理函数,参数一指定事件类型,参数二指定事件处理对象,参数三定义处理函数 div.events.register( "userselect" , div, userselectHandler) //将处理函数和调用对象绑定,函数最终调用里面的this对象即指向第二个参数指定的object对象 var observeFun = OpenLayers.Function.bindAsEventListener(clickHandler, div); OpenLayers.Event.observe(div, "click" ,observeFun); //也可以通过编写下面的代码,即和map本身的监听处理一致,两者效果一样 //events.on({"click": observeFun}); } function clickHandler(event){ //this对象指向绑定的object,即DIV本身 this .events.triggerEvent( "userselect" , event); } //响应自定义事件处理 function userselectHandler(event){ alert( this .innerText + event.type); } |