上一篇博客简单介绍了该系统的元数据模型,这周继续为大家介绍SIGNAVIO流程设计器这一开源系统部分功能的核心代码。
事件驱动原型
首先看整个系统的事件原型。和其他核心操作一样,事件相关接口也封装在ORYX.Editor对象中,包括注册、取消注册、执行、暂停事件、激活事件等接口,由于JS语言本身对函数式编程的良好支持,所以系统事件原型实现起来就很容易了。
系统事件响应类型主要包括:
1、动作响应事件,如鼠标点击事件、键盘操作事件,这一事件主要由HTML DOCUMENT实现,设计器只需要针对自身业务动态添加事件监听就可以了。
2、功能响应事件,整个设计器系统基于这一事件模型实现,如工具栏命令执行事件、节点元素拖拽事件、菜单事件等。
所有事件类型的常量定义如以下代码所示:
//动作响应事件、主要由HTML DOCUMENT实现 ORYX.CONFIG.EVENT_MOUSEDOWN = "mousedown"; ORYX.CONFIG.EVENT_MOUSEUP = "mouseup"; ORYX.CONFIG.EVENT_MOUSEOVER = "mouseover"; ORYX.CONFIG.EVENT_MOUSEOUT = "mouseout"; ORYX.CONFIG.EVENT_MOUSEMOVE = "mousemove"; ORYX.CONFIG.EVENT_DBLCLICK = "dblclick"; ORYX.CONFIG.EVENT_KEYDOWN = "keydown"; ORYX.CONFIG.EVENT_KEYUP = "keyup"; ORYX.CONFIG.EVENT_LOADED = "editorloaded"; //功能响应事件 ORYX.CONFIG.EVENT_EXECUTE_COMMANDS = "executeCommands"; ORYX.CONFIG.EVENT_STENCIL_SET_LOADED = "stencilSetLoaded"; ORYX.CONFIG.EVENT_SELECTION_CHANGED = "selectionchanged"; ORYX.CONFIG.EVENT_SHAPEADDED = "shapeadded"; ORYX.CONFIG.EVENT_SHAPEREMOVED = "shaperemoved"; ORYX.CONFIG.EVENT_PROPERTY_CHANGED = "propertyChanged"; ORYX.CONFIG.EVENT_DRAGDROP_START = "dragdrop.start"; ORYX.CONFIG.EVENT_SHAPE_MENU_CLOSE = "shape.menu.close"; ORYX.CONFIG.EVENT_DRAGDROP_END = "dragdrop.end"; ORYX.CONFIG.EVENT_RESIZE_START = "resize.start"; ORYX.CONFIG.EVENT_RESIZE_END = "resize.end"; ORYX.CONFIG.EVENT_DRAGDOCKER_DOCKED = "dragDocker.docked"; ORYX.CONFIG.EVENT_HIGHLIGHT_SHOW = "highlight.showHighlight"; ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE = "highlight.hideHighlight"; ORYX.CONFIG.EVENT_LOADING_ENABLE = "loading.enable"; ORYX.CONFIG.EVENT_LOADING_DISABLE = "loading.disable"; ORYX.CONFIG.EVENT_LOADING_STATUS = "loading.status"; ORYX.CONFIG.EVENT_OVERLAY_SHOW = "overlay.show"; ORYX.CONFIG.EVENT_OVERLAY_HIDE = "overlay.hide"; ORYX.CONFIG.EVENT_ARRANGEMENT_TOP = "arrangement.setToTop"; ORYX.CONFIG.EVENT_ARRANGEMENT_BACK = "arrangement.setToBack"; ORYX.CONFIG.EVENT_ARRANGEMENT_FORWARD = "arrangement.setForward"; ORYX.CONFIG.EVENT_ARRANGEMENT_BACKWARD = "arrangement.setBackward"; ORYX.CONFIG.EVENT_PROPWINDOW_PROP_CHANGED = "propertyWindow.propertyChanged"; ORYX.CONFIG.EVENT_LAYOUT_ROWS = "layout.rows"; ORYX.CONFIG.EVENT_LAYOUT_BPEL = "layout.BPEL"; ORYX.CONFIG.EVENT_LAYOUT_BPEL_VERTICAL = "layout.BPEL.vertical"; ORYX.CONFIG.EVENT_LAYOUT_BPEL_HORIZONTAL = "layout.BPEL.horizontal"; ORYX.CONFIG.EVENT_LAYOUT_BPEL_SINGLECHILD = "layout.BPEL.singlechild"; ORYX.CONFIG.EVENT_LAYOUT_BPEL_AUTORESIZE = "layout.BPEL.autoresize"; ORYX.CONFIG.EVENT_AUTOLAYOUT_LAYOUT = "autolayout.layout"; ORYX.CONFIG.EVENT_UNDO_EXECUTE = "undo.execute"; ORYX.CONFIG.EVENT_UNDO_ROLLBACK = "undo.rollback"; ORYX.CONFIG.EVENT_BUTTON_UPDATE = "toolbar.button.update"; ORYX.CONFIG.EVENT_LAYOUT = "layout.dolayout"; ORYX.CONFIG.EVENT_GLOSSARY_LINK_EDIT = "glossary.link.edit"; ORYX.CONFIG.EVENT_GLOSSARY_SHOW = "glossary.show.info"; ORYX.CONFIG.EVENT_GLOSSARY_NEW = "glossary.show.new"; ORYX.CONFIG.EVENT_DOCKERDRAG = "dragTheDocker";
以上代码位于oryx.debug.js文件中。
事件操作相关代码如下所示:
disableEvent: function(eventType){ if(eventType == ORYX.CONFIG.EVENT_KEYDOWN) { this._keydownEnabled = false; } if(eventType == ORYX.CONFIG.EVENT_KEYUP) { this._keyupEnabled = false; } if(this.DOMEventListeners.keys().member(eventType)) { var value = this.DOMEventListeners.remove(eventType); this.DOMEventListeners['disable_' + eventType] = value; } }, enableEvent: function(eventType){ if(eventType == ORYX.CONFIG.EVENT_KEYDOWN) { this._keydownEnabled = true; } if(eventType == ORYX.CONFIG.EVENT_KEYUP) { this._keyupEnabled = true; } if(this.DOMEventListeners.keys().member("disable_" + eventType)) { var value = this.DOMEventListeners.remove("disable_" + eventType); this.DOMEventListeners[eventType] = value; } }, /** * Methods for the PluginFacade */ registerOnEvent: function(eventType, callback) { if(!(this.DOMEventListeners.keys().member(eventType))) { this.DOMEventListeners[eventType] = []; } this.DOMEventListeners[eventType].push(callback); }, unregisterOnEvent: function(eventType, callback) { if(this.DOMEventListeners.keys().member(eventType)) { this.DOMEventListeners[eventType] = this.DOMEventListeners[eventType].without(callback); } else { // Event is not supported // TODO: Error Handling } },
事件执行:
/** * Helper method to execute an event immediately. The event is not * scheduled in the _eventsQueue. Needed to handle Layout-Callbacks. */ _executeEventImmediately: function(eventObj) { if(this.DOMEventListeners.keys().member(eventObj.event.type)) { this.DOMEventListeners[eventObj.event.type].each((function(value) { value(eventObj.event, eventObj.arg); }).bind(this)); } }, _executeEvents: function() { this._queueRunning = true; while(this._eventsQueue.length > 0) { var val = this._eventsQueue.shift(); this._executeEventImmediately(val); } this._queueRunning = false; }, /** * Leitet die Events an die Editor-Spezifischen Event-Methoden weiter * @param {Object} event Event , welches gefeuert wurde * @param {Object} uiObj Target-UiObj */ handleEvents: function(event, uiObj) { ORYX.Log.trace("Dispatching event type %0 on %1", event.type, uiObj); switch(event.type) { case ORYX.CONFIG.EVENT_MOUSEDOWN: this._handleMouseDown(event, uiObj); break; case ORYX.CONFIG.EVENT_MOUSEMOVE: this._handleMouseMove(event, uiObj); break; case ORYX.CONFIG.EVENT_MOUSEUP: this._handleMouseUp(event, uiObj); break; case ORYX.CONFIG.EVENT_MOUSEOVER: this._handleMouseHover(event, uiObj); break; case ORYX.CONFIG.EVENT_MOUSEOUT: this._handleMouseOut(event, uiObj); break; } /* Force execution if necessary. Used while handle Layout-Callbacks. */ if(event.forceExecution) { this._executeEventImmediately({event: event, arg: uiObj}); } else { this._eventsQueue.push({event: event, arg: uiObj}); } if(!this._queueRunning) { this._executeEvents(); } // TODO: Make this return whether no listener returned false. // So that, when one considers bubbling undesireable, it won't happen. return false; },
事件初始化:
_initEventListener: function(){ // Register on Events document.documentElement.addEventListener(ORYX.CONFIG.EVENT_KEYDOWN, this.catchKeyDownEvents.bind(this), false); document.documentElement.addEventListener(ORYX.CONFIG.EVENT_KEYUP, this.catchKeyUpEvents.bind(this), false); // Enable Key up and down Event this._keydownEnabled = true; this._keyupEnabled = true; this.DOMEventListeners[ORYX.CONFIG.EVENT_MOUSEDOWN] = []; this.DOMEventListeners[ORYX.CONFIG.EVENT_MOUSEUP] = []; this.DOMEventListeners[ORYX.CONFIG.EVENT_MOUSEOVER] = []; this.DOMEventListeners[ORYX.CONFIG.EVENT_MOUSEOUT] = []; this.DOMEventListeners[ORYX.CONFIG.EVENT_SELECTION_CHANGED] = []; this.DOMEventListeners[ORYX.CONFIG.EVENT_MOUSEMOVE] = []; },
基于以上事件模型,系统很多复杂功能就可以顺利实现了。
比如节点元素选中开始拖拽时需要隐藏节点元素所支持的规则菜单项,当节点元素终止拖拽时需要默认选中且展示节点元素所支持的规则菜单项。
熟悉该系统的读者应该知道这一功能。其核心代码如下所示:
事件注册代码:
ORYX.Plugins.ShapeMenuPlugin = { construct: function(facade) { this.facade = facade; this.alignGroups = new Hash(); var containerNode = this.facade.getCanvas().getHTMLContainer(); this.shapeMenu = new ORYX.Plugins.ShapeMenu(containerNode); this.currentShapes = []; // Register on dragging and resizing events for show/hide of ShapeMenu this.facade.registerOnEvent(ORYX.CONFIG.EVENT_DRAGDROP_START, this.hideShapeMenu.bind(this)); this.facade.registerOnEvent(ORYX.CONFIG.EVENT_DRAGDROP_END, this.showShapeMenu.bind(this));
事件方法相关代码:
hideShapeMenu: function(event) { window.clearTimeout(this.timer); this.timer = null; this.shapeMenu.hide(); }, showShapeMenu: function( dontGenerateNew ) { if( !dontGenerateNew || this.resetElements ){ window.clearTimeout(this.timer); this.timer = window.setTimeout(function(){ // Close all Buttons this.shapeMenu.closeAllButtons(); // Show the Morph Button this.showMorphButton(this.currentShapes); // Show the Stencil Buttons this.showStencilButtons(this.currentShapes); // Show the ShapeMenu this.shapeMenu.show(this.currentShapes); this.resetElements = false; }.bind(this), 300) } else { window.clearTimeout(this.timer); this.timer = null; // Show the ShapeMenu this.shapeMenu.show(this.currentShapes); } },
在比如线条添加DragDocker的时候,如果线条上已经存在Docker,此时如果鼠标移动到细条上时需要动态显示已经添加的Docker,核心代码如下所示:
事件注册代码:
ORYX.Plugins.DragDocker = Clazz.extend({ /** * Constructor * @param {Object} Facade: The Facade of the Editor */ construct: function(facade) { this.facade = facade; // Set the valid and invalid color this.VALIDCOLOR = ORYX.CONFIG.SELECTION_VALID_COLOR; this.INVALIDCOLOR = ORYX.CONFIG.SELECTION_INVALID_COLOR; // Define Variables this.shapeSelection = undefined; this.docker = undefined; this.dockerParent = undefined; this.dockerSource = undefined; this.dockerTarget = undefined; this.lastUIObj = undefined; this.isStartDocker = undefined; this.isEndDocker = undefined; this.undockTreshold = 10; this.initialDockerPosition = undefined; this.outerDockerNotMoved = undefined; this.isValid = false; // For the Drag and Drop // Register on MouseDown-Event on a Docker this.facade.registerOnEvent(ORYX.CONFIG.EVENT_MOUSEDOWN, this.handleMouseDown.bind(this)); this.facade.registerOnEvent(ORYX.CONFIG.EVENT_DOCKERDRAG, this.handleDockerDrag.bind(this)); // Register on over/out to show / hide a docker this.facade.registerOnEvent(ORYX.CONFIG.EVENT_MOUSEOVER, this.handleMouseOver.bind(this)); this.facade.registerOnEvent(ORYX.CONFIG.EVENT_MOUSEOUT, this.handleMouseOut.bind(this)); },
事件方法实现代码:
/** * MouseOver Handler * */ handleMouseOver: function(event, uiObj) { // If there is a Docker, show this if(!this.docker && uiObj instanceof ORYX.Core.Controls.Docker) { uiObj.show() } else if(!this.docker && uiObj instanceof ORYX.Core.Edge) { uiObj.dockers.each(function(docker){ docker.show(); }) } },
OOP思想
为了使系统更具有灵活性和可扩展性,SIGNAVIO整体框架也采用的OOP思想进行编程,OOP相关知识、以及JS相关基础知识都不再赘述(在JAVA语言和BS架构大行其道的今天,一个不懂得OOP和JS的程序员是无法想象的)。类的继承基于JS的prototype实现。
实现的核心代码如下所示(包括代码的英文注释):
/** * The super class for all classes in ORYX. Adds some OOP feeling to javascript. * See article "Object Oriented Super Class Method Calling with JavaScript" on * http://truecode.blogspot.com/2006/08/object-oriented-super-class-method.html * for a documentation on this. Fairly good article that points out errors in * Douglas Crockford's inheritance and super method calling approach. * Worth reading. * @class Clazz */ var Clazz = function() {}; /** * Empty constructor. * @methodOf Clazz.prototype */ Clazz.prototype.construct = function() {}; /** * Can be used to build up inheritances of classes. * @example * var MyClass = Clazz.extend({ * construct: function(myParam){ * // Do sth. * } * }); * var MySubClass = MyClass.extend({ * construct: function(myParam){ * // Use this to call constructor of super class * arguments.callee.$.construct.apply(this, arguments); * // Do sth. * } * }); * @param {Object} def The definition of the new class. */ Clazz.extend = function(def) { var classDef = function() { if (arguments[0] !== Clazz) { this.construct.apply(this, arguments); } }; var proto = new this(Clazz); var superClass = this.prototype; for (var n in def) { var item = def[n]; if (item instanceof Function) item.$ = superClass; proto[n] = item; } classDef.prototype = proto; //Give this new class the same static extend method classDef.extend = this.extend; return classDef; };
以上代码相当于提供了一个实现继承功能的模板,采用以上模板定义的对象,都具有继承功能。
最核心代码解释:
1、classDef.extend = this.extend 功能:当采用该模板定义一个对象时,将该继承模板也赋予这一个对象,从而使得该对象具有定义子对象的能力,从而实现真正意义上的继承。
2、this.construct.apply(this, arguments)这一样代码的功能是:当采用该模板定义一个对象时,调用对应对象的构造函数construct,(construct函数必须在对象内部显示的定义,大家有没有想到OO里面的依赖倒置原则呢?);
3、classDef.prototype = proto;继承属性。
花自飘零水自流,模板定义好了,调用就很容易了!
看看系统这一行代码:
ORYX.Core.UIObject = Clazz.extend(ORYX.Core.UIObject);Clazz.extend正是我们刚刚定义的继承模板方法。我么也说了采用模板方法定义的对象内部必须显示定义有construct构造函数,所以得验证一下,抽取ORYX.Core.UIObject原型定义:
ORYX.Core.UIObject原型定义的部分代码:
ORYX.Core.UIObject = { /** * Constructor of the UIObject class. */ construct: function(options) { this.isChanged = true; //Flag, if UIObject has been changed since last update. this.isResized = true; this.isVisible = true; //Flag, if UIObject's display attribute is set to 'inherit' or 'none' this.isSelectable = false; //Flag, if UIObject is selectable. this.isResizable = false; //Flag, if UIObject is resizable. this.isMovable = false; //Flag, if UIObject is movable. this.id = ORYX.Editor.provideId(); //get unique id this.parent = undefined; //parent is defined, if this object is added to another uiObject. this.node = undefined; //this is a reference to the SVG representation, either locally or in DOM. this.children = []; //array for all add uiObjects this.bounds = new ORYX.Core.Bounds(); //bounds with undefined values this._changedCallback = this._changed.bind(this); //callback reference for calling _changed this.bounds.registerCallback(this._changedCallback); //set callback in bounds if(options && options.eventHandlerCallback) { this.eventHandlerCallback = options.eventHandlerCallback; } },
看到没? construct出来了! 虽然ORYX.Core.UIObject是系统自定义的一个JS对象,但是最后是采用继承模板实现,所以现在的ORYX.Core.UIObject对象应该是一个有个extend功能的父类对象,可以扩展子类对象。
那到底是不是呢?
我们在系统里面找到ORYX.Core.AbstractShape这个类,看其定义:
ORYX.Core.AbstractShape部分定义代码:
ORYX.Core.AbstractShape = ORYX.Core.UIObject.extend( /** @lends ORYX.Core.AbstractShape.prototype */ { /** * Constructor */ construct: function(options, stencil) { arguments.callee.$.construct.apply(this, arguments); this.resourceId = ORYX.Editor.provideId(); //Id of resource in DOM // stencil reference this._stencil = stencil; // if the stencil defines a super stencil that should be used for its instances, set it. if (this._stencil._jsonStencil.superId){ stencilId = this._stencil.id() superStencilId = stencilId.substring(0, stencilId.indexOf("#") + 1) + stencil._jsonStencil.superId; stencilSet = this._stencil.stencilSet(); this._stencil = stencilSet.stencil(superStencilId); } //Hash map for all properties. Only stores the values of the properties. this.properties = new Hash(); this.propertiesChanged = new Hash(); // List of properties which are not included in the stencilset, // but which gets (de)serialized this.hiddenProperties = new Hash(); //Initialization of property map and initial value. this._stencil.properties().each((function(property) { var key = property.prefix() + "-" + property.id(); this.properties[key] = property.value(); this.propertiesChanged[key] = true; }).bind(this)); // if super stencil was defined, also regard stencil's properties: if (stencil._jsonStencil.superId) { stencil.properties().each((function(property) { var key = property.prefix() + "-" + property.id(); var value = property.value(); var oldValue = this.properties[key]; this.properties[key] = value; this.propertiesChanged[key] = true; // Raise an event, to show that the property has changed // required for plugins like processLink.js //window.setTimeout( function(){ this._delegateEvent({ type : ORYX.CONFIG.EVENT_PROPERTY_CHANGED, name : key, value : value, oldValue: oldValue }); //}.bind(this), 10) }).bind(this)); } },
谜底彻底揭晓了,我们可以发现 ORYX.Core.AbstractShape的定义不再是简单的JS语法“{}”,而是通过ORYX.Core.UIObject对象的extend方法,我们也可以发现 ORYX.Core.AbstractShape的构造函数中多了这么一行代码 “arguments.callee.$.construct.apply(this, arguments)” ,聪明的你一定猜到了这是在调用ORYX.Core.UIObjec对象的构造函数。由于ORYX.Core.UIObject最终是采用的继承模板实现的对象,有了extend功能,同宗原理,ORYX.Core.AbstractShape也有了extend功能,这样一来,整个系统继承体系雏形也就出现了,就是SIGNAVIO现在的面向对象体系结构。这样一来系统的开发就不在是过程式函数式了,系统也更具有灵活性,可扩展性和可维护性。
====================================================================
声明:本文首发iteye blog,转载请注明作者信息及原文地址,谢谢
作者信息:
马恩亮(elma@wisedu.com)
=====================================================================