接着学习 kityminder, 前面其实没看完 kity, 大致了解之后, 我们先看看 minder 部分, 两者结合起来学更好些.
下载 kityminder-core (按文档说只含核心部分), 从 git-hub, 建立起开发/构造环境, 打开网页 dev.html 看
果然能出来脑图了, 真不错! 于是从 dev.html 网页开始, 其先加载 kity.js, 然后通过 seajs 加载 kityminder
各个 js 模块. 前述我们分别简单了解过 kity, seajs, 这里就略过, 直接去 kityminder src 目录找.
于是找到 src/kityminder.js, 代码大致是:
define( ... // seajs 要求的格式, CMD 规范.
kityminder = {
许多许多 module = require('...xxx.js')
}
注解说顺序是有讲究的 (有依赖关系的), 所以我们也大致按照顺序(大致)了解下各个 module.
== core/utils.js ==
提供一组函数, 常见的如 extend, each, trim, clone 等就略了.
另有 uuid(group): 负责为指定 group 生成下一个可用 id. 估计为每种 svg 元素生成 id.
guid(): 生成一个类似于(像) guid 的长的随机字符串?
我们实验一下, 在浏览器 console 输入:
var utils = seajs.require('core/utils.js')
utils.guid() --> 打印出 `azzsqpc8jm68' (随机的), 再来一个: `azzsqq169tz4', 前几个字符总是 azzsq, 不太满意吧...
comparePlainObject(a,b): 比较两个 plain 对象是否等价? 方法是用将两个对象用 JSON 字符串化, 然后
比较两个字符串... 我试验了一下, 这两个 plain-object 不等价:
var a = {x:1, y:2}, b = {y:2, x:1};
utils.comparePlainObject(a, b) --> false! 所以不要弄太刁钻的对象......
剩下的看着也不是很重要, 暂时略...
== kityminder.Minder (core/minder.js) ==
注释说暴露在 window 上的唯一变量. 应该就是 window.km 对象.
定义为 Minder = kity.createClass(..., ctor)
其中这里 ctor 不是很复杂, 也没有别的方法, 估计会在别的模块 extend 方法到 minder 类.
在 ctor 中使用了 initHooks[] 机制, 对每个注册的 init-hook 用 this 产生调用.
似乎还有 event 机制, 在 ctor 最后使用 fire() 触发 'finishInitHook' 事件.
但是我没看到 Minder 类从其它类派生(这里没看到), 以及混入别的mixin, 那么哪里来的 event 机制?
也许后续的模块 mixin 的...?
== Command (core/command.js) ==
按照架构文档说明, abstract Command 表示一条在 KityMinder 上执行的命令.
var Command = createClass(..., {
execute: 子类必须实现.
(optional) revert: 子类可选实现, 估计用于实现 undo.
set/get ContentChanged: 设置/获取(内容变更)状态
set/get SelectionChanged: 类似...
以及其它几个可选重载的函数.
});
可以认为这是一个接口定义: ICommand.
然后向 Minder 类扩展了几个方法:
_getCommand(name): 获取维护在 _commands{} 中的命令. 里面有一堆各种命令(对象).
_queryCommand(n, t, a): 看起来像先获得 cmd, 然后调用其(可选的) query+t 方法 ...?
我找了一个 move 方法看, 里面有 queryState, queryValue 方法... 通过这种方法调用?
queryCommandState(): 相当于调用 cmd.queryState() ...?
queryCommandValue(): 相当于 cmd.queryValue() ... 用 _queryCommand() 方法实现的.
execCommand(name): 执行指定命令.
特色是有防止重入(?), 调用命令前产生事件 `beforeExecCommand', `preExecCommand',
执行之后产生事件 `execCommand', 'contentchange'? 等.
== Node (core/node.js) ==
Node/MinderNode 表示一个脑图节点. (这里节点的概念是什么呢? 一个文本框就是一个节点吧?)
var MinderNode = createClass(..., {
ctor: 创建一个游离的脑图节点. 看到里面设置的数据有:
parent: 估计是父节点. 根节点的 parent=null.
children: 顾名思义, 此节点的子节点, 是一个数组.
root: 可能是此图的根节点, 如果自己是 root, 则就指向自己.
data: 某些内部数据? ctor 中看到有 guid, created(time). 但是在 DOM 中未看到? 也许被后续覆盖了?
initContainer(): 设置一个 rc, 是一个 Group 对象. 后面看 Group 时再理解.
这里 rc 似乎是 RenderContainer 的缩写, 不是 rect...
isRoot(), getRoot(), isLeaf(), getParent(), getSiblings() 等比较简单, 就略了.
有些函数的实现看着有点那个...
getData(), setData(): 获取/设置 this.data 中的值.
getText(), setText(): 获取/设置节点文本.
preTraverse(): 先序(前序?)遍历当前节点树. 含递归.
postTraverse(): 后序遍历, 递归的. 这里后序遍历指先遍历所有子元素, 再访问自己.
traverse(): 即后序遍历.
大量树有关获取信息, 维护的函数先略了, 总之和 DOM 树有点像就是了.
clone(): 克隆一个节点(含所有子节点).
compareTo(other): 比较 this vs. other, 用前面的 comparePlainObject()...
getMinder(): 每个 node 都属于且只属于一个 minder. 新建的估计为 null?
});
实验: 在 console 中输入 var node = km.getRoot() 估计得到根节点, 类型就是 MinderNode.
向 Minder 类扩展了不少方法: {
get/setRoot(): 获取/设置根 node.
getAllNode(), getNodeById(), create/remove/appendNode(), ... 看名字就差不多了, 略.
}
== core/option.js: 提供脑图选项支持 ==
注册一个 init-hook: 为 minder 添加一个属性 this._defaultOption={}
向 Minder 类扩展了方法: {
get/set[Default]Option() ... 看名字也就差不多了.
}
这个类这么小, 是否有必要单独弄一个...?
== core/animate.js 动画控制 ==
注册 init-hook: 根据选项 使能/禁用 animate. 这里依赖先有 option.js 部分.
扩展 Minder 类方法 enable/disable Animation(). 简单略.
这个类叫动画控制, 不如叫动画选项...
== MinderEvent(core/event.js): 表示一个脑图中发生的事件 ==
定义类 MinderEvent = createClass(..., {
ctor(): 构造, 复杂参数先略, 可能有些事件是从 kity Event 派生的.
getPosition(): 获取事件发生的坐标?
getTargetNode(), stopPropagation(), preventDefault() ... 看起来像 DOM 事件模式.
});
使用注册 init-hook 的方法初始化 _initEvents().
向 Minder 类扩展: {
_initEvents(), _resetEvents(): 略.
_bindEvents(): 绑定很多事件, 有 click,dblclick, mousedown, contextmenu, mouseup, mousemove
mouseover, mousewheel, DOMMousScroll (各种鼠标事件), touchxxx(触摸屏事件), dragxxx(拖放事件)
以及整个窗口的 resize 事件. 通过一个属性 _firePharse bind(), 稍后了解.
dispatchKeyEvent(): 派发键盘事件. 注意上面的绑定不含键盘事件, 也许绑定到整个页面?
_firePharse(e): 创建 MinderEvent() 包装原生 DOM 事件 e;
产生 before+e, pre+e, e, after+e 事件调用序列. 看到要创建大量事件对象, 我担心是否有必要...
_interactChange(e): 似乎是异步触发一个 'interactchange' 事件. 估计是当前不方便立刻执行, 需要
在下一个空闲(timeout) 时间执行.
_listen(): 注册指定类型的(脑图)事件的回调(侦听).
_fire(): 根据脑图事件类型, 找到注册的回调函数(数组), 产生回调. (有些 status, propagation 部分暂略).
on(), off(), fire(): 事件相关别名/包装.
}
这里也回答了前面的问题, 即 Minder 的 event 从哪里来的问题.
== core/compatibility.js ==
处理脑图数据从低版本到当前版本升级.
== core/keymap.js ==
键的名字(如 Escape) 到值(如=27) 的映射表的建立.
== core/shortcut.js 快捷键支持 ==
向 MinderEvent 扩展 isShortcutKey() 方法: 其根据 metakey? 判断是否是快捷键.
注册 init-hook: 初始化 shortcut-key 支持.
向 Minder 类扩展方法: {
_initShortcutKey(), _bindShortcutKey(): 绑定 'keydown' 事件, 事件发生时查找绑定于该键的处理函数,
并调用它. 实际看了一下, 大约有 backspace, del, enter, tab 等20个键盘快捷键.
addShortcut(keys, fn): 添加对 keys(可空格分隔多个) 快捷键的回调函数 fn. 用 '::' 分隔表示某种东西暂略.
get/addCommandShortcutKeys(cmd, keys): 可能是快捷键 keys 和命令 cmd 绑定起来.
查看了一下, 如有 bold='ctrl+b', appendchildnode='normal::Insert|Tab' etc.
supportClipboardEvent(): 判断是否支持原生 clipboard.
== core/status.js 状态切换控制 ==
向 Minder 类扩展: {
_initStatus(): 此为 init-hook, 初始化两个成员变量值.
get/setStatus(): 获取/设置状态. 状态的变化引发 'statuschange' 事件.
rollbackStatus(): 设置状态为 _rollbackStatus, 略.
}
似乎意味着状态(status)能切换到其它值, 并支持 rollback 状态. 但只能 rollback 一次, 是什么意图呢?
== paper.js: 初始化渲染容器 ==
向 Minder 类扩展方法: {
_initPaper(): 此为 init-hook 方法. 创建 this._paper = new kity.Paper() 并设置一些东西,
创建根节点, 创建 render-container 等.
get/_addRenderContainer(): 创建 kity.Group() 应该是 <g> 元素 作为 render-container.
经过查看, 在 svg 元素下面有 g#kity_g_8 元素下面有 g#minder1 元素, 估计是此处创建的 rc.
renderTo(): 估计是将 minder 渲染(放置)到指定 div 元素(容器), 产生事件 paperrender.
getPaper(), getRenderTarget(): 略.
}
这里把 minder 和 paper 关联起来.
== select.js 选区管理 ==
向 Minder 类扩展: {
_initSelection(): 此为 init-hook 方法.
renderChangedSelection(): 如果选择的元素集合发生了变化则产生事件 'selectionchange'.
并对每个改变了的元素(节点)调用 render() 方法.
getSelectedNodes(): 得到当前被选中的节点(数组引用).
getSelectedNode(): 单数的, 得到第一个被选中的节点, 没有则为 null.
removeAllSelectedNodes(): 清空被选中, 产生事件 'selectionclear'.
removeSelectedNodes(): 清除部分被选中..
select(): 选中指定节点s. selectById() 相似.
toggleSelect(): 略. (有注解)
getSelectedAncestors(): 似乎是找被选中节点的祖先节点?
}
向 MinderNode 扩展方法 isSelected(): 判断是否被选中.
我是设置一个标志到 node 中的...
== core/focus.js ==
注册 init-hook: 绑定事件 'beforemousedown', 'paperrender'.
扩展 Minder 类: {
focus(): 设置焦点给此 minder, 产生 'focus' 事件.
blur(): 取消焦点, 产生 'blur' 事件.
isFocused(): 判定. 方法是将 'focus' css 类名添加/删除自 renderTarget(g 元素)
}
(更多待学习...)