mxgraph整合vue入门和事件处理

本文详细介绍了mxGraph的事件机制,包括mxEventSource、mxEvent和mxEventObject的使用,以及如何封装和获取参数。同时,探讨了如何将mxGraph的事件系统与Vue框架整合,提供了一种在Vue中监听和响应mxGraph事件的方法。文章还提到了固有事件以及如何自定义事件,为读者提供了在实际项目中结合两者进行开发的思路。
摘要由CSDN通过智能技术生成

前言

mxGraph定义了自己完整的事件冒泡和监听机制,但是其既不同于mouseOver,click等原生的DOM事件,也不同于vue中的emit,和v-on等. 本文提供一些mxGraph事件和vue事件整合的思路. 环境:
mxGraph: 4.2.2
vue: 2.6

mxGraph的事件机制

之前的整合el-tree那篇文章中提供了简单的样例,在js中fireEvent,在vue中addListener,回顾:
mxGraph中的fireEvent和addListener

自定义事件

相关的对象

mxEventSource

和它的名字一样,表示一个事件的来源,具体讲就是可以fireEvent的类,原文的注释

Known Subclasses:
<mxGraphModel>, <mxGraph>, <mxGraphView>, <mxEditor>, <mxCellOverlay>, <mxToolbar>, <mxWindow>

也就意味着只有上述的可以通过fireEvent方法进行事件的主动触发,其他的类只能使用mxGraph库中定义的方法.

mxEvent

这个类里的函数都是对DOM事件方法addEventListener/removeEventListener等原生函数的再次封装,你如果想使用mouseup,mousedown等原生DOM事件,可以使用mxEvent. 比如常用的禁用右键功能mxEvent.disableContextMenu的源码:

disableContextMenu: function(element)
	{
		mxEvent.addListener(element, 'contextmenu', function(evt)
		{
			if (evt.preventDefault)
			{
				evt.preventDefault();
			}
			
			return false;
		});
	},

第三行调用了addListener处理contextmenu事件,而mxEvent.addListener又调用了element.addEventListener:

if (window.addEventListener)
		{
			return function(element, eventName, funct)
			{
				element.addEventListener(eventName, funct, false);
				updateListenerList(element, eventName, funct);
			};
		}

所以你可以直接用这个类处理一些原生的DOM事件,注意参数element是DOM元素而不是mxGraph中的对象. 其余的属性都是一些常量,给mxGraph自己使用,比如’add’,'remove’等,见下方固有事件

mxEventObject

我们想连接vue和mxGraph的事件其实很简单,主要的需求是参数的传递,mxEventObject的作用就在这里,我们先看mxEventSource.fireEvent的源码注释

函数定义参数1参数2
mxEventSource.prototype.fireEvent = function(evt, sender)evt - mxEventObject that represents the eventsender- Optional sender to be passed to the listener. Default value is the return value of getEventSource
代表这个事件的mxEventObject对象事件触发者(可选),默认就是getEventSource的返回值,也就是fireEvent函数的调用者

再来看mxEventObject的定义

function mxEventObject(name)
{
	this.name = name;
	this.properties = [];
	
	for (var i = 1; i < arguments.length; i += 2)
	{
		if (arguments[i + 1] != null)
		{
			this.properties[arguments[i]] = arguments[i + 1];
		}
	}
};

也就是说你可以通过new mxEventObject(“eventName”, key1, val1, .., keyN, valN) 的方式构造你自己的一个事件,源码中参数表只有name,但是for循环中使用arguments,从i=1也就是第二个开始读取,每次循环i自增2,依次将key,val放进叫做properties的属性中.

参数封装

比如我想让我定义的画布*mygraph*触发一个hello事件,传递参数格式为

{
	name: "xiaoming",
	age: 30
}

那么我就写作:

let evtObj = new mxEventObject('hello','name','xiaoming','age',30);
mygraph.fireEvent(evtObj)
参数获取

通过上面的封装,我的name和age已经放入properties里,获取很简单,先看源代码:


/**
 * Function: getProperties
 * 
 * Returns <properties>.
 */
mxEventObject.prototype.getProperties = function()
{
	return this.properties;
};

/**
 * Function: getProperty
 * 
 * Returns the property for the given key.
 */
mxEventObject.prototype.getProperty = function(key)
{
	return this.properties[key];
};

它提供了两个函数,获取所有参数/获取某个叫做key的参数.还是我自己定义的*mygraph*对象为例,接收事件和参数分为两步,先定义对hello事件的监听,再获取里面的参数,这里先看如何增加监听:

函数定义参数1参数2
mxEventSource.prototype.addListener = function(name, funct)name-mxEventObject的事件名funct-对应的回调函数

参数2这个回调函数funct的源码注释

The parameters of the listener are the sender and an mxEventObject

也就是说这个回调函数的参数是fireEvent的参数反过来,这里要注意:

xxx.fireEvent(evtObj,sender)
xxx.addListener('hello',function(sender,evtObj){

})

好了,现在可以添加监听器并取出我放在mxEventObject中的两对属性了,这里直接用箭头函数定义回调,和function一样:

mygraph.addListener('hello',(sender,evt)=>{
	let mingzi = evt.getProperty('name');
	let nianling = evt.getProperty('age');
	// 其他后续操作......
})

那第一个参数,事件触发者有什么作用呢?这个就是用来去除同名的事件被多次监听,然后错误地响应,你可以在回调里判断hello是谁发的,我只要我自己的:

mygraph.addListener('hello',(sender,evt)=>{
	if(sender === mygraph){
		let mingzi = evt.getProperty('name');
		let nianling = evt.getProperty('age');
		// 其他后续操作......
	}
})

yourgraph.addListener('hello',(sender,evt)=>{
	if(sender === yourgraph){
		let mingzi = evt.getProperty('name');
		let nianling = evt.getProperty('age');
		// 其他后续操作......
	}
})

那如果你这个事件确实是一对多广播的,这个判断就可以丢掉,或者直接避免同名事件的触发.除此之外你可以对sender进行其他操作,比如打印信息/记录日志等:

yourgraph.addListener('hello',(sender,evt)=>{
	console.log(sender.name + "发了个hello事件")
	// 如果这个addListener是写在vue的生命周期或者methods中,那你还可以做其他的
	
	this.其他方法(sender.属性) //具体看你的sender是什么
	let mingzi = evt.getProperty('name');
	let nianling = evt.getProperty('age');
	this.后端请求(mingzi,nianling)
	// 其他后续操作......
})
整合vue

仅供参考,了解原理之后mxGraph只是个对象,存在哪里/如何读取等可以随意结合

(一)画布只有一个单文件组件使用:

export default{
	data:{
		mygraph:null
	},
	mounted(){
		//上期讲过的初始化方法
		this.mygraph = drawMyOwnGraph(dom元素)
		this.mygraph.addListener('hello',(sender,evtObj)=>{
			evtObj获取你的参数
			this.$emit('给父组件冒泡事件',参数)
			this.做其他的,调用methods或者api等
		})
		this.mygraph.addListener('其他事件',function(sder,evObj){
			和上面一样
		})
	},
}

(二)画布对象好多vue组件都用:

A.vue
<template>A组件的结构</template>
import 部分;
export default{
	mounted(){
		let yourgraph = drawMyOwnGraph(dom元素)
		然后找个全局可读取的地方给它存起来,Session,cookie,storage,IDB,我这里用vuex
		语法看vuex文档
		this.$store.dispatch('graph/initGraph',yourgraph)
	}
}
//====================================================
//====================================================

B.vue
<template>B组件的结构</template>
import 部分;
export default{
	
	computed:{
		这里可以用mapState或者mapGetters等,把vuex中的yourgraph取出来,前提是你定义了
		...mapGetters([
			'yourgraph'
		])
	},
	mounted(){
		然后在你的B组件中就可以用那个画布了,但是你要确保BA之后挂载
		this.yourgraph.mxGraph对象中的函数()........
		this.yourgraph.addListener(同上)
	}
}
原理

事件监听原理很简单,addListener就是定义事件名和回调的对应关系,然后放进叫做eventListeners的属性中:

mxEventSource.prototype.addListener = function(name, funct)
{
	if (this.eventListeners == null)
	{
		this.eventListeners = [];
	}
	
	this.eventListeners.push(name);
	this.eventListeners.push(funct);
};

fireEvent则是找事件名对应的回调并执行:

mxEventSource.prototype.fireEvent = function(evt, sender)
{
	if (this.eventListeners != null && this.isEventsEnabled())
	{
		//省略
		var args = [sender, evt];
		for (var i = 0; i < this.eventListeners.length; i += 2)
		{
			var listen = this.eventListeners[i];
			if (listen == null || listen == evt.getName())
			{
				this.eventListeners[i+1].apply(this, args);
			}
		}
	}
};

这个eventListener是一个数组,也是每隔两个算作一对,和mxEventObject的参数表key1,val1,key2,val2…类似,它是name1,funct1,name2,funct2…,所以遍历步长是2

固有事件

固有事件有需要的朋友可以私信或者留言,因为大部分都是参数表的翻译,这部分只需要大家熟悉api文档即可,和vue组件的@click @input @blur这些一样,都有固定保留字和一定的写法限制,而且上期我也举了一个重写固有事件回调的例子
画布中元素被删除事件重写

// 这是文档中常用的类似java重载的手段
// 先暂存旧的
let oldcellsRemoved = firstGraph.cellsRemoved
// 再定义新的
firstGraph.cellsRemoved = function(cells){
	cells.forEach(iter=>{
		console.log(iter.name+'被删掉了')
	})
	//然后再调用之前的,或者也可以不调用,或者先调用旧的,再新增你自己的逻辑,没有return可以随意调换位置
	oldcellsRemoved.call(this,cells)
}

这里我用有元素新增的事件为例,讲一下它的流程,其实源代码中也是使用上面的自定义事件.这里用到了上面的mxEvent中的那些预定义的常量作为事件名,比如新增的事件名叫做mxEvent.CELLS_ADDED,

/**
	 * Variable: CELLS_ADDED
	 *
	 * Specifies the event name for cellsAdded.
	 */
	CELLS_ADDED: 'cellsAdded',

当你在执行yourgraph.insertVertex()时,先创建一个节点,然后再调用addCell把它放进画布:

mxGraph.prototype.insertVertex = function(parent, id, value,
	x, y, width, height, style, relative)
{
	var vertex = this.createVertex(parent, id, value, x, y, width, height, style, relative);
	return this.addCell(vertex, parent);
};

在addCell里直接去addCells,addCells中开启一次model更新事务

mxGraph.prototype.addCell = function(cell, parent, index, source, target)
{
	return this.addCells([cell], parent, index, source, target)[0];
};
//=============================

mxGraph.prototype.addCells = function(cells, parent, index, source, target, absolute)
{
	this.model.beginUpdate();
	try
	{
		this.cellsAdded(cells, parent, index, source, target, (absolute != null) ? absolute : false, true);
		this.fireEvent(new mxEventObject(mxEvent.ADD_CELLS, 'cells', cells,
				'parent', parent, 'index', index, 'source', source, 'target', target));
	}
	finally
	{
		this.model.endUpdate();
	}
	return cells;
};

能看到,它其实主动调用了mxGraph.cellsAdded这个有组件新增时的回调,然后使用我们上面讲过的fireEvent触发mxEvent.ADD_CELLS这个方法并带了一系列参数,只不过直接在参数里new mxEventObject而已;

然后在cellsAdded中再真正触发mxEvent.CELLS_ADDED并携带许多参数:

mxGraph.prototype.cellsAdded = function(cells, parent, index, source, target, absolute, constrain, extend)
{
//省略了源码中对参数的判断和各种操作
	this.model.beginUpdate();
	try
	{
		this.fireEvent(new mxEventObject(mxEvent.CELLS_ADDED, 'cells', cells,
			'parent', parent, 'index', index, 'source', source, 'target', target,
			'absolute', absolute));
	}
	finally
	{
		this.model.endUpdate();
	}
}

组件新增/删除,画布缩放等这些固有的事件都被提前定义,而且文档中已经告知用户这个事件的触发时机,我们可以直接addListener进行监听即可,比方说上面的CELLS_ADDED,cells_added触发时机
文档中说了

Fires between begin- and endUpdate in cellsAdded.
这和我们看的源码一样,在cellsAdded的update事务中触发

如果你想监听,你只需要像之前的hello一样给这个事件加回调就行了

yourgraph.addListener(mxEvent.CELLS_ADDED,(sender,evtObj)=>{
	写自己的逻辑,和hello一模一样,只不过是它固有的而已
})

so,那干嘛不直接改它的回调呢?它都调用cellsAdded了,你直接在那里改不是更简单吗?
总的来说,一般都是修改固有事件的回调方法进行效果和功能的修改,除非你在新增组件之后不想让它触发mxEvent.CELLS_ADDED,那你把整个原型prototype方法重写,修改源代码

yourgraph.cellsAdded = function(源码入参复制过来){
	源码不修改的部分直接复制过来
	this.fireEvent(new mxEventObject('我不想要CELLS_ADDED',参数1,1,参数2,2))
	然后不调用原有的cellsAdded,就修改好了
	其他的事件也是一样,去文档里找它在哪里触发的,把它毙掉就可以,换成你的
}

这里没有讲详细的鼠标/键盘等事件的触发方式,因为那属于对mxGraph对象某些方法的触发,之后会讲到.关于事件的处理有疑问请留言,欢迎讨论.

拖拽的画布如何保存呢? 下期更新: mxGraph整合vue入门之画布xml/mysql/redis持久化

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值