UNPKG

basicprimitives

Version:

Basic Primitives Diagrams for JavaScript - data visualization components library that implements organizational chart and multi-parent dependency diagrams, contains implementations of JavaScript Controls and PDF rendering plugins.

781 lines (687 loc) 26.8 kB
import Point from './graphics/structs/Point'; import Rect from './graphics/structs/Rect'; import { UpdateMode, OrientationType } from './enums'; import { isNullOrEmpty } from './common'; import createGraphics, { isChrome } from './graphics/createGraphics'; import { getFixOfPixelAlignment, getInnerSize, getElementOffset } from './graphics/dom'; import JsonML from './common/jsonml-html'; import { mergeObjects } from './common'; /** * JavaScript Abstract Control * @class BaseControl */ export default function BaseControl(element, options, taskManagerFactory, eventArgsFactory, templates) { var _data = { name: "orgdiagram", options: options, tasks: null, graphics: null, mouse: null, layout: { element: element, controlPanel: null, frameMousePanel: null, framePlaceholder: null, titlesMousePanel: null, titlesPlaceholder: null, scrollPanel: null, mousePanel: null, placeholder: null, calloutPlaceholder: null, forceCenterOnCursor: true } }, _dragFrom, _scrollFrom, _scrollTo, _dragImage, _dragTimer, _debug = false, _timer = null; /** * Performs full redraw of the diagram contents via reevaluating all API options. This method has to be called explicitly * after all options are set in order to update controls contents. * * @param {UpdateMode} updateMode * @param {boolean} forceCenterOnCursor */ function update(updateMode, forceCenterOnCursor) { if (forceCenterOnCursor == null) { forceCenterOnCursor = true; } switch (updateMode) { case UpdateMode.Refresh: refresh(forceCenterOnCursor, _debug); break; case UpdateMode.PositonHighlight: positionHighlight(_debug); break; default: redraw(); break; } } /** * Removes all elements control added to DOM including event listeners. */ function destroy() { unbind(_data.layout); cleanLayout(_data.layout); _data.tasks = null; _data.graphics = null; } function redraw() { unbind(_data.layout); cleanLayout(_data.layout); createLayout(_data.layout, _data.name); bind(_data.layout); _data.tasks = taskManagerFactory(getOptions, getGraphics, getLayout, setLayout, templates); _data.graphics = createGraphics(_data.layout.element); _data.graphics.debug = _debug; refresh(true, _debug); } function refresh(forceCenterOnCursor, debug) { var centerOnCursorTask, placeholderOffset; //_data.layout.scrollPanel.css({ // "display": "none", // "-webkit-overflow-scrolling": "auto" //}); //this.graphics.begin(); _data.layout.forceCenterOnCursor = forceCenterOnCursor; _data.tasks.process('OptionsTask', null, debug); _data.graphics.end(); //_data.layout.scrollPanel.css({ // "display": "block" //}); if (forceCenterOnCursor) { /* scroll to offset */ centerOnCursorTask = _data.tasks.getTask("CenterOnCursorTask"); placeholderOffset = centerOnCursorTask.getPlaceholderOffset(); _data.layout.scrollPanel.scrollLeft = placeholderOffset.x; _data.layout.scrollPanel.scrollTop = placeholderOffset.y; } //_data.layout.scrollPanel.css({ // "-webkit-overflow-scrolling": "touch" //}); /* fix pixel alignment */ var pixelAlignmentFix = getFixOfPixelAlignment(_data.layout.element); JsonML.applyStyles(_data.layout.scrollPanel, { "marginBottom": "0px", "marginRight": "0px", "marginLeft": pixelAlignmentFix.width + "px", /* fixes div pixel alignment */ "marginTop": pixelAlignmentFix.height + "px" }); } function positionHighlight(debug) { _data.layout.forceCenterOnCursor = false; _data.tasks.process('HighlightItemOptionTask', null, debug); _data.graphics.end(); } function redrawCurrentViewPort(debug) { _data.layout.forceCenterOnCursor = false; _data.tasks.process('LayoutOptionsTask', null, debug); _data.graphics.end(); } function onScroll(event) { if (_timer == null) { _timer = window.setTimeout(function () { redrawCurrentViewPort(_debug); window.clearTimeout(_timer); _timer = null; }, 200); } } /** * Call this method to update controls configuration. Control uses default Config instance to initialize itself, * so it sets only options provided in the options parameter. * * @param {object} options Options */ function setOptions(options) { for (var option in options) { if (options.hasOwnProperty(option)) { _data.options[option] = options[option]; } } } /** * This method returns current configuration object. * * @returns {object} Returns reference to current configuration object */ function getOptions() { return _data.options; } /** * This method returns configuration option by name. * * @param {*} option Option name */ function getOption(option) { return _data.options[option]; } /** * Sets configuration option of the control by name. * * @param {*} option Option name * @param {*} value Option value */ function setOption(option, value) { return _data.options[option] = value; } function getGraphics() { return _data.graphics; } function getLayout() { var layout = _data.layout, scrollPanelSize = getInnerSize(layout.controlPanel), placeholderOffset = new Point(layout.scrollPanel.scrollLeft, layout.scrollPanel.scrollTop); return { forceCenterOnCursor: layout.forceCenterOnCursor, scrollPanelSize: scrollPanelSize, placeholderOffset: placeholderOffset } } function setLayout(layoutOptions) { var layout = _data.layout, pixelAlignmentFix = getFixOfPixelAlignment(layout.element); JsonML.applyStyles(layout.controlPanel, { "marginLeft": pixelAlignmentFix.width + "px", /* fixes div pixel alignment */ "marginTop": pixelAlignmentFix.height + "px" }); /* set scroll panel position */ JsonML.applyStyles(layout.scrollPanel, layoutOptions.scrollPanelRect.getCSS()); /* set scaled content panel for tracking mouse events without scaling */ JsonML.applyStyles(layout.mousePanel, layoutOptions.mousePanelSize.getCSS()); /* set size and scale of content panel */ var scaleText = "scale(" + layoutOptions.scale + "," + layoutOptions.scale + ")"; var scaleProperties = { "transform-origin": "0 0", "transform": scaleText, "-ms-transform": scaleText, /* IE 9 */ "-webkit-transform": scaleText, /* Safari and Chrome */ "-o-transform": scaleText, /* Opera */ "-moz-transform": scaleText /* Firefox */ }; JsonML.applyStyles(layout.placeholder, mergeObjects(scaleProperties, layoutOptions.placeholderSize.getCSS())); if (layoutOptions.autoSize) { /* resize element to fit placeholder if control in auto size mode */ JsonML.applyStyles(layout.element, layoutOptions.controlSize.getCSS()); } /* set titles panel scale and size */ JsonML.applyStyles(layout.titlesMousePanel, layoutOptions.titlesMousePanelRect.getCSS()); JsonML.applyStyles(layout.titlesPlaceholder, mergeObjects(scaleProperties, layoutOptions.titlesPlaceholderSize.getCSS())); /* set frame panel scale and size */ JsonML.applyStyles(layout.frameMousePanel, layoutOptions.frameMousePanelRect.getCSS()); JsonML.applyStyles(layout.framePlaceholder, mergeObjects(scaleProperties, layoutOptions.framePlaceholderSize.getCSS())); layout.scrollPanel.setAttribute("class", layoutOptions.scrollPanelRect.left > 0 ? name : "bp-scrollframe " + name); } function createLayout(layout, name) { var viewportSize = getInnerSize(layout.element), viewportRect = new Rect(0, 0, viewportSize.width, viewportSize.height), pixelAlignmentFix = getFixOfPixelAlignment(element); JsonML.appendDOM(layout.element, JsonML.toHTML( ["div", /* root control panel */ { "tabindex": 0, "style": { "position": "relative", "overflow": "hidden", "top": "0px", "left": "0px", "width": "100%", "height": "100%", "padding": "0px", "marginBottom": "0px", "marginRight": "0px", "marginLeft": pixelAlignmentFix.width + "px", /* fixes div pixel alignment */ "marginTop": pixelAlignmentFix.height + "px" }, "name": "controlPanel", "class": name, "$": function (element) { layout.controlPanel = element; } }, ["div", /* frameMousePanel - frame mouse tracking events panel */ { "style": mergeObjects({ position: "absolute", overflow: "hidden" }, viewportRect.getCSS()), "name": "frameMousePanel", "class": name, "$": function (element) { layout.frameMousePanel = element; } }, ["div", /* frameplaceholder - contents scalable panel */ { "style": mergeObjects({ position: "absolute", overflow: "hidden" }, viewportRect.getCSS()), "name": "framePlaceholder", "class": ["frameplaceholder", name], "$": function (element) { layout.framePlaceholder = element; } } ] ], ["div", /* titlesMousePanel - titles mouse tracking events panel */ { "style": mergeObjects({ position: "absolute", overflow: "hidden" }, viewportRect.getCSS()), "name": "titlesMousePanel", "class": ["bp-titles-frame", name], "$": function (element) { layout.titlesMousePanel = element; } }, ["div", /* titlesplaceholder - contents scalable panel */ { "style": mergeObjects({ position: "absolute", overflow: "hidden" }, viewportRect.getCSS()), "name": "titlesPlaceholder", "class": ["titlesplaceholder", name], "$": function (element) { layout.titlesPlaceholder = element; } } ] ], ["div", /* scrollPanel - root scroll panel */ { "style": mergeObjects({ position: "absolute", "overflow": "auto", "-webkit-overflow-scrolling": "touch", "top": "0px", "left": "0px", "width": viewportRect.width + "px", "height": viewportRect.height + "px" }, viewportRect.getCSS()), "name": "scrollPanel", "class": name, "$": function (element) { layout.scrollPanel = element; } }, ["div", /* mousePanel - mouse tracking events panel */ { "style": mergeObjects({ position: "absolute", overflow: "visible" }, viewportRect.getCSS()), "name": "mousePanel", "class": name, "$": function (element) { layout.mousePanel = element; } }, ["div", /* placeholder - contents scalable panel */ { "style": mergeObjects({ position: "absolute", overflow: "hidden" }, viewportRect.getCSS()), "name": "placeholder", "class": ["placeholder", name], "$": function (element) { layout.placeholder = element; } }, ["div", /* calloutPlaceholder - callout panel */ { "style": { position: "absolute", overflow: "visible", top: "0px", left: "0px", width: "0px", height: "0px" }, "name": "calloutPlaceholder", "class": ["calloutplaceholder", name], "$": function (element) { layout.calloutPlaceholder = element; } } ] ] ] ] ]) ); } function cleanLayout() { var controlPanel = _data.layout.controlPanel; if (controlPanel != null) { var parent = controlPanel.parentNode; if (parent != null) { parent.removeChild(controlPanel); } } } function bind(layout) { layout.mousePanel.addEventListener('mousemove', onMouseMove); layout.mousePanel.addEventListener('click', onMouseClick); layout.mousePanel.addEventListener('dblclick', onMouseDblClick); layout.mousePanel.addEventListener('change', onCheckboxChange); layout.element.addEventListener('keydown', onKeyDown); layout.scrollPanel.addEventListener('scroll', onScroll); if (_data.options.enablePanning && isChrome()) { layout.scrollPanel.draggable = true; layout.scrollPanel.addEventListener('dragstart', onDragStart); layout.scrollPanel.addEventListener('drag', onDragScroll); layout.scrollPanel.addEventListener('dragend', onDragScroll); layout.scrollPanel.addEventListener('dragover', onDragOver); } layout.frameMousePanel.addEventListener('mousemove', onFrameMouseMove); layout.frameMousePanel.addEventListener('click', onFrameMouseClick); } function unbind(layout) { if (layout.mousePanel != null) { layout.mousePanel.removeEventListener("mousemove", onMouseMove); layout.mousePanel.removeEventListener("click", onMouseClick); layout.mousePanel.removeEventListener("dblclick", onMouseDblClick); layout.mousePanel.removeEventListener("change", onCheckboxChange); } if (layout.element != null) { layout.element.removeEventListener("keydown", onKeyDown); } if (layout.scrollPanel != null) { layout.scrollPanel.removeEventListener("scroll", onScroll); layout.scrollPanel.removeEventListener('dragstart', onDragStart); layout.scrollPanel.removeEventListener('drag', onDragScroll); layout.scrollPanel.removeEventListener('dragend', onDragScroll); layout.scrollPanel.removeEventListener('dragover', onDragOver); } if (layout.frameMousePanel != null) { layout.frameMousePanel.removeEventListener('mousemove', onFrameMouseMove); layout.frameMousePanel.removeEventListener('click', onFrameMouseClick); } } function onFrameMouseMove(event) { var placeholderOffset = getElementOffset(_data.layout.frameMousePanel), x = event.pageX - placeholderOffset.left, y = event.pageY - placeholderOffset.top; var projectItemsToFrameTask = _data.tasks.getTask("ProjectItemsToFrameTask"), highlightItemOptionTask = _data.tasks.getTask("HighlightItemOptionTask"), itemId; if (highlightItemOptionTask.hasHighlightEnabled()) { itemId = projectItemsToFrameTask.getTreeItemForMousePosition(x, y, highlightItemOptionTask.getGravityRadius()); setHighlightItem(event, itemId); } } function onFrameMouseClick(event) { var placeholderOffset = getElementOffset(_data.layout.frameMousePanel), x = event.pageX - placeholderOffset.left, y = event.pageY - placeholderOffset.top, projectItemsToFrameTask = _data.tasks.getTask("ProjectItemsToFrameTask"), cursorItemOptionTask = _data.tasks.getTask("CursorItemOptionTask"), highlightItemOptionTask = _data.tasks.getTask("HighlightItemOptionTask"), newCursorItemId = projectItemsToFrameTask.getTreeItemForMousePosition(x, y, highlightItemOptionTask.getGravityRadius()), target, eventArgs; target = event.target; if (newCursorItemId !== null) { eventArgs = getEventArgs(null, newCursorItemId); trigger("onMouseClick", event, eventArgs); if (!eventArgs.cancel) { if (cursorItemOptionTask.hasCursorEnabled()) { setCursorItem(event, newCursorItemId); _data.layout.element.focus(); } } } } function onMouseMove(event) { var placeholderOffset = getElementOffset(_data.layout.mousePanel), x = event.pageX - placeholderOffset.left, y = event.pageY - placeholderOffset.top, createTransformTask = _data.tasks.getTask("CreateTransformTask"), highlightItemOptionTask = _data.tasks.getTask("HighlightItemOptionTask"), itemId; if (highlightItemOptionTask.hasHighlightEnabled()) { itemId = createTransformTask.getTreeItemForMousePosition(x, y, highlightItemOptionTask.getGravityRadius()); setHighlightItem(event, itemId); } } function onCheckboxChange(event) { var target = event.target; var selectedId = target.getAttribute("data-id"); if (selectedId != null) { var selectedItems = (_data.options.selectedItems || []).slice(); var eventArgs = getEventArgs(null, selectedId); var position = selectedItems.findIndex(function(itemid) { return selectedId == itemid}); trigger("onSelectionChanging", event, eventArgs); if (!eventArgs.cancel) { if (position >= 0) { selectedItems.splice(position, 1); } else { selectedItems.push(selectedId); } _data.options.selectedItems = selectedItems; if (position < 0) { target.setAttribute("checked", "checked"); } else { target.removeAttribute("checked"); } //refresh(false, false); trigger("onSelectionChanged", event, eventArgs); } } } function onMouseClick(event) { var placeholderOffset = getElementOffset(_data.layout.mousePanel), x = event.pageX - placeholderOffset.left, y = event.pageY - placeholderOffset.top, createTransformTask = _data.tasks.getTask("CreateTransformTask"), cursorItemOptionTask = _data.tasks.getTask("CursorItemOptionTask"), highlightItemOptionTask = _data.tasks.getTask("HighlightItemOptionTask"), newCursorItemId = createTransformTask.getTreeItemForMousePosition(x, y, highlightItemOptionTask.getGravityRadius()), target, buttonName, eventArgs; target = event.target; if (newCursorItemId !== null) { var buttonName = target.getAttribute("data-buttonname"); if(isNullOrEmpty(buttonName)) { buttonName = target.parentNode && target.parentNode.getAttribute("data-buttonname"); }; if (!isNullOrEmpty(buttonName)) { eventArgs = getEventArgs(null, newCursorItemId, buttonName); trigger("onButtonClick", event, eventArgs); } else if (target.getAttribute("name") === "checkbox" || target.getAttribute("name") === "selectiontext") { } else { eventArgs = getEventArgs(null, newCursorItemId); trigger("onMouseClick", event, eventArgs); if (!eventArgs.cancel) { if (cursorItemOptionTask.hasCursorEnabled()) { setCursorItem(event, newCursorItemId); _data.layout.element.focus(); } } } } } function onMouseDblClick(event) { var eventArgs, highlightItemTask = _data.tasks.getTask("HighlightItemTask"), highlightTreeItem = highlightItemTask.getHighlightTreeItem(); if (highlightTreeItem !== null) { eventArgs = getEventArgs(null, highlightTreeItem); trigger("onMouseDblClick", event, eventArgs); } } function onKeyDown(event) { var highlightItemTask = _data.tasks.getTask("HighlightItemTask"), highlightItemOptionTask = _data.tasks.getTask("HighlightItemOptionTask"), cursorItemTask = _data.tasks.getTask("CursorItemTask"), cursorItemOptionTask = _data.tasks.getTask("CursorItemOptionTask"), alignDiagramTask = _data.tasks.getTask('AlignDiagramTask'), createTransformTask = _data.tasks.getTask('CreateTransformTask'), transform = createTransformTask.getTransform(), navigationItem = null, newNavigationItem, direction = null, accepted, layout = _data.layout; if (highlightItemOptionTask.hasHighlightEnabled() && cursorItemOptionTask.hasCursorEnabled()) { navigationItem = highlightItemTask.getHighlightTreeItem(); if (navigationItem === null) { navigationItem = cursorItemTask.getCursorTreeItem(); } } else if (highlightItemOptionTask.hasHighlightEnabled()) { navigationItem = highlightItemTask.getHighlightTreeItem(); } else if (cursorItemOptionTask.hasCursorEnabled()) { navigationItem = cursorItemTask.getCursorTreeItem(); } if (navigationItem != null) { switch (event.which) { case 13: /*Enter*/ if (cursorItemOptionTask.hasCursorEnabled()) { setCursorItem(event, navigationItem); event.preventDefault(); layout.element.focus(); } break; case 40: /*Down*/ direction = OrientationType.Bottom; break; case 38: /*Up*/ direction = OrientationType.Top; break; case 37: /*Left*/ direction = OrientationType.Left; break; case 39: /*Right*/ direction = OrientationType.Right; break; } if (direction != null) { accepted = false; while (!accepted) { accepted = true; direction = transform.getOrientation(direction); newNavigationItem = alignDiagramTask.getNextItem(navigationItem, direction); if (newNavigationItem != null) { event.preventDefault(); if (highlightItemOptionTask.hasHighlightEnabled()) { setHighlightItem(event, newNavigationItem); } else if (cursorItemOptionTask.hasCursorEnabled()) { setCursorItem(event, newNavigationItem); } } } layout.element.focus(); } } } function onDragStart(event) { var scrollPanel = _data.layout.scrollPanel; _dragFrom = new Point(event.clientX, event.clientY); _scrollFrom = new Point(scrollPanel.scrollLeft, scrollPanel.scrollTop); _dragImage = _dragImage || new Image(); event.dataTransfer.setDragImage(_dragImage, 0, 0); } function onDragOver(event) { event.preventDefault(); event.dataTransfer.dropEffect = "move"; } function onDragScroll(event) { _scrollTo = new Point(_scrollFrom.x - (event.clientX - _dragFrom.x), _scrollFrom.y - (event.clientY - _dragFrom.y)); if (_dragTimer == null) { _dragTimer = window.setTimeout(function () { var scrollPanel = _data.layout.scrollPanel; scrollPanel.scrollLeft = _scrollTo.x; scrollPanel.scrollTop = _scrollTo.y; window.clearTimeout(_dragTimer); _dragTimer = null; }, 50); } } function setHighlightItem(event, newHighlightItemId) { var result = true, eventArgs; if (newHighlightItemId !== null) { if (newHighlightItemId !== _data.options.highlightItem) { eventArgs = getEventArgs(_data.options.highlightItem, newHighlightItemId); _data.options.highlightItem = newHighlightItemId; trigger("onHighlightChanging", event, eventArgs); if (!eventArgs.cancel) { refresh(false, false); trigger("onHighlightChanged", event, eventArgs); } else { result = false; } } } else { if (_data.options.highlightItem !== null) { eventArgs = getEventArgs(_data.options.highlightItem, null); _data.options.highlightItem = null; trigger("onHighlightChanging", event, eventArgs); if (!eventArgs.cancel) { refresh(false, false); trigger("onHighlightChanged", event, eventArgs); } else { result = false; } } } return result; } function setCursorItem(event, newCursorItemId) { var eventArgs; if (newCursorItemId !== _data.options.cursorItem) { eventArgs = getEventArgs(_data.options.cursorItem, newCursorItemId); _data.options.cursorItem = newCursorItemId; trigger("onCursorChanging", event, eventArgs); if (!eventArgs.cancel) { refresh(true, _debug); trigger("onCursorChanged", event, eventArgs); } } } function getEventArgs(oldTreeItemId, newTreeItemId, name) { return eventArgsFactory(_data, oldTreeItemId, newTreeItemId, name); } /** * This method returns item position relative to the control's element. * * @returns {Rect} Returns item position */ function getItemPosition(itemId) { var combinedContextsTask = _data.tasks.getTask("CombinedContextsTask"), alignDiagramTask = _data.tasks.getTask("AlignDiagramTask"), itemConfig = combinedContextsTask.getConfig(itemId), itemPosition, result, offset, panelOffset; if (itemConfig && itemConfig.id != null) { panelOffset = getElementOffset(_data.layout.mousePanel); offset = getElementOffset(_data.layout.element); itemPosition = alignDiagramTask.getItemPosition(itemId); result = new Rect(itemPosition.actualPosition) .translate(panelOffset.left, panelOffset.top) .translate(-offset.left, -offset.top); } return result; }; function trigger(eventHandlerName, event, eventArgs) { var eventHandler = _data.options[eventHandlerName]; if (eventHandler != null) { eventHandler(event, eventArgs); } } update(); /* init control on create */ return { destroy: destroy, setOptions: setOptions, getOptions: getOptions, setOption: setOption, getOption: getOption, update: update, getItemPosition: getItemPosition }; };