UNPKG

zui

Version:

一个基于 Bootstrap 深度定制开源前端实践方案,帮助你快速构建现代跨屏应用。

1,323 lines (1,143 loc) 45.3 kB
/* ======================================================================== * ZUI: mindmap.js * http://zui.sexy * ======================================================================== * Copyright (c) 2014-2016 cnezsoft.com; Licensed MIT * ======================================================================== */ (function($, window, document, Math) { 'use strict'; var UDF = 'undefined', newColorIndex = 0; var selectText = function($e) { var doc = document; var element = $e[0], range; if(doc.body.createTextRange) { range = document.body.createTextRange(); range.moveToElementText(element); range.select(); } else if(window.getSelection) { var selection = window.getSelection(); range = document.createRange(); range.selectNodeContents(element); selection.removeAllRanges(); selection.addRange(range); } }; var Mindmap = function(element, options) { this.$ = $(element); this.options = this.getOptions(options); this.lang = this.getLang(); this.data = this.options.data; this.dirtyData = true; this.offsetX = 0; this.offsetY = 0; this.init(); if(!this.options.hotkeyEnable) { $.zui.messager.show(this.lang.hotkeyDisabled); } }; Mindmap.DEFAULTS = { hotkeyEnable: true, hotkeys: { selectPrev: 'Up', selectNext: 'Down', selectLeft: 'left', selectRight: 'right', deleteNode: 'del backspace', addBorther: 'return', addChild: 'tab insert', centerCanvas: 'space' }, lang: 'zh_cn', langs: { 'zh_cn': { defaultName: '灵光闪现', defaultSubName: '灵光', defaultNodeName: '闪现', readonlyTip: '该节点已被设置为只读,无法进行编辑。', hotkeyDisabled: '快捷键不可用,需要 <a target="_blank" href="https://github.com/jeresig/jquery.hotkeys">jquery.hotkeys</a> 插件支持。' } }, data: { text: 'New Project', type: 'root', expand: true, theme: 'default', caption: '', id: $.zui.uuid() + '' }, nodeTeamplate: "<div id='node-{id}' class='mindmap-node expand-{expand}' data-type='{type}' data-id='{id}' data-parent='{parent}'><div class='wrapper'><div class='text'>{text}</div><div class='caption'>{caption}</div></div></div>", hSpace: 100, vSpace: 10, canvasPadding: 20, removingNodeTip: null, lineCurvature: 60, subLineWidth: 4, lineColor: 'rainbow', lineOpacity: 1, lineSaturation: 90, lineLightness: 40, nodeLineWidth: 2 }; // default options Mindmap.prototype.getOptions = function(options) { Mindmap.DEFAULTS.data.text = Mindmap.DEFAULTS.langs.zh_cn.defaultName; options = $.extend({}, Mindmap.DEFAULTS, this.$.data(), options); options.hotkeyEnable = options.hotkeyEnable && (typeof($.hotkeys) != UDF); return options; }; Mindmap.prototype.getLang = function() { var options = this.options; if(!options.lang) { if(typeof(window.config) != 'undefined' && window.config.clientLang) { options.lang = config.clientLang; } else { var hl = $('html').attr('lang'); options.lang = hl ? hl : 'en'; } options.lang = options.lang.replace(/-/, '_').toLowerCase(); } var lang = options.langs[options.lang] || options.langs[Mindmap.DEFAULTS.lang]; if(options.defaultSubName) lang.defaultSubName = options.defaultSubName; if(options.defaultNodeName) lang.defaultNodeName = options.defaultNodeName; return lang; }; Mindmap.prototype.init = function() { this.initDom(); this.initSize(); this.bindEvents(); this.render(); }; Mindmap.prototype.initDom = function() { var $this = this.$; if(!$this.attr('id')) { $this.attr('id', 'mindmap-' + $.zui.uuid()); } this.id = $this.attr('id'); this.$container = $this.children('.mindmap-container'); if(!this.$container.length) { $this.prepend("<div class='mindmap-container'></div>"); this.$container = $this.children('.mindmap-container'); } this.$canvas = this.$container.children('canvas'); if(!this.$canvas.length) { this.$container.prepend("<canvas class='mindmap-bg'></canvas>".format(this.options.canvasSize)); this.$canvas = this.$container.children('canvas'); } this.$desktop = this.$container.children('.mindmap-desktop'); if(!this.$desktop.length) { this.$container.append("<div class='mindmap-desktop'></div>"); this.$desktop = this.$container.children('.mindmap-desktop'); } this.$desktop.attr('unselectable', 'on'); var $shadows = $this.children('.shadow'); if(!$shadows.length) { $this.append("<div class='mindmap-shadow shadow-top'></div><div class='mindmap-shadow shadow-right'></div><div class='mindmap-shadow shadow-bottom'></div><div class='mindmap-shadow shadow-left'></div>"); } }; Mindmap.prototype.initSize = function() { var $this = this.$; this.winWidth = $this.width(); this.winHeight = $this.height(); this.centerX = Math.floor(this.winWidth / 2); this.centerY = Math.floor(this.winHeight / 2); // $canvas.attr('width', this.width) // .attr('height', this.height); if(!this.dirtyData) this.display(); }; Mindmap.prototype.callEvent = function(name, params) { return $.zui.callEvent(this.options[name], params, this); }; /* compute position with offset */ Mindmap.prototype.computePosition = function(pos, inverse) { var flag = inverse ? -1 : 1; if(typeof(pos.left) != UDF) pos.left -= (this.left - this.options.canvasPadding) * flag; if(typeof(pos.top) != UDF) pos.top -= this.top * flag; if(typeof(pos.x) != UDF) pos.x -= (this.left - this.options.canvasPadding) * flag; if(typeof(pos.y) != UDF) pos.y -= this.top * flag; return pos; }; /* compute color with hue value in hsl format*/ Mindmap.prototype.computeColor = function(hue, alpha) { return 'hsla({h}, {s}%, {l}%, {a})'.format({ h: hue, s: this.options.lineSaturation, l: this.options.lineLightness, a: alpha || this.options.lineOpacity }); }; Mindmap.prototype.getNodeData = function(id, nodeData) { if(typeof(id) == 'number') id = id.toString(); if(!nodeData) { nodeData = this.data; } if(id == nodeData.id) { return nodeData; } else if($.isArray(nodeData.children) && nodeData.children.length > 0) { for(var i in nodeData.children) { var node = this.getNodeData(id, nodeData.children[i]); if(node) return node; } } return null; }; Mindmap.prototype.createDefaultNodeData = function(parentData) { var data = { expand: true, id: $.zui.uuid() + '', parent: parentData.id }; if(parentData.type === 'root') { data.type = 'sub'; data.text = this.lang.defaultSubName; } else { data.type = 'node'; data.text = this.lang.defaultNodeName; } return data; }; Mindmap.prototype.getNode = function(idOrData) { if(typeof(idOrData) === 'string') { return this.$desktop.children('[data-id="' + idOrData + '"]'); } if(typeof(idOrData.id) != UDF) { return idOrData.ui.element; } }; Mindmap.prototype.removeNode = function(nodeData) { this.getNode(nodeData).remove(); if(nodeData.count > 0) { for(var i in nodeData.children) { this.removeNode(nodeData.children[i]); } } }; /* update nodeData changes and decide whether to rerender mindmap */ Mindmap.prototype.update = function(changes, forceShow, forceLoad) { var changed = false, options = this.options; if($.isPlainObject(changes)) { changes = [changes]; } if($.isArray(changes)) { for(var i in changes) { var change = changes[i]; var node = change.data; if(!node) node = this.getNodeData(change.id); if(!node) return; var action = change.action || 'update'; if(action === 'remove' || action === 'delete') { var parent = this.getNodeData(node.parent); if(parent) { parent.children.splice(node.index, 1); this.removeNode(node); this.clearNodeStatus(); if(node.changed != 'add') { if(!$.isArray(parent.deletions)) { parent.deletions = [node]; } else { parent.deletions.push(node); } node.changed = 'delete'; parent.changed = 'delete children'; } forceLoad = true; forceShow = true; changed = true; } } else if(action === 'add') { if(!$.isArray(node.children)) { node.children = [change.newData]; } else { node.children.push(change.newData); } node.count += 1; change.newData.index = node.count; change.newData.changed = 'add'; forceLoad = true; forceShow = true; changed = true; } else if(action === 'move') { if(change.newParent != node.parent) { var newParent = this.getNodeData(change.newParent); var parent = this.getNodeData(node.parent); if(parent && newParent) { if(newParent.type === 'root') { if(node.type === 'node') { node.colorHue = null; } node.type = 'sub'; node.subSide = null; } else { node.type = 'node'; node.subSide = newParent.subSide; } parent.children.splice(node.index, 1); parent.count -= 1; if(!$.isArray(newParent.children)) { newParent.children = [node]; } else { newParent.children.push(node); } newParent.count += 1; node.index = newParent.count; forceLoad = true; forceShow = true; changed = true; if(node.changed != 'add') node.changed = 'move'; } } } else if(action === 'sort') { if(node.count > 1) { node.children.sort(change.func); for(var i in node.children) { var child = node.children[i]; if(child.index != i) { child.index = i; if(child.changed != 'add') child.changed = 'sort'; forceLoad = true; forceShow = true; changed = true; } } } } else { if(typeof(change.text) != UDF && change.text != node.text) { node.text = change.text; // if(node.count > 0 || node.subSide === 'left') // { forceShow = true; // } changed = true; } if(typeof(change.subSide) != UDF && change.subSide != node.subSide) { node.subSide = change.subSide; forceLoad = true; forceShow = true; changed = true; } if(changed) { if(node.changed != 'add') node.changed = 'edit'; } } } } if(forceLoad) this.loadNode(); if(forceShow) this.showNode(); if(changed) { this.callEvent('onChange', { changes: changes, data: this.data }); } }; Mindmap.prototype.clearChangeFlag = function(nodeData) { if(typeof nodeData === UDF) { nodeData = this.data; } if(nodeData.changed) nodeData.changed = null; if(nodeData.children) { for(var i in nodeData.children) { this.clearChangeFlag(nodeData.children[i]); } } }; Mindmap.prototype.exportData = function() { var data = $.extend({}, this.data); this.fixExport(data); return data; }; Mindmap.prototype.exportJSON = function() { return JSON.stringify(this.exportData()); }; Mindmap.prototype.exportArray = function(nodeData, ar) { if(typeof nodeData === UDF) { nodeData = this.data; } if(typeof ar === UDF) { ar = []; } var data = $.extend({}, nodeData); delete data.ui; delete data.children; delete data.subSide; ar.push(data); if(nodeData.children) { for(var i in nodeData.children) { this.exportArray(nodeData.children[i], ar); } } return ar; }; Mindmap.prototype.fixExport = function(data) { delete data.ui; if(data.children) { for(var i in data.children) { this.fixExport(data.children[i]); } } }; Mindmap.prototype.load = function(data) { this.data = data; this.render(data); }; Mindmap.prototype.render = function() { this.loadNode(); this.showNode(); // console.log(this.data); // console.log(JSON.stringify(this.data)); }; Mindmap.prototype.loadNode = function(nodeData, parent, index) { if(!nodeData) { nodeData = this.data; } var options = this.options, $desktop = this.$desktop, parentId = typeof(parent) == 'object' ? (parent.id ? parent.id : '') : ''; if(typeof(nodeData.expand) === UDF) nodeData.expand = true; if(typeof(nodeData.data) === UDF) nodeData.data = {}; if(typeof(nodeData.type) === UDF) nodeData.type = 'node'; if(typeof(nodeData.id) === UDF) nodeData.id = $.zui.uuid() + ''; if(typeof(nodeData.readonly) === UDF) nodeData.readonly = false; if(typeof(nodeData.ui) === UDF) nodeData.ui = {}; nodeData.parent = parentId; var $node = $desktop.children('.mindmap-node[data-id="' + nodeData.id + '"]'); if($node.length) // updated node existed { $node.toggleClass('expand-false', !nodeData.expand) .toggleClass('expand-true', nodeData.expand) .attr('data-type', nodeData.type) .attr('data-parent', parentId || 'root'); $node.children('.text').html(nodeData.text); $node.children('.caption').html(nodeData.caption); } else // create new node { $node = $(options.nodeTeamplate.format({ type: nodeData.type || 'node', expand: nodeData.expand, caption: nodeData.caption || '', id: nodeData.id, parent: parentId || 'root', text: nodeData.text })).appendTo($desktop); this.bindNodeEvents($node); nodeData.ui.element = $node; } if(nodeData.type === 'root') { nodeData.ui.subLeftSide = 0; nodeData.ui.subRightSide = 0; nodeData.ui.vLeftSpan = 0; nodeData.ui.vRightSpan = 0; } else if(nodeData.type === 'sub') { var subSide = nodeData.subSide; if(!subSide) { if(parent.ui.subRightSide > parent.ui.subLeftSide) { subSide = 'left'; } else { subSide = 'right'; } } if(subSide === 'left') { parent.ui.subLeftSide++; } else { parent.ui.subRightSide++; } nodeData.subSide = subSide; if(typeof nodeData.colorHue === UDF) { nodeData.colorHue = Math.floor(((newColorIndex++) * 55) % 360); } } else { nodeData.subSide = parent.subSide; } $node.data('origin-text', nodeData.text); $node.toggleClass('readonly', nodeData.readonly); /* load children nodes */ var vSpan = 1, topSpan = 0; nodeData.count = 0; if($.isArray(nodeData.children) && nodeData.children.length > 0) { nodeData.ui.topSpanTemp = (nodeData.type === 'root') ? { left: 0, right: 0 } : 0; var vLeftSpan = 0, vRightSpan = 0, lastChild = null; vSpan = 0; nodeData.children.sort(function(nodeA, nodeB) { return nodeA.index - nodeB.index; }); for(var i in nodeData.children) { var child = nodeData.children[i]; if(typeof(child.ui) === UDF) child.ui = {}; if(child.type != 'sub') { child.colorHue = nodeData.colorHue; } child.ui.nextBorther = null; if(lastChild) { child.ui.prevBorther = lastChild.id; lastChild.ui.nextBorther = child.id; } else { child.ui.prevBorther = null; } lastChild = child; this.loadNode(child, nodeData, i); child.index = nodeData.count++; if(typeof(child['order']) === 'undifined') { child.order = child.index; } vSpan += child.ui.vSpan; if(child.type === 'sub') { if(child.subSide === 'left') { vLeftSpan += child.ui.vSpan; child.ui.topSpan = nodeData.ui.topSpanTemp.left; nodeData.ui.topSpanTemp.left += child.ui.vSpan; } else { vRightSpan += child.ui.vSpan; child.ui.topSpan = nodeData.ui.topSpanTemp.right; nodeData.ui.topSpanTemp.right += child.ui.vSpan; } } else { child.ui.topSpan = nodeData.ui.topSpanTemp; nodeData.ui.topSpanTemp += child.ui.vSpan; } } if(nodeData.type === 'root') { nodeData.ui.vLeftSpan = vLeftSpan; nodeData.ui.vRightSpan = vRightSpan; } } if(nodeData.type != 'root') { nodeData.ui.vSpan = vSpan; } this.dirtyData = false; if(nodeData.type === 'root') { this.callEvent('afterLoad', { data: nodeData }); // console.log(this.data); } }; /* show on desktop with right position */ Mindmap.prototype.showNode = function(nodeData, parent) { if(!nodeData) { nodeData = this.data; } var css = {}, options = this.options, ui = nodeData.ui, node = nodeData.ui.element; ui.width = node.outerWidth(), ui.height = node.outerHeight(); if(nodeData.type === 'root') { ui.left = 0 - Math.floor(ui.width / 2); ui.top = 0 - Math.floor(ui.height / 2); this.left = 0 - Math.floor(Math.max(ui.width + options.canvasPadding, this.winWidth) / 4); this.right = 0 - this.left; this.top = 0 - Math.floor(Math.max(ui.height + options.canvasPadding, this.winHeight) / 4); this.bottom = 0 - this.top; } else if($.isPlainObject(ui.dragPos)) { ui.left = ui.dragPos.left; ui.top = ui.dragPos.top; } else { if(nodeData.type === 'sub') { var parentVSpan = 0; if(nodeData.subSide === 'left') { ui.left = parent.ui.left - options.hSpace - 20 - ui.width; parentVSpan = parent.ui.vLeftSpan; } else { ui.left = parent.ui.left + parent.ui.width + options.hSpace + 20; parentVSpan = parent.ui.vRightSpan; } var areaHeight = parentVSpan * ui.height + (parentVSpan - 1) * options.vSpace; var areaX = 0 - Math.floor(areaHeight / 2); var nodeAreaTop = ui.topSpan * (ui.height) + (ui.topSpan) * options.vSpace; var nodeAreaHeight = ui.vSpan * ui.height + (ui.vSpan - 1) * options.vSpace; ui.top = areaX + nodeAreaTop + Math.floor((nodeAreaHeight - ui.height) / 2); } else { if(nodeData.subSide === 'left') { ui.left = parent.ui.left - options.hSpace - ui.width; } else { ui.left = parent.ui.left + parent.ui.width + options.hSpace; } var areaHeight = parent.ui.vSpan * ui.height + (parent.ui.vSpan - 1) * options.vSpace; var areaX = parent.ui.top + Math.floor(parent.ui.height / 2) - Math.floor(areaHeight / 2); var nodeAreaTop = ui.topSpan * ui.height + (ui.topSpan) * options.vSpace; var nodeAreaHeight = ui.vSpan * ui.height + (ui.vSpan - 1) * options.vSpace; ui.top = areaX + nodeAreaTop + Math.floor((nodeAreaHeight - ui.height) / 2); } if(nodeData.subSide === 'left') { this.left = Math.min(this.left, ui.left); } else { this.right = Math.max(this.right, ui.left + ui.width); } this.top = Math.min(this.top, ui.top); this.bottom = Math.max(this.bottom, ui.top + ui.height); } /* load children nodes */ if($.isArray(nodeData.children) && nodeData.children.length > 0) { for(var i in nodeData.children) { this.showNode(nodeData.children[i], nodeData); } } if(nodeData.type === 'root') { var pd = this.options.canvasPadding; this.left -= pd; this.top -= pd; this.right += pd * 2; this.bottom += pd; this.width = this.right - this.left; this.height = this.bottom - this.top; this.display(); this.draw(); this.callEvent('afterShow', { data: nodeData }) } }; Mindmap.prototype.display = function(x, y, relative) { if(typeof(x) === 'number' && typeof(y) === 'number') { if(relative) { x += this.offsetX; y += this.offsetY; } this.offsetX = x; this.offsetY = y; } this.x = this.centerX + this.left + this.offsetX; this.y = this.centerY + this.top + this.offsetY; this.$container.css({ width: this.width, height: this.height, top: this.y, left: this.x }); var pd = this.options.canvasPadding; this.$.toggleClass('shadow-left', this.x < 0 - pd) .toggleClass('shadow-right', this.x + this.width > this.winWidth + pd) .toggleClass('shadow-top', this.y < 0 - pd) .toggleClass('shadow-bottom', this.y + this.height > this.winHeight + pd); }; Mindmap.prototype.makeNodeVisble = function($node) { var node = this.getNodeData($node.data('id')); if(!node) return; var realX = node.ui.left - this.left + this.x, realY = node.ui.top - this.top + this.y, pd = this.options.canvasPadding; if(realX < 0) { this.offsetX += 0 - realX; } else { var delta = realX + node.ui.width - (this.winWidth + pd); if(delta > 0) this.offsetX += 0 - delta - 80; } if(realY < 0) { this.offsetY += 0 - realY; } else { var delta = realY + node.ui.height - (this.winWidth + pd); if(delta > 0) this.offsetY += 0 - delta - 80; } this.display(); }; Mindmap.prototype.clearCanvasArea = function(nodeData, parent) { this.$canvas[0].getContext("2d").clearRect(0, 0, this.width, this.height); }; Mindmap.prototype.draw = function(nodeData, parent) { if(!nodeData) { nodeData = this.data; } if(nodeData.type === 'root') { this.$canvas.attr({ width: this.width, height: this.height }); this.clearCanvasArea(); } if(parent) { var isLeft = (nodeData.subSide === 'left'), options = this.options; var ctx = this.$canvas[0].getContext("2d"), start = { x: parent.ui.left + (parent.type === 'root' ? (Math.floor(parent.ui.width / 2)) : (isLeft ? 0 : parent.ui.width)), y: parent.ui.top + Math.floor(parent.ui.height / 2) }, end = { x: nodeData.ui.left + (isLeft ? nodeData.ui.width : 0), y: nodeData.ui.top + Math.floor(nodeData.ui.height / 2) }; var p1 = { x: start.x + (isLeft ? -1 : 1) * options.lineCurvature, y: start.y }, p2 = { x: end.x + (isLeft ? 1 : -1) * options.lineCurvature, y: end.y }; start = this.computePosition(start); end = this.computePosition(end); p1 = this.computePosition(p1); p2 = this.computePosition(p2); ctx.beginPath(); ctx.strokeStyle = this.computeColor(nodeData.colorHue, nodeData.ui.canDrop ? '.25' : '1'); ctx.lineCap = "round"; ctx.lineWidth = (nodeData.type === 'sub') ? options.subLineWidth : options.nodeLineWidth; ctx.moveTo(start.x, start.y); ctx.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, end.x, end.y); ctx.stroke(); } nodeData.ui.element.css(this.computePosition({ left: nodeData.ui.left, top: nodeData.ui.top })); if($.isArray(nodeData.children) && nodeData.children.length > 0) { for(var i in nodeData.children) { this.draw(nodeData.children[i], nodeData); } } }; Mindmap.prototype.bindNodeEvents = function($node) { var that = this, data; $node.on('click', function(event) { that.onNodeClick(event, $node); }) .mousedown(function(event) { event.stopPropagation(); }); $node.find('.text') .on('keyup paste blur', function(event) { that.onNodeTextChanged(event, $node); }) .on('keydown', function(event) {}); if($node.data('type') != 'root') { $node.droppable({ container: that.$, target: '#' + that.id + ' .mindmap-node:not([data-id="' + $node.data('id') + '"])', before: function(e) { if(!that.callEvent('beforeDrag', { node: $node })) return false; if(e.element.hasClass('focus')) { return false; } data = that.getNodeData(e.element.data('id')); if(!data) return false; }, start: function(e) { that.callEvent('startDrag', { node: $node }); if(!e.element.hasClass('active')) { that.clearNodeStatus(); that.activeNode($node); } }, drag: function(e) { e.position.left -= that.x; e.position.top -= that.y; data.ui.dragPos = that.computePosition(e.position, true); data.ui.canDrop = e.isIn; that.showNode(); }, beforeDrop: function(e) { if(e.isIn) { if(e.target.data('id') == data.parent) return false; } else { if(!that.callEvent('beforeSort', { node: $node, event: e })) return; var subSide = data.subSide; if(data.type === 'sub') { if(data.ui.left < -30) { subSide = 'left'; } else if(data.ui.left > 30) { subSide = 'right'; } } that.update([{ action: 'sort', data: that.getNodeData(data.parent), func: function(a, b) { return a.ui.top - b.ui.top; } }, { data: data, subSide: subSide }]); that.callEvent('afterSort', { node: $node, event: e }); } }, drop: function(e) { if(!that.callEvent('beforeMove', { node: data, event: e })) return; that.update({ action: 'move', data: data, newParent: e.target.data('id') }); that.callEvent('afterMove', { node: data, event: e }); }, finish: function(e) { data.ui.dragPos = null; data.ui.canDrop = false; that.showNode(); } }); } this.callEvent('onBindEvents', { node: $node }); } Mindmap.prototype.onNodeClick = function(event, $node) { if($node.hasClass('active')) { this.focusNode($node); } else { this.clearNodeStatus(); this.activeNode($node); } this.callEvent('onNodeClick', { node: $node }); event.stopPropagation(); }; Mindmap.prototype.onNodeTextChanged = function(event, $node) { var text = $node.find('.text').text(); if(text != $node.data('origin-text')) { if(text === '') { $node.find('.text').text(''); } $node.data('origin-text', text); this.update({ id: $node.data('id'), text: text }); this.callEvent('onTextChanged', { node: $node, text: text }); } }; Mindmap.prototype.activeNode = function($node) { if(typeof($node) === UDF) $node = this.$desktop.children('.mindmap-node[data-type="sub"]').first(); if(!$node.length) $node = this.$desktop.children('.mindmap-node').first(); if(!this.callEvent('beforeNodeActive', { node: $node })) return; $node.addClass('active'); this.makeNodeVisble($node); this.activedNode = $node; this.isActive = true; this.callEvent('onNodeActive', { node: $node }); }; Mindmap.prototype.focusNode = function($node, selectAll) { if($node.hasClass('readonly')) { window.messager.show(this.lang.readonlyTip); return; } if(!$node.hasClass('active')) return; if(!this.callEvent('beforeNodeFocus', { node: $node })) return; var text = $node.addClass('focus').find('.text'); text.attr('contenteditable', 'true'); this.makeNodeVisble($node); text.focus(); if(selectAll || typeof selectAll === UDF) selectText(text); this.isFocus = true; }; Mindmap.prototype.clearActiveNode = function($node) { if(typeof($node) === UDF) $node = this.$desktop.children('.mindmap-node.active'); $node.removeClass('active'); this.isActive = false; this.activedNode = null; }; Mindmap.prototype.clearFocusNode = function($node) { if(typeof($node) === UDF) $node = this.$desktop.children('.mindmap-node.focus'); $node.removeClass('focus').find('.text').attr('contenteditable', 'false').blur(); this.isFocus = false; }; Mindmap.prototype.bindEvents = function() { var $this = this.$, that = this; $this.resize($.proxy(this.initSize, this)).click($.proxy(this.onDesktopClick, this)); this.bindGlobalHotkeys(); this.$container.draggable({ before: function(e) { if(!that.callEvent('beforeMoveCanvas')) return false; }, finish: function(e) { that.display(e.smallOffset.x, e.smallOffset.y, true); }, drag: function(e) { that.display(e.smallOffset.x, e.smallOffset.y, true); } }); }; Mindmap.prototype.bindGlobalHotkeys = function() { var options = this.options; if(!options.hotkeyEnable) return; var that = this, hotkeys = options.hotkeys; $(document).on('keydown', null, hotkeys.selectPrev, function() { if(!that.callEvent('beforeHotkey', { event: event, hotkey: hotkeys.selectPrev })) return; that.selectNode('prev'); }).on('keydown', null, hotkeys.selectNext, function() { if(!that.callEvent('beforeHotkey', { event: event, hotkey: hotkeys.selectNext })) return; that.selectNode('next'); }).on('keydown', null, hotkeys.selectLeft, function() { if(!that.callEvent('beforeHotkey', { event: event, hotkey: hotkeys.selectLeft })) return; that.selectNode('left'); }).on('keydown', null, hotkeys.selectRight, function() { if(!that.callEvent('beforeHotkey', { event: event, hotkey: hotkeys.selectRight })) return; that.selectNode('right'); }).on('keydown', null, hotkeys.deleteNode, function() { if(!that.callEvent('beforeHotkey', { event: event, hotkey: hotkeys.deleteNode })) return; that.deleteNode(); if(event.keyCode == 8 && !that.isFocus) { event.preventDefault(); } }).on('keydown', null, hotkeys.addBorther, function() { if(!that.callEvent('beforeHotkey', { event: event, hotkey: hotkeys.addBorther })) return; that.addBortherNode(); }).on('keydown', null, hotkeys.addChild, function(event) { if(!that.callEvent('beforeHotkey', { event: event, hotkey: hotkeys.addChild })) return; that.addChildNode(); if(event.keyCode == 9) { event.preventDefault(); } }).on('keydown', function() { if(!that.callEvent('beforeHotkey', { event: event, type: 'keydown' })) return; if(event.keyCode >= 48 && event.keyCode <= 111 && that.isActive && (!that.isFocus)) { var node = that.activedNode; if(node) { node.find('.text').text(''); that.focusNode(node); } } }).on('keydown', null, hotkeys.centerCanvas, function() { if(!that.callEvent('beforeHotkey', { event: event, hotkey: hotkeys.centerCanvas })) return; that.display(0, 0); }); }; Mindmap.prototype.addBortherNode = function() { if(this.isActive) { var node = this.getNodeData(this.activedNode.data('id')); if(node) { var parent = node.type === 'root' ? node : this.getNodeData(node.parent); var newNode = this.createDefaultNodeData(parent); if(!this.callEvent('beforeAdd', { node: parent, newNode: newNode })) return; this.update({ action: 'add', data: parent, newData: newNode }); this.clearNodeStatus(); var $newNode = this.getNode(newNode.id); this.activeNode($newNode); this.focusNode($newNode, true); this.callEvent('afterAdd', { node: parent, newNode: newNode }); } } }; Mindmap.prototype.addChildNode = function() { if(this.isActive) { var node = this.getNodeData(this.activedNode.data('id')); if(node) { var newNode = this.createDefaultNodeData(node); if(!this.callEvent('beforeAdd', { node: node, newNode: newNode })) return; this.update({ action: 'add', data: node, newData: newNode }); this.clearNodeStatus(); var $newNode = this.getNode(newNode.id); this.activeNode($newNode); this.focusNode($newNode, true); this.callEvent('afterAdd', { node: node, newNode: newNode }); } } }; Mindmap.prototype.deleteNode = function() { if(this.isFocus) return; if(this.isActive) { var node = this.getNodeData(this.activedNode.data('id')); if(node) { if(!this.callEvent('beforeDelete', { node: node })) return; this.update({ action: 'remove', data: node }); this.callEvent('afterDelete', { node: node }); } } }; /* select node */ Mindmap.prototype.selectNode = function(type) { if(this.isFocus) return; if(!this.isActive) { this.activeNode(); return; } var nodeData = null; var node = this.getNodeData(this.activedNode.data('id')); var selectId = null; if(node.type === 'root') { if(type === 'prev' || type === 'left') type = 'left'; else type = 'right'; } else if(type === 'left') { type = (node.subSide == 'left') ? 'child' : 'parent'; } else if(type === 'right') { type = (node.subSide == 'right') ? 'child' : 'parent'; } switch(type) { case 'prev': selectId = node.ui.prevBorther; break; case 'next': selectId = node.ui.nextBorther; break; case 'parent': selectId = node.parent; break; case 'child': if(node.count > 0) selectId = node.children[0].id; break; case 'left': if(node.count > 0) { selectId = node.children[0].id; for(var i in node.children) { var child = node.children[i]; if(child.subSide == 'left') { selectId = child.id; break; } } } break; case 'right': if(node.count > 0) { selectId = node.children[0].id; for(var i in node.children) { var child = node.children[i]; if(child.subSide == 'right') { selectId = child.id; break; } } } break; } if(selectId) { nodeData = this.getNodeData(selectId); } if(nodeData) { this.clearNodeStatus(); this.activeNode(nodeData.ui.element); } }; /* select next borther node */ Mindmap.prototype.selectNext = function() { if(this.isFocus) return; var next = null; if(!this.isActive) { this.activeNode(); } var node = this.getNodeData(this.activedNode.data('id')); if(node.ui.nextBorther !== null) { next = this.getNodeData(node.ui.nextBorther); } if(next) { this.clearNodeStatus(); this.activeNode(next.ui.element); } }; /* select next borther node */ Mindmap.prototype.selectLeft = function() { if(this.isFocus) return; var left = null; if(!this.isActive) { this.activeNode(); } var node = this.getNodeData(this.activedNode.data('id')); if(node.ui.leftBorther !== null) { left = this.getNodeData(node.ui.leftBorther); } if(left) { this.clearNodeStatus(); this.activeNode(left.ui.element); } }; Mindmap.prototype.onDesktopClick = function() { var $desktop = this.$desktop; /* remove status flag from node elements */ this.clearNodeStatus(); }; Mindmap.prototype.clearNodeStatus = function() { this.clearActiveNode(); this.clearFocusNode(); }; $.fn.mindmap = function(option) { return this.each(function() { var $this = $(this); var data = $this.data('zui.mindmap'); var options = typeof option == 'object' && option; if(!data) $this.data('zui.mindmap', (data = new Mindmap(this, options))); if(typeof option == 'string') data[option](); }); }; $.fn.mindmap.Constructor = Mindmap; }(jQuery, window, document, Math));