Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 
 

3010 řádky
109 KiB

  1. /*
  2. * Released under BSD License
  3. * Copyright (c) 2014-2016 hizzgdev@163.com
  4. *
  5. * Project Home:
  6. * https://github.com/hizzgdev/jsmind/
  7. */
  8. ; (function ($w) {
  9. 'use strict';
  10. // set 'jsMind' as the library name.
  11. // __name__ should be a const value, Never try to change it easily.
  12. var __name__ = 'jsMind';
  13. // library version
  14. var __version__ = '0.4.6';
  15. // author
  16. var __author__ = 'hizzgdev@163.com';
  17. // an noop function define
  18. var _noop = function () { };
  19. var logger = (typeof console === 'undefined') ? {
  20. log: _noop, debug: _noop, error: _noop, warn: _noop, info: _noop
  21. } : console;
  22. // check global variables
  23. if (typeof module === 'undefined' || !module.exports) {
  24. if (typeof $w[__name__] != 'undefined') {
  25. logger.log(__name__ + ' has been already exist.');
  26. return;
  27. }
  28. }
  29. // shortcut of methods in dom
  30. var $d = $w.document;
  31. var $g = function (id) { return $d.getElementById(id); };
  32. var $c = function (tag) { return $d.createElement(tag); };
  33. var $t = function (n, t) { if (n.hasChildNodes()) { n.firstChild.nodeValue = t; } else { n.appendChild($d.createTextNode(t)); } };
  34. var $h = function (n, t) {
  35. if (t instanceof HTMLElement) {
  36. n.innerHTML = '';
  37. n.appendChild(t)
  38. } else {
  39. n.innerHTML = t;
  40. }
  41. };
  42. // detect isElement
  43. var $i = function (el) { return !!el && (typeof el === 'object') && (el.nodeType === 1) && (typeof el.style === 'object') && (typeof el.ownerDocument === 'object'); };
  44. if (typeof String.prototype.startsWith != 'function') { String.prototype.startsWith = function (p) { return this.slice(0, p.length) === p; }; }
  45. var DEFAULT_OPTIONS = {
  46. container: '', // id of the container
  47. editable: false, // you can change it in your options
  48. theme: null,
  49. mode: 'full', // full or side
  50. support_html: true,
  51. view: {
  52. engine: 'canvas',
  53. hmargin: 100,
  54. vmargin: 50,
  55. line_width: 2,
  56. line_color: '#555'
  57. },
  58. layout: {
  59. hspace: 30,
  60. vspace: 20,
  61. pspace: 13
  62. },
  63. default_event_handle: {
  64. enable_mousedown_handle: true,
  65. enable_click_handle: true,
  66. enable_dblclick_handle: true
  67. },
  68. shortcut: {
  69. enable: true,
  70. handles: {
  71. },
  72. mapping: {
  73. addchild: 45, // Insert
  74. addbrother: 13, // Enter
  75. editnode: 113,// F2
  76. delnode: 46, // Delete
  77. toggle: 32, // Space
  78. left: 37, // Left
  79. up: 38, // Up
  80. right: 39, // Right
  81. down: 40, // Down
  82. }
  83. },
  84. };
  85. // core object
  86. var jm = function (options) {
  87. jm.current = this;
  88. this.version = __version__;
  89. var opts = {};
  90. jm.util.json.merge(opts, DEFAULT_OPTIONS);
  91. jm.util.json.merge(opts, options);
  92. if (!opts.container) {
  93. logger.error('the options.container should not be null or empty.');
  94. return;
  95. }
  96. this.options = opts;
  97. this.inited = false;
  98. this.mind = null;
  99. this.event_handles = [];
  100. this.init();
  101. };
  102. // ============= static object =============================================
  103. jm.direction = { left: -1, center: 0, right: 1 };
  104. jm.event_type = { show: 1, resize: 2, edit: 3, select: 4 };
  105. jm.key = { meta: 1 << 13, ctrl: 1 << 12, alt: 1 << 11, shift: 1 << 10 };
  106. jm.node = function (sId, iIndex, sTopic, oData, bIsRoot, oParent, eDirection, bExpanded) {
  107. if (!sId) { logger.error('invalid nodeid'); return; }
  108. if (typeof iIndex != 'number') { logger.error('invalid node index'); return; }
  109. if (typeof bExpanded === 'undefined') { bExpanded = true; }
  110. this.id = sId;
  111. this.index = iIndex;
  112. this.topic = sTopic;
  113. this.data = oData || {};
  114. this.isroot = bIsRoot;
  115. this.parent = oParent;
  116. this.direction = eDirection;
  117. this.expanded = !!bExpanded;
  118. this.children = [];
  119. this._data = {};
  120. };
  121. jm.node.compare = function (node1, node2) {
  122. // '-1' is alwary the last
  123. var r = 0;
  124. var i1 = node1.index;
  125. var i2 = node2.index;
  126. if (i1 >= 0 && i2 >= 0) {
  127. r = i1 - i2;
  128. } else if (i1 == -1 && i2 == -1) {
  129. r = 0;
  130. } else if (i1 == -1) {
  131. r = 1;
  132. } else if (i2 == -1) {
  133. r = -1;
  134. } else {
  135. r = 0;
  136. }
  137. //logger.debug(i1+' <> '+i2+' = '+r);
  138. return r;
  139. };
  140. jm.node.inherited = function (pnode, node) {
  141. if (!!pnode && !!node) {
  142. if (pnode.id === node.id) {
  143. return true;
  144. }
  145. if (pnode.isroot) {
  146. return true;
  147. }
  148. var pid = pnode.id;
  149. var p = node;
  150. while (!p.isroot) {
  151. p = p.parent;
  152. if (p.id === pid) {
  153. return true;
  154. }
  155. }
  156. }
  157. return false;
  158. };
  159. jm.node.prototype = {
  160. get_location: function () {
  161. var vd = this._data.view;
  162. return {
  163. x: vd.abs_x,
  164. y: vd.abs_y
  165. };
  166. },
  167. get_size: function () {
  168. var vd = this._data.view;
  169. return {
  170. w: vd.width,
  171. h: vd.height
  172. }
  173. }
  174. };
  175. jm.mind = function () {
  176. this.name = null;
  177. this.author = null;
  178. this.version = null;
  179. this.root = null;
  180. this.selected = null;
  181. this.nodes = {};
  182. };
  183. jm.mind.prototype = {
  184. get_node: function (nodeid) {
  185. if (nodeid in this.nodes) {
  186. return this.nodes[nodeid];
  187. } else {
  188. logger.warn('the node[id=' + nodeid + '] can not be found');
  189. return null;
  190. }
  191. },
  192. set_root: function (nodeid, topic, data) {
  193. if (this.root == null) {
  194. this.root = new jm.node(nodeid, 0, topic, data, true);
  195. this._put_node(this.root);
  196. } else {
  197. logger.error('root node is already exist');
  198. }
  199. },
  200. add_node: function (parent_node, nodeid, topic, data, idx, direction, expanded) {
  201. if (!jm.util.is_node(parent_node)) {
  202. var the_parent_node = this.get_node(parent_node);
  203. if (!the_parent_node) {
  204. logger.error('the parent_node[id=' + parent_node + '] can not be found.');
  205. return null;
  206. } else {
  207. return this.add_node(the_parent_node, nodeid, topic, data, idx, direction, expanded);
  208. }
  209. }
  210. var nodeindex = idx || -1;
  211. var node = null;
  212. if (parent_node.isroot) {
  213. var d = jm.direction.right;
  214. if (isNaN(direction)) {
  215. var children = parent_node.children;
  216. var children_len = children.length;
  217. var r = 0;
  218. for (var i = 0; i < children_len; i++) { if (children[i].direction === jm.direction.left) { r--; } else { r++; } }
  219. d = (children_len > 1 && r > 0) ? jm.direction.left : jm.direction.right
  220. } else {
  221. d = (direction != jm.direction.left) ? jm.direction.right : jm.direction.left;
  222. }
  223. node = new jm.node(nodeid, nodeindex, topic, data, false, parent_node, d, expanded);
  224. } else {
  225. node = new jm.node(nodeid, nodeindex, topic, data, false, parent_node, parent_node.direction, expanded);
  226. }
  227. if (this._put_node(node)) {
  228. parent_node.children.push(node);
  229. this._reindex(parent_node);
  230. } else {
  231. logger.error('fail, the nodeid \'' + node.id + '\' has been already exist.');
  232. node = null;
  233. }
  234. return node;
  235. },
  236. insert_node_before: function (node_before, nodeid, topic, data) {
  237. if (!jm.util.is_node(node_before)) {
  238. var the_node_before = this.get_node(node_before);
  239. if (!the_node_before) {
  240. logger.error('the node_before[id=' + node_before + '] can not be found.');
  241. return null;
  242. } else {
  243. return this.insert_node_before(the_node_before, nodeid, topic, data);
  244. }
  245. }
  246. var node_index = node_before.index - 0.5;
  247. return this.add_node(node_before.parent, nodeid, topic, data, node_index);
  248. },
  249. get_node_before: function (node) {
  250. if (!jm.util.is_node(node)) {
  251. var the_node = this.get_node(node);
  252. if (!the_node) {
  253. logger.error('the node[id=' + node + '] can not be found.');
  254. return null;
  255. } else {
  256. return this.get_node_before(the_node);
  257. }
  258. }
  259. if (node.isroot) { return null; }
  260. var idx = node.index - 2;
  261. if (idx >= 0) {
  262. return node.parent.children[idx];
  263. } else {
  264. return null;
  265. }
  266. },
  267. insert_node_after: function (node_after, nodeid, topic, data) {
  268. if (!jm.util.is_node(node_after)) {
  269. var the_node_after = this.get_node(node_before);
  270. if (!the_node_after) {
  271. logger.error('the node_after[id=' + node_after + '] can not be found.');
  272. return null;
  273. } else {
  274. return this.insert_node_after(the_node_after, nodeid, topic, data);
  275. }
  276. }
  277. var node_index = node_after.index + 0.5;
  278. return this.add_node(node_after.parent, nodeid, topic, data, node_index);
  279. },
  280. get_node_after: function (node) {
  281. if (!jm.util.is_node(node)) {
  282. var the_node = this.get_node(node);
  283. if (!the_node) {
  284. logger.error('the node[id=' + node + '] can not be found.');
  285. return null;
  286. } else {
  287. return this.get_node_after(the_node);
  288. }
  289. }
  290. if (node.isroot) { return null; }
  291. var idx = node.index;
  292. var brothers = node.parent.children;
  293. if (brothers.length >= idx) {
  294. return node.parent.children[idx];
  295. } else {
  296. return null;
  297. }
  298. },
  299. move_node: function (node, beforeid, parentid, direction) {
  300. if (!jm.util.is_node(node)) {
  301. var the_node = this.get_node(node);
  302. if (!the_node) {
  303. logger.error('the node[id=' + node + '] can not be found.');
  304. return null;
  305. } else {
  306. return this.move_node(the_node, beforeid, parentid, direction);
  307. }
  308. }
  309. if (!parentid) {
  310. parentid = node.parent.id;
  311. }
  312. return this._move_node(node, beforeid, parentid, direction);
  313. },
  314. _flow_node_direction: function (node, direction) {
  315. if (typeof direction === 'undefined') {
  316. direction = node.direction;
  317. } else {
  318. node.direction = direction;
  319. }
  320. var len = node.children.length;
  321. while (len--) {
  322. this._flow_node_direction(node.children[len], direction);
  323. }
  324. },
  325. _move_node_internal: function (node, beforeid) {
  326. if (!!node && !!beforeid) {
  327. if (beforeid == '_last_') {
  328. node.index = -1;
  329. this._reindex(node.parent);
  330. } else if (beforeid == '_first_') {
  331. node.index = 0;
  332. this._reindex(node.parent);
  333. } else {
  334. var node_before = (!!beforeid) ? this.get_node(beforeid) : null;
  335. if (node_before != null && node_before.parent != null && node_before.parent.id == node.parent.id) {
  336. node.index = node_before.index - 0.5;
  337. this._reindex(node.parent);
  338. }
  339. }
  340. }
  341. return node;
  342. },
  343. _move_node: function (node, beforeid, parentid, direction) {
  344. if (!!node && !!parentid) {
  345. if (node.parent.id != parentid) {
  346. // remove from parent's children
  347. var sibling = node.parent.children;
  348. var si = sibling.length;
  349. while (si--) {
  350. if (sibling[si].id == node.id) {
  351. sibling.splice(si, 1);
  352. break;
  353. }
  354. }
  355. node.parent = this.get_node(parentid);
  356. node.parent.children.push(node);
  357. }
  358. if (node.parent.isroot) {
  359. if (direction == jm.direction.left) {
  360. node.direction = direction;
  361. } else {
  362. node.direction = jm.direction.right;
  363. }
  364. } else {
  365. node.direction = node.parent.direction;
  366. }
  367. this._move_node_internal(node, beforeid);
  368. this._flow_node_direction(node);
  369. }
  370. return node;
  371. },
  372. remove_node: function (node) {
  373. if (!jm.util.is_node(node)) {
  374. var the_node = this.get_node(node);
  375. if (!the_node) {
  376. logger.error('the node[id=' + node + '] can not be found.');
  377. return false;
  378. } else {
  379. return this.remove_node(the_node);
  380. }
  381. }
  382. if (!node) {
  383. logger.error('fail, the node can not be found');
  384. return false;
  385. }
  386. if (node.isroot) {
  387. logger.error('fail, can not remove root node');
  388. return false;
  389. }
  390. if (this.selected != null && this.selected.id == node.id) {
  391. this.selected = null;
  392. }
  393. // clean all subordinate nodes
  394. var children = node.children;
  395. var ci = children.length;
  396. while (ci--) {
  397. this.remove_node(children[ci]);
  398. }
  399. // clean all children
  400. children.length = 0;
  401. // remove from parent's children
  402. var sibling = node.parent.children;
  403. var si = sibling.length;
  404. while (si--) {
  405. if (sibling[si].id == node.id) {
  406. sibling.splice(si, 1);
  407. break;
  408. }
  409. }
  410. // remove from global nodes
  411. delete this.nodes[node.id];
  412. // clean all properties
  413. for (var k in node) {
  414. delete node[k];
  415. }
  416. // remove it's self
  417. node = null;
  418. //delete node;
  419. return true;
  420. },
  421. _put_node: function (node) {
  422. if (node.id in this.nodes) {
  423. logger.warn('the nodeid \'' + node.id + '\' has been already exist.');
  424. return false;
  425. } else {
  426. this.nodes[node.id] = node;
  427. return true;
  428. }
  429. },
  430. _reindex: function (node) {
  431. if (node instanceof jm.node) {
  432. node.children.sort(jm.node.compare);
  433. for (var i = 0; i < node.children.length; i++) {
  434. node.children[i].index = i + 1;
  435. }
  436. }
  437. },
  438. };
  439. jm.format = {
  440. node_tree: {
  441. example: {
  442. "meta": {
  443. "name": __name__,
  444. "author": __author__,
  445. "version": __version__
  446. },
  447. "format": "node_tree",
  448. "data": { "id": "root", "topic": "jsMind Example" }
  449. },
  450. get_mind: function (source) {
  451. var df = jm.format.node_tree;
  452. var mind = new jm.mind();
  453. mind.name = source.meta.name;
  454. mind.author = source.meta.author;
  455. mind.version = source.meta.version;
  456. df._parse(mind, source.data);
  457. return mind;
  458. },
  459. get_data: function (mind) {
  460. var df = jm.format.node_tree;
  461. var json = {};
  462. json.meta = {
  463. name: mind.name,
  464. author: mind.author,
  465. version: mind.version
  466. };
  467. json.format = 'node_tree';
  468. json.data = df._buildnode(mind.root);
  469. return json;
  470. },
  471. _parse: function (mind, node_root) {
  472. var df = jm.format.node_tree;
  473. var data = df._extract_data(node_root);
  474. mind.set_root(node_root.id, node_root.topic, data);
  475. if ('children' in node_root) {
  476. var children = node_root.children;
  477. for (var i = 0; i < children.length; i++) {
  478. df._extract_subnode(mind, mind.root, children[i]);
  479. }
  480. }
  481. },
  482. _extract_data: function (node_json) {
  483. var data = {};
  484. for (var k in node_json) {
  485. if (k == 'id' || k == 'topic' || k == 'children' || k == 'direction' || k == 'expanded') {
  486. continue;
  487. }
  488. data[k] = node_json[k];
  489. }
  490. return data;
  491. },
  492. _extract_subnode: function (mind, node_parent, node_json) {
  493. var df = jm.format.node_tree;
  494. var data = df._extract_data(node_json);
  495. var d = null;
  496. if (node_parent.isroot) {
  497. d = node_json.direction == 'left' ? jm.direction.left : jm.direction.right;
  498. }
  499. var node = mind.add_node(node_parent, node_json.id, node_json.topic, data, null, d, node_json.expanded);
  500. if ('children' in node_json) {
  501. var children = node_json.children;
  502. for (var i = 0; i < children.length; i++) {
  503. df._extract_subnode(mind, node, children[i]);
  504. }
  505. }
  506. },
  507. _buildnode: function (node) {
  508. var df = jm.format.node_tree;
  509. if (!(node instanceof jm.node)) { return; }
  510. var o = {
  511. id: node.id,
  512. topic: node.topic,
  513. expanded: node.expanded
  514. };
  515. if (!!node.parent && node.parent.isroot) {
  516. o.direction = node.direction == jm.direction.left ? 'left' : 'right';
  517. }
  518. if (node.data != null) {
  519. var node_data = node.data;
  520. for (var k in node_data) {
  521. o[k] = node_data[k];
  522. }
  523. }
  524. var children = node.children;
  525. if (children.length > 0) {
  526. o.children = [];
  527. for (var i = 0; i < children.length; i++) {
  528. o.children.push(df._buildnode(children[i]));
  529. }
  530. }
  531. return o;
  532. }
  533. },
  534. node_array: {
  535. example: {
  536. "meta": {
  537. "name": __name__,
  538. "author": __author__,
  539. "version": __version__
  540. },
  541. "format": "node_array",
  542. "data": [
  543. { "id": "root", "topic": "jsMind Example", "isroot": true }
  544. ]
  545. },
  546. get_mind: function (source) {
  547. var df = jm.format.node_array;
  548. var mind = new jm.mind();
  549. mind.name = source.meta.name;
  550. mind.author = source.meta.author;
  551. mind.version = source.meta.version;
  552. df._parse(mind, source.data);
  553. return mind;
  554. },
  555. get_data: function (mind) {
  556. var df = jm.format.node_array;
  557. var json = {};
  558. json.meta = {
  559. name: mind.name,
  560. author: mind.author,
  561. version: mind.version
  562. };
  563. json.format = 'node_array';
  564. json.data = [];
  565. df._array(mind, json.data);
  566. return json;
  567. },
  568. _parse: function (mind, node_array) {
  569. var df = jm.format.node_array;
  570. var narray = node_array.slice(0);
  571. // reverse array for improving looping performance
  572. narray.reverse();
  573. var root_id = df._extract_root(mind, narray);
  574. if (!!root_id) {
  575. df._extract_subnode(mind, root_id, narray);
  576. } else {
  577. logger.error('root node can not be found');
  578. }
  579. },
  580. _extract_root: function (mind, node_array) {
  581. var df = jm.format.node_array;
  582. var i = node_array.length;
  583. while (i--) {
  584. if ('isroot' in node_array[i] && node_array[i].isroot) {
  585. var root_json = node_array[i];
  586. var data = df._extract_data(root_json);
  587. mind.set_root(root_json.id, root_json.topic, data);
  588. node_array.splice(i, 1);
  589. return root_json.id;
  590. }
  591. }
  592. return null;
  593. },
  594. _extract_subnode: function (mind, parentid, node_array) {
  595. var df = jm.format.node_array;
  596. var i = node_array.length;
  597. var node_json = null;
  598. var data = null;
  599. var extract_count = 0;
  600. while (i--) {
  601. node_json = node_array[i];
  602. if (node_json.parentid == parentid) {
  603. data = df._extract_data(node_json);
  604. var d = null;
  605. var node_direction = node_json.direction;
  606. if (!!node_direction) {
  607. d = node_direction == 'left' ? jm.direction.left : jm.direction.right;
  608. }
  609. mind.add_node(parentid, node_json.id, node_json.topic, data, null, d, node_json.expanded);
  610. node_array.splice(i, 1);
  611. extract_count++;
  612. var sub_extract_count = df._extract_subnode(mind, node_json.id, node_array);
  613. if (sub_extract_count > 0) {
  614. // reset loop index after extract subordinate node
  615. i = node_array.length;
  616. extract_count += sub_extract_count;
  617. }
  618. }
  619. }
  620. return extract_count;
  621. },
  622. _extract_data: function (node_json) {
  623. var data = {};
  624. for (var k in node_json) {
  625. if (k == 'id' || k == 'topic' || k == 'parentid' || k == 'isroot' || k == 'direction' || k == 'expanded') {
  626. continue;
  627. }
  628. data[k] = node_json[k];
  629. }
  630. return data;
  631. },
  632. _array: function (mind, node_array) {
  633. var df = jm.format.node_array;
  634. df._array_node(mind.root, node_array);
  635. },
  636. _array_node: function (node, node_array) {
  637. var df = jm.format.node_array;
  638. if (!(node instanceof jm.node)) { return; }
  639. var o = {
  640. id: node.id,
  641. topic: node.topic,
  642. expanded: node.expanded
  643. };
  644. if (!!node.parent) {
  645. o.parentid = node.parent.id;
  646. }
  647. if (node.isroot) {
  648. o.isroot = true;
  649. }
  650. if (!!node.parent && node.parent.isroot) {
  651. o.direction = node.direction == jm.direction.left ? 'left' : 'right';
  652. }
  653. if (node.data != null) {
  654. var node_data = node.data;
  655. for (var k in node_data) {
  656. o[k] = node_data[k];
  657. }
  658. }
  659. node_array.push(o);
  660. var ci = node.children.length;
  661. for (var i = 0; i < ci; i++) {
  662. df._array_node(node.children[i], node_array);
  663. }
  664. },
  665. },
  666. freemind: {
  667. example: {
  668. "meta": {
  669. "name": __name__,
  670. "author": __author__,
  671. "version": __version__
  672. },
  673. "format": "freemind",
  674. "data": "<map version=\"1.0.1\"><node ID=\"root\" TEXT=\"freemind Example\"/></map>"
  675. },
  676. get_mind: function (source) {
  677. var df = jm.format.freemind;
  678. var mind = new jm.mind();
  679. mind.name = source.meta.name;
  680. mind.author = source.meta.author;
  681. mind.version = source.meta.version;
  682. var xml = source.data;
  683. var xml_doc = df._parse_xml(xml);
  684. var xml_root = df._find_root(xml_doc);
  685. df._load_node(mind, null, xml_root);
  686. return mind;
  687. },
  688. get_data: function (mind) {
  689. var df = jm.format.freemind;
  690. var json = {};
  691. json.meta = {
  692. name: mind.name,
  693. author: mind.author,
  694. version: mind.version
  695. };
  696. json.format = 'freemind';
  697. var xmllines = [];
  698. xmllines.push('<map version=\"1.0.1\">');
  699. df._buildmap(mind.root, xmllines);
  700. xmllines.push('</map>');
  701. json.data = xmllines.join(' ');
  702. return json;
  703. },
  704. _parse_xml: function (xml) {
  705. var xml_doc = null;
  706. if (window.DOMParser) {
  707. var parser = new DOMParser();
  708. xml_doc = parser.parseFromString(xml, 'text/xml');
  709. } else { // Internet Explorer
  710. xml_doc = new ActiveXObject('Microsoft.XMLDOM');
  711. xml_doc.async = false;
  712. xml_doc.loadXML(xml);
  713. }
  714. return xml_doc;
  715. },
  716. _find_root: function (xml_doc) {
  717. var nodes = xml_doc.childNodes;
  718. var node = null;
  719. var root = null;
  720. var n = null;
  721. for (var i = 0; i < nodes.length; i++) {
  722. n = nodes[i];
  723. if (n.nodeType == 1 && n.tagName == 'map') {
  724. node = n;
  725. break;
  726. }
  727. }
  728. if (!!node) {
  729. var ns = node.childNodes;
  730. node = null;
  731. for (var i = 0; i < ns.length; i++) {
  732. n = ns[i];
  733. if (n.nodeType == 1 && n.tagName == 'node') {
  734. node = n;
  735. break;
  736. }
  737. }
  738. }
  739. return node;
  740. },
  741. _load_node: function (mind, parent_id, xml_node) {
  742. var df = jm.format.freemind;
  743. var node_id = xml_node.getAttribute('ID');
  744. var node_topic = xml_node.getAttribute('TEXT');
  745. // look for richcontent
  746. if (node_topic == null) {
  747. var topic_children = xml_node.childNodes;
  748. var topic_child = null;
  749. for (var i = 0; i < topic_children.length; i++) {
  750. topic_child = topic_children[i];
  751. //logger.debug(topic_child.tagName);
  752. if (topic_child.nodeType == 1 && topic_child.tagName === 'richcontent') {
  753. node_topic = topic_child.textContent;
  754. break;
  755. }
  756. }
  757. }
  758. var node_data = df._load_attributes(xml_node);
  759. var node_expanded = ('expanded' in node_data) ? (node_data.expanded == 'true') : true;
  760. delete node_data.expanded;
  761. var node_position = xml_node.getAttribute('POSITION');
  762. var node_direction = null;
  763. if (!!node_position) {
  764. node_direction = node_position == 'left' ? jm.direction.left : jm.direction.right;
  765. }
  766. //logger.debug(node_position +':'+ node_direction);
  767. if (!!parent_id) {
  768. mind.add_node(parent_id, node_id, node_topic, node_data, null, node_direction, node_expanded);
  769. } else {
  770. mind.set_root(node_id, node_topic, node_data);
  771. }
  772. var children = xml_node.childNodes;
  773. var child = null;
  774. for (var i = 0; i < children.length; i++) {
  775. child = children[i];
  776. if (child.nodeType == 1 && child.tagName == 'node') {
  777. df._load_node(mind, node_id, child);
  778. }
  779. }
  780. },
  781. _load_attributes: function (xml_node) {
  782. var children = xml_node.childNodes;
  783. var attr = null;
  784. var attr_data = {};
  785. for (var i = 0; i < children.length; i++) {
  786. attr = children[i];
  787. if (attr.nodeType == 1 && attr.tagName === 'attribute') {
  788. attr_data[attr.getAttribute('NAME')] = attr.getAttribute('VALUE');
  789. }
  790. }
  791. return attr_data;
  792. },
  793. _buildmap: function (node, xmllines) {
  794. var df = jm.format.freemind;
  795. var pos = null;
  796. if (!!node.parent && node.parent.isroot) {
  797. pos = node.direction === jm.direction.left ? 'left' : 'right';
  798. }
  799. xmllines.push('<node');
  800. xmllines.push('ID=\"' + node.id + '\"');
  801. if (!!pos) {
  802. xmllines.push('POSITION=\"' + pos + '\"');
  803. }
  804. xmllines.push('TEXT=\"' + node.topic + '\">');
  805. // store expanded status as an attribute
  806. xmllines.push('<attribute NAME=\"expanded\" VALUE=\"' + node.expanded + '\"/>');
  807. // for attributes
  808. var node_data = node.data;
  809. if (node_data != null) {
  810. for (var k in node_data) {
  811. xmllines.push('<attribute NAME=\"' + k + '\" VALUE=\"' + node_data[k] + '\"/>');
  812. }
  813. }
  814. // for children
  815. var children = node.children;
  816. for (var i = 0; i < children.length; i++) {
  817. df._buildmap(children[i], xmllines);
  818. }
  819. xmllines.push('</node>');
  820. },
  821. },
  822. };
  823. // ============= utility object =============================================
  824. jm.util = {
  825. is_node: function (node) {
  826. return !!node && node instanceof jm.node;
  827. },
  828. ajax: {
  829. _xhr: function () {
  830. var xhr = null;
  831. if (window.XMLHttpRequest) {
  832. xhr = new XMLHttpRequest();
  833. } else {
  834. try {
  835. xhr = new ActiveXObject('Microsoft.XMLHTTP');
  836. } catch (e) { }
  837. }
  838. return xhr;
  839. },
  840. _eurl: function (url) {
  841. return encodeURIComponent(url);
  842. },
  843. request: function (url, param, method, callback, fail_callback) {
  844. var a = jm.util.ajax;
  845. var p = null;
  846. var tmp_param = [];
  847. for (var k in param) {
  848. tmp_param.push(a._eurl(k) + '=' + a._eurl(param[k]));
  849. }
  850. if (tmp_param.length > 0) {
  851. p = tmp_param.join('&');
  852. }
  853. var xhr = a._xhr();
  854. if (!xhr) { return; }
  855. xhr.onreadystatechange = function () {
  856. if (xhr.readyState == 4) {
  857. if (xhr.status == 200 || xhr.status == 0) {
  858. if (typeof callback === 'function') {
  859. var data = jm.util.json.string2json(xhr.responseText);
  860. if (data != null) {
  861. callback(data);
  862. } else {
  863. callback(xhr.responseText);
  864. }
  865. }
  866. } else {
  867. if (typeof fail_callback === 'function') {
  868. fail_callback(xhr);
  869. } else {
  870. logger.error('xhr request failed.', xhr);
  871. }
  872. }
  873. }
  874. }
  875. method = method || 'GET';
  876. xhr.open(method, url, true);
  877. xhr.setRequestHeader('If-Modified-Since', '0');
  878. if (method == 'POST') {
  879. xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=utf-8');
  880. xhr.send(p);
  881. } else {
  882. xhr.send();
  883. }
  884. },
  885. get: function (url, callback) {
  886. return jm.util.ajax.request(url, {}, 'GET', callback);
  887. },
  888. post: function (url, param, callback) {
  889. return jm.util.ajax.request(url, param, 'POST', callback);
  890. }
  891. },
  892. dom: {
  893. //target,eventType,handler
  894. add_event: function (t, e, h) {
  895. if (!!t.addEventListener) {
  896. t.addEventListener(e, h, false);
  897. } else {
  898. t.attachEvent('on' + e, h);
  899. }
  900. }
  901. },
  902. file: {
  903. read: function (file_data, fn_callback) {
  904. var reader = new FileReader();
  905. reader.onload = function () {
  906. if (typeof fn_callback === 'function') {
  907. fn_callback(this.result, file_data.name);
  908. }
  909. };
  910. reader.readAsText(file_data);
  911. },
  912. save: function (file_data, type, name) {
  913. var blob;
  914. if (typeof $w.Blob === 'function') {
  915. blob = new Blob([file_data], { type: type });
  916. } else {
  917. var BlobBuilder = $w.BlobBuilder || $w.MozBlobBuilder || $w.WebKitBlobBuilder || $w.MSBlobBuilder;
  918. var bb = new BlobBuilder();
  919. bb.append(file_data);
  920. blob = bb.getBlob(type);
  921. }
  922. if (navigator.msSaveBlob) {
  923. navigator.msSaveBlob(blob, name);
  924. } else {
  925. var URL = $w.URL || $w.webkitURL;
  926. var bloburl = URL.createObjectURL(blob);
  927. var anchor = $c('a');
  928. if ('download' in anchor) {
  929. anchor.style.visibility = 'hidden';
  930. anchor.href = bloburl;
  931. anchor.download = name;
  932. $d.body.appendChild(anchor);
  933. var evt = $d.createEvent('MouseEvents');
  934. evt.initEvent('click', true, true);
  935. anchor.dispatchEvent(evt);
  936. $d.body.removeChild(anchor);
  937. } else {
  938. location.href = bloburl;
  939. }
  940. }
  941. }
  942. },
  943. json: {
  944. json2string: function (json) {
  945. if (!!JSON) {
  946. try {
  947. var json_str = JSON.stringify(json);
  948. return json_str;
  949. } catch (e) {
  950. logger.warn(e);
  951. logger.warn('can not convert to string');
  952. return null;
  953. }
  954. }
  955. },
  956. string2json: function (json_str) {
  957. if (!!JSON) {
  958. try {
  959. var json = JSON.parse(json_str);
  960. return json;
  961. } catch (e) {
  962. logger.warn(e);
  963. logger.warn('can not parse to json');
  964. return null;
  965. }
  966. }
  967. },
  968. merge: function (b, a) {
  969. for (var o in a) {
  970. if (o in b) {
  971. if (typeof b[o] === 'object' &&
  972. Object.prototype.toString.call(b[o]).toLowerCase() == '[object object]' &&
  973. !b[o].length) {
  974. jm.util.json.merge(b[o], a[o]);
  975. } else {
  976. b[o] = a[o];
  977. }
  978. } else {
  979. b[o] = a[o];
  980. }
  981. }
  982. return b;
  983. }
  984. },
  985. uuid: {
  986. newid: function () {
  987. return (new Date().getTime().toString(16) + Math.random().toString(16).substr(2)).substr(2, 16);
  988. }
  989. },
  990. text: {
  991. is_empty: function (s) {
  992. if (!s) { return true; }
  993. return s.replace(/\s*/, '').length == 0;
  994. }
  995. }
  996. };
  997. jm.prototype = {
  998. init: function () {
  999. if (this.inited) { return; }
  1000. this.inited = true;
  1001. var opts = this.options;
  1002. var opts_layout = {
  1003. mode: opts.mode,
  1004. hspace: opts.layout.hspace,
  1005. vspace: opts.layout.vspace,
  1006. pspace: opts.layout.pspace
  1007. }
  1008. var opts_view = {
  1009. container: opts.container,
  1010. support_html: opts.support_html,
  1011. engine: opts.view.engine,
  1012. hmargin: opts.view.hmargin,
  1013. vmargin: opts.view.vmargin,
  1014. line_width: opts.view.line_width,
  1015. line_color: opts.view.line_color
  1016. };
  1017. // create instance of function provider
  1018. this.data = new jm.data_provider(this);
  1019. this.layout = new jm.layout_provider(this, opts_layout);
  1020. this.view = new jm.view_provider(this, opts_view);
  1021. this.shortcut = new jm.shortcut_provider(this, opts.shortcut);
  1022. this.data.init();
  1023. this.layout.init();
  1024. this.view.init();
  1025. this.shortcut.init();
  1026. this._event_bind();
  1027. jm.init_plugins(this);
  1028. },
  1029. enable_edit: function () {
  1030. this.options.editable = true;
  1031. },
  1032. disable_edit: function () {
  1033. this.options.editable = false;
  1034. },
  1035. // call enable_event_handle('dblclick')
  1036. // options are 'mousedown', 'click', 'dblclick'
  1037. enable_event_handle: function (event_handle) {
  1038. this.options.default_event_handle['enable_' + event_handle + '_handle'] = true;
  1039. },
  1040. // call disable_event_handle('dblclick')
  1041. // options are 'mousedown', 'click', 'dblclick'
  1042. disable_event_handle: function (event_handle) {
  1043. this.options.default_event_handle['enable_' + event_handle + '_handle'] = false;
  1044. },
  1045. get_editable: function () {
  1046. return this.options.editable;
  1047. },
  1048. set_theme: function (theme) {
  1049. var theme_old = this.options.theme;
  1050. this.options.theme = (!!theme) ? theme : null;
  1051. if (theme_old != this.options.theme) {
  1052. this.view.reset_theme();
  1053. this.view.reset_custom_style();
  1054. }
  1055. },
  1056. _event_bind: function () {
  1057. this.view.add_event(this, 'mousedown', this.mousedown_handle);
  1058. this.view.add_event(this, 'click', this.click_handle);
  1059. this.view.add_event(this, 'dblclick', this.dblclick_handle);
  1060. },
  1061. mousedown_handle: function (e) {
  1062. if (!this.options.default_event_handle['enable_mousedown_handle']) {
  1063. return;
  1064. }
  1065. var element = e.target || event.srcElement;
  1066. var nodeid = this.view.get_binded_nodeid(element);
  1067. if (!!nodeid) {
  1068. if (element.tagName.toLowerCase() == 'jmnode') {
  1069. this.select_node(nodeid);
  1070. }
  1071. } else {
  1072. this.select_clear();
  1073. }
  1074. },
  1075. click_handle: function (e) {
  1076. if (!this.options.default_event_handle['enable_click_handle']) {
  1077. return;
  1078. }
  1079. var element = e.target || event.srcElement;
  1080. var isexpander = this.view.is_expander(element);
  1081. if (isexpander) {
  1082. var nodeid = this.view.get_binded_nodeid(element);
  1083. if (!!nodeid) {
  1084. this.toggle_node(nodeid);
  1085. }
  1086. }
  1087. },
  1088. dblclick_handle: function (e) {
  1089. if (!this.options.default_event_handle['enable_dblclick_handle']) {
  1090. return;
  1091. }
  1092. if (this.get_editable()) {
  1093. var element = e.target || event.srcElement;
  1094. var nodeid = this.view.get_binded_nodeid(element);
  1095. if (!!nodeid) {
  1096. this.begin_edit(nodeid);
  1097. }
  1098. }
  1099. },
  1100. begin_edit: function (node) {
  1101. if (!jm.util.is_node(node)) {
  1102. var the_node = this.get_node(node);
  1103. if (!the_node) {
  1104. logger.error('the node[id=' + node + '] can not be found.');
  1105. return false;
  1106. } else {
  1107. return this.begin_edit(the_node);
  1108. }
  1109. }
  1110. if (this.get_editable()) {
  1111. this.view.edit_node_begin(node);
  1112. } else {
  1113. logger.error('fail, this mind map is not editable.');
  1114. return;
  1115. }
  1116. },
  1117. end_edit: function () {
  1118. this.view.edit_node_end();
  1119. },
  1120. toggle_node: function (node) {
  1121. if (!jm.util.is_node(node)) {
  1122. var the_node = this.get_node(node);
  1123. if (!the_node) {
  1124. logger.error('the node[id=' + node + '] can not be found.');
  1125. return;
  1126. } else {
  1127. return this.toggle_node(the_node);
  1128. }
  1129. }
  1130. if (node.isroot) { return; }
  1131. this.view.save_location(node);
  1132. this.layout.toggle_node(node);
  1133. this.view.relayout();
  1134. this.view.restore_location(node);
  1135. },
  1136. expand_node: function (node) {
  1137. if (!jm.util.is_node(node)) {
  1138. var the_node = this.get_node(node);
  1139. if (!the_node) {
  1140. logger.error('the node[id=' + node + '] can not be found.');
  1141. return;
  1142. } else {
  1143. return this.expand_node(the_node);
  1144. }
  1145. }
  1146. if (node.isroot) { return; }
  1147. this.view.save_location(node);
  1148. this.layout.expand_node(node);
  1149. this.view.relayout();
  1150. this.view.restore_location(node);
  1151. },
  1152. collapse_node: function (node) {
  1153. if (!jm.util.is_node(node)) {
  1154. var the_node = this.get_node(node);
  1155. if (!the_node) {
  1156. logger.error('the node[id=' + node + '] can not be found.');
  1157. return;
  1158. } else {
  1159. return this.collapse_node(the_node);
  1160. }
  1161. }
  1162. if (node.isroot) { return; }
  1163. this.view.save_location(node);
  1164. this.layout.collapse_node(node);
  1165. this.view.relayout();
  1166. this.view.restore_location(node);
  1167. },
  1168. expand_all: function () {
  1169. this.layout.expand_all();
  1170. this.view.relayout();
  1171. },
  1172. collapse_all: function () {
  1173. this.layout.collapse_all();
  1174. this.view.relayout();
  1175. },
  1176. expand_to_depth: function (depth) {
  1177. this.layout.expand_to_depth(depth);
  1178. this.view.relayout();
  1179. },
  1180. _reset: function () {
  1181. this.view.reset();
  1182. this.layout.reset();
  1183. this.data.reset();
  1184. },
  1185. _show: function (mind) {
  1186. var m = mind || jm.format.node_array.example;
  1187. this.mind = this.data.load(m);
  1188. if (!this.mind) {
  1189. logger.error('data.load error');
  1190. return;
  1191. } else {
  1192. logger.debug('data.load ok');
  1193. }
  1194. this.view.load();
  1195. logger.debug('view.load ok');
  1196. this.layout.layout();
  1197. logger.debug('layout.layout ok');
  1198. this.view.show(true);
  1199. logger.debug('view.show ok');
  1200. this.invoke_event_handle(jm.event_type.show, { data: [mind] });
  1201. },
  1202. show: function (mind) {
  1203. this._reset();
  1204. this._show(mind);
  1205. },
  1206. get_meta: function () {
  1207. return {
  1208. name: this.mind.name,
  1209. author: this.mind.author,
  1210. version: this.mind.version
  1211. };
  1212. },
  1213. get_data: function (data_format) {
  1214. var df = data_format || 'node_tree';
  1215. return this.data.get_data(df);
  1216. },
  1217. get_root: function () {
  1218. return this.mind.root;
  1219. },
  1220. get_node: function (nodeid) {
  1221. return this.mind.get_node(nodeid);
  1222. },
  1223. add_node: function (parent_node, nodeid, topic, data) {
  1224. if (this.get_editable()) {
  1225. var node = this.mind.add_node(parent_node, nodeid, topic, data);
  1226. if (!!node) {
  1227. this.view.add_node(node);
  1228. this.layout.layout();
  1229. this.view.show(false);
  1230. this.view.reset_node_custom_style(node);
  1231. this.expand_node(parent_node);
  1232. this.invoke_event_handle(jm.event_type.edit, { evt: 'add_node', data: [parent_node.id, nodeid, topic, data], node: nodeid });
  1233. }
  1234. return node;
  1235. } else {
  1236. logger.error('fail, this mind map is not editable');
  1237. return null;
  1238. }
  1239. },
  1240. insert_node_before: function (node_before, nodeid, topic, data) {
  1241. if (this.get_editable()) {
  1242. var beforeid = jm.util.is_node(node_before) ? node_before.id : node_before;
  1243. var node = this.mind.insert_node_before(node_before, nodeid, topic, data);
  1244. if (!!node) {
  1245. this.view.add_node(node);
  1246. this.layout.layout();
  1247. this.view.show(false);
  1248. this.invoke_event_handle(jm.event_type.edit, { evt: 'insert_node_before', data: [beforeid, nodeid, topic, data], node: nodeid });
  1249. }
  1250. return node;
  1251. } else {
  1252. logger.error('fail, this mind map is not editable');
  1253. return null;
  1254. }
  1255. },
  1256. insert_node_after: function (node_after, nodeid, topic, data) {
  1257. if (this.get_editable()) {
  1258. var afterid = jm.util.is_node(node_after) ? node_after.id : node_after;
  1259. var node = this.mind.insert_node_after(node_after, nodeid, topic, data);
  1260. if (!!node) {
  1261. this.view.add_node(node);
  1262. this.layout.layout();
  1263. this.view.show(false);
  1264. this.invoke_event_handle(jm.event_type.edit, { evt: 'insert_node_after', data: [afterid, nodeid, topic, data], node: nodeid });
  1265. }
  1266. return node;
  1267. } else {
  1268. logger.error('fail, this mind map is not editable');
  1269. return null;
  1270. }
  1271. },
  1272. remove_node: function (node) {
  1273. if (!jm.util.is_node(node)) {
  1274. var the_node = this.get_node(node);
  1275. if (!the_node) {
  1276. logger.error('the node[id=' + node + '] can not be found.');
  1277. return false;
  1278. } else {
  1279. return this.remove_node(the_node);
  1280. }
  1281. }
  1282. if (this.get_editable()) {
  1283. if (node.isroot) {
  1284. logger.error('fail, can not remove root node');
  1285. return false;
  1286. }
  1287. var nodeid = node.id;
  1288. var parentid = node.parent.id;
  1289. var parent_node = this.get_node(parentid);
  1290. this.view.save_location(parent_node);
  1291. this.view.remove_node(node);
  1292. this.mind.remove_node(node);
  1293. this.layout.layout();
  1294. this.view.show(false);
  1295. this.view.restore_location(parent_node);
  1296. this.invoke_event_handle(jm.event_type.edit, { evt: 'remove_node', data: [nodeid], node: parentid });
  1297. return true;
  1298. } else {
  1299. logger.error('fail, this mind map is not editable');
  1300. return false;
  1301. }
  1302. },
  1303. update_node: function (nodeid, topic) {
  1304. if (this.get_editable()) {
  1305. if (jm.util.text.is_empty(topic)) {
  1306. logger.warn('fail, topic can not be empty');
  1307. return;
  1308. }
  1309. var node = this.get_node(nodeid);
  1310. if (!!node) {
  1311. if (node.topic === topic) {
  1312. logger.info('nothing changed');
  1313. this.view.update_node(node);
  1314. return;
  1315. }
  1316. node.topic = topic;
  1317. this.view.update_node(node);
  1318. this.layout.layout();
  1319. this.view.show(false);
  1320. this.invoke_event_handle(jm.event_type.edit, { evt: 'update_node', data: [nodeid, topic], node: nodeid });
  1321. }
  1322. } else {
  1323. logger.error('fail, this mind map is not editable');
  1324. return;
  1325. }
  1326. },
  1327. move_node: function (nodeid, beforeid, parentid, direction) {
  1328. if (this.get_editable()) {
  1329. var node = this.mind.move_node(nodeid, beforeid, parentid, direction);
  1330. if (!!node) {
  1331. this.view.update_node(node);
  1332. this.layout.layout();
  1333. this.view.show(false);
  1334. this.invoke_event_handle(jm.event_type.edit, { evt: 'move_node', data: [nodeid, beforeid, parentid, direction], node: nodeid });
  1335. }
  1336. } else {
  1337. logger.error('fail, this mind map is not editable');
  1338. return;
  1339. }
  1340. },
  1341. select_node: function (node) {
  1342. if (!jm.util.is_node(node)) {
  1343. var the_node = this.get_node(node);
  1344. if (!the_node) {
  1345. logger.error('the node[id=' + node + '] can not be found.');
  1346. return;
  1347. } else {
  1348. return this.select_node(the_node);
  1349. }
  1350. }
  1351. if (!this.layout.is_visible(node)) {
  1352. return;
  1353. }
  1354. this.mind.selected = node;
  1355. this.view.select_node(node);
  1356. },
  1357. get_selected_node: function () {
  1358. if (!!this.mind) {
  1359. return this.mind.selected;
  1360. } else {
  1361. return null;
  1362. }
  1363. },
  1364. select_clear: function () {
  1365. if (!!this.mind) {
  1366. this.mind.selected = null;
  1367. this.view.select_clear();
  1368. }
  1369. },
  1370. is_node_visible: function (node) {
  1371. return this.layout.is_visible(node);
  1372. },
  1373. find_node_before: function (node) {
  1374. if (!jm.util.is_node(node)) {
  1375. var the_node = this.get_node(node);
  1376. if (!the_node) {
  1377. logger.error('the node[id=' + node + '] can not be found.');
  1378. return;
  1379. } else {
  1380. return this.find_node_before(the_node);
  1381. }
  1382. }
  1383. if (node.isroot) { return null; }
  1384. var n = null;
  1385. if (node.parent.isroot) {
  1386. var c = node.parent.children;
  1387. var prev = null;
  1388. var ni = null;
  1389. for (var i = 0; i < c.length; i++) {
  1390. ni = c[i];
  1391. if (node.direction === ni.direction) {
  1392. if (node.id === ni.id) {
  1393. n = prev;
  1394. }
  1395. prev = ni;
  1396. }
  1397. }
  1398. } else {
  1399. n = this.mind.get_node_before(node);
  1400. }
  1401. return n;
  1402. },
  1403. find_node_after: function (node) {
  1404. if (!jm.util.is_node(node)) {
  1405. var the_node = this.get_node(node);
  1406. if (!the_node) {
  1407. logger.error('the node[id=' + node + '] can not be found.');
  1408. return;
  1409. } else {
  1410. return this.find_node_after(the_node);
  1411. }
  1412. }
  1413. if (node.isroot) { return null; }
  1414. var n = null;
  1415. if (node.parent.isroot) {
  1416. var c = node.parent.children;
  1417. var getthis = false;
  1418. var ni = null;
  1419. for (var i = 0; i < c.length; i++) {
  1420. ni = c[i];
  1421. if (node.direction === ni.direction) {
  1422. if (getthis) {
  1423. n = ni;
  1424. break;
  1425. }
  1426. if (node.id === ni.id) {
  1427. getthis = true;
  1428. }
  1429. }
  1430. }
  1431. } else {
  1432. n = this.mind.get_node_after(node);
  1433. }
  1434. return n;
  1435. },
  1436. set_node_color: function (nodeid, bgcolor, fgcolor) {
  1437. if (this.get_editable()) {
  1438. var node = this.mind.get_node(nodeid);
  1439. if (!!node) {
  1440. if (!!bgcolor) {
  1441. node.data['background-color'] = bgcolor;
  1442. }
  1443. if (!!fgcolor) {
  1444. node.data['foreground-color'] = fgcolor;
  1445. }
  1446. this.view.reset_node_custom_style(node);
  1447. }
  1448. } else {
  1449. logger.error('fail, this mind map is not editable');
  1450. return null;
  1451. }
  1452. },
  1453. set_node_font_style: function (nodeid, size, weight, style) {
  1454. if (this.get_editable()) {
  1455. var node = this.mind.get_node(nodeid);
  1456. if (!!node) {
  1457. if (!!size) {
  1458. node.data['font-size'] = size;
  1459. }
  1460. if (!!weight) {
  1461. node.data['font-weight'] = weight;
  1462. }
  1463. if (!!style) {
  1464. node.data['font-style'] = style;
  1465. }
  1466. this.view.reset_node_custom_style(node);
  1467. this.view.update_node(node);
  1468. this.layout.layout();
  1469. this.view.show(false);
  1470. }
  1471. } else {
  1472. logger.error('fail, this mind map is not editable');
  1473. return null;
  1474. }
  1475. },
  1476. set_node_background_image: function (nodeid, image, width, height, rotation) {
  1477. if (this.get_editable()) {
  1478. var node = this.mind.get_node(nodeid);
  1479. if (!!node) {
  1480. if (!!image) {
  1481. node.data['background-image'] = image;
  1482. }
  1483. if (!!width) {
  1484. node.data['width'] = width;
  1485. }
  1486. if (!!height) {
  1487. node.data['height'] = height;
  1488. }
  1489. if (!!rotation) {
  1490. node.data['background-rotation'] = rotation;
  1491. }
  1492. this.view.reset_node_custom_style(node);
  1493. this.view.update_node(node);
  1494. this.layout.layout();
  1495. this.view.show(false);
  1496. }
  1497. } else {
  1498. logger.error('fail, this mind map is not editable');
  1499. return null;
  1500. }
  1501. },
  1502. set_node_background_rotation: function (nodeid, rotation) {
  1503. if (this.get_editable()) {
  1504. var node = this.mind.get_node(nodeid);
  1505. if (!!node) {
  1506. if (!node.data['background-image']) {
  1507. logger.error('fail, only can change rotation angle of node with background image');
  1508. return null;
  1509. }
  1510. node.data['background-rotation'] = rotation;
  1511. this.view.reset_node_custom_style(node);
  1512. this.view.update_node(node);
  1513. this.layout.layout();
  1514. this.view.show(false);
  1515. }
  1516. } else {
  1517. logger.error('fail, this mind map is not editable');
  1518. return null;
  1519. }
  1520. },
  1521. resize: function () {
  1522. this.view.resize();
  1523. },
  1524. // callback(type ,data)
  1525. add_event_listener: function (callback) {
  1526. if (typeof callback === 'function') {
  1527. this.event_handles.push(callback);
  1528. }
  1529. },
  1530. invoke_event_handle: function (type, data) {
  1531. var j = this;
  1532. $w.setTimeout(function () {
  1533. j._invoke_event_handle(type, data);
  1534. }, 0);
  1535. },
  1536. _invoke_event_handle: function (type, data) {
  1537. var l = this.event_handles.length;
  1538. for (var i = 0; i < l; i++) {
  1539. this.event_handles[i](type, data);
  1540. }
  1541. }
  1542. };
  1543. // ============= data provider =============================================
  1544. jm.data_provider = function (jm) {
  1545. this.jm = jm;
  1546. };
  1547. jm.data_provider.prototype = {
  1548. init: function () {
  1549. logger.debug('data.init');
  1550. },
  1551. reset: function () {
  1552. logger.debug('data.reset');
  1553. },
  1554. load: function (mind_data) {
  1555. var df = null;
  1556. var mind = null;
  1557. if (typeof mind_data === 'object') {
  1558. if (!!mind_data.format) {
  1559. df = mind_data.format;
  1560. } else {
  1561. df = 'node_tree';
  1562. }
  1563. } else {
  1564. df = 'freemind';
  1565. }
  1566. if (df == 'node_array') {
  1567. mind = jm.format.node_array.get_mind(mind_data);
  1568. } else if (df == 'node_tree') {
  1569. mind = jm.format.node_tree.get_mind(mind_data);
  1570. } else if (df == 'freemind') {
  1571. mind = jm.format.freemind.get_mind(mind_data);
  1572. } else {
  1573. logger.warn('unsupported format');
  1574. }
  1575. return mind;
  1576. },
  1577. get_data: function (data_format) {
  1578. var data = null;
  1579. if (data_format == 'node_array') {
  1580. data = jm.format.node_array.get_data(this.jm.mind);
  1581. } else if (data_format == 'node_tree') {
  1582. data = jm.format.node_tree.get_data(this.jm.mind);
  1583. } else if (data_format == 'freemind') {
  1584. data = jm.format.freemind.get_data(this.jm.mind);
  1585. } else {
  1586. logger.error('unsupported ' + data_format + ' format');
  1587. }
  1588. return data;
  1589. },
  1590. };
  1591. // ============= layout provider ===========================================
  1592. jm.layout_provider = function (jm, options) {
  1593. this.opts = options;
  1594. this.jm = jm;
  1595. this.isside = (this.opts.mode == 'side');
  1596. this.bounds = null;
  1597. this.cache_valid = false;
  1598. };
  1599. jm.layout_provider.prototype = {
  1600. init: function () {
  1601. logger.debug('layout.init');
  1602. },
  1603. reset: function () {
  1604. logger.debug('layout.reset');
  1605. this.bounds = { n: 0, s: 0, w: 0, e: 0 };
  1606. },
  1607. layout: function () {
  1608. logger.debug('layout.layout');
  1609. this.layout_direction();
  1610. this.layout_offset();
  1611. },
  1612. layout_direction: function () {
  1613. this._layout_direction_root();
  1614. },
  1615. _layout_direction_root: function () {
  1616. var node = this.jm.mind.root;
  1617. // logger.debug(node);
  1618. var layout_data = null;
  1619. if ('layout' in node._data) {
  1620. layout_data = node._data.layout;
  1621. } else {
  1622. layout_data = {};
  1623. node._data.layout = layout_data;
  1624. }
  1625. var children = node.children;
  1626. var children_count = children.length;
  1627. layout_data.direction = jm.direction.center;
  1628. layout_data.side_index = 0;
  1629. if (this.isside) {
  1630. var i = children_count;
  1631. while (i--) {
  1632. this._layout_direction_side(children[i], jm.direction.right, i);
  1633. }
  1634. } else {
  1635. var i = children_count;
  1636. var subnode = null;
  1637. while (i--) {
  1638. subnode = children[i];
  1639. if (subnode.direction == jm.direction.left) {
  1640. this._layout_direction_side(subnode, jm.direction.left, i);
  1641. } else {
  1642. this._layout_direction_side(subnode, jm.direction.right, i);
  1643. }
  1644. }
  1645. /*
  1646. var boundary = Math.ceil(children_count/2);
  1647. var i = children_count;
  1648. while(i--){
  1649. if(i>=boundary){
  1650. this._layout_direction_side(children[i],jm.direction.left, children_count-i-1);
  1651. }else{
  1652. this._layout_direction_side(children[i],jm.direction.right, i);
  1653. }
  1654. }*/
  1655. }
  1656. },
  1657. _layout_direction_side: function (node, direction, side_index) {
  1658. var layout_data = null;
  1659. if ('layout' in node._data) {
  1660. layout_data = node._data.layout;
  1661. } else {
  1662. layout_data = {};
  1663. node._data.layout = layout_data;
  1664. }
  1665. var children = node.children;
  1666. var children_count = children.length;
  1667. layout_data.direction = direction;
  1668. layout_data.side_index = side_index;
  1669. var i = children_count;
  1670. while (i--) {
  1671. this._layout_direction_side(children[i], direction, i);
  1672. }
  1673. },
  1674. layout_offset: function () {
  1675. var node = this.jm.mind.root;
  1676. var layout_data = node._data.layout;
  1677. layout_data.offset_x = 0;
  1678. layout_data.offset_y = 0;
  1679. layout_data.outer_height = 0;
  1680. var children = node.children;
  1681. var i = children.length;
  1682. var left_nodes = [];
  1683. var right_nodes = [];
  1684. var subnode = null;
  1685. while (i--) {
  1686. subnode = children[i];
  1687. if (subnode._data.layout.direction == jm.direction.right) {
  1688. right_nodes.unshift(subnode);
  1689. } else {
  1690. left_nodes.unshift(subnode);
  1691. }
  1692. }
  1693. layout_data.left_nodes = left_nodes;
  1694. layout_data.right_nodes = right_nodes;
  1695. layout_data.outer_height_left = this._layout_offset_subnodes(left_nodes);
  1696. layout_data.outer_height_right = this._layout_offset_subnodes(right_nodes);
  1697. this.bounds.e = node._data.view.width / 2;
  1698. this.bounds.w = 0 - this.bounds.e;
  1699. //logger.debug(this.bounds.w);
  1700. this.bounds.n = 0;
  1701. this.bounds.s = Math.max(layout_data.outer_height_left, layout_data.outer_height_right);
  1702. },
  1703. // layout both the x and y axis
  1704. _layout_offset_subnodes: function (nodes) {
  1705. var total_height = 0;
  1706. var nodes_count = nodes.length;
  1707. var i = nodes_count;
  1708. var node = null;
  1709. var node_outer_height = 0;
  1710. var layout_data = null;
  1711. var base_y = 0;
  1712. var pd = null; // parent._data
  1713. while (i--) {
  1714. node = nodes[i];
  1715. layout_data = node._data.layout;
  1716. if (pd == null) {
  1717. pd = node.parent._data;
  1718. }
  1719. node_outer_height = this._layout_offset_subnodes(node.children);
  1720. if (!node.expanded) {
  1721. node_outer_height = 0;
  1722. this.set_visible(node.children, false);
  1723. }
  1724. node_outer_height = Math.max(node._data.view.height, node_outer_height);
  1725. layout_data.outer_height = node_outer_height;
  1726. layout_data.offset_y = base_y - node_outer_height / 2;
  1727. layout_data.offset_x = this.opts.hspace * layout_data.direction + pd.view.width * (pd.layout.direction + layout_data.direction) / 2;
  1728. if (!node.parent.isroot) {
  1729. layout_data.offset_x += this.opts.pspace * layout_data.direction;
  1730. }
  1731. base_y = base_y - node_outer_height - this.opts.vspace;
  1732. total_height += node_outer_height;
  1733. }
  1734. if (nodes_count > 1) {
  1735. total_height += this.opts.vspace * (nodes_count - 1);
  1736. }
  1737. i = nodes_count;
  1738. var middle_height = total_height / 2;
  1739. while (i--) {
  1740. node = nodes[i];
  1741. node._data.layout.offset_y += middle_height;
  1742. }
  1743. return total_height;
  1744. },
  1745. // layout the y axis only, for collapse/expand a node
  1746. _layout_offset_subnodes_height: function (nodes) {
  1747. var total_height = 0;
  1748. var nodes_count = nodes.length;
  1749. var i = nodes_count;
  1750. var node = null;
  1751. var node_outer_height = 0;
  1752. var layout_data = null;
  1753. var base_y = 0;
  1754. var pd = null; // parent._data
  1755. while (i--) {
  1756. node = nodes[i];
  1757. layout_data = node._data.layout;
  1758. if (pd == null) {
  1759. pd = node.parent._data;
  1760. }
  1761. node_outer_height = this._layout_offset_subnodes_height(node.children);
  1762. if (!node.expanded) {
  1763. node_outer_height = 0;
  1764. }
  1765. node_outer_height = Math.max(node._data.view.height, node_outer_height);
  1766. layout_data.outer_height = node_outer_height;
  1767. layout_data.offset_y = base_y - node_outer_height / 2;
  1768. base_y = base_y - node_outer_height - this.opts.vspace;
  1769. total_height += node_outer_height;
  1770. }
  1771. if (nodes_count > 1) {
  1772. total_height += this.opts.vspace * (nodes_count - 1);
  1773. }
  1774. i = nodes_count;
  1775. var middle_height = total_height / 2;
  1776. while (i--) {
  1777. node = nodes[i];
  1778. node._data.layout.offset_y += middle_height;
  1779. //logger.debug(node.topic);
  1780. //logger.debug(node._data.layout.offset_y);
  1781. }
  1782. return total_height;
  1783. },
  1784. get_node_offset: function (node) {
  1785. var layout_data = node._data.layout;
  1786. var offset_cache = null;
  1787. if (('_offset_' in layout_data) && this.cache_valid) {
  1788. offset_cache = layout_data._offset_;
  1789. } else {
  1790. offset_cache = { x: -1, y: -1 };
  1791. layout_data._offset_ = offset_cache;
  1792. }
  1793. if (offset_cache.x == -1 || offset_cache.y == -1) {
  1794. var x = layout_data.offset_x;
  1795. var y = layout_data.offset_y;
  1796. if (!node.isroot) {
  1797. var offset_p = this.get_node_offset(node.parent);
  1798. x += offset_p.x;
  1799. y += offset_p.y;
  1800. }
  1801. offset_cache.x = x;
  1802. offset_cache.y = y;
  1803. }
  1804. return offset_cache;
  1805. },
  1806. get_node_point: function (node) {
  1807. var view_data = node._data.view;
  1808. var offset_p = this.get_node_offset(node);
  1809. //logger.debug(offset_p);
  1810. var p = {};
  1811. p.x = offset_p.x + view_data.width * (node._data.layout.direction - 1) / 2;
  1812. p.y = offset_p.y - view_data.height / 2;
  1813. //logger.debug(p);
  1814. return p;
  1815. },
  1816. get_node_point_in: function (node) {
  1817. var p = this.get_node_offset(node);
  1818. return p;
  1819. },
  1820. get_node_point_out: function (node) {
  1821. var layout_data = node._data.layout;
  1822. var pout_cache = null;
  1823. if (('_pout_' in layout_data) && this.cache_valid) {
  1824. pout_cache = layout_data._pout_;
  1825. } else {
  1826. pout_cache = { x: -1, y: -1 };
  1827. layout_data._pout_ = pout_cache;
  1828. }
  1829. if (pout_cache.x == -1 || pout_cache.y == -1) {
  1830. if (node.isroot) {
  1831. pout_cache.x = 0;
  1832. pout_cache.y = 0;
  1833. } else {
  1834. var view_data = node._data.view;
  1835. var offset_p = this.get_node_offset(node);
  1836. pout_cache.x = offset_p.x + (view_data.width + this.opts.pspace) * node._data.layout.direction;
  1837. pout_cache.y = offset_p.y;
  1838. //logger.debug('pout');
  1839. //logger.debug(pout_cache);
  1840. }
  1841. }
  1842. return pout_cache;
  1843. },
  1844. get_expander_point: function (node) {
  1845. var p = this.get_node_point_out(node);
  1846. var ex_p = {};
  1847. if (node._data.layout.direction == jm.direction.right) {
  1848. ex_p.x = p.x - this.opts.pspace;
  1849. } else {
  1850. ex_p.x = p.x;
  1851. }
  1852. ex_p.y = p.y - Math.ceil(this.opts.pspace / 2);
  1853. return ex_p;
  1854. },
  1855. get_min_size: function () {
  1856. var nodes = this.jm.mind.nodes;
  1857. var node = null;
  1858. var pout = null;
  1859. for (var nodeid in nodes) {
  1860. node = nodes[nodeid];
  1861. pout = this.get_node_point_out(node);
  1862. if (pout.x > this.bounds.e) { this.bounds.e = pout.x; }
  1863. if (pout.x < this.bounds.w) { this.bounds.w = pout.x; }
  1864. }
  1865. return {
  1866. w: this.bounds.e - this.bounds.w,
  1867. h: this.bounds.s - this.bounds.n
  1868. }
  1869. },
  1870. toggle_node: function (node) {
  1871. if (node.isroot) {
  1872. return;
  1873. }
  1874. if (node.expanded) {
  1875. this.collapse_node(node);
  1876. } else {
  1877. this.expand_node(node);
  1878. }
  1879. },
  1880. expand_node: function (node) {
  1881. node.expanded = true;
  1882. this.part_layout(node);
  1883. this.set_visible(node.children, true);
  1884. },
  1885. collapse_node: function (node) {
  1886. node.expanded = false;
  1887. this.part_layout(node);
  1888. this.set_visible(node.children, false);
  1889. },
  1890. expand_all: function () {
  1891. var nodes = this.jm.mind.nodes;
  1892. var c = 0;
  1893. var node;
  1894. for (var nodeid in nodes) {
  1895. node = nodes[nodeid];
  1896. if (!node.expanded) {
  1897. node.expanded = true;
  1898. c++;
  1899. }
  1900. }
  1901. if (c > 0) {
  1902. var root = this.jm.mind.root;
  1903. this.part_layout(root);
  1904. this.set_visible(root.children, true);
  1905. }
  1906. },
  1907. collapse_all: function () {
  1908. var nodes = this.jm.mind.nodes;
  1909. var c = 0;
  1910. var node;
  1911. for (var nodeid in nodes) {
  1912. node = nodes[nodeid];
  1913. if (node.expanded && !node.isroot) {
  1914. node.expanded = false
  1915. c++;
  1916. }
  1917. }
  1918. if (c > 0) {
  1919. var root = this.jm.mind.root;
  1920. this.part_layout(root);
  1921. this.set_visible(root.children, true);
  1922. }
  1923. },
  1924. expand_to_depth: function (target_depth, curr_nodes, curr_depth) {
  1925. if (target_depth < 1) { return; }
  1926. var nodes = curr_nodes || this.jm.mind.root.children;
  1927. var depth = curr_depth || 1;
  1928. var i = nodes.length;
  1929. var node = null;
  1930. while (i--) {
  1931. node = nodes[i];
  1932. if (depth < target_depth) {
  1933. if (!node.expanded) {
  1934. this.expand_node(node);
  1935. }
  1936. this.expand_to_depth(target_depth, node.children, depth + 1);
  1937. }
  1938. if (depth == target_depth) {
  1939. if (node.expanded) {
  1940. this.collapse_node(node);
  1941. }
  1942. }
  1943. }
  1944. },
  1945. part_layout: function (node) {
  1946. var root = this.jm.mind.root;
  1947. if (!!root) {
  1948. var root_layout_data = root._data.layout;
  1949. if (node.isroot) {
  1950. root_layout_data.outer_height_right = this._layout_offset_subnodes_height(root_layout_data.right_nodes);
  1951. root_layout_data.outer_height_left = this._layout_offset_subnodes_height(root_layout_data.left_nodes);
  1952. } else {
  1953. if (node._data.layout.direction == jm.direction.right) {
  1954. root_layout_data.outer_height_right = this._layout_offset_subnodes_height(root_layout_data.right_nodes);
  1955. } else {
  1956. root_layout_data.outer_height_left = this._layout_offset_subnodes_height(root_layout_data.left_nodes);
  1957. }
  1958. }
  1959. this.bounds.s = Math.max(root_layout_data.outer_height_left, root_layout_data.outer_height_right);
  1960. this.cache_valid = false;
  1961. } else {
  1962. logger.warn('can not found root node');
  1963. }
  1964. },
  1965. set_visible: function (nodes, visible) {
  1966. var i = nodes.length;
  1967. var node = null;
  1968. var layout_data = null;
  1969. while (i--) {
  1970. node = nodes[i];
  1971. layout_data = node._data.layout;
  1972. if (node.expanded) {
  1973. this.set_visible(node.children, visible);
  1974. } else {
  1975. this.set_visible(node.children, false);
  1976. }
  1977. if (!node.isroot) {
  1978. node._data.layout.visible = visible;
  1979. }
  1980. }
  1981. },
  1982. is_expand: function (node) {
  1983. return node.expanded;
  1984. },
  1985. is_visible: function (node) {
  1986. var layout_data = node._data.layout;
  1987. if (('visible' in layout_data) && !layout_data.visible) {
  1988. return false;
  1989. } else {
  1990. return true;
  1991. }
  1992. }
  1993. };
  1994. jm.graph_canvas = function (view) {
  1995. this.opts = view.opts;
  1996. this.e_canvas = $c('canvas');
  1997. this.canvas_ctx = this.e_canvas.getContext('2d');
  1998. this.size = { w: 0, h: 0 };
  1999. };
  2000. jm.graph_canvas.prototype = {
  2001. element: function () {
  2002. return this.e_canvas;
  2003. },
  2004. set_size: function (w, h) {
  2005. this.size.w = w;
  2006. this.size.h = h;
  2007. this.e_canvas.width = w;
  2008. this.e_canvas.height = h;
  2009. },
  2010. clear: function () {
  2011. this.canvas_ctx.clearRect(0, 0, this.size.w, this.size.h);
  2012. },
  2013. draw_line: function (pout, pin, offset) {
  2014. var ctx = this.canvas_ctx;
  2015. ctx.strokeStyle = this.opts.line_color;
  2016. ctx.lineWidth = this.opts.line_width;
  2017. ctx.lineCap = 'round';
  2018. this._bezier_to(ctx,
  2019. pin.x + offset.x,
  2020. pin.y + offset.y,
  2021. pout.x + offset.x,
  2022. pout.y + offset.y);
  2023. },
  2024. copy_to: function (dest_canvas_ctx, callback) {
  2025. dest_canvas_ctx.drawImage(this.e_canvas, 0, 0);
  2026. !!callback && callback();
  2027. },
  2028. _bezier_to: function (ctx, x1, y1, x2, y2) {
  2029. ctx.beginPath();
  2030. ctx.moveTo(x1, y1);
  2031. ctx.bezierCurveTo(x1 + (x2 - x1) * 2 / 3, y1, x1, y2, x2, y2);
  2032. ctx.stroke();
  2033. },
  2034. _line_to: function (ctx, x1, y1, x2, y2) {
  2035. ctx.beginPath();
  2036. ctx.moveTo(x1, y1);
  2037. ctx.lineTo(x2, y2);
  2038. ctx.stroke();
  2039. }
  2040. };
  2041. jm.graph_svg = function (view) {
  2042. this.view = view;
  2043. this.opts = view.opts;
  2044. this.e_svg = jm.graph_svg.c('svg');
  2045. this.size = { w: 0, h: 0 };
  2046. this.lines = [];
  2047. };
  2048. jm.graph_svg.c = function (tag) {
  2049. return $d.createElementNS('http://www.w3.org/2000/svg', tag);
  2050. };
  2051. jm.graph_svg.prototype = {
  2052. element: function () {
  2053. return this.e_svg;
  2054. },
  2055. set_size: function (w, h) {
  2056. this.size.w = w;
  2057. this.size.h = h;
  2058. this.e_svg.setAttribute('width', w);
  2059. this.e_svg.setAttribute('height', h);
  2060. },
  2061. clear: function () {
  2062. var len = this.lines.length;
  2063. while (len--) {
  2064. this.e_svg.removeChild(this.lines[len]);
  2065. }
  2066. this.lines.length = 0;
  2067. },
  2068. draw_line: function (pout, pin, offset) {
  2069. var line = jm.graph_svg.c('path');
  2070. line.setAttribute('stroke', this.opts.line_color);
  2071. line.setAttribute('stroke-width', this.opts.line_width);
  2072. line.setAttribute('fill', 'transparent');
  2073. this.lines.push(line);
  2074. this.e_svg.appendChild(line);
  2075. this._bezier_to(line, pin.x + offset.x, pin.y + offset.y, pout.x + offset.x, pout.y + offset.y);
  2076. },
  2077. copy_to: function (dest_canvas_ctx, callback) {
  2078. var img = new Image();
  2079. img.onload = function () {
  2080. dest_canvas_ctx.drawImage(img, 0, 0);
  2081. !!callback && callback();
  2082. }
  2083. img.src = 'data:image/svg+xml;base64,' + btoa(new XMLSerializer().serializeToString(this.e_svg));
  2084. },
  2085. _bezier_to: function (path, x1, y1, x2, y2) {
  2086. path.setAttribute('d', 'M' + x1 + ' ' + y1 + ' C ' + (x1 + (x2 - x1) * 2 / 3) + ' ' + y1 + ', ' + x1 + ' ' + y2 + ', ' + x2 + ' ' + y2);
  2087. },
  2088. _line_to: function (path, x1, y1, x2, y2) {
  2089. path.setAttribute('d', 'M ' + x1 + ' ' + y1 + ' L ' + x2 + ' ' + y2);
  2090. }
  2091. };
  2092. // view provider
  2093. jm.view_provider = function (jm, options) {
  2094. this.opts = options;
  2095. this.jm = jm;
  2096. this.layout = jm.layout;
  2097. this.container = null;
  2098. this.e_panel = null;
  2099. this.e_nodes = null;
  2100. this.size = { w: 0, h: 0 };
  2101. this.selected_node = null;
  2102. this.editing_node = null;
  2103. this.graph = null;
  2104. };
  2105. jm.view_provider.prototype = {
  2106. init: function () {
  2107. logger.debug('view.init');
  2108. this.container = $i(this.opts.container) ? this.opts.container : $g(this.opts.container);
  2109. if (!this.container) {
  2110. logger.error('the options.view.container was not be found in dom');
  2111. return;
  2112. }
  2113. this.e_panel = $c('div');
  2114. this.e_nodes = $c('jmnodes');
  2115. this.e_editor = $c('input');
  2116. this.graph = this.opts.engine.toLowerCase() === 'svg' ? new jm.graph_svg(this) : new jm.graph_canvas(this);
  2117. this.e_panel.className = 'jsmind-inner';
  2118. this.e_panel.appendChild(this.graph.element());
  2119. this.e_panel.appendChild(this.e_nodes);
  2120. this.e_editor.className = 'jsmind-editor';
  2121. this.e_editor.type = 'text';
  2122. this.actualZoom = 1;
  2123. this.zoomStep = 0.1;
  2124. this.minZoom = 0.5;
  2125. this.maxZoom = 2;
  2126. var v = this;
  2127. jm.util.dom.add_event(this.e_editor, 'keydown', function (e) {
  2128. var evt = e || event;
  2129. if (evt.keyCode == 13) { v.edit_node_end(); evt.stopPropagation(); }
  2130. });
  2131. jm.util.dom.add_event(this.e_editor, 'blur', function (e) {
  2132. v.edit_node_end();
  2133. });
  2134. this.container.appendChild(this.e_panel);
  2135. },
  2136. add_event: function (obj, event_name, event_handle) {
  2137. jm.util.dom.add_event(this.e_nodes, event_name, function (e) {
  2138. var evt = e || event;
  2139. event_handle.call(obj, evt);
  2140. });
  2141. },
  2142. get_binded_nodeid: function (element) {
  2143. if (element == null) {
  2144. return null;
  2145. }
  2146. var tagName = element.tagName.toLowerCase();
  2147. if (tagName == 'jmnodes' || tagName == 'body' || tagName == 'html') {
  2148. return null;
  2149. }
  2150. if (tagName == 'jmnode' || tagName == 'jmexpander') {
  2151. return element.getAttribute('nodeid');
  2152. } else {
  2153. return this.get_binded_nodeid(element.parentElement);
  2154. }
  2155. },
  2156. is_expander: function (element) {
  2157. return (element.tagName.toLowerCase() == 'jmexpander');
  2158. },
  2159. reset: function () {
  2160. logger.debug('view.reset');
  2161. this.selected_node = null;
  2162. this.clear_lines();
  2163. this.clear_nodes();
  2164. this.reset_theme();
  2165. },
  2166. reset_theme: function () {
  2167. var theme_name = this.jm.options.theme;
  2168. if (!!theme_name) {
  2169. this.e_nodes.className = 'theme-' + theme_name;
  2170. } else {
  2171. this.e_nodes.className = '';
  2172. }
  2173. },
  2174. reset_custom_style: function () {
  2175. var nodes = this.jm.mind.nodes;
  2176. for (var nodeid in nodes) {
  2177. this.reset_node_custom_style(nodes[nodeid]);
  2178. }
  2179. },
  2180. load: function () {
  2181. logger.debug('view.load');
  2182. this.init_nodes();
  2183. },
  2184. expand_size: function () {
  2185. var min_size = this.layout.get_min_size();
  2186. var min_width = min_size.w + this.opts.hmargin * 2;
  2187. var min_height = min_size.h + this.opts.vmargin * 2;
  2188. var client_w = this.e_panel.clientWidth;
  2189. var client_h = this.e_panel.clientHeight;
  2190. if (client_w < min_width) { client_w = min_width; }
  2191. if (client_h < min_height) { client_h = min_height; }
  2192. this.size.w = client_w;
  2193. this.size.h = client_h;
  2194. },
  2195. init_nodes_size: function (node) {
  2196. var view_data = node._data.view;
  2197. view_data.width = view_data.element.clientWidth;
  2198. view_data.height = view_data.element.clientHeight;
  2199. },
  2200. init_nodes: function () {
  2201. var nodes = this.jm.mind.nodes;
  2202. var doc_frag = $d.createDocumentFragment();
  2203. for (var nodeid in nodes) {
  2204. this.create_node_element(nodes[nodeid], doc_frag);
  2205. }
  2206. this.e_nodes.appendChild(doc_frag);
  2207. for (var nodeid in nodes) {
  2208. this.init_nodes_size(nodes[nodeid]);
  2209. }
  2210. },
  2211. add_node: function (node) {
  2212. this.create_node_element(node, this.e_nodes);
  2213. this.init_nodes_size(node);
  2214. },
  2215. create_node_element: function (node, parent_node) {
  2216. var view_data = null;
  2217. if ('view' in node._data) {
  2218. view_data = node._data.view;
  2219. } else {
  2220. view_data = {};
  2221. node._data.view = view_data;
  2222. }
  2223. var d = $c('jmnode');
  2224. if (node.isroot) {
  2225. d.className = 'root';
  2226. } else {
  2227. var d_e = $c('jmexpander');
  2228. $t(d_e, '-');
  2229. d_e.setAttribute('nodeid', node.id);
  2230. d_e.style.visibility = 'hidden';
  2231. parent_node.appendChild(d_e);
  2232. view_data.expander = d_e;
  2233. }
  2234. if (!!node.topic) {
  2235. if (this.opts.support_html) {
  2236. $h(d, node.topic);
  2237. } else {
  2238. $t(d, node.topic);
  2239. }
  2240. }
  2241. d.setAttribute('nodeid', node.id);
  2242. d.style.visibility = 'hidden';
  2243. this._reset_node_custom_style(d, node.data);
  2244. parent_node.appendChild(d);
  2245. view_data.element = d;
  2246. },
  2247. remove_node: function (node) {
  2248. if (this.selected_node != null && this.selected_node.id == node.id) {
  2249. this.selected_node = null;
  2250. }
  2251. if (this.editing_node != null && this.editing_node.id == node.id) {
  2252. node._data.view.element.removeChild(this.e_editor);
  2253. this.editing_node = null;
  2254. }
  2255. var children = node.children;
  2256. var i = children.length;
  2257. while (i--) {
  2258. this.remove_node(children[i]);
  2259. }
  2260. if (node._data.view) {
  2261. var element = node._data.view.element;
  2262. var expander = node._data.view.expander;
  2263. this.e_nodes.removeChild(element);
  2264. this.e_nodes.removeChild(expander);
  2265. node._data.view.element = null;
  2266. node._data.view.expander = null;
  2267. }
  2268. },
  2269. update_node: function (node) {
  2270. var view_data = node._data.view;
  2271. var element = view_data.element;
  2272. if (!!node.topic) {
  2273. if (this.opts.support_html) {
  2274. $h(element, node.topic);
  2275. } else {
  2276. $t(element, node.topic);
  2277. }
  2278. }
  2279. view_data.width = element.clientWidth;
  2280. view_data.height = element.clientHeight;
  2281. },
  2282. select_node: function (node) {
  2283. if (!!this.selected_node) {
  2284. this.selected_node._data.view.element.className =
  2285. this.selected_node._data.view.element.className.replace(/\s*selected\b/i, '');
  2286. this.reset_node_custom_style(this.selected_node);
  2287. }
  2288. if (!!node) {
  2289. this.selected_node = node;
  2290. node._data.view.element.className += ' selected';
  2291. this.clear_node_custom_style(node);
  2292. }
  2293. },
  2294. select_clear: function () {
  2295. this.select_node(null);
  2296. },
  2297. get_editing_node: function () {
  2298. return this.editing_node;
  2299. },
  2300. is_editing: function () {
  2301. return (!!this.editing_node);
  2302. },
  2303. edit_node_begin: function (node) {
  2304. if (!node.topic) {
  2305. logger.warn("don't edit image nodes");
  2306. return;
  2307. }
  2308. if (this.editing_node != null) {
  2309. this.edit_node_end();
  2310. }
  2311. this.editing_node = node;
  2312. var view_data = node._data.view;
  2313. var element = view_data.element;
  2314. var topic = node.topic;
  2315. var ncs = getComputedStyle(element);
  2316. this.e_editor.value = topic;
  2317. this.e_editor.style.width = (element.clientWidth - parseInt(ncs.getPropertyValue('padding-left')) - parseInt(ncs.getPropertyValue('padding-right'))) + 'px';
  2318. element.innerHTML = '';
  2319. element.appendChild(this.e_editor);
  2320. element.style.zIndex = 5;
  2321. this.e_editor.focus();
  2322. this.e_editor.select();
  2323. },
  2324. edit_node_end: function () {
  2325. if (this.editing_node != null) {
  2326. var node = this.editing_node;
  2327. this.editing_node = null;
  2328. var view_data = node._data.view;
  2329. var element = view_data.element;
  2330. var topic = this.e_editor.value;
  2331. element.style.zIndex = 'auto';
  2332. element.removeChild(this.e_editor);
  2333. if (jm.util.text.is_empty(topic) || node.topic === topic) {
  2334. if (this.opts.support_html) {
  2335. $h(element, node.topic);
  2336. } else {
  2337. $t(element, node.topic);
  2338. }
  2339. } else {
  2340. this.jm.update_node(node.id, topic);
  2341. }
  2342. }
  2343. },
  2344. get_view_offset: function () {
  2345. var bounds = this.layout.bounds;
  2346. var _x = (this.size.w - bounds.e - bounds.w) / 2;
  2347. var _y = this.size.h / 2;
  2348. return { x: _x, y: _y };
  2349. },
  2350. resize: function () {
  2351. this.graph.set_size(1, 1);
  2352. this.e_nodes.style.width = '1px';
  2353. this.e_nodes.style.height = '1px';
  2354. this.expand_size();
  2355. this._show();
  2356. },
  2357. _show: function () {
  2358. this.graph.set_size(this.size.w, this.size.h);
  2359. this.e_nodes.style.width = this.size.w + 'px';
  2360. this.e_nodes.style.height = this.size.h + 'px';
  2361. this.show_nodes();
  2362. this.show_lines();
  2363. //this.layout.cache_valid = true;
  2364. this.jm.invoke_event_handle(jm.event_type.resize, { data: [] });
  2365. },
  2366. zoomIn: function () {
  2367. return this.setZoom(this.actualZoom + this.zoomStep);
  2368. },
  2369. zoomOut: function () {
  2370. return this.setZoom(this.actualZoom - this.zoomStep);
  2371. },
  2372. setZoom: function (zoom) {
  2373. if ((zoom < this.minZoom) || (zoom > this.maxZoom)) {
  2374. return false;
  2375. }
  2376. this.actualZoom = zoom;
  2377. for (var i = 0; i < this.e_panel.children.length; i++) {
  2378. this.e_panel.children[i].style.transform = 'scale(' + zoom + ')';
  2379. };
  2380. this.show(true);
  2381. return true;
  2382. },
  2383. _center_root: function () {
  2384. // center root node
  2385. var outer_w = this.e_panel.clientWidth;
  2386. var outer_h = this.e_panel.clientHeight;
  2387. if (this.size.w > outer_w) {
  2388. var _offset = this.get_view_offset();
  2389. this.e_panel.scrollLeft = _offset.x - outer_w / 2;
  2390. }
  2391. if (this.size.h > outer_h) {
  2392. this.e_panel.scrollTop = (this.size.h - outer_h) / 2;
  2393. }
  2394. },
  2395. show: function (keep_center) {
  2396. logger.debug('view.show');
  2397. this.expand_size();
  2398. this._show();
  2399. if (!!keep_center) {
  2400. this._center_root();
  2401. }
  2402. },
  2403. relayout: function () {
  2404. this.expand_size();
  2405. this._show();
  2406. },
  2407. save_location: function (node) {
  2408. var vd = node._data.view;
  2409. vd._saved_location = {
  2410. x: parseInt(vd.element.style.left) - this.e_panel.scrollLeft,
  2411. y: parseInt(vd.element.style.top) - this.e_panel.scrollTop,
  2412. };
  2413. },
  2414. restore_location: function (node) {
  2415. var vd = node._data.view;
  2416. this.e_panel.scrollLeft = parseInt(vd.element.style.left) - vd._saved_location.x;
  2417. this.e_panel.scrollTop = parseInt(vd.element.style.top) - vd._saved_location.y;
  2418. },
  2419. clear_nodes: function () {
  2420. var mind = this.jm.mind;
  2421. if (mind == null) {
  2422. return;
  2423. }
  2424. var nodes = mind.nodes;
  2425. var node = null;
  2426. for (var nodeid in nodes) {
  2427. node = nodes[nodeid];
  2428. node._data.view.element = null;
  2429. node._data.view.expander = null;
  2430. }
  2431. this.e_nodes.innerHTML = '';
  2432. },
  2433. show_nodes: function () {
  2434. var nodes = this.jm.mind.nodes;
  2435. var node = null;
  2436. var node_element = null;
  2437. var expander = null;
  2438. var p = null;
  2439. var p_expander = null;
  2440. var expander_text = '-';
  2441. var view_data = null;
  2442. var _offset = this.get_view_offset();
  2443. for (var nodeid in nodes) {
  2444. node = nodes[nodeid];
  2445. view_data = node._data.view;
  2446. node_element = view_data.element;
  2447. expander = view_data.expander;
  2448. if (!this.layout.is_visible(node)) {
  2449. node_element.style.display = 'none';
  2450. expander.style.display = 'none';
  2451. continue;
  2452. }
  2453. this.reset_node_custom_style(node);
  2454. p = this.layout.get_node_point(node);
  2455. view_data.abs_x = _offset.x + p.x;
  2456. view_data.abs_y = _offset.y + p.y;
  2457. node_element.style.left = (_offset.x + p.x) + 'px';
  2458. node_element.style.top = (_offset.y + p.y) + 'px';
  2459. node_element.style.display = '';
  2460. node_element.style.visibility = 'visible';
  2461. if (!node.isroot && node.children.length > 0) {
  2462. expander_text = node.expanded ? '-' : '+';
  2463. p_expander = this.layout.get_expander_point(node);
  2464. expander.style.left = (_offset.x + p_expander.x) + 'px';
  2465. expander.style.top = (_offset.y + p_expander.y) + 'px';
  2466. expander.style.display = '';
  2467. expander.style.visibility = 'visible';
  2468. $t(expander, expander_text);
  2469. }
  2470. // hide expander while all children have been removed
  2471. if (!node.isroot && node.children.length == 0) {
  2472. expander.style.display = 'none';
  2473. expander.style.visibility = 'hidden';
  2474. }
  2475. }
  2476. },
  2477. reset_node_custom_style: function (node) {
  2478. this._reset_node_custom_style(node._data.view.element, node.data);
  2479. },
  2480. _reset_node_custom_style: function (node_element, node_data) {
  2481. if ('background-color' in node_data) {
  2482. node_element.style.backgroundColor = node_data['background-color'];
  2483. }
  2484. if ('foreground-color' in node_data) {
  2485. node_element.style.color = node_data['foreground-color'];
  2486. }
  2487. if ('width' in node_data) {
  2488. node_element.style.width = node_data['width'] + 'px';
  2489. }
  2490. if ('height' in node_data) {
  2491. node_element.style.height = node_data['height'] + 'px';
  2492. }
  2493. if ('font-size' in node_data) {
  2494. node_element.style.fontSize = node_data['font-size'] + 'px';
  2495. }
  2496. if ('font-weight' in node_data) {
  2497. node_element.style.fontWeight = node_data['font-weight'];
  2498. }
  2499. if ('font-style' in node_data) {
  2500. node_element.style.fontStyle = node_data['font-style'];
  2501. }
  2502. if ('background-image' in node_data) {
  2503. var backgroundImage = node_data['background-image'];
  2504. if (backgroundImage.startsWith('data') && node_data['width'] && node_data['height']) {
  2505. var img = new Image();
  2506. img.onload = function () {
  2507. var c = $c('canvas');
  2508. c.width = node_element.clientWidth;
  2509. c.height = node_element.clientHeight;
  2510. var img = this;
  2511. if (c.getContext) {
  2512. var ctx = c.getContext('2d');
  2513. ctx.drawImage(img, 2, 2, node_element.clientWidth, node_element.clientHeight);
  2514. var scaledImageData = c.toDataURL();
  2515. node_element.style.backgroundImage = 'url(' + scaledImageData + ')';
  2516. }
  2517. };
  2518. img.src = backgroundImage;
  2519. } else {
  2520. node_element.style.backgroundImage = 'url(' + backgroundImage + ')';
  2521. }
  2522. node_element.style.backgroundSize = '99%';
  2523. if ('background-rotation' in node_data) {
  2524. node_element.style.transform = 'rotate(' + node_data['background-rotation'] + 'deg)';
  2525. }
  2526. }
  2527. },
  2528. clear_node_custom_style: function (node) {
  2529. var node_element = node._data.view.element;
  2530. node_element.style.backgroundColor = "";
  2531. node_element.style.color = "";
  2532. },
  2533. clear_lines: function () {
  2534. this.graph.clear();
  2535. },
  2536. show_lines: function () {
  2537. this.clear_lines();
  2538. var nodes = this.jm.mind.nodes;
  2539. var node = null;
  2540. var pin = null;
  2541. var pout = null;
  2542. var _offset = this.get_view_offset();
  2543. for (var nodeid in nodes) {
  2544. node = nodes[nodeid];
  2545. if (!!node.isroot) { continue; }
  2546. if (('visible' in node._data.layout) && !node._data.layout.visible) { continue; }
  2547. pin = this.layout.get_node_point_in(node);
  2548. pout = this.layout.get_node_point_out(node.parent);
  2549. this.graph.draw_line(pout, pin, _offset);
  2550. }
  2551. },
  2552. };
  2553. // shortcut provider
  2554. jm.shortcut_provider = function (jm, options) {
  2555. this.jm = jm;
  2556. this.opts = options;
  2557. this.mapping = options.mapping;
  2558. this.handles = options.handles;
  2559. this._mapping = {};
  2560. };
  2561. jm.shortcut_provider.prototype = {
  2562. init: function () {
  2563. jm.util.dom.add_event($d, 'keydown', this.handler.bind(this));
  2564. this.handles['addchild'] = this.handle_addchild;
  2565. this.handles['addbrother'] = this.handle_addbrother;
  2566. this.handles['editnode'] = this.handle_editnode;
  2567. this.handles['delnode'] = this.handle_delnode;
  2568. this.handles['toggle'] = this.handle_toggle;
  2569. this.handles['up'] = this.handle_up;
  2570. this.handles['down'] = this.handle_down;
  2571. this.handles['left'] = this.handle_left;
  2572. this.handles['right'] = this.handle_right;
  2573. for (var handle in this.mapping) {
  2574. if (!!this.mapping[handle] && (handle in this.handles)) {
  2575. this._mapping[this.mapping[handle]] = this.handles[handle];
  2576. }
  2577. }
  2578. },
  2579. enable_shortcut: function () {
  2580. this.opts.enable = true;
  2581. },
  2582. disable_shortcut: function () {
  2583. this.opts.enable = false;
  2584. },
  2585. handler: function (e) {
  2586. if (this.jm.view.is_editing()) { return; }
  2587. var evt = e || event;
  2588. if (!this.opts.enable) { return true; }
  2589. var kc = evt.keyCode + (evt.metaKey << 13) + (evt.ctrlKey << 12) + (evt.altKey << 11) + (evt.shiftKey << 10);
  2590. if (kc in this._mapping) {
  2591. this._mapping[kc].call(this, this.jm, e);
  2592. }
  2593. },
  2594. handle_addchild: function (_jm, e) {
  2595. var selected_node = _jm.get_selected_node();
  2596. if (!!selected_node) {
  2597. var nodeid = jm.util.uuid.newid();
  2598. var node = _jm.add_node(selected_node, nodeid, 'New Node');
  2599. if (!!node) {
  2600. _jm.select_node(nodeid);
  2601. _jm.begin_edit(nodeid);
  2602. }
  2603. }
  2604. },
  2605. handle_addbrother: function (_jm, e) {
  2606. var selected_node = _jm.get_selected_node();
  2607. if (!!selected_node && !selected_node.isroot) {
  2608. var nodeid = jm.util.uuid.newid();
  2609. var node = _jm.insert_node_after(selected_node, nodeid, 'New Node');
  2610. if (!!node) {
  2611. _jm.select_node(nodeid);
  2612. _jm.begin_edit(nodeid);
  2613. }
  2614. }
  2615. },
  2616. handle_editnode: function (_jm, e) {
  2617. var selected_node = _jm.get_selected_node();
  2618. if (!!selected_node) {
  2619. _jm.begin_edit(selected_node);
  2620. }
  2621. },
  2622. handle_delnode: function (_jm, e) {
  2623. var selected_node = _jm.get_selected_node();
  2624. if (!!selected_node && !selected_node.isroot) {
  2625. _jm.select_node(selected_node.parent);
  2626. _jm.remove_node(selected_node);
  2627. }
  2628. },
  2629. handle_toggle: function (_jm, e) {
  2630. var evt = e || event;
  2631. var selected_node = _jm.get_selected_node();
  2632. if (!!selected_node) {
  2633. _jm.toggle_node(selected_node.id);
  2634. evt.stopPropagation();
  2635. evt.preventDefault();
  2636. }
  2637. },
  2638. handle_up: function (_jm, e) {
  2639. var evt = e || event;
  2640. var selected_node = _jm.get_selected_node();
  2641. if (!!selected_node) {
  2642. var up_node = _jm.find_node_before(selected_node);
  2643. if (!up_node) {
  2644. var np = _jm.find_node_before(selected_node.parent);
  2645. if (!!np && np.children.length > 0) {
  2646. up_node = np.children[np.children.length - 1];
  2647. }
  2648. }
  2649. if (!!up_node) {
  2650. _jm.select_node(up_node);
  2651. }
  2652. evt.stopPropagation();
  2653. evt.preventDefault();
  2654. }
  2655. },
  2656. handle_down: function (_jm, e) {
  2657. var evt = e || event;
  2658. var selected_node = _jm.get_selected_node();
  2659. if (!!selected_node) {
  2660. var down_node = _jm.find_node_after(selected_node);
  2661. if (!down_node) {
  2662. var np = _jm.find_node_after(selected_node.parent);
  2663. if (!!np && np.children.length > 0) {
  2664. down_node = np.children[0];
  2665. }
  2666. }
  2667. if (!!down_node) {
  2668. _jm.select_node(down_node);
  2669. }
  2670. evt.stopPropagation();
  2671. evt.preventDefault();
  2672. }
  2673. },
  2674. handle_left: function (_jm, e) {
  2675. this._handle_direction(_jm, e, jm.direction.left);
  2676. },
  2677. handle_right: function (_jm, e) {
  2678. this._handle_direction(_jm, e, jm.direction.right);
  2679. },
  2680. _handle_direction: function (_jm, e, d) {
  2681. var evt = e || event;
  2682. var selected_node = _jm.get_selected_node();
  2683. var node = null;
  2684. if (!!selected_node) {
  2685. if (selected_node.isroot) {
  2686. var c = selected_node.children;
  2687. var children = [];
  2688. for (var i = 0; i < c.length; i++) {
  2689. if (c[i].direction === d) {
  2690. children.push(i)
  2691. }
  2692. }
  2693. node = c[children[Math.floor((children.length - 1) / 2)]];
  2694. }
  2695. else if (selected_node.direction === d) {
  2696. var children = selected_node.children;
  2697. var childrencount = children.length;
  2698. if (childrencount > 0) {
  2699. node = children[Math.floor((childrencount - 1) / 2)]
  2700. }
  2701. } else {
  2702. node = selected_node.parent;
  2703. }
  2704. if (!!node) {
  2705. _jm.select_node(node);
  2706. }
  2707. evt.stopPropagation();
  2708. evt.preventDefault();
  2709. }
  2710. },
  2711. };
  2712. // plugin
  2713. jm.plugin = function (name, init) {
  2714. this.name = name;
  2715. this.init = init;
  2716. };
  2717. jm.plugins = [];
  2718. jm.register_plugin = function (plugin) {
  2719. if (plugin instanceof jm.plugin) {
  2720. jm.plugins.push(plugin);
  2721. }
  2722. };
  2723. jm.init_plugins = function (sender) {
  2724. $w.setTimeout(function () {
  2725. jm._init_plugins(sender);
  2726. }, 0);
  2727. };
  2728. jm._init_plugins = function (sender) {
  2729. var l = jm.plugins.length;
  2730. var fn_init = null;
  2731. for (var i = 0; i < l; i++) {
  2732. fn_init = jm.plugins[i].init;
  2733. if (typeof fn_init === 'function') {
  2734. fn_init(sender);
  2735. }
  2736. }
  2737. };
  2738. // quick way
  2739. jm.show = function (options, mind) {
  2740. var _jm = new jm(options);
  2741. _jm.show(mind);
  2742. return _jm;
  2743. };
  2744. // export jsmind
  2745. if (typeof module !== 'undefined' && typeof exports === 'object') {
  2746. module.exports = jm;
  2747. } else if (typeof define === 'function' && (define.amd || define.cmd)) {
  2748. define(function () { return jm; });
  2749. } else {
  2750. $w[__name__] = jm;
  2751. }
  2752. })(typeof window !== 'undefined' ? window : global);