索引
做一个全功能的拓扑编辑器或3D
场景编辑器,撤销和重做
功能是不可或缺的,想象一下;用户费劲心思设计了一个图元,然后一不小心按到了delete
键,如果不能撤销,用户只能欲哭无泪了。
此插件提供了ht.HistoryManager
类,监听记录DataModel
中图元的属性变化并提供API
接口撤销或重做历史记录。在正式使用API
之前,您的页面里需要引入相关的js
文件:
<script src="ht.js"></script> <!--先引入ht.js-->
<script src="ht-historymanager.js"></script>
使用方式非常简单,创建一个HistoryManager
并绑定DataModel
即可:
var historyManager = new ht.HistoryManager(dataModel);
dataModel
中所有图元的属性变化都会被historyManager
对象记录,当要撤销操作时,调用:
historyManager.undo();
要重做时,调用
historyManager.redo();
用户无法直接操作API
,所以通常把上面API
绑定到页面的快捷键上:
window.addEventListener('keydown', function(e) {
if (e.ctrlKey) {
if (e.keyCode == 90) {//ctrl + z
historyManager.undo();
} else if (e.keyCode == 89) {//ctrl + y
historyManager.redo();
}
}
})
ht.HistoryManager
提供的API
如下:
dataModel
通过setDataModel(dataModel)
和getDataModel()
操作,监听并记录此dataModel
中所有图元的属性变化getHistories()
获得历史记录数组historyIndex
表示当前所处的记录下标,比如用户做了10
次操作,历史记录数组中有10
条记录,historyIndex
指向最后一条记录,值为9
(数组下标从0开始),undo
一次,historyIndex
往前移动,变成8
;redo
一次,historyIndex
往后移动,再次变成9
,也可以直接调用setHistoryIndex(newIndex)
跳到指定的历史记录。undo()
恢复操作,historyIndex
往前移动一次redo()
重做操作,historyIndex
往后移动一次beginTransaction()
类似数据库里开启事务,从beginTransaction()
到endTransaction()
之间所有的修改可被一次性撤销或重做endTransaction()
类似数据库里结束事务,从beginTransaction()
到endTransaction()
之间所有的修改可被一次性撤销或重做maxHistoryCount
通过setMaxHistoryCount(newCount)
和getMaxHistoryCount()
操作,表示最大的历史记录条数,默认为10
,只记录用户10
次最近的操作(旧记录被替换)clear()
清除掉所有的历史记录isPropertyUndoable(property)
判断属性变化是否被记录,默认返回 true,可以重载过滤掉不需要记录历史的属性接下来看一个例子:
这个例子里,左上角是一个Palette
组件,可以拖动其中的Node
到中间的拓扑来创建Node
;左下角是一个TreeView
组件,可以呈现父子关系;
中间是拓扑组件,可以通过拖拽等方式操作改变图元属性;右上角是一个属性页组件,可以编辑label
和label
背景色;右下角是一个ListView
,展示HistoryManager
中的历史记录,选中不同的记录还可以跳转历史记录。
historyManager.mp(function(e) {//监听historyManager属性变化,把历史记录同步到右下角的ListView中展示
var property = e.property;
if (property === 'historyIndex' || property === 'histories') {
var histories = historyManager.getHistories(),
historyIndex = historyManager.getHistoryIndex(),
dataModel = list.dm();
list._betweenUpdate = 1;
dataModel.clear();
var initData = new ht.Data();
initData.setName('init status');
dataModel.add(initData);
histories.forEach(function(history) {
var actions = [];
history.forEach(function(action) {
actions.push(action.kind);
});
var actionsStr = actions.join(', ');
var data = new ht.Data();
data.setName(actionsStr);
dataModel.add(data);
});
list.sm().ss(dataModel.getDatas().get(historyIndex + 1));
list._betweenUpdate = 0;
}
});
list.sm().ms(function(e) {//监听右下角ListView选中状态变化,跳转到相应的历史记录
if (!list._betweenUpdate) {
var data = list.sm().ld(),
index = list.dm().getDatas().indexOf(data);
historyManager.setHistoryIndex(index - 1);
}
});
window.addEventListener('keydown', function(e) {//增加快捷键支持
if (e.ctrlKey) {
if (e.keyCode == 90) {//ctrl+z 撤销
historyManager.undo();
} else if (e.keyCode == 89) {//ctrl+y 重做
historyManager.redo();
}
}
})