jsmind-simbiotico
Version:
jsMind is a pure javascript library for mindmap, it base on html5 canvas. jsMind was released under BSD license, you can embed it in any project, if only you observe the license.
1,369 lines (1,262 loc) • 112 kB
JavaScript
/**
* @license BSD
* @copyright 2014-2023 hizzgdev@163.com
*
* Project Home:
* https://github.com/hizzgdev/jsmind/
*/
; (function ($w) {
'use strict';
// set 'jsMind' as the library name.
// __name__ should be a const value, Never try to change it easily.
var __name__ = 'jsMind';
// library version
var __version__ = '0.5.2';
// author
var __author__ = 'hizzgdev@163.com';
// an noop function define
var _noop = function () { };
var logger = (typeof console === 'undefined') ? {
log: _noop, debug: _noop, error: _noop, warn: _noop, info: _noop
} : console;
// check global variables
if (typeof module === 'undefined' || !module.exports) {
if (typeof $w[__name__] != 'undefined') {
logger.log(__name__ + ' has been already exist.');
return;
}
}
// shortcut of methods in dom
var $d = $w.document;
var $g = function (id) { return $d.getElementById(id); };
var $c = function (tag) { return $d.createElement(tag); };
var $t = function (n, t) { if (n.hasChildNodes()) { n.firstChild.nodeValue = t; } else { n.appendChild($d.createTextNode(t)); } };
var $h = function (n, t) {
if (t instanceof HTMLElement) {
n.innerHTML = '';
n.appendChild(t);
} else {
n.innerHTML = t;
}
};
// detect isElement
var $i = function (el) { return !!el && (typeof el === 'object') && (el.nodeType === 1) && (typeof el.style === 'object') && (typeof el.ownerDocument === 'object'); };
if (typeof String.prototype.startsWith != 'function') { String.prototype.startsWith = function (p) { return this.slice(0, p.length) === p; }; }
var DEFAULT_OPTIONS = {
container: '', // id of the container
editable: false, // you can change it in your options
theme: null,
mode: 'full', // full or side
support_html: true,
view: {
engine: 'canvas',
hmargin: 100,
vmargin: 50,
line_width: 2,
line_color: '#555',
draggable: false, // drag the mind map with your mouse, when it's larger that the container
hide_scrollbars_when_draggable: false, // hide container scrollbars, when mind map is larger than container and draggable option is true.
node_overflow: 'hidden' // hidden or wrap
},
layout: {
hspace: 30,
vspace: 20,
pspace: 13
},
default_event_handle: {
enable_mousedown_handle: true,
enable_click_handle: true,
enable_dblclick_handle: true,
enable_mousewheel_handle: true
},
shortcut: {
enable: true,
handles: {
},
mapping: {
addchild: [45, 4096+13], // Insert, Ctrl+Enter
addbrother: 13, // Enter
editnode: 113,// F2
delnode: 46, // Delete
toggle: 32, // Space
left: 37, // Left
up: 38, // Up
right: 39, // Right
down: 40, // Down
}
},
};
// core object
var jm = function (options) {
jm.current = this;
this.version = __version__;
var opts = {};
jm.util.json.merge(opts, DEFAULT_OPTIONS);
jm.util.json.merge(opts, options);
if (!opts.container) {
logger.error('the options.container should not be null or empty.');
return;
}
this.options = opts;
this.initialized = false;
this.mind = null;
this.event_handles = [];
this.init();
};
// ============= static object =============================================
jm.direction = {
left: -1, center: 0, right: 1, of: function (dir) {
if (!dir || dir === -1 || dir === 0 || dir === 1) {
return dir;
}
if (dir === '-1' || dir === '0' || dir === '1') {
return parseInt(dir);
}
if (dir.toLowerCase() === 'left') {
return this.left;
}
if (dir.toLowerCase() === 'right') {
return this.right;
}
if (dir.toLowerCase() === 'center') {
return this.center;
}
}
};
jm.event_type = { show: 1, resize: 2, edit: 3, select: 4 };
jm.key = { meta: 1 << 13, ctrl: 1 << 12, alt: 1 << 11, shift: 1 << 10 };
jm.node = function (sId, iIndex, sTopic, oData, bIsRoot, oParent, eDirection, bExpanded) {
if (!sId) { logger.error('invalid node id'); return; }
if (typeof iIndex != 'number') { logger.error('invalid node index'); return; }
if (typeof bExpanded === 'undefined') { bExpanded = true; }
this.id = sId;
this.index = iIndex;
this.topic = sTopic;
this.data = oData || {};
this.isroot = bIsRoot;
this.parent = oParent;
this.direction = eDirection;
this.expanded = !!bExpanded;
this.children = [];
this._data = {};
};
jm.node.compare = function (node1, node2) {
// '-1' is alwary the last
var r = 0;
var i1 = node1.index;
var i2 = node2.index;
if (i1 >= 0 && i2 >= 0) {
r = i1 - i2;
} else if (i1 == -1 && i2 == -1) {
r = 0;
} else if (i1 == -1) {
r = 1;
} else if (i2 == -1) {
r = -1;
} else {
r = 0;
}
//logger.debug(i1+' <> '+i2+' = '+r);
return r;
};
jm.node.inherited = function (pnode, node) {
if (!!pnode && !!node) {
if (pnode.id === node.id) {
return true;
}
if (pnode.isroot) {
return true;
}
var pid = pnode.id;
var p = node;
while (!p.isroot) {
p = p.parent;
if (p.id === pid) {
return true;
}
}
}
return false;
};
jm.node.is_node = function (n) {
return !!n && n instanceof jm.node;
};
jm.node.prototype = {
get_location: function () {
var vd = this._data.view;
return {
x: vd.abs_x,
y: vd.abs_y
};
},
get_size: function () {
var vd = this._data.view;
return {
w: vd.width,
h: vd.height
}
}
};
jm.mind = function () {
this.name = null;
this.author = null;
this.version = null;
this.root = null;
this.selected = null;
this.nodes = {};
};
jm.mind.prototype = {
get_node: function (nodeid) {
if (nodeid in this.nodes) {
return this.nodes[nodeid];
} else {
logger.warn('the node[id=' + nodeid + '] can not be found');
return null;
}
},
set_root: function (nodeid, topic, data) {
if (this.root == null) {
this.root = new jm.node(nodeid, 0, topic, data, true);
this._put_node(this.root);
return this.root;
} else {
logger.error('root node is already exist');
return null;
}
},
add_node: function (parent_node, nodeid, topic, data, direction, expanded, idx) {
if (!jm.util.is_node(parent_node)) {
logger.error('the parent_node ' + parent_node + ' is not a node.');
return null;
}
var node_index = idx || -1;
var node = new jm.node(nodeid, node_index, topic, data, false, parent_node, parent_node.direction, expanded);
if (parent_node.isroot) {
node.direction = direction || jm.direction.right;
}
if (this._put_node(node)) {
parent_node.children.push(node);
this._reindex(parent_node);
} else {
logger.error('fail, the nodeid \'' + node.id + '\' has been already exist.');
node = null;
}
return node;
},
insert_node_before: function (node_before, nodeid, topic, data, direction) {
if (!jm.util.is_node(node_before)) {
logger.error('the node_before ' + node_before + ' is not a node.');
return null;
}
var node_index = node_before.index - 0.5;
return this.add_node(node_before.parent, nodeid, topic, data, direction, true, node_index);
},
get_node_before: function (node) {
if (!jm.util.is_node(node)) {
var the_node = this.get_node(node);
if (!the_node) {
logger.error('the node[id=' + node + '] can not be found.');
return null;
} else {
return this.get_node_before(the_node);
}
}
if (node.isroot) { return null; }
var idx = node.index - 2;
if (idx >= 0) {
return node.parent.children[idx];
} else {
return null;
}
},
insert_node_after: function (node_after, nodeid, topic, data, direction) {
if (!jm.util.is_node(node_after)) {
logger.error('the node_after ' + node_after + ' is not a node.');
return null;
}
var node_index = node_after.index + 0.5;
return this.add_node(node_after.parent, nodeid, topic, data, direction, true, node_index);
},
get_node_after: function (node) {
if (!jm.util.is_node(node)) {
var the_node = this.get_node(node);
if (!the_node) {
logger.error('the node[id=' + node + '] can not be found.');
return null;
} else {
return this.get_node_after(the_node);
}
}
if (node.isroot) { return null; }
var idx = node.index;
var brothers = node.parent.children;
if (brothers.length > idx) {
return node.parent.children[idx];
} else {
return null;
}
},
move_node: function (node, before_id, parent_id, direction) {
if (!jm.util.is_node(node)) {
logger.error('the parameter node ' + node + ' is not a node.');
return null;
}
if (!parent_id) {
parent_id = node.parent.id;
}
return this._move_node(node, before_id, parent_id, direction);
},
_flow_node_direction: function (node, direction) {
if (typeof direction === 'undefined') {
direction = node.direction;
} else {
node.direction = direction;
}
var len = node.children.length;
while (len--) {
this._flow_node_direction(node.children[len], direction);
}
},
_move_node_internal: function (node, beforeid) {
if (!!node && !!beforeid) {
if (beforeid == '_last_') {
node.index = -1;
this._reindex(node.parent);
} else if (beforeid == '_first_') {
node.index = 0;
this._reindex(node.parent);
} else {
var node_before = (!!beforeid) ? this.get_node(beforeid) : null;
if (node_before != null && node_before.parent != null && node_before.parent.id == node.parent.id) {
node.index = node_before.index - 0.5;
this._reindex(node.parent);
}
}
}
return node;
},
_move_node: function (node, beforeid, parentid, direction) {
if (!!node && !!parentid) {
var parent_node = this.get_node(parentid)
if (jm.node.inherited(node, parent_node)) {
logger.error('can not move a node to its children');
return null;
}
if (node.parent.id != parentid) {
// remove from parent's children
var sibling = node.parent.children;
var si = sibling.length;
while (si--) {
if (sibling[si].id == node.id) {
sibling.splice(si, 1);
break;
}
}
node.parent = parent_node;
parent_node.children.push(node);
}
if (node.parent.isroot) {
if (direction == jm.direction.left) {
node.direction = direction;
} else {
node.direction = jm.direction.right;
}
} else {
node.direction = node.parent.direction;
}
this._move_node_internal(node, beforeid);
this._flow_node_direction(node);
}
return node;
},
remove_node: function (node) {
if (!jm.util.is_node(node)) {
logger.error('the parameter node ' + node + ' is not a node.');
return false;
}
if (node.isroot) {
logger.error('fail, can not remove root node');
return false;
}
if (this.selected != null && this.selected.id == node.id) {
this.selected = null;
}
// clean all subordinate nodes
var children = node.children;
var ci = children.length;
while (ci--) {
this.remove_node(children[ci]);
}
// clean all children
children.length = 0;
// remove from parent's children
var sibling = node.parent.children;
var si = sibling.length;
while (si--) {
if (sibling[si].id == node.id) {
sibling.splice(si, 1);
break;
}
}
// remove from global nodes
delete this.nodes[node.id];
// clean all properties
for (var k in node) {
delete node[k];
}
// remove it's self
node = null;
//delete node;
return true;
},
_put_node: function (node) {
if (node.id in this.nodes) {
logger.warn('the nodeid \'' + node.id + '\' has been already exist.');
return false;
} else {
this.nodes[node.id] = node;
return true;
}
},
_reindex: function (node) {
if (node instanceof jm.node) {
node.children.sort(jm.node.compare);
for (var i = 0; i < node.children.length; i++) {
node.children[i].index = i + 1;
}
}
},
};
jm.format = {
node_tree: {
example: {
"meta": {
"name": __name__,
"author": __author__,
"version": __version__
},
"format": "node_tree",
"data": { "id": "root", "topic": "jsMind Example" }
},
get_mind: function (source) {
var df = jm.format.node_tree;
var mind = new jm.mind();
mind.name = source.meta.name;
mind.author = source.meta.author;
mind.version = source.meta.version;
df._parse(mind, source.data);
return mind;
},
get_data: function (mind) {
var df = jm.format.node_tree;
var json = {};
json.meta = {
name: mind.name,
author: mind.author,
version: mind.version
};
json.format = 'node_tree';
json.data = df._buildnode(mind.root);
return json;
},
_parse: function (mind, node_root) {
var df = jm.format.node_tree;
var data = df._extract_data(node_root);
mind.set_root(node_root.id, node_root.topic, data);
if ('children' in node_root) {
var children = node_root.children;
for (var i = 0; i < children.length; i++) {
df._extract_subnode(mind, mind.root, children[i]);
}
}
},
_extract_data: function (node_json) {
var data = {};
for (var k in node_json) {
if (k == 'id' || k == 'topic' || k == 'children' || k == 'direction' || k == 'expanded') {
continue;
}
data[k] = node_json[k];
}
return data;
},
_extract_subnode: function (mind, node_parent, node_json) {
var df = jm.format.node_tree;
var data = df._extract_data(node_json);
var d = null;
if (node_parent.isroot) {
d = node_json.direction == 'left' ? jm.direction.left : jm.direction.right;
}
var node = mind.add_node(node_parent, node_json.id, node_json.topic, data, d, node_json.expanded);
if (!!node_json['children']) {
var children = node_json.children;
for (var i = 0; i < children.length; i++) {
df._extract_subnode(mind, node, children[i]);
}
}
},
_buildnode: function (node) {
var df = jm.format.node_tree;
if (!(node instanceof jm.node)) { return; }
var o = {
id: node.id,
topic: node.topic,
expanded: node.expanded
};
if (!!node.parent && node.parent.isroot) {
o.direction = node.direction == jm.direction.left ? 'left' : 'right';
}
if (node.data != null) {
var node_data = node.data;
for (var k in node_data) {
o[k] = node_data[k];
}
}
var children = node.children;
if (children.length > 0) {
o.children = [];
for (var i = 0; i < children.length; i++) {
o.children.push(df._buildnode(children[i]));
}
}
return o;
}
},
node_array: {
example: {
"meta": {
"name": __name__,
"author": __author__,
"version": __version__
},
"format": "node_array",
"data": [
{ "id": "root", "topic": "jsMind Example", "isroot": true }
]
},
get_mind: function (source) {
var df = jm.format.node_array;
var mind = new jm.mind();
mind.name = source.meta.name;
mind.author = source.meta.author;
mind.version = source.meta.version;
df._parse(mind, source.data);
return mind;
},
get_data: function (mind) {
var df = jm.format.node_array;
var json = {};
json.meta = {
name: mind.name,
author: mind.author,
version: mind.version
};
json.format = 'node_array';
json.data = [];
df._array(mind, json.data);
return json;
},
_parse: function (mind, node_array) {
var df = jm.format.node_array;
var narray = node_array.slice(0);
// reverse array for improving looping performance
narray.reverse();
var root_node = df._extract_root(mind, narray);
if (!!root_node) {
df._extract_subnode(mind, root_node, narray);
} else {
logger.error('root node can not be found');
}
},
_extract_root: function (mind, node_array) {
var df = jm.format.node_array;
var i = node_array.length;
while (i--) {
if ('isroot' in node_array[i] && node_array[i].isroot) {
var root_json = node_array[i];
var data = df._extract_data(root_json);
var node = mind.set_root(root_json.id, root_json.topic, data);
node_array.splice(i, 1);
return node;
}
}
return null;
},
_extract_subnode: function (mind, parent_node, node_array) {
var df = jm.format.node_array;
var i = node_array.length;
var node_json = null;
var data = null;
var extract_count = 0;
while (i--) {
node_json = node_array[i];
if (node_json.parentid == parent_node.id) {
data = df._extract_data(node_json);
var d = null;
var node_direction = node_json.direction;
if (!!node_direction) {
d = node_direction == 'left' ? jm.direction.left : jm.direction.right;
}
var node = mind.add_node(parent_node, node_json.id, node_json.topic, data, d, node_json.expanded);
node_array.splice(i, 1);
extract_count++;
var sub_extract_count = df._extract_subnode(mind, node, node_array);
if (sub_extract_count > 0) {
// reset loop index after extract subordinate node
i = node_array.length;
extract_count += sub_extract_count;
}
}
}
return extract_count;
},
_extract_data: function (node_json) {
var data = {};
for (var k in node_json) {
if (k == 'id' || k == 'topic' || k == 'parentid' || k == 'isroot' || k == 'direction' || k == 'expanded') {
continue;
}
data[k] = node_json[k];
}
return data;
},
_array: function (mind, node_array) {
var df = jm.format.node_array;
df._array_node(mind.root, node_array);
},
_array_node: function (node, node_array) {
var df = jm.format.node_array;
if (!(node instanceof jm.node)) { return; }
var o = {
id: node.id,
topic: node.topic,
expanded: node.expanded
};
if (!!node.parent) {
o.parentid = node.parent.id;
}
if (node.isroot) {
o.isroot = true;
}
if (!!node.parent && node.parent.isroot) {
o.direction = node.direction == jm.direction.left ? 'left' : 'right';
}
if (node.data != null) {
var node_data = node.data;
for (var k in node_data) {
o[k] = node_data[k];
}
}
node_array.push(o);
var ci = node.children.length;
for (var i = 0; i < ci; i++) {
df._array_node(node.children[i], node_array);
}
},
},
freemind: {
example: {
"meta": {
"name": __name__,
"author": __author__,
"version": __version__
},
"format": "freemind",
"data": "<map version=\"1.0.1\"><node ID=\"root\" TEXT=\"freemind Example\"/></map>"
},
get_mind: function (source) {
var df = jm.format.freemind;
var mind = new jm.mind();
mind.name = source.meta.name;
mind.author = source.meta.author;
mind.version = source.meta.version;
var xml = source.data;
var xml_doc = df._parse_xml(xml);
var xml_root = df._find_root(xml_doc);
df._load_node(mind, null, xml_root);
return mind;
},
get_data: function (mind) {
var df = jm.format.freemind;
var json = {};
json.meta = {
name: mind.name,
author: mind.author,
version: mind.version
};
json.format = 'freemind';
var xmllines = [];
xmllines.push('<map version=\"1.0.1\">');
df._buildmap(mind.root, xmllines);
xmllines.push('</map>');
json.data = xmllines.join(' ');
return json;
},
_parse_xml: function (xml) {
var xml_doc = null;
if (window.DOMParser) {
var parser = new DOMParser();
xml_doc = parser.parseFromString(xml, 'text/xml');
} else { // Internet Explorer
xml_doc = new ActiveXObject('Microsoft.XMLDOM');
xml_doc.async = false;
xml_doc.loadXML(xml);
}
return xml_doc;
},
_find_root: function (xml_doc) {
var nodes = xml_doc.childNodes;
var node = null;
var root = null;
var n = null;
for (var i = 0; i < nodes.length; i++) {
n = nodes[i];
if (n.nodeType == 1 && n.tagName == 'map') {
node = n;
break;
}
}
if (!!node) {
var ns = node.childNodes;
node = null;
for (var i = 0; i < ns.length; i++) {
n = ns[i];
if (n.nodeType == 1 && n.tagName == 'node') {
node = n;
break;
}
}
}
return node;
},
_load_node: function (mind, parent_node, xml_node) {
var df = jm.format.freemind;
var node_id = xml_node.getAttribute('ID');
var node_topic = xml_node.getAttribute('TEXT');
// look for richcontent
if (node_topic == null) {
var topic_children = xml_node.childNodes;
var topic_child = null;
for (var i = 0; i < topic_children.length; i++) {
topic_child = topic_children[i];
//logger.debug(topic_child.tagName);
if (topic_child.nodeType == 1 && topic_child.tagName === 'richcontent') {
node_topic = topic_child.textContent;
break;
}
}
}
var node_data = df._load_attributes(xml_node);
var node_expanded = ('expanded' in node_data) ? (node_data.expanded == 'true') : true;
delete node_data.expanded;
var node_position = xml_node.getAttribute('POSITION');
var node_direction = null;
if (!!node_position) {
node_direction = node_position == 'left' ? jm.direction.left : jm.direction.right;
}
//logger.debug(node_position +':'+ node_direction);
var node = null;
if (!!parent_node) {
node = mind.add_node(parent_node, node_id, node_topic, node_data, node_direction, node_expanded);
} else {
node = mind.set_root(node_id, node_topic, node_data);
}
var children = xml_node.childNodes;
var child = null;
for (var i = 0; i < children.length; i++) {
child = children[i];
if (child.nodeType == 1 && child.tagName == 'node') {
df._load_node(mind, node, child);
}
}
},
_load_attributes: function (xml_node) {
var children = xml_node.childNodes;
var attr = null;
var attr_data = {};
for (var i = 0; i < children.length; i++) {
attr = children[i];
if (attr.nodeType == 1 && attr.tagName === 'attribute') {
attr_data[attr.getAttribute('NAME')] = attr.getAttribute('VALUE');
}
}
return attr_data;
},
_buildmap: function (node, xmllines) {
var df = jm.format.freemind;
var pos = null;
if (!!node.parent && node.parent.isroot) {
pos = node.direction === jm.direction.left ? 'left' : 'right';
}
xmllines.push('<node');
xmllines.push('ID=\"' + node.id + '\"');
if (!!pos) {
xmllines.push('POSITION=\"' + pos + '\"');
}
xmllines.push('TEXT=\"' + node.topic + '\">');
// store expanded status as an attribute
xmllines.push('<attribute NAME=\"expanded\" VALUE=\"' + node.expanded + '\"/>');
// for attributes
var node_data = node.data;
if (node_data != null) {
for (var k in node_data) {
xmllines.push('<attribute NAME=\"' + k + '\" VALUE=\"' + node_data[k] + '\"/>');
}
}
// for children
var children = node.children;
for (var i = 0; i < children.length; i++) {
df._buildmap(children[i], xmllines);
}
xmllines.push('</node>');
},
},
};
// ============= utility object =============================================
jm.util = {
is_node: function (node) {
return !!node && node instanceof jm.node;
},
ajax: {
request: function (url, param, method, callback, fail_callback) {
var a = jm.util.ajax;
var p = null;
var tmp_param = [];
for (var k in param) {
tmp_param.push(encodeURIComponent(k) + '=' + encodeURIComponent(param[k]));
}
if (tmp_param.length > 0) {
p = tmp_param.join('&');
}
var xhr = new XMLHttpRequest();
if (!xhr) { return; }
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200 || xhr.status == 0) {
if (typeof callback === 'function') {
var data = jm.util.json.string2json(xhr.responseText);
if (data != null) {
callback(data);
} else {
callback(xhr.responseText);
}
}
} else {
if (typeof fail_callback === 'function') {
fail_callback(xhr);
} else {
logger.error('xhr request failed.', xhr);
}
}
}
}
method = method || 'GET';
xhr.open(method, url, true);
xhr.setRequestHeader('If-Modified-Since', '0');
if (method == 'POST') {
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=utf-8');
xhr.send(p);
} else {
xhr.send();
}
},
get: function (url, callback) {
return jm.util.ajax.request(url, {}, 'GET', callback);
},
post: function (url, param, callback) {
return jm.util.ajax.request(url, param, 'POST', callback);
}
},
dom: {
//target,eventType,handler
add_event: function (t, e, h) {
if (!!t.addEventListener) {
t.addEventListener(e, h, false);
} else {
t.attachEvent('on' + e, h);
}
}
},
file: {
read: function (file_data, fn_callback) {
var reader = new FileReader();
reader.onload = function () {
if (typeof fn_callback === 'function') {
fn_callback(this.result, file_data.name);
}
};
reader.readAsText(file_data);
},
save: function (file_data, type, name) {
var blob;
if (typeof $w.Blob === 'function') {
blob = new Blob([file_data], { type: type });
} else {
var BlobBuilder = $w.BlobBuilder || $w.MozBlobBuilder || $w.WebKitBlobBuilder || $w.MSBlobBuilder;
var bb = new BlobBuilder();
bb.append(file_data);
blob = bb.getBlob(type);
}
if (navigator.msSaveBlob) {
navigator.msSaveBlob(blob, name);
} else {
var URL = $w.URL || $w.webkitURL;
var bloburl = URL.createObjectURL(blob);
var anchor = $c('a');
if ('download' in anchor) {
anchor.style.visibility = 'hidden';
anchor.href = bloburl;
anchor.download = name;
$d.body.appendChild(anchor);
var evt = $d.createEvent('MouseEvents');
evt.initEvent('click', true, true);
anchor.dispatchEvent(evt);
$d.body.removeChild(anchor);
} else {
location.href = bloburl;
}
}
}
},
json: {
json2string: function (json) {
return JSON.stringify(json);
},
string2json: function (json_str) {
return JSON.parse(json_str);
},
merge: function (b, a) {
for (var o in a) {
if (o in b) {
if (typeof b[o] === 'object' &&
Object.prototype.toString.call(b[o]).toLowerCase() == '[object object]' &&
!b[o].length) {
jm.util.json.merge(b[o], a[o]);
} else {
b[o] = a[o];
}
} else {
b[o] = a[o];
}
}
return b;
}
},
uuid: {
newid: function () {
return (new Date().getTime().toString(16) + Math.random().toString(16).substring(2)).substring(2, 18);
}
},
text: {
is_empty: function (s) {
if (!s) { return true; }
return s.replace(/\s*/, '').length == 0;
}
}
};
jm.prototype = {
init: function () {
if (this.initialized) { return; }
this.initialized = true;
var opts = this.options;
var opts_layout = {
mode: opts.mode,
hspace: opts.layout.hspace,
vspace: opts.layout.vspace,
pspace: opts.layout.pspace
}
var opts_view = {
container: opts.container,
support_html: opts.support_html,
engine: opts.view.engine,
hmargin: opts.view.hmargin,
vmargin: opts.view.vmargin,
line_width: opts.view.line_width,
line_color: opts.view.line_color,
draggable: opts.view.draggable,
hide_scrollbars_when_draggable: opts.view.hide_scrollbars_when_draggable,
node_overflow: opts.view.node_overflow
};
// create instance of function provider
this.data = new jm.data_provider(this);
this.layout = new jm.layout_provider(this, opts_layout);
this.view = new jm.view_provider(this, opts_view);
this.shortcut = new jm.shortcut_provider(this, opts.shortcut);
this.data.init();
this.layout.init();
this.view.init();
this.shortcut.init();
this._event_bind();
jm.init_plugins(this);
},
enable_edit: function () {
this.options.editable = true;
},
disable_edit: function () {
this.options.editable = false;
},
// call enable_event_handle('dblclick')
// options are 'mousedown', 'click', 'dblclick'
enable_event_handle: function (event_handle) {
this.options.default_event_handle['enable_' + event_handle + '_handle'] = true;
},
// call disable_event_handle('dblclick')
// options are 'mousedown', 'click', 'dblclick'
disable_event_handle: function (event_handle) {
this.options.default_event_handle['enable_' + event_handle + '_handle'] = false;
},
get_editable: function () {
return this.options.editable;
},
set_theme: function (theme) {
var theme_old = this.options.theme;
this.options.theme = (!!theme) ? theme : null;
if (theme_old != this.options.theme) {
this.view.reset_theme();
this.view.reset_custom_style();
}
},
_event_bind: function () {
this.view.add_event(this, 'mousedown', this.mousedown_handle);
this.view.add_event(this, 'click', this.click_handle);
this.view.add_event(this, 'dblclick', this.dblclick_handle);
this.view.add_event(this, "mousewheel", this.mousewheel_handle)
},
mousedown_handle: function (e) {
if (!this.options.default_event_handle['enable_mousedown_handle']) {
return;
}
var element = e.target || event.srcElement;
var nodeid = this.view.get_binded_nodeid(element);
if (!!nodeid) {
if (element.tagName.toLowerCase() == 'jmnode') {
this.select_node(nodeid);
}
} else {
this.select_clear();
}
},
click_handle: function (e) {
if (!this.options.default_event_handle['enable_click_handle']) {
return;
}
var element = e.target || event.srcElement;
var isexpander = this.view.is_expander(element);
if (isexpander) {
var nodeid = this.view.get_binded_nodeid(element);
if (!!nodeid) {
this.toggle_node(nodeid);
}
}
},
dblclick_handle: function (e) {
if (!this.options.default_event_handle['enable_dblclick_handle']) {
return;
}
if (this.get_editable()) {
var element = e.target || event.srcElement;
var nodeid = this.view.get_binded_nodeid(element);
if (!!nodeid) {
this.begin_edit(nodeid);
}
}
},
// Use [Ctrl] + Mousewheel, to zoom in/out.
mousewheel_handle: function (event) {
// Test if mousewheel option is enabled and Ctrl key is pressed.
if (!this.options.default_event_handle["enable_mousewheel_handle"] || !window.event.ctrlKey) {
return
}
// Avoid default page scrolling behavior.
event.preventDefault()
if (event.deltaY < 0) {
this.view.zoomIn()
} else {
this.view.zoomOut()
}
},
begin_edit: function (node) {
if (!jm.util.is_node(node)) {
var the_node = this.get_node(node);
if (!the_node) {
logger.error('the node[id=' + node + '] can not be found.');
return false;
} else {
return this.begin_edit(the_node);
}
}
if (this.get_editable()) {
this.view.edit_node_begin(node);
} else {
logger.error('fail, this mind map is not editable.');
return;
}
},
end_edit: function () {
this.view.edit_node_end();
},
toggle_node: function (node) {
if (!jm.util.is_node(node)) {
var the_node = this.get_node(node);
if (!the_node) {
logger.error('the node[id=' + node + '] can not be found.');
return;
} else {
return this.toggle_node(the_node);
}
}
if (node.isroot) { return; }
this.view.save_location(node);
this.layout.toggle_node(node);
this.view.relayout();
this.view.restore_location(node);
},
expand_node: function (node) {
if (!jm.util.is_node(node)) {
var the_node = this.get_node(node);
if (!the_node) {
logger.error('the node[id=' + node + '] can not be found.');
return;
} else {
return this.expand_node(the_node);
}
}
if (node.isroot) { return; }
this.view.save_location(node);
this.layout.expand_node(node);
this.view.relayout();
this.view.restore_location(node);
},
collapse_node: function (node) {
if (!jm.util.is_node(node)) {
var the_node = this.get_node(node);
if (!the_node) {
logger.error('the node[id=' + node + '] can not be found.');
return;
} else {
return this.collapse_node(the_node);
}
}
if (node.isroot) { return; }
this.view.save_location(node);
this.layout.collapse_node(node);
this.view.relayout();
this.view.restore_location(node);
},
expand_all: function () {
this.layout.expand_all();
this.view.relayout();
},
collapse_all: function () {
this.layout.collapse_all();
this.view.relayout();
},
expand_to_depth: function (depth) {
this.layout.expand_to_depth(depth);
this.view.relayout();
},
_reset: function () {
this.view.reset();
this.layout.reset();
this.data.reset();
},
_show: function (mind) {
var m = mind || jm.format.node_array.example;
this.mind = this.data.load(m);
if (!this.mind) {
logger.error('data.load error');
return;
} else {
logger.debug('data.load ok');
}
this.view.load();
logger.debug('view.load ok');
this.layout.layout();
logger.debug('layout.layout ok');
this.view.show(true);
logger.debug('view.show ok');
this.invoke_event_handle(jm.event_type.show, { data: [mind] });
},
show: function (mind) {
this._reset();
this._show(mind);
},
get_meta: function () {
return {
name: this.mind.name,
author: this.mind.author,
version: this.mind.version
};
},
get_data: function (data_format) {
var df = data_format || 'node_tree';
return this.data.get_data(df);
},
get_root: function () {
return this.mind.root;
},
get_node: function (node) {
if (jm.util.is_node(node)) {
return node;
}
return this.mind.get_node(node);
},
add_node: function (parent_node, nodeid, topic, data, direction) {
if (this.get_editable()) {
var the_parent_node = this.get_node(parent_node);
var dir = jm.direction.of(direction)
if (dir === undefined) {
dir = this.layout.calculate_next_child_direction(the_parent_node);
}
var node = this.mind.add_node(the_parent_node, nodeid, topic, data, dir);
if (!!node) {
this.view.add_node(node);
this.layout.layout();
this.view.show(false);
this.view.reset_node_custom_style(node);
this.expand_node(the_parent_node);
this.invoke_event_handle(jm.event_type.edit, { evt: 'add_node', data: [the_parent_node.id, nodeid, topic, data, dir], node: nodeid });
}
return node;
} else {
logger.error('fail, this mind map is not editable');
return null;
}
},
insert_node_before: function (node_before, nodeid, topic, data, direction) {
if (this.get_editable()) {
var the_node_before = this.get_node(node_before);
var dir = jm.direction.of(direction)
if (dir === undefined) {
dir = this.layout.calculate_next_child_direction(the_node_before.parent);
}
var node = this.mind.insert_node_before(the_node_before, nodeid, topic, data, dir);
if (!!node) {
this.view.add_node(node);
this.layout.layout();
this.view.show(false);
this.invoke_event_handle(jm.event_type.edit, { evt: 'insert_node_before', data: [the_node_before.id, nodeid, topic, data, dir], node: nodeid });
}
return node;
} else {
logger.error('fail, this mind map is not editable');
return null;
}
},
insert_node_after: function (node_after, nodeid, topic, data, direction) {
if (this.get_editable()) {
var the_node_after = this.get_node(node_after);
var dir = jm.direction.of(direction)
if (dir === undefined) {
dir = this.layout.calculate_next_child_direction(the_node_after.parent);
}
var node = this.mind.insert_node_after(the_node_after, nodeid, topic, data, dir);
if (!!node) {
this.view.add_node(node);
this.layout.layout();
this.view.show(false);
this.invoke_event_handle(jm.event_type.edit, { evt: 'insert_node_after', data: [the_node_after.id, nodeid, topic, data, dir], node: nodeid });
}
return node;
} else {
logger.error('fail, thi