RuntimeInCommon.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. // 用于拖拽节点时屏蔽键盘事件
  2. MWF.xApplication.MinderEditor.Drag = new Class({
  3. initialize : function( editor ){
  4. this.editor = editor;
  5. this.fsm = editor.fsm;
  6. this.minder = editor.minder;
  7. this.popmenu = editor.popmenu;
  8. this.receiver = editor.receiver;
  9. this.receiverElement = this.receiver.element;
  10. this.setupFsm();
  11. var downX, downY;
  12. var MOUSE_HAS_DOWN = 0;
  13. var MOUSE_HAS_UP = 1;
  14. var BOUND_CHECK = 20;
  15. var flag = MOUSE_HAS_UP;
  16. var maxX, maxY, osx, osy, containerY;
  17. var freeHorizen = this.freeHorizen = false;
  18. var freeVirtical = this.freeVirtical = false;
  19. this.frame = null;
  20. this.minder.on('mousedown', function(e) {
  21. flag = MOUSE_HAS_DOWN;
  22. var rect = this.minder.getPaper().container.getBoundingClientRect();
  23. downX = e.originEvent.clientX;
  24. downY = e.originEvent.clientY;
  25. containerY = rect.top;
  26. maxX = rect.width;
  27. maxY = rect.height;
  28. }.bind(this));
  29. this.minder.on('mousemove', function(e) {
  30. if ( this.fsm.state() === 'drag' && flag == MOUSE_HAS_DOWN && this.minder.getSelectedNode()
  31. && (Math.abs(downX - e.originEvent.clientX) > BOUND_CHECK
  32. || Math.abs(downY - e.originEvent.clientY) > BOUND_CHECK)) {
  33. osx = e.originEvent.clientX;
  34. osy = e.originEvent.clientY - containerY;
  35. if (osx < BOUND_CHECK) {
  36. this.move('right', BOUND_CHECK - osx);
  37. } else if (osx > maxX - BOUND_CHECK) {
  38. this.move('left', BOUND_CHECK + osx - maxX);
  39. } else {
  40. freeHorizen = true;
  41. }
  42. if (osy < BOUND_CHECK) {
  43. this.move('bottom', osy);
  44. } else if (osy > maxY - BOUND_CHECK) {
  45. this.move('top', BOUND_CHECK + osy - maxY);
  46. } else {
  47. freeVirtical = true;
  48. }
  49. if (freeHorizen && freeVirtical) {
  50. this.move(false);
  51. }
  52. }
  53. if (this.fsm.state() !== 'drag'
  54. && flag === MOUSE_HAS_DOWN
  55. && this.minder.getSelectedNode()
  56. && (Math.abs(downX - e.originEvent.clientX) > BOUND_CHECK
  57. || Math.abs(downY - e.originEvent.clientY) > BOUND_CHECK)) {
  58. if (this.fsm.state() === 'popmenu') {
  59. popmenu.active(Popmenu.STATE_IDLE);
  60. }
  61. return this.fsm.jump('drag', 'user-drag');
  62. }
  63. }.bind(this));
  64. window.addEventListener('mouseup', function () {
  65. flag = MOUSE_HAS_UP;
  66. if (this.fsm.state() === 'drag') {
  67. this.move(false);
  68. return this.fsm.jump('normal', 'drag-finish');
  69. }
  70. }.bind(this), false);
  71. },
  72. setupFsm: function(){
  73. // when jumped to drag mode, enter
  74. this.fsm.when('* -> drag', function() {
  75. // now is drag mode
  76. });
  77. this.fsm.when('drag -> *', function(exit, enter, reason) {
  78. if (reason == 'drag-finish') {
  79. // now exit drag mode
  80. }
  81. });
  82. },
  83. move: function(direction, speed) {
  84. if (!direction) {
  85. this.freeHorizen = this.freeVirtical = false;
  86. this.frame && kity.releaseFrame(this.frame);
  87. this.frame = null;
  88. return;
  89. }
  90. if (!this.frame) {
  91. this.frame = kity.requestFrame((function (direction, speed, minder) {
  92. return function (frame) {
  93. switch (direction) {
  94. case 'left':
  95. minder._viewDragger.move({x: -speed, y: 0}, 0);
  96. break;
  97. case 'top':
  98. minder._viewDragger.move({x: 0, y: -speed}, 0);
  99. break;
  100. case 'right':
  101. minder._viewDragger.move({x: speed, y: 0}, 0);
  102. break;
  103. case 'bottom':
  104. minder._viewDragger.move({x: 0, y: speed}, 0);
  105. break;
  106. default:
  107. return;
  108. }
  109. frame.next();
  110. };
  111. })(direction, speed, this.minder));
  112. }
  113. }
  114. });
  115. MWF.xApplication.MinderEditor.FSM = new Class({
  116. initialize: function( defaultState ){
  117. this.currentState = defaultState;
  118. this.BEFORE_ARROW = ' - ';
  119. this.AFTER_ARROW = ' -> ';
  120. this.handlers = [];
  121. this.debug = new MWF.xApplication.MinderEditor.Debug('fsm');
  122. },
  123. /**
  124. * 状态跳转
  125. *
  126. * 会通知所有的状态跳转监视器
  127. *
  128. * @param {string} newState 新状态名称
  129. * @param {any} reason 跳转的原因,可以作为参数传递给跳转监视器
  130. */
  131. jump: function(newState, reason) {
  132. if (!reason) throw new Error('Please tell fsm the reason to jump');
  133. var oldState = this.currentState;
  134. var notify = [oldState, newState].concat([].slice.call(arguments, 1));
  135. var i, handler;
  136. // 跳转前
  137. for (i = 0; i < this.handlers.length; i++) {
  138. handler = this.handlers[i];
  139. if (this.handlerConditionMatch(handler.condition, 'before', oldState, newState)) {
  140. if (handler.apply(null, notify)) return;
  141. }
  142. }
  143. this.currentState = newState;
  144. this.debug.log('[{0}] {1} -> {2}', reason, oldState, newState);
  145. // 跳转后
  146. for (i = 0; i < this.handlers.length; i++) {
  147. handler = this.handlers[i];
  148. if (this.handlerConditionMatch(handler.condition, 'after', oldState, newState)) {
  149. handler.apply(null, notify);
  150. }
  151. }
  152. return this.currentState;
  153. },
  154. /**
  155. * 返回当前状态
  156. * @return {string}
  157. */
  158. state : function() {
  159. return this.currentState;
  160. },
  161. /**
  162. * 添加状态跳转监视器
  163. *
  164. * @param {string} condition
  165. * 监视的时机
  166. * "* => *" (默认)
  167. *
  168. * @param {Function} handler
  169. * 监视函数,当状态跳转的时候,会接收三个参数
  170. * * from - 跳转前的状态
  171. * * to - 跳转后的状态
  172. * * reason - 跳转的原因
  173. */
  174. when : function(condition, handler) {
  175. this.debug.log('[{0}] {1} ', condition, handler );
  176. if (arguments.length == 1) {
  177. handler = condition;
  178. condition = '* -> *';
  179. }
  180. var when, resolved, exit, enter;
  181. resolved = condition.split(this.BEFORE_ARROW);
  182. if (resolved.length == 2) {
  183. when = 'before';
  184. } else {
  185. resolved = condition.split(this.AFTER_ARROW);
  186. if (resolved.length == 2) {
  187. when = 'after';
  188. }
  189. }
  190. if (!when) throw new Error('Illegal fsm condition: ' + condition);
  191. exit = resolved[0];
  192. enter = resolved[1];
  193. handler.condition = {
  194. when: when,
  195. exit: exit,
  196. enter: enter
  197. };
  198. this.handlers.push(handler);
  199. },
  200. handlerConditionMatch: function (condition, when, exit, enter) {
  201. if (condition.when != when) return false;
  202. if (condition.enter != '*' && condition.enter != enter) return false;
  203. if (condition.exit != '*' && condition.exit != exit) return;
  204. return true;
  205. }
  206. });
  207. //键盘事件接收/分发器
  208. MWF.xApplication.MinderEditor.Receiver = new Class({
  209. initialize: function( editor ){
  210. this.editor = editor;
  211. this.minder = editor.minder;
  212. this.fsm = editor.fsm;
  213. this.key = editor.key;
  214. // 接收事件的 div
  215. var element = this.element = document.createElement('div');
  216. element.contentEditable = true;
  217. /**
  218. * @Desc: 增加tabindex属性使得element的contenteditable不管是trur还是false都能有focus和blur事件
  219. * @Editor: Naixor
  220. * @Date: 2015.09.14
  221. */
  222. element.setAttribute("tabindex", -1);
  223. element.classList.add('receiver');
  224. element.onkeydown = element.onkeypress = element.onkeyup = this.dispatchKeyEvent.bind(this);
  225. this.editor.contentNode.appendChild(element);
  226. this.selectAll();
  227. this.minder.on('beforemousedown', this.selectAll.bind(this));
  228. this.minder.on('receiverfocus', this.selectAll.bind(this));
  229. this.minder.on('readonly', function() {
  230. // 屏蔽minder的事件接受,删除receiver和popmenu
  231. this.minder.disable();
  232. this.element.parentElement.removeChild(this.element);
  233. //this.editor.popmenu.$container.removeChild(editor.popmenu.$element);
  234. }.bind(this));
  235. // 侦听器,接收到的事件会派发给所有侦听器
  236. this.listeners = [];
  237. },
  238. selectAll: function() {
  239. // 保证有被选中的
  240. if (!this.element.innerHTML) this.element.innerHTML = '&nbsp;';
  241. var range = document.createRange();
  242. var selection = window.getSelection();
  243. range.selectNodeContents(this.element);
  244. selection.removeAllRanges();
  245. selection.addRange(range);
  246. this.element.focus();
  247. },
  248. /**
  249. * @Desc: 增加enable和disable方法用于解决热核态的输入法屏蔽问题
  250. * @Editor: Naixor
  251. * @Date: 2015.09.14
  252. */
  253. enable: function() {
  254. this.element.setAttribute("contenteditable", true);
  255. },
  256. disable: function() {
  257. this.element.setAttribute("contenteditable", false);
  258. },
  259. /**
  260. * @Desc: hack FF下div contenteditable的光标丢失BUG
  261. * @Editor: Naixor
  262. * @Date: 2015.10.15
  263. */
  264. fixFFCaretDisappeared: function() {
  265. this.element.removeAttribute("contenteditable");
  266. this.element.setAttribute("contenteditable", "true");
  267. this.element.blur();
  268. this.element.focus();
  269. },
  270. /**
  271. * 以此事件代替通过mouse事件来判断receiver丢失焦点的事件
  272. * @editor Naixor
  273. * @Date 2015-12-2
  274. */
  275. onblur: function (handler) {
  276. this.element.onblur = handler;
  277. },
  278. // 侦听指定状态下的事件,如果不传 state,侦听所有状态
  279. listen : function(state, listener) {
  280. if (arguments.length == 1) {
  281. listener = state;
  282. state = '*';
  283. }
  284. listener.notifyState = state;
  285. this.listeners.push(listener);
  286. },
  287. dispatchKeyEvent: function (e) {
  288. var _self = this;
  289. e.is = function(keyExpression) {
  290. var subs = keyExpression.split('|');
  291. for (var i = 0; i < subs.length; i++) {
  292. if (_self.key.is(this, subs[i])) return true;
  293. }
  294. return false;
  295. };
  296. var listener, jumpState;
  297. for (var i = 0; i < this.listeners.length; i++) {
  298. listener = this.listeners[i];
  299. // 忽略不在侦听状态的侦听器
  300. if (listener.notifyState != '*' && listener.notifyState != this.fsm.state()) {
  301. continue;
  302. }
  303. /**
  304. *
  305. * 对于所有的侦听器,只允许一种处理方式:跳转状态。
  306. * 如果侦听器确定要跳转,则返回要跳转的状态。
  307. * 每个事件只允许一个侦听器进行状态跳转
  308. * 跳转动作由侦听器自行完成(因为可能需要在跳转时传递 reason),返回跳转结果即可。
  309. * 比如:
  310. *
  311. * ```js
  312. * receiver.listen('normal', function(e) {
  313. * if (isSomeReasonForJumpState(e)) {
  314. * return fsm.jump('newstate', e);
  315. * }
  316. * });
  317. * ```
  318. */
  319. if ( listener.call(null, e)) {
  320. return;
  321. }
  322. }
  323. }
  324. });