├── src ├── state │ ├── dragState.js │ ├── resizeState │ ├── gridEngineState.js │ ├── renderState.js │ ├── mouseState.js │ ├── boxState.js │ └── gridState.js ├── lib │ ├── events.js │ ├── shims.js │ ├── mouse.js │ ├── gridMethod.js │ ├── utils.js │ ├── drag.js │ ├── resize.js │ ├── render.js │ └── gridEngine.js ├── element │ ├── gridDOM.js │ ├── boxContainerElement.js │ ├── boxElement.js │ ├── gridElement.js │ ├── gridVisual.js │ └── resizeHandleElement.js ├── component │ ├── box.js │ └── grid.js └── dashgrid.js ├── specs ├── tests │ ├── dragger.test.js │ ├── gridResize.test.js │ ├── performance.test.js │ ├── boxAddRemove.test.js │ ├── engine.test.js │ ├── boxCollision.test.js │ ├── boxMove.test.js │ ├── boxResize.test.js │ └── initGrid.test.js ├── index.html ├── util.js ├── demo.css └── test.js ├── .npmignore ├── res └── demo.gif ├── .gitignore ├── dist ├── dashgrid.js ├── dashgrid.css └── dashgrid.min.js ├── .babelrc ├── demo ├── index.html ├── demo.css └── main.js ├── package.json ├── LICENSE ├── CHANGELOG └── README.md /src/state/dragState.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/state/resizeState: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /specs/tests/dragger.test.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | tests/ 3 | -------------------------------------------------------------------------------- /src/lib/events.js: -------------------------------------------------------------------------------- 1 | export const click = new WeakMap(); 2 | -------------------------------------------------------------------------------- /src/element/gridDOM.js: -------------------------------------------------------------------------------- 1 | let DOM = new Map(); 2 | 3 | export {DOM}; -------------------------------------------------------------------------------- /res/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alajmo/dashgrid/HEAD/res/demo.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | docs/ 4 | .package.json* 5 | notes.md 6 | 7 | # ctags 8 | .tags* 9 | 10 | # builds 11 | bundle.js 12 | -------------------------------------------------------------------------------- /dist/dashgrid.js: -------------------------------------------------------------------------------- 1 | console.error("\n/home/samir/Projects/module/dashgrid/src/dashgrid.js:1\nimport './lib/shims.js';\n^\nParseError: 'import' and 'export' may appear only with 'sourceType: module'"); -------------------------------------------------------------------------------- /src/element/boxContainerElement.js: -------------------------------------------------------------------------------- 1 | export {BoxContainerElement}; 2 | 3 | function BoxContainerElement() { 4 | let boxesElement = document.createElement('div'); 5 | boxesElement.className = 'dashgrid-boxes;' 6 | 7 | return boxesElement; 8 | } 9 | -------------------------------------------------------------------------------- /src/state/gridEngineState.js: -------------------------------------------------------------------------------- 1 | export {GridEngineState}; 2 | 3 | function GridEngineState() { 4 | let gridEngineState = { 5 | movingBox: undefined, 6 | movedBoxes: [] 7 | }; 8 | 9 | return Object.seal(gridEngineState); 10 | } 11 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"], 3 | "env": { 4 | "development": { 5 | "plugins": [ 6 | ["react-transform", { 7 | "transforms": [{ 8 | "transform": "livereactload/babel-transform", 9 | "imports": ["react"] 10 | }] 11 | }] 12 | ] 13 | } 14 | } 15 | } 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/lib/shims.js: -------------------------------------------------------------------------------- 1 | // shim layer with setTimeout fallback for requiestAnimationFrame 2 | window.requestAnimFrame = (function(){ 3 | return window.requestAnimationFrame || 4 | window.webkitRequestAnimationFrame || 5 | window.mozRequestAnimationFrame || 6 | function (cb){ 7 | cb = cb || function () {}; 8 | window.setTimeout(cb, 1000 / 60); 9 | }; 10 | })(); 11 | -------------------------------------------------------------------------------- /specs/tests/gridResize.test.js: -------------------------------------------------------------------------------- 1 | var diff = require('deep-diff').diff; 2 | var deepcopy = require('deepcopy'); 3 | 4 | import {isNumber, arraysEqual} from '../util.js'; 5 | 6 | export default function gridResize(dashGridGlobal, test) { 7 | // Mockup. 8 | let differences, prevState; 9 | let boxes = [{row: 0, column: 0, rowspan: 3, columnspan: 3}]; 10 | let grid = dashGridGlobal('#grid', {boxes: boxes}); 11 | } 12 | -------------------------------------------------------------------------------- /specs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /specs/tests/performance.test.js: -------------------------------------------------------------------------------- 1 | testStats(grid, boxes, numRows, numColumns); 2 | 3 | export function testStats(grid, boxes, numRows, numColumns) { 4 | var t1 = window.performance.now(); 5 | for (let i = 0; i < numRows; i += 1) { 6 | for (let j = 0; j < numColumns; j += 1) { 7 | grid.updateBox(boxes[0], {row: i, column: j}); 8 | } 9 | } 10 | var t2 = window.performance.now(); 11 | console.log(t2 - t1); 12 | } 13 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |

Dashgrid

12 | 13 |
14 |
15 |
16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/state/renderState.js: -------------------------------------------------------------------------------- 1 | export {RenderState}; 2 | 3 | /** 4 | * 5 | */ 6 | function RenderState () { 7 | let state = { 8 | // Start row / column denotes the pixel at which each cell starts at. 9 | startColumn: [], 10 | startRow: [], 11 | columnWidth: undefined, 12 | rowHeight: undefined, 13 | minLeft: undefined, 14 | minTop: undefined, 15 | maxLeft: undefined, 16 | maxTop: undefined 17 | }; 18 | 19 | return Object.seal(state); 20 | } 21 | -------------------------------------------------------------------------------- /src/state/mouseState.js: -------------------------------------------------------------------------------- 1 | export {MouseState}; 2 | 3 | function MouseState() { 4 | let state = { 5 | eX: undefined, 6 | eY: undefined, 7 | eW: undefined, 8 | eH: undefined, 9 | mouseX: 0, 10 | mouseY: 0, 11 | lastMouseX: 0, 12 | lastMouseY: 0, 13 | mOffX: 0, 14 | mOffY: 0, 15 | previousPosition: Object.seal({row: undefined, column: undefined}), 16 | currentPosition: Object.seal({row: undefined, column: undefined}) 17 | }; 18 | 19 | return Object.seal(state); 20 | } 21 | -------------------------------------------------------------------------------- /dist/dashgrid.css: -------------------------------------------------------------------------------- 1 | .dashgrid{position:absolute;display:block;z-index:1000}.dashgridBox{position:absolute;cursor:move;transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;z-index:1002}.dashgridBoxMoving{transition:none}.grid-shadow-box{background-color:#e8e8e8;transition:none}.dashgridBoxMoving,.grid,.grid-box,.grid-shadow-box{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.horizontal-line,.vertical-line{background:#fff;position:absolute}.grid-centroid{position:absolute;background:#000;width:5px;height:5px} -------------------------------------------------------------------------------- /src/component/box.js: -------------------------------------------------------------------------------- 1 | import * as Event from '../lib/events.js'; 2 | import {BoxState} from '../state/boxState.js'; 3 | import {BoxElement} from '../element/boxElement.js'; 4 | import {resizeHandle} from '../element/resizeHandleElement.js'; 5 | 6 | export {Box}; 7 | 8 | function Box({boxOption, gridState}) { 9 | const box = { 10 | component: {}, 11 | state: { 12 | box: BoxState({boxOption}) 13 | }, 14 | element: { 15 | box: BoxElement(boxOption.content) 16 | }, 17 | events: { 18 | boxEvents: new WeakMap() 19 | } 20 | }; 21 | 22 | Event.click.set(box.element.box, box); 23 | 24 | return Object.seal(box); 25 | } 26 | -------------------------------------------------------------------------------- /src/state/boxState.js: -------------------------------------------------------------------------------- 1 | export {BoxState}; 2 | 3 | /** 4 | * Template function for box objects. 5 | * @returns {Object} Box object. 6 | */ 7 | function BoxState({boxOption}) { 8 | let state = { 9 | row: boxOption.row, 10 | column: boxOption.column, 11 | rowspan: boxOption.rowspan, 12 | columnspan: boxOption.columnspan, 13 | draggable: boxOption.draggable, 14 | resizable: boxOption.resizable, 15 | pushable: boxOption.pushable, 16 | floating: boxOption.floating, 17 | stacking: boxOption.stacking, 18 | swapping: boxOption.swapping, 19 | transition: boxOption.transition 20 | }; 21 | 22 | return Object.seal(state); 23 | } 24 | -------------------------------------------------------------------------------- /src/element/boxElement.js: -------------------------------------------------------------------------------- 1 | export {BoxElement}; 2 | 3 | /** 4 | * 5 | * @param {Object} element The DOM element to which to attach the grid element content. 6 | * @param {Object} gridState The grid state. 7 | * @returns {Object} The dom element. 8 | */ 9 | function BoxElement(contentElement) { 10 | const element = document.createElement('div'); 11 | 12 | // Properties. 13 | element.className = 'dashgrid-box'; 14 | element.style.position = 'absolute'; 15 | element.style.cursor = 'move'; 16 | // TODOD change transition to grid setting. 17 | element.style.transition = 'opacity .3s, left .3s, top .3s, width .3s, height .3s'; 18 | element.style.zIndex = 1003; 19 | element.appendChild(contentElement); 20 | 21 | return element; 22 | } 23 | -------------------------------------------------------------------------------- /src/element/gridElement.js: -------------------------------------------------------------------------------- 1 | // Contains core grid elements. 2 | 3 | export {gridElement, boxesElement}; 4 | 5 | /** 6 | * Modifies element. 7 | * @param {Object} element The DOM element to which to attach the grid element content. 8 | * @param {Object} gridState The grid state. 9 | * @returns {Object} The dom element. 10 | */ 11 | function gridElement(element) { 12 | // Properties. 13 | element.style.position = 'absolute'; 14 | element.style.top = '0px'; 15 | element.style.left = '0px'; 16 | element.style.display = 'block'; 17 | element.style.zIndex = '1000'; 18 | 19 | return element; 20 | }; 21 | 22 | function boxesElement() { 23 | const element = document.createElement('div'); 24 | element.className = 'dg-boxes'; 25 | element.style.position = 'absolute'; 26 | element.style.top = '0px'; 27 | element.style.left = '0px'; 28 | element.style.block = 'block'; 29 | element.style.zIndex = '1001'; 30 | 31 | return element; 32 | } 33 | -------------------------------------------------------------------------------- /specs/util.js: -------------------------------------------------------------------------------- 1 | export function eventFire(el, etype){ 2 | if (el.fireEvent) { 3 | el.fireEvent('on' + etype); 4 | } else { 5 | var evObj = document.createEvent('Events'); 6 | evObj.initEvent(etype, true, false); 7 | el.dispatchEvent(evObj); 8 | } 9 | } 10 | 11 | export function decorateRunAll(t, o1, o2) { 12 | t.all = function () { 13 | Object.keys(t).forEach(function (key) { 14 | if (key !== 'all') {t[key](o1, o2);} 15 | }); 16 | }; 17 | } 18 | 19 | export function isNumber(obj) { 20 | return !Array.isArray(obj) && (obj - parseFloat(obj) + 1) >= 0; 21 | } 22 | 23 | export function isFunction(object) { 24 | // return object && getClass.call(object) == '[object Function]'; 25 | } 26 | 27 | export function arraysEqual(a, b) { 28 | if (a === b) return true; 29 | if (a == null || b == null) return false; 30 | if (a.length != b.length) return false; 31 | 32 | // If you don't care about the order of the elements inside 33 | // the array, you should sort both arrays here. 34 | 35 | for (var i = 0; i < a.length; ++i) { 36 | if (a[i] !== b[i]) return false; 37 | } 38 | return true; 39 | } 40 | -------------------------------------------------------------------------------- /src/dashgrid.js: -------------------------------------------------------------------------------- 1 | import './lib/shims.js'; 2 | import * as Event from './lib/events.js'; 3 | 4 | // Component. 5 | import {Grid} from './component/grid.js'; 6 | import * as GridElement from './element/gridElement.js'; 7 | import * as GridVisual from './element/gridVisual.js'; 8 | import * as GridMethod from './lib/gridMethod.js'; 9 | import * as GridEngine from './lib/gridEngine.js'; 10 | import * as Render from './lib/render.js'; 11 | 12 | export default Dashgrid; 13 | 14 | /** 15 | * @param {Object} element Element to which bind Dashgrid to. 16 | * @param {Array.} boxes Boxes. 17 | * @param {Object} gridOptions Grid Options. 18 | * @returns {Function} updateBox API Update Box. 19 | * @returns {Function} insertBox API Insert Box. 20 | * @returns {Function} removeBox API Remove Box. 21 | * @returns {Function} refreshGrid API Refresh Grid. 22 | * @returns {Object} dashgrid API Copy of dashgrid. 23 | * 24 | */ 25 | function Dashgrid(element, boxes, gridOptions) { 26 | let grid = Grid(element, gridOptions); 27 | GridMethod.initializeGrid(grid, boxes, element); 28 | 29 | // API. 30 | return Object.freeze({ 31 | // updateBox: dashgrid.updateBox, 32 | // insertBox: dashgrid.insertBox, 33 | // removeBox: dashgrid.removeBox, 34 | // refreshGrid: dashgrid.refreshGrid, 35 | // dashgrid: gridState 36 | }); 37 | }; 38 | -------------------------------------------------------------------------------- /demo/demo.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | width: 100%; 3 | height: 100%; 4 | font-size: 1.25em; 5 | margin: 0; 6 | padding: 0; 7 | font-family: arial; 8 | color: #444444; 9 | } 10 | 11 | .dashgridContainer { 12 | position: relative; 13 | /*top: 1%;*/ 14 | /*margin: 0 auto;*/ 15 | width: 100%; 16 | height: 100%; 17 | /*height: 800px;*/ 18 | /*height: 800px;*/ 19 | } 20 | 21 | .dashgridBox { 22 | background: #E1E1E1; 23 | position: absolute; 24 | top: 20%; 25 | left: 0; 26 | width: 100%; 27 | height: 80%; 28 | } 29 | 30 | /** 31 | * Dashgrid relevant classes. 32 | */ 33 | 34 | .dashgrid { 35 | background: #F9F9F9; 36 | } 37 | 38 | .dashgrid-box { 39 | background: red; 40 | } 41 | 42 | .dashgrid-shadow-box { 43 | background-color: #E8E8E8; 44 | opacity: 0.5; 45 | } 46 | 47 | /** 48 | * GRID DRAW HELPERS. 49 | */ 50 | .dashgrid-horizontal-line, 51 | .dashgrid-vertical-line { 52 | background: #FFFFFF; 53 | } 54 | 55 | .dashgrid-grid-centroid { 56 | background: #000000; 57 | width: 5px; 58 | height: 5px; 59 | } 60 | 61 | /** 62 | * Resize Handlers 63 | */ 64 | .dashgrid-box-resize-handle-s {} 65 | .dashgrid-box-resize-handle-e {} 66 | .dashgrid-box-resize-handle-w {} 67 | .dashgrid-box-resize-handle-n {} 68 | .dashgrid-box-resize-handle-ne {} 69 | .dashgrid-box-resize-handle-nw {} 70 | .dashgrid-box-resize-handle-sw {} 71 | .dashgrid-box-resize-handle-se {} 72 | -------------------------------------------------------------------------------- /specs/demo.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | width: 100%; 3 | height: 100%; 4 | font-size: 1.25em; 5 | margin: 0; 6 | padding: 0; 7 | font-family: arial; 8 | color: #444444; 9 | } 10 | 11 | .dashgridContainer { 12 | position: relative; 13 | /*top: 1%;*/ 14 | /*margin: 0 auto;*/ 15 | width: 100%; 16 | height: 100%; 17 | /*height: 800px;*/ 18 | /*height: 800px;*/ 19 | } 20 | 21 | .dashgridBox { 22 | background: #E1E1E1; 23 | position: absolute; 24 | top: 20%; 25 | left: 0; 26 | width: 100%; 27 | height: 80%; 28 | } 29 | 30 | /** 31 | * Dashgrid relevant classes. 32 | */ 33 | 34 | .dashgrid { 35 | background: #F9F9F9; 36 | } 37 | 38 | .dashgrid-box { 39 | background: red; 40 | } 41 | 42 | .dashgrid-shadow-box { 43 | background-color: #E8E8E8; 44 | opacity: 0.5; 45 | } 46 | 47 | /** 48 | * GRID DRAW HELPERS. 49 | */ 50 | .dashgrid-horizontal-line, 51 | .dashgrid-vertical-line { 52 | background: #FFFFFF; 53 | } 54 | 55 | .dashgrid-grid-centroid { 56 | background: #000000; 57 | width: 5px; 58 | height: 5px; 59 | } 60 | 61 | /** 62 | * Resize Handlers 63 | */ 64 | 65 | .dashgrid-box-resize-handle-s {} 66 | .dashgrid-box-resize-handle-e {} 67 | .dashgrid-box-resize-handle-w {} 68 | .dashgrid-box-resize-handle-n {} 69 | .dashgrid-box-resize-handle-ne {} 70 | .dashgrid-box-resize-handle-nw {} 71 | .dashgrid-box-resize-handle-sw {} 72 | .dashgrid-box-resize-handle-se {} 73 | -------------------------------------------------------------------------------- /specs/tests/boxAddRemove.test.js: -------------------------------------------------------------------------------- 1 | var diff = require('deep-diff').diff; 2 | var deepcopy = require('deepcopy'); 3 | 4 | import {isNumber, arraysEqual} from '../util.js'; 5 | 6 | export default function boxAddRemove(dashGridGlobal, test) { 7 | 8 | test('Valid box inserts', function (t) { 9 | // Mockup. 10 | let differences, prevState; 11 | let boxes = [{row: 0, column: 0, rowspan: 3, columnspan: 3}]; 12 | let grid = dashGridGlobal('#grid', {boxes: boxes}); 13 | 14 | t.plan(4); 15 | /** 16 | * Valid Add / Remove. 17 | */ 18 | prevState = deepcopy(grid.grid); 19 | grid.insertBox({row: 0, column: 0, rowspan: 1, columnspan: 1}); 20 | differences = diff(grid.grid, prevState); 21 | t.equal(differences.length, 2, 'Inserted box on a non-empty cell'); 22 | 23 | prevState = deepcopy(grid.grid); 24 | grid.insertBox({row: 4, column: 4, rowspan: 1, columnspan: 1}); 25 | differences = diff(grid.grid, prevState); 26 | t.equal(differences.length, 1, 'Inserted box on an empty cell'); 27 | 28 | prevState = deepcopy(grid.grid); 29 | grid.removeBox(1); 30 | differences = diff(grid.grid, prevState); 31 | // TODO: checkout difference 32 | t.equal(differences.length, 3, 'Removing an inserted box'); 33 | 34 | prevState = deepcopy(grid.grid); 35 | grid.removeBox(0); 36 | differences = diff(grid.grid, prevState); 37 | t.equal(differences.length, 5, 'Removing an initial box'); 38 | 39 | t.end(); 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /specs/test.js: -------------------------------------------------------------------------------- 1 | import test from 'tape'; 2 | 3 | // Dashgrid. 4 | import './demo.css'; 5 | import dashGridGlobal from '../dist/dashgrid.js'; 6 | 7 | // Util. 8 | import {decorateRunAll} from './util.js'; 9 | 10 | // Tests. 11 | import initGrid from './tests/initGrid.test.js'; 12 | import boxAddRemove from './tests/boxAddRemove.test.js'; 13 | import boxMove from './tests/boxMove.test.js'; 14 | import boxCollisions from './tests/boxCollision.test.js'; 15 | import boxResize from './tests/boxResize.test.js'; 16 | import gridResize from './tests/gridResize.test.js'; 17 | import dragger from './tests/dragger.test.js'; 18 | 19 | document.addEventListener('DOMContentLoaded', function() { 20 | tests(); 21 | }); 22 | 23 | /** Testing is done feature wise: 24 | * Move / resize box: 25 | * - Inside border edge. 26 | * - Outside border edge. 27 | * - Dragging disabled, globally and box-wise. 28 | * - Collisions. 29 | * 30 | * Insert / remove box: 31 | * - Valid insert. 32 | * - Non-valid insert. 33 | * 34 | * Toggle properties: 35 | * - Initialization. 36 | */ 37 | function tests (){ 38 | let t = { 39 | initGrid: () => {initGrid(dashGridGlobal, test)}, 40 | boxMove: () => {boxMove(dashGridGlobal, test)}, 41 | boxResize: () => {boxResize(dashGridGlobal, test)}, 42 | boxAddRemove: () => {boxAddRemove(dashGridGlobal, test)}, 43 | boxCollisions: () => {boxCollisions(dashGridGlobal, test)}, 44 | // propertyToggle: () => {propertyToggle(dashGridGlobal, test)} 45 | }; 46 | 47 | decorateRunAll(t, dashGridGlobal, test); 48 | 49 | // t.initGrid(); 50 | // t.boxMove(); 51 | 52 | } 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dashgrid", 3 | "version": "0.2.0", 4 | "author": "alajmo", 5 | "homepage": "https://github.com/alajmo/dashgrid", 6 | "repository": "https://github.com/alajmo/dashgrid", 7 | "description": "A framework agnostic and dependecy free javascript drag-and-drop grid.", 8 | "license": "MIT", 9 | "private": false, 10 | "keywords": [ 11 | "dashgrid", 12 | "grid", 13 | "gridster", 14 | "dashboard", 15 | "drag-and-drop" 16 | ], 17 | "bugs": { 18 | "url": "https://github.com/alajmo/dashgrid/issues" 19 | }, 20 | "files": [ 21 | "dist/" 22 | ], 23 | "scripts": { 24 | "watch-src": "watchify demo/main.js -o demo/bundle.js -dv -t babelify -t browserify-css", 25 | "server": "http-server demo -p 1337", 26 | "start": "npm run watch-src & npm run server", 27 | "test": "npm run watch-src & npm run watch-test-server & npm run watch-test-source", 28 | "build": "browserify src/dashgrid.js -o dist/dashgrid.js -t babelify -s dashgrid" 29 | }, 30 | "devDependencies": { 31 | "babel-cli": "^6.1.18", 32 | "babel-loader": "^8.1.0", 33 | "babel-plugin-react-transform": "^3.0.0", 34 | "babel-preset-es2015": "^6.1.18", 35 | "babel-preset-react": "^6.5.0", 36 | "babel-tape-runner": "^3.0.0", 37 | "babelify": "^10.0.0", 38 | "browserify-css": "^0.15.0", 39 | "browserify-hmr": "^0.4.0", 40 | "clone": "^2.1.2", 41 | "deep-diff": "^1.0.2", 42 | "deepcopy": "^2.1.0", 43 | "express": "^4.13.4", 44 | "braces": ">=2.3.1", 45 | "http-server": "^0.12.3", 46 | "jsdoc": "^3.6.6", 47 | "livereactload": "^3.5.0", 48 | "nodemon": "^2.0.6", 49 | "tape": "^5.0.1", 50 | "watchify": "^3.7.0", 51 | "ecstatic": "^4.1.2", 52 | "ini": ">=1.3.6" 53 | }, 54 | "dependencies": {} 55 | } 56 | -------------------------------------------------------------------------------- /demo/main.js: -------------------------------------------------------------------------------- 1 | import './demo.css'; 2 | import Dashgrid from '../src/dashgrid.js'; 3 | 4 | document.addEventListener('DOMContentLoaded', function() { 5 | main(); 6 | }); 7 | 8 | function main() { 9 | let boxes; 10 | let numRows = 6; 11 | let numColumns = 6; 12 | 13 | let elem = document.createElement('div'); 14 | elem.className = 'dashgridBox'; 15 | 16 | let elemTwo = document.createElement('div'); 17 | elemTwo.className = 'dashgridBox'; 18 | 19 | let elemThree = document.createElement('div'); 20 | elemThree.className = 'dashgridBox'; 21 | 22 | boxes = [ 23 | {row: 0, column: 1, rowspan: 2, columnspan: 2, content: elem}, 24 | {row: 1, column: 3, rowspan: 3, columnspan: 2, content: elemTwo}, 25 | // {row: 15, column: 3, rowspan: 2, columnspan: 2, content: elemThree} 26 | ]; 27 | // boxes = fillCells(numRows, numColumns); 28 | 29 | let gridElement = document.getElementById('grid'); 30 | let gridOptions = { 31 | floating: true, 32 | 33 | xMargin: 20, 34 | yMargin: 20, 35 | 36 | draggable: {enabled: true, handle: 'dashgrid-box'}, 37 | 38 | rowHeight: 'auto', 39 | minRows: numRows, 40 | maxRows: numRows + 5, 41 | 42 | columnWidth: 'auto', 43 | minColumns: numColumns, 44 | maxColumns: numColumns, 45 | 46 | showVerticalLine: true, 47 | showHorizontalLine: true, 48 | showGridCentroids: false, 49 | liveChanges: true 50 | }; 51 | 52 | let dashgrid = Dashgrid(gridElement, boxes, gridOptions); 53 | } 54 | 55 | 56 | function fillCells(numRows, numColumns) { 57 | let elem; 58 | let boxesAll = []; 59 | let id = 0; 60 | for (let i = 0; i < numRows; i += 1) { 61 | for (let j = 0; j < numColumns; j += 1) { 62 | elem = document.createElement('div'); 63 | elem.className = 'dragHandle'; 64 | elem.style.width = '100%'; 65 | elem.style.height = '100%'; 66 | id += 1; 67 | boxesAll.push({row: i, column: j, rowspan: 1, columnspan: 1}); 68 | } 69 | } 70 | 71 | return boxesAll; 72 | } 73 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2015, alajmo 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | ======= 26 | The MIT License (MIT) 27 | 28 | Copyright (c) 2015 Samir Alajmovic 29 | 30 | Permission is hereby granted, free of charge, to any person obtaining a copy 31 | of this software and associated documentation files (the "Software"), to deal 32 | in the Software without restriction, including without limitation the rights 33 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 34 | copies of the Software, and to permit persons to whom the Software is 35 | furnished to do so, subject to the following conditions: 36 | 37 | The above copyright notice and this permission notice shall be included in all 38 | copies or substantial portions of the Software. 39 | 40 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 41 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 42 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 43 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 44 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 45 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 46 | SOFTWARE. 47 | 48 | >>>>>>> 724a5238962b36bc5058857177602e45891b69f3 49 | -------------------------------------------------------------------------------- /src/lib/mouse.js: -------------------------------------------------------------------------------- 1 | import {findParent} from './utils.js'; 2 | import * as Drag from './drag.js'; 3 | 4 | export {mouseDown, dragEvent}; 5 | 6 | function mouseDown(e, element) { 7 | let inputTags = ['select', 'input', 'textarea', 'button']; 8 | // Handle drag / resize event. 9 | if (node.className.search(/dashgrid-box-resize-handle/) > -1) { 10 | handleEvent(e, resizeEvent); 11 | } 12 | else if (node.className.search(grid.draggable.handle) > -1) { 13 | handleEvent(e, dragEvent); 14 | } 15 | } 16 | 17 | /** 18 | * Handle mouse event, click or resize. 19 | * @param {Object} e 20 | * @param {Function} cb 21 | */ 22 | function handleEvent(e, cb) { 23 | let boxElement = findParent(e.target, /^dashgrid-box$/); 24 | let box = grid.getBox(boxElement); 25 | if (box) { cb(box, e); } 26 | } 27 | 28 | /** 29 | * Drag event, sets off start drag, during drag and end drag. 30 | * @param {Object} box 31 | * @param {Object} e 32 | */ 33 | function dragEvent({grid, box, e}) { 34 | // if (!dashgrid.draggable.enabled || !box.draggable) {return;} 35 | Drag.start({grid, box, e}); 36 | 37 | document.addEventListener('mouseup', dragEndEvent, false); 38 | document.addEventListener('mousemove', draggingEvent, false); 39 | 40 | function draggingEvent(e) { 41 | Drag.dragging({grid, box, e}); 42 | e.preventDefault(); 43 | } 44 | 45 | function dragEndEvent(e) { 46 | Drag.end({grid, box, e}); 47 | e.preventDefault(); 48 | document.removeEventListener('mouseup', dragEndEvent, false); 49 | document.removeEventListener('mousemove', draggingEvent, false); 50 | } 51 | } 52 | 53 | /** 54 | * Resize event, sets off start resize, during resize and end resize. 55 | * @param {Object} box 56 | * @param {Object} e 57 | */ 58 | function resizeEvent(box, e) { 59 | if (!dashgrid.resizable.enabled || !box.resizable) {return;} 60 | resize.resizeStart(box, e); 61 | 62 | document.addEventListener('mouseup', resizeEnd, false); 63 | document.addEventListener('mousemove', resize, false); 64 | 65 | function resize(e) {resize.resize(box, e);e.preventDefault();} 66 | 67 | function resizeEnd(e) { 68 | document.removeEventListener('mouseup', resizeEnd, false); 69 | document.removeEventListener('mousemove', resize, false); 70 | 71 | resize.resizeEnd(box, e); 72 | e.preventDefault(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | v0.1.0-alpha: 2 | Date: 2015-10-15 3 | - Alpha release. 4 | 5 | -------------------------------------------------------------------------------- 6 | 7 | v0.1.1-alpha: 8 | Date: 2015-10-15 9 | Changes: 10 | - Added resize. 11 | - Fixed default values for grid. 12 | - Fixed default values for box. 13 | 14 | -------------------------------------------------------------------------------- 15 | 16 | v0.1.2-alpha: 17 | Date: 2015-11-22 18 | Changes: 19 | - Added updateBox api. 20 | - Removed box rendering css from css files (now the css file is not 21 | necessary). 22 | - Removed box id, now box id is not necessary when inserting boxes. 23 | - Fixed default values for box. 24 | 25 | Bug fixes: 26 | - During resizing, when it was close to the bottom of the screen, 27 | it didn't stop at the inward margin. 28 | 29 | -------------------------------------------------------------------------------- 30 | 31 | v0.1.3-alpha: 32 | Date: 2015-12-06 33 | Changes: 34 | - Changed how grid height / width / columnWidth / rowHeight is set. 35 | - Added options of setting rowHeight / columnWidth. 36 | - Added content element to box (ability to add custom html). 37 | 38 | Bug fixes: 39 | - Less than 0 rowspan / columnspan fixed. 40 | - Fixed that boxes went outside border after indirect collision. 41 | 42 | Misc: 43 | - Added collision tests. 44 | 45 | -------------------------------------------------------------------------------- 46 | 47 | v0.2.0-alpha: 48 | Date: 2016-01-15 49 | Changes: 50 | - Added drag handle. 51 | - Faster animations using window.requestAnimationFrame. About 25% better 52 | performance overall. 53 | - Added dragstart, dragging, dragend / resizestart, resizing, resize end 54 | callback events. 55 | - Added resizeHandle option. 56 | - Added insert / remove box api. 57 | - Added handleWidth option. 58 | - Cleaned up code. 59 | 60 | -------------------------------------------------------------------------------- 61 | 62 | v0.2.1-alpha: 63 | Date: 2016-03-19 64 | Changes: 65 | - Fixed numRows / numColumns when moving box and on collision. 66 | - Refactored gridEngine / engineCore / engineView so gridCore only contains 67 | logic and no graphic calls. Also gridEngine is now a wrapper for 68 | updateBox and other API. 69 | 70 | -------------------------------------------------------------------------------- 71 | -------------------------------------------------------------------------------- /specs/tests/engine.test.js: -------------------------------------------------------------------------------- 1 | export function basicTests(test) { 2 | 3 | test('Resizing a box', function (t) { 4 | t.plan(12); 5 | 6 | /** 7 | * In-bound increase / decrease rowspan / columnspan 8 | */ 9 | // Increase rowspan by 1. 10 | let oldRowspan = boxes[0].rowspan; 11 | grid.api.updateBox(boxes[0], {rowspan: oldRowspan + 1}); 12 | t.equal(boxes[0].rowspan, oldRowspan + 1); 13 | 14 | // Decrease rowspan by 1. 15 | oldRowspan = boxes[0].rowspan; 16 | grid.api.updateBox(boxes[0], {rowspan: oldRowspan - 1}); 17 | t.equal(boxes[0].rowspan, oldRowspan - 1); 18 | 19 | // Increase rowspan by 2. 20 | oldRowspan = boxes[0].rowspan; 21 | grid.api.updateBox(boxes[0], {rowspan: oldRowspan + 2}); 22 | t.equal(boxes[0].rowspan, oldRowspan + 2); 23 | 24 | // Decrease rowspan by 2. 25 | oldRowspan = boxes[0].rowspan; 26 | grid.api.updateBox(boxes[0], {rowspan: oldRowspan - 2}); 27 | t.equal(boxes[0].rowspan, oldRowspan - 2); 28 | 29 | // Increase columnspan by 1. 30 | let oldColumnspan = boxes[0].columnspan; 31 | grid.api.updateBox(boxes[0], {columnspan: oldColumnspan + 1}); 32 | t.equal(boxes[0].columnspan, oldColumnspan + 1); 33 | 34 | // Decrease columnspan by 1. 35 | oldColumnspan = boxes[0].columnspan; 36 | grid.api.updateBox(boxes[0], {columnspan: oldColumnspan - 1}); 37 | t.equal(boxes[0].columnspan, oldColumnspan - 1); 38 | 39 | // Increase columnspan by 2. 40 | oldColumnspan = boxes[0].columnspan; 41 | grid.api.updateBox(boxes[0], {columnspan: oldColumnspan + 2}); 42 | t.equal(boxes[0].columnspan, oldColumnspan + 2); 43 | 44 | // Decrease columnspan by 2. 45 | oldColumnspan = boxes[0].columnspan; 46 | grid.api.updateBox(boxes[0], {columnspan: oldColumnspan - 2}); 47 | t.equal(boxes[0].columnspan, oldColumnspan - 2); 48 | 49 | /** 50 | * Out-of-bound increase / decrease rowspan / columnspan 51 | */ 52 | 53 | oldRowspan = boxes[0].rowspan; 54 | grid.api.updateBox(boxes[0], {rowspan: oldRowspan + 9999}); 55 | t.equal(boxes[0].rowspan, oldRowspan, 'Should not increase rowspan outside south boundary.'); 56 | 57 | oldRowspan = boxes[0].rowspan; 58 | grid.api.updateBox(boxes[0], {rowspan: oldRowspan - 9999}); 59 | t.equal(boxes[0].rowspan, oldRowspan, 'Should not decrease rowspan below 1'); 60 | 61 | oldColumnspan = boxes[0].columnspan; 62 | grid.api.updateBox(boxes[0], {columnspan: oldColumnspan + 9999}); 63 | t.equal(boxes[0].columnspan, oldColumnspan, 'Should not increase columnspan outside east boundary.'); 64 | 65 | oldColumnspan = boxes[0].columnspan; 66 | grid.api.updateBox(boxes[0], {columnspan: oldColumnspan - 9999}); 67 | t.equal(boxes[0].columnspan, oldColumnspan, 'Should not decrease columnspan below 1.'); 68 | 69 | t.end(); 70 | }); 71 | } 72 | 73 | // import {basicTests} from '../../tests/grid.test.js'; 74 | // import {collisionTest} from '../../tests/collision.test.js'; 75 | // import {insertTest} from '../../tests/box.test.js'; 76 | 77 | // basicTests(test); 78 | // collisionTest(test); 79 | // insertTest(test); 80 | 81 | export function engineTest(test) { 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/component/grid.js: -------------------------------------------------------------------------------- 1 | // Grid Component. 2 | 3 | // Events. 4 | import * as Event from '../lib/events.js'; 5 | 6 | // States. 7 | import {GridState} from '../state/gridState.js'; 8 | import {GridEngineState} from '../state/gridEngineState.js'; 9 | import {MouseState} from '../state/mouseState.js'; 10 | import {RenderState} from '../state/renderState.js'; 11 | 12 | // Components. 13 | import {Box} from './box.js'; 14 | 15 | // Functions. 16 | import {GridEngine} from '../lib/gridEngine.js'; 17 | import * as Mouse from '../lib/mouse.js'; 18 | 19 | export {Grid}; 20 | 21 | /** 22 | * High Order Grid Component. 23 | * 24 | * @param {Object} dashgrid 25 | * @param {Object} renderer 26 | * @param {Object} boxHandler 27 | * @returns {Function} init Initialize Grid. 28 | * @returns {Function} updateBox API for updating box, moving / resizing. 29 | * @returns {Function} insertBox Insert a new box. 30 | * @returns {Function} removeBox Remove a box. 31 | * @returns {Function} getBox Return box object given DOM element. 32 | * @returns {Function} updateStart When drag / resize starts. 33 | * @returns {Function} update During dragging / resizing. 34 | * @returns {Function} updateEnd After drag / resize ends. 35 | * @returns {Function} renderGrid Update grid element. 36 | */ 37 | function Grid(element, gridOptions) { 38 | let grid = Object.assign({}, { 39 | component: { 40 | boxes: [] 41 | }, 42 | state: { 43 | grid: GridState(gridOptions), 44 | engine: GridEngineState(), 45 | render: RenderState(), 46 | mouse: MouseState({}), 47 | // resize: ResizeState({}) 48 | }, 49 | element: { 50 | grid: undefined, 51 | boxes: undefined, 52 | vertical: undefined, 53 | horizontal: undefined, 54 | centroid: undefined, 55 | shadowBox: undefined 56 | }, 57 | events: { 58 | resize: new WeakMap(), 59 | click: gridEvents 60 | } 61 | }); 62 | 63 | return Object.seal(grid); 64 | } 65 | 66 | function gridEvents(grid) { 67 | // TODO, need access to grid component. 68 | // Initialize events for mouse interaction. 69 | grid.element.grid.addEventListener('mousedown', function (e) { 70 | let node = e.target; 71 | let inputTags = ['select', 'input', 'textarea', 'button']; 72 | // Exit if: 73 | // 1. the target has it's own click event or 74 | // 2. target has onclick attribute or 75 | // 3. Right / middle button clicked instead of left. 76 | if (inputTags.indexOf(node.nodeName.toLowerCase()) > -1) {return;} 77 | if (node.hasAttribute('onclick')) {return;} 78 | if (e.which === 2 || e.which === 3) {return;} 79 | 80 | if (Event.click.has(e.target)) { 81 | let box = Event.click.get(e.target); 82 | // if (event === 'drag') { 83 | Mouse.dragEvent({grid, box, e}); 84 | // } 85 | } 86 | 87 | // mouseDown(e, element); 88 | e.preventDefault(); 89 | }, false); 90 | } 91 | 92 | // // // User event after grid is done loading. 93 | // // if (gridState.onGridReady) {gridState.onGridReady();} // user event. 94 | // return; 95 | 96 | function addEvents() { 97 | // Event listeners. 98 | addEvent(window, 'resize', () => { 99 | renderer.setColumnWidth(); 100 | renderer.setRowHeight(); 101 | grid.refreshGrid(); 102 | }); 103 | } 104 | -------------------------------------------------------------------------------- /src/element/gridVisual.js: -------------------------------------------------------------------------------- 1 | // Contains grid decoration elements, lines / centroids / shadowBox. 2 | 3 | export { 4 | ShadowBoxElement, 5 | VerticalLineElement, 6 | VerticalLines, 7 | HorizontalLineElement, 8 | HorizontalLines, 9 | CentroidElement, 10 | Centroids 11 | }; 12 | 13 | /** 14 | * [ShadowBoxElement description] 15 | */ 16 | function ShadowBoxElement() { 17 | let element = document.createElement('div'); 18 | 19 | element.className = 'dashgrid-shadow-box'; 20 | element.style.position = 'absolute'; 21 | element.style.display = ''; 22 | element.style.zIndex = 1002; 23 | 24 | return element; 25 | } 26 | 27 | /** 28 | * [VerticalLineElement description] 29 | * @param {[type]} grid [description] 30 | */ 31 | function VerticalLineElement(grid) { 32 | const element = document.createElement('div'); 33 | element.className = 'dashgrid-grid-lines;' 34 | return element; 35 | } 36 | 37 | /** 38 | * [VerticalLines description] 39 | * @param {[type]} grid [description] 40 | */ 41 | function VerticalLines(grid) { 42 | let fragment = document.createDocumentFragment(); 43 | let element = document.createElement('div'); 44 | for (let i = 0; i <= grid.state.grid.numColumns; i += 1) { 45 | element = document.createElement('div'); 46 | element.className = 'dashgrid-vertical-line'; 47 | element.style.position = 'absolute'; 48 | element.style.top = '0px'; 49 | element.style.left = i * (grid.state.render.columnWidth + grid.state.grid.xMargin) + 'px'; 50 | element.style.width = grid.state.grid.xMargin + 'px'; 51 | element.style.height = '100%'; 52 | fragment.appendChild(element); 53 | } 54 | 55 | return fragment; 56 | } 57 | 58 | /** 59 | * [HorizontalLineElement description] 60 | * @param {[type]} grid [description] 61 | */ 62 | function HorizontalLineElement(grid) { 63 | const horizontalElement = document.createElement('div'); 64 | horizontalElement.className = 'dashgrid-grid-lines;' 65 | 66 | return horizontalElement; 67 | } 68 | 69 | /** 70 | * [HorizontalLines description] 71 | * @param {[type]} grid [description] 72 | */ 73 | function HorizontalLines(grid) { 74 | let fragment = document.createDocumentFragment(); 75 | let element = document.createElement('div'); 76 | for (let i = 0; i <= grid.state.grid.numColumns; i += 1) { 77 | element = document.createElement('div'); 78 | element.className = 'dashgrid-horizontal-line'; 79 | element.style.position = 'absolute'; 80 | element.style.top = i * (grid.state.render.rowHeight + grid.state.grid.yMargin) + 'px'; 81 | element.style.left = '0'; 82 | element.style.width = '100%'; 83 | element.style.height = grid.state.grid.yMargin + 'px'; 84 | fragment.appendChild(element); 85 | } 86 | 87 | return fragment; 88 | } 89 | 90 | /** 91 | * [CentroidElement description] 92 | * @param {[type]} grid [description] 93 | */ 94 | function CentroidElement(grid) { 95 | const centroidElement = document.createElement('div'); 96 | centroidElement.className = 'dashgrid-centroids'; 97 | 98 | return centroidElement; 99 | } 100 | 101 | /** 102 | * [Centroids description] 103 | * @param {[type]} grid [description] 104 | */ 105 | function Centroids(grid) { 106 | let fragment = document.createDocumentFragment(); 107 | let element = document.createElement('div'); 108 | for (let i = 0; i < grid.state.grid.numRows; i += 1) { 109 | for (let j = 0; j < grid.state.grid.numColumns; j += 1) { 110 | element = document.createElement('div'); 111 | element.className = 'dashgrid-grid-centroid'; 112 | element.style.position = 'absolute'; 113 | element.style.top = (i * (grid.state.render.rowHeight + grid.state.grid.yMargin) + 114 | grid.state.render.rowHeight / 2 + grid.state.grid.yMargin) + 'px'; 115 | element.style.left = (j * (grid.state.render.columnWidth + grid.state.grid.xMargin) + 116 | grid.state.render.columnWidth / 2 + grid.state.grid.xMargin) + 'px'; 117 | fragment.appendChild(element); 118 | } 119 | } 120 | 121 | return fragment; 122 | } 123 | -------------------------------------------------------------------------------- /src/lib/gridMethod.js: -------------------------------------------------------------------------------- 1 | // Manipulates Grid Component. 2 | 3 | // Elements. 4 | import * as GridElement from '../element/gridElement.js'; 5 | import * as GridVisual from '../element/gridVisual.js'; 6 | 7 | import * as Render from './render.js'; 8 | import * as GridEngine from './gridEngine.js'; 9 | import {Box} from '../component/box.js' 10 | 11 | export {initializeGrid, addBox, removeBox, updateBox, updateRenderState}; 12 | 13 | function initializeGrid(grid, boxes, element) { 14 | grid.element.grid = GridElement.gridElement(element); 15 | 16 | grid.events.click(grid); 17 | 18 | // State Init. 19 | grid.state.grid.numRows = GridEngine.updateNumRows({grid}); 20 | grid.state.grid.numColumns = GridEngine.updateNumColumns({grid}); 21 | 22 | // Grid Element Structure. 23 | const shadowBoxElement = GridVisual.ShadowBoxElement(); 24 | const boxesElement = GridElement.boxesElement(); 25 | const verticalElement = GridVisual.VerticalLineElement(grid); 26 | const horizontalElement = GridVisual.HorizontalLineElement(grid); 27 | const centroidElement = GridVisual.CentroidElement(grid); 28 | 29 | grid.element.shadowBox = shadowBoxElement; 30 | grid.element.boxes = boxesElement; 31 | grid.element.vertical = verticalElement; 32 | grid.element.horizontal = horizontalElement; 33 | grid.element.centroid = centroidElement; 34 | 35 | grid.element.grid.appendChild(shadowBoxElement); 36 | grid.element.grid.appendChild(boxesElement); 37 | grid.element.grid.appendChild(verticalElement); 38 | grid.element.grid.appendChild(horizontalElement); 39 | grid.element.grid.appendChild(centroidElement); 40 | 41 | // Initialize Grid. 42 | Render.renderGrid(grid); 43 | addBox({grid, boxes}); 44 | Render.renderBox({grid, boxes: grid.component.boxes}); 45 | updateRenderState(grid); 46 | } 47 | 48 | /** 49 | * [updateBox description] 50 | * @return {[type]} [description] 51 | */ 52 | function updateBox({grid, box, updateTo}) { 53 | let movedBoxes = GridEngine.updateBox({grid, box, updateTo}); 54 | 55 | if (movedBoxes.length > 0) { 56 | Render.renderBox({grid, boxes: movedBoxes, excludeBox: box}); 57 | Render.renderGrid(grid); 58 | return true; 59 | } 60 | 61 | return false; 62 | } 63 | 64 | /** 65 | * Add box(es) to Grid. 66 | * @param {Object} grid 67 | * @param {Array.} boxes 68 | */ 69 | function addBox({grid, boxes}) { 70 | if (!Array.isArray(boxes)) { boxes = [boxes]; } 71 | boxes.forEach(function (boxOption) { 72 | const box = Box({boxOption, gridState: grid.state.grid}); 73 | if (box !== undefined) { 74 | grid.component.boxes.push(box); 75 | grid.element.boxes.appendChild(box.element.box); 76 | } 77 | }); 78 | } 79 | 80 | /** 81 | * Removes a box. 82 | * @param {Object} box 83 | */ 84 | function removeBox(box) { 85 | GridEngine.removeBox(box); 86 | // gridView.renderGrid(); 87 | }; 88 | 89 | /** 90 | * Resizes a box. 91 | * @param {Object} box 92 | */ 93 | function resizeBox(box) { 94 | // In case box is not updated by dragging / resizing. 95 | // gridView.renderBox(movedBoxes); 96 | // gridView.renderGrid(); 97 | }; 98 | 99 | /** 100 | * Called when either resize or drag starts. 101 | * @param {Object} box 102 | */ 103 | function updateStart(box) { 104 | GridEngine.increaseNumRows(box, 1); 105 | GridEngine.increaseNumColumns(box, 1); 106 | // gridView.renderGrid(); 107 | }; 108 | 109 | /** 110 | * When dragging / resizing is dropped. 111 | */ 112 | function updateEnd() { 113 | GridEngine.decreaseNumRows(); 114 | GridEngine.decreaseNumColumns(); 115 | // gridView.renderGrid(); 116 | }; 117 | 118 | /** 119 | * 120 | * @param {} 121 | * @returns 122 | */ 123 | function refreshGrid(grid) { 124 | renderGrid(grid); 125 | renderBox({grid, boxes: grid.component.boxes}); 126 | }; 127 | 128 | function updateRenderState(grid) { 129 | Object.assign(grid.state.render, Render.getCellCentroidPosition({ 130 | numRows: grid.state.grid.numRows, 131 | numColumns: grid.state.grid.numColumns, 132 | yMargin: grid.state.grid.yMargin, 133 | xMargin: grid.state.grid.xMargin, 134 | rowHeight: grid.state.render.rowHeight, 135 | columnWidth: grid.state.render.columnWidth 136 | })); 137 | } 138 | -------------------------------------------------------------------------------- /src/state/gridState.js: -------------------------------------------------------------------------------- 1 | export {GridState}; 2 | 3 | /** 4 | * 5 | * @param {} 6 | * @returns 7 | */ 8 | function GridState(gridOptions) { 9 | let state = { 10 | boxes: [], 11 | rowHeight: gridOptions.rowHeight, 12 | numRows: (gridOptions.numRows !== undefined) ? gridOptions.numRows : 6, 13 | minRows: (gridOptions.minRows !== undefined) ? gridOptions.minRows : 6, 14 | maxRows: (gridOptions.maxRows !== undefined) ? gridOptions.maxRows : 10, 15 | 16 | extraRows: 0, 17 | extraColumns: 0, 18 | 19 | columnWidth: gridOptions.columnWidth, 20 | numColumns: (gridOptions.numColumns !== undefined) ? gridOptions.numColumns : 6, 21 | minColumns: (gridOptions.minColumns !== undefined) ? gridOptions.minColumns : 6, 22 | maxColumns: (gridOptions.maxColumns !== undefined) ? gridOptions.maxColumns : 10, 23 | 24 | xMargin: (gridOptions.xMargin !== undefined) ? gridOptions.xMargin : 20, 25 | yMargin: (gridOptions.yMargin !== undefined) ? gridOptions.yMargin : 20, 26 | 27 | defaultBoxRowspan: 2, 28 | defaultBoxColumnspan: 1, 29 | 30 | minRowspan: (gridOptions.minRowspan !== undefined) ? gridOptions.minRowspan : 1, 31 | maxRowspan: (gridOptions.maxRowspan !== undefined) ? gridOptions.maxRowspan : 9999, 32 | 33 | minColumnspan: (gridOptions.minColumnspan !== undefined) ? gridOptions.minColumnspan : 1, 34 | maxColumnspan: (gridOptions.maxColumnspan !== undefined) ? gridOptions.maxColumnspan : 9999, 35 | 36 | pushable: (gridOptions.pushable === false) ? false : true, 37 | floating: (gridOptions.floating === true) ? true : false, 38 | stacking: (gridOptions.stacking === true) ? true : false, 39 | swapping: (gridOptions.swapping === true) ? true : false, 40 | animate: (gridOptions.animate === true) ? true : false, 41 | 42 | liveChanges: (gridOptions.liveChanges === false) ? false : true, 43 | 44 | // Drag handle can be a custom classname or if not set revers to the 45 | // box container with classname 'dashgrid-box'. 46 | draggable: { 47 | enabled: (gridOptions.draggable && gridOptions.draggable.enabled === false) ? false : true, 48 | handle: (gridOptions.draggable && gridOptions.draggable.handle) || 'dashgrid-box', 49 | 50 | // user cb's. 51 | dragStart: gridOptions.draggable && gridOptions.draggable.dragStart, 52 | dragging: gridOptions.draggable && gridOptions.draggable.dragging, 53 | dragEnd: gridOptions.draggable && gridOptions.draggable.dragEnd 54 | }, 55 | 56 | resizable: { 57 | enabled: (gridOptions.resizable && gridOptions.resizable.enabled === false) ? false : true, 58 | handle: (gridOptions.resizable && gridOptions.resizable.handle) || ['n', 'e', 's', 'w', 'ne', 'se', 'sw', 'nw'], 59 | northHandleThickness: (gridOptions.resizable && gridOptions.resizable.northHandleThickness !== undefined) ? gridOptions.resizable.northHandleThickness : 10, 60 | southHandleThickness: (gridOptions.resizable && gridOptions.resizable.southHandleThickness !== undefined) ? gridOptions.resizable.southHandleThickness : 10, 61 | eastHandleThickness: (gridOptions.resizable && gridOptions.resizable.eastHandleThickness !== undefined) ? gridOptions.resizable.eastHandleThickness : 10, 62 | westHandleThickness: (gridOptions.resizable && gridOptions.resizable.westhHandleThickness !== undefined) ? gridOptions.resizable.westthHandleThickness : 10, 63 | 64 | // user cb's. 65 | resizeStart: gridOptions.resizable && gridOptions.resizable.resizeStart, 66 | resizing: gridOptions.resizable && gridOptions.resizable.resizing, 67 | resizeEnd: gridOptions.resizable && gridOptions.resizable.resizeEnd 68 | }, 69 | 70 | onUpdate: () => {}, 71 | 72 | transition: 'opacity .3s, left .3s, top .3s, width .3s, height .3s', 73 | scrollSensitivity: 20, 74 | scrollSpeed: 10, 75 | snapBackTime: (gridOptions.snapBackTime === undefined) ? 300 : gridOptions.snapBackTime, 76 | 77 | showVerticalLine: (gridOptions.showVerticalLine === false) ? false : true, 78 | showHorizontalLine: (gridOptions.showHorizontalLine === false) ? false : true, 79 | showCentroid: (gridOptions.showCentroid === false) ? false : true, 80 | showShadowBox: (gridOptions.showShadowBox === false) ? false : true 81 | }; 82 | 83 | return Object.seal(state); 84 | }; 85 | -------------------------------------------------------------------------------- /src/lib/utils.js: -------------------------------------------------------------------------------- 1 | export { 2 | getMaxNum, 3 | getSortedArr, 4 | insertByOrder, 5 | insertionSort, 6 | ObjectLength, 7 | addEvent, 8 | removeNodes, 9 | findParent, 10 | render 11 | }; 12 | 13 | function render(destObj, srcObj) { 14 | for (var key in srcObj) { 15 | if (srcObj.hasOwnProperty(key)) { 16 | var element = {}; 17 | element[key] = {element: srcObj[key]}; 18 | Object.assign(destObj, element); 19 | destObj.element.appendChild(srcObj[key]); 20 | } 21 | } 22 | } 23 | 24 | /** 25 | * 26 | * @param {Object} box 27 | * @param {string} at1 28 | * @param {string} at2 29 | * @returns {Number} 30 | */ 31 | function getMaxNum(box, at1, at2) { 32 | let maxVal = 0; 33 | for (var i = 0, len = box.length; i < len; i++) { 34 | if (box[i][at1] + box[i][at2] >= maxVal) { 35 | maxVal = box[i][at1] + box[i][at2]; 36 | } 37 | } 38 | 39 | return maxVal; 40 | } 41 | 42 | /** 43 | * 44 | * @param {string} order 45 | * @param {string} attr 46 | * @param {Array.} objs 47 | * @returns {Array.} 48 | */ 49 | function getSortedArr(order, attr, objs) { 50 | let key; 51 | let arr = []; 52 | 53 | Object.keys(objs).forEach(function (i) { 54 | insertByOrder(order, attr, objs[i], arr); 55 | }); 56 | 57 | return arr; 58 | } 59 | 60 | /** 61 | * Sort array with newly inserted object. 62 | * @param {string} box 63 | * @param {string} at1 64 | * @param {Object} at2 65 | */ 66 | function insertByOrder(order, attr, o, arr) { 67 | let len = arr.length; 68 | 69 | if (len === 0) { 70 | arr.push(o); 71 | } else { 72 | // Insert by order, start furthest down. 73 | // Insert between 0 and n -1. 74 | for (let i = 0; i < len; i += 1) { 75 | if (order === 'desc') { 76 | if (o.row > arr[i].row) { 77 | arr.splice(i, 0, o); 78 | break; 79 | } 80 | } else { 81 | if (o.row < arr[i].row) { 82 | arr.splice(i, 0, o); 83 | break; 84 | } 85 | } 86 | } 87 | 88 | // If not inbetween 0 and n - 1, insert last. 89 | if (len === arr.length) {arr.push(o);} 90 | } 91 | } 92 | 93 | /** 94 | * 95 | * @param {Array.} a 96 | * @param {string} a 97 | */ 98 | function insertionSort(a, attr) { 99 | if (a.length < 2) { 100 | return; 101 | } 102 | 103 | var i = a.length; 104 | var temp; 105 | var j; 106 | while (i--) { 107 | j = i; 108 | while (j > 0 && a[j - 1][attr] < a[j][attr]) { 109 | temp = a[j]; 110 | a[j] = a[j - 1]; 111 | a[j - 1] = temp; 112 | j -= 1; 113 | } 114 | } 115 | } 116 | 117 | /** 118 | * 119 | * @param {Object} obj 120 | * @returns {number} Number of properties in object. 121 | */ 122 | function ObjectLength(obj) { 123 | let length = 0, 124 | key; 125 | for (key in obj) { 126 | if (obj.hasOwnProperty(key)) { 127 | length += 1; 128 | } 129 | } 130 | return length; 131 | } 132 | 133 | /** 134 | * Add event, and not overwrite. 135 | * @param {Object} element 136 | * @param {string} type 137 | * @param {Function} eventHandle 138 | * @returns 139 | */ 140 | function addEvent(element, type, eventHandle) { 141 | if (element === null || typeof(element) === 'undefined') return; 142 | if (element.addEventListener) { 143 | element.addEventListener( type, eventHandle, false ); 144 | } else if (element.attachEvent) { 145 | element.attachEvent( 'on' + type, eventHandle ); 146 | } else { 147 | element['on' + type] = eventHandle; 148 | } 149 | } 150 | 151 | /** 152 | * Remove nodes from element. 153 | * @param {Object} element 154 | */ 155 | function removeNodes(element) { 156 | while (element.firstChild) {element.removeChild(element.firstChild);} 157 | } 158 | 159 | /** 160 | * 161 | * @param {Object} node 162 | * @param {string} className 163 | * @returns {Object|Boolean} DOM element object or false if not found. 164 | */ 165 | function findParent(node, className) { 166 | while (node.nodeType === 1 && node !== document.body) { 167 | if (node.className.search(className) > -1) {return node;} 168 | node = node.parentNode; 169 | } 170 | return false; 171 | } 172 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dashgrid 2 | 3 | A highly customizable drag-and-drop grid built on pure es6 with no 4 | external dependencies. Inspired by gridster and angular-gridster and other excellent grid 5 | systems. 6 | 7 | Don't use in production just yet! Check out [demo](http://alajmo.github.io/dashgrid) in the meantime! 8 | 9 | ## Motivation 10 | 11 | As far as I know there isn't a grid system that is dependency free, maintained actively, 12 | and customizable to the core. 13 | 14 | # Installation 15 | 16 | Either via npm: 17 | ```shell 18 | npm install dashgrid 19 | ``` 20 | 21 | or download the dist/dashgrid.js (dashgrid.min.js). 22 | 23 | ## Quick Start 24 | 25 | Include the dashgrid.js file either via html, commonjs or es6 syntax. 26 | 27 | ```javascript 28 | // Wrapped in DOMContentLoaded to make sure DOM is loaded before Grid. 29 | document.addEventListener('DOMContentLoaded', function() { 30 | var boxes = [ 31 | {'row': 1, 'column': 1, 'rowspan': 1, 'columnspan': 1} 32 | ]; 33 | 34 | var options = { 35 | boxes: boxes, 36 | floating: true 37 | }; 38 | 39 | var element = document.getElementById('grid'); 40 | var grid = Grid(element, options); 41 | 42 | }); 43 | ``` 44 | 45 | # API 46 | 47 | ## Methods 48 | 49 | * updateBox 50 | * insertBox 51 | * removeBox 52 | * refreshGrid 53 | 54 | ## Events 55 | 56 | * dragStart 57 | * dragging 58 | * dragEnd 59 | * resizeStart 60 | * resizing 61 | * resizeEnd 62 | 63 | ## Properties 64 | 65 | ## Default Configuration 66 | 67 | ```javascript 68 | var gridOptions = { 69 | rowHeight: 'auto', 70 | numRows: 6, 71 | minRows: 6, 72 | maxRows: 10, 73 | columnWidth: 'auto', 74 | numColumns: 6, 75 | minColumns: 6, 76 | maxColumns: 10, 77 | xMargin: 20, 78 | yMargin: 20, 79 | defaultBoxRowspan: 2, 80 | defaultBoxColumnspan: 1, 81 | minRowspan: 1, 82 | maxRowspan: 9999, 83 | minColumnspan: 1, 84 | maxColumnspan: 9999, 85 | liveChanges: true, 86 | draggable: { 87 | enabled: true, 88 | handles: 'auto', 89 | 90 | dragStart: function () {}, 91 | dragging: function () {}, 92 | dragEnd: function () {} 93 | }, 94 | resizable: { 95 | enabled: true, 96 | handles: ['n', 'e', 's', 'w', 'ne', 'se', 'sw', 'nw'], 97 | handleWidth: 10, 98 | 99 | resizeStart: function () {}, 100 | resizing: function () {}, 101 | resizeEnd: function () {} 102 | }, 103 | scrollSensitivity: 20, 104 | scrollSpeed: 10, 105 | snapbacktime: 300, 106 | displayGrid: true 107 | }; 108 | ``` 109 | 110 | # Miscellaneous 111 | 112 | ## Grid Width and Height 113 | 114 | If rowHeight / columnWidth is set to 'auto', then the grid 115 | height / width is set to that of the parent element. 116 | rowHeight then becomes gridHeight / numRows + yMargins and 117 | columnWidth gridWidth / numColumns + xMargins. 118 | 119 | If rowHeight / columnWidth is set to a number, then the grid 120 | height is set to: 121 | 122 | gridHeight = numRows * rowHeight 123 | 124 | and the grid width is set to: 125 | 126 | gridWidth = numColumns * columnWidth 127 | 128 | ## Styling 129 | 130 | If you want another design on the box, drag handlers, resize handlers, the placeholder 131 | box (the shadow box shown when dragging / resizing a box) you can edit these to your liking. 132 | 133 | The only css done in Dashgrid is the necessary positioning. 134 | 135 | The DOM structure of dashgrid is: 136 | 137 | ``` 138 |
139 | 140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 | 154 | 155 |
156 | 157 | 158 |
159 | 160 | 161 |
162 |
163 | ``` 164 | 165 | The way z-index works in this case is: 166 | * dashgrid: 1000 167 | * dashgrid-box: 1003 168 | * moving dashgrid-box: 1004 169 | * dashgrid-shadow-box: 1002 170 | * dashgrid-box-resize-handle: 1003 171 | * dashgrid-grid-lines: 1001 172 | * dashgrid-grid-centroids: 1001 173 | 174 | # Inspiration 175 | 176 | * gridlist 177 | * Packery 178 | * angular gridster 179 | * gridster 180 | -------------------------------------------------------------------------------- /src/element/resizeHandleElement.js: -------------------------------------------------------------------------------- 1 | export { 2 | createNorthResizeHandleElement, 3 | southResizeHandleElement, 4 | createEastResizeHandleElement, 5 | createWestResizeHandleElement, 6 | createNorthEastResizeHandleElement, 7 | createNorthWestResizeHandleElement, 8 | createSouthEastResizeHandleElement, 9 | createSouthWestResizeHandleElement 10 | }; 11 | 12 | /** 13 | * North Handler. 14 | */ 15 | function createNorthResizeHandleElement(northHandleThickness) { 16 | let element = document.createElement('div'); 17 | element.className = 'dashgrid-box-resize-element-n'; 18 | element.style.left = 0 + 'px'; 19 | element.style.top = 0 + 'px'; 20 | element.style.width = '100%'; 21 | element.style.height = northHandleThickness + 'px'; 22 | element.style.cursor = 'n-resize'; 23 | element.style.position = 'absolute'; 24 | element.style.display = 'block'; 25 | element.style.zIndex = 1003; 26 | 27 | return element; 28 | } 29 | 30 | /** 31 | * South Handler. 32 | */ 33 | function southResizeHandleElement(southHandleThickness) { 34 | let element = document.createElement('div'); 35 | element.className = 'dashgrid-box-resize-element-s'; 36 | element.style.left = 0 + 'px'; 37 | element.style.bottom = 0 + 'px'; 38 | element.style.width = '100%'; 39 | element.style.height = southHandleThickness + 'px'; 40 | element.style.cursor = 's-resize'; 41 | element.style.position = 'absolute'; 42 | element.style.display = 'block'; 43 | element.style.zIndex = 1003; 44 | 45 | return element; 46 | } 47 | 48 | /** 49 | * WEST Handler. 50 | */ 51 | function createWestResizeHandleElement(westHandleThickness) { 52 | let element = document.createElement('div'); 53 | element.className = 'dashgrid-box-resize-element-w'; 54 | element.style.left = 0 + 'px'; 55 | element.style.top = 0 + 'px'; 56 | element.style.width = westHandleThickness + 'px'; 57 | element.style.height = '100%'; 58 | element.style.cursor = 'w-resize'; 59 | element.style.position = 'absolute'; 60 | element.style.display = 'block'; 61 | element.style.zIndex = 1003; 62 | 63 | return element; 64 | } 65 | 66 | /** 67 | * EAST Handler. 68 | */ 69 | function createEastResizeHandleElement(eastHandleThickness) { 70 | let element = document.createElement('div'); 71 | element.className = 'dashgrid-box-resize-element-e'; 72 | element.style.right = 0 + 'px'; 73 | element.style.top = 0 + 'px'; 74 | element.style.width = eastHandleThickness + 'px'; 75 | element.style.height = '100%'; 76 | element.style.cursor = 'e-resize'; 77 | element.style.position = 'absolute'; 78 | element.style.display = 'block'; 79 | element.style.zIndex = 1003; 80 | return element; 81 | } 82 | 83 | /** 84 | * NORTH-EAST Handler. 85 | */ 86 | function createNorthEastResizeHandleElement(northHandleThickness, eastHandleThickness) { 87 | let element = document.createElement('div'); 88 | element.className = 'dashgrid-box-resize-element-ne'; 89 | element.style.right = 0 + 'px'; 90 | element.style.top = 0 + 'px'; 91 | element.style.width = eastHandleThickness + 'px'; 92 | element.style.height = northHandleThickness + 'px'; 93 | element.style.cursor = 'ne-resize'; 94 | element.style.position = 'absolute'; 95 | element.style.display = 'block'; 96 | element.style.zIndex = 1003; 97 | 98 | return element; 99 | } 100 | 101 | /** 102 | * NORTH-WEST Handler. 103 | */ 104 | function createNorthWestResizeHandleElement(northHandleThickness, westHandleThickness) { 105 | let element = document.createElement('div'); 106 | element.className = 'dashgrid-box-resize-element-nw'; 107 | element.style.left = 0 + 'px'; 108 | element.style.top = 0 + 'px'; 109 | element.style.width = westHandleThickness + 'px'; 110 | element.style.height = northHandleThickness + 'px'; 111 | element.style.cursor = 'nw-resize'; 112 | element.style.position = 'absolute'; 113 | element.style.display = 'block'; 114 | element.style.zIndex = 1003; 115 | 116 | return element; 117 | } 118 | 119 | /** 120 | * SOUTH-EAST Handler. 121 | */ 122 | function createSouthEastResizeHandleElement(southHandleThickness, eastHandleThickness) { 123 | let element = document.createElement('div'); 124 | element.className = 'dashgrid-box-resize-element-se'; 125 | element.style.right = 0 + 'px'; 126 | element.style.bottom = 0 + 'px'; 127 | element.style.width = eastHandleThickness + 'px'; 128 | element.style.height = southHandleThickness + 'px'; 129 | element.style.cursor = 'se-resize'; 130 | element.style.position = 'absolute'; 131 | element.style.display = 'block'; 132 | element.style.zIndex = 1003; 133 | 134 | return element; 135 | } 136 | 137 | /** 138 | * SOUTH-WEST Handler. 139 | */ 140 | function createSouthWestResizeHandleElement(southHandleThickness, westHandleThickness) { 141 | let element = document.createElement('div'); 142 | element.className = 'dashgrid-box-resize-element-sw'; 143 | element.style.left = 0 + 'px'; 144 | element.style.bottom = 0 + 'px'; 145 | element.style.width = southHandleThickness + 'px'; 146 | element.style.height = westHandleThickness + 'px'; 147 | element.style.cursor = 'sw-resize'; 148 | element.style.position = 'absolute'; 149 | element.style.display = 'block'; 150 | element.style.zIndex = 1003; 151 | 152 | return element; 153 | } 154 | -------------------------------------------------------------------------------- /specs/tests/boxCollision.test.js: -------------------------------------------------------------------------------- 1 | var diff = require('deep-diff').diff; 2 | var deepcopy = require('deepcopy'); 3 | 4 | import {isNumber, arraysEqual} from '../util.js'; 5 | 6 | export default function boxCollisions(dashGridGlobal, test) { 7 | test('Propogated row collision', function (t) { 8 | let differences, prevState; 9 | 10 | // Mockup. 11 | let boxes = [ 12 | {'row': 0, 'column': 0, 'rowspan': 2, 'columnspan': 3}, 13 | {'row': 2, 'column': 0, 'rowspan': 1, 'columnspan': 4}, 14 | {'row': 3, 'column': 0, 'rowspan': 1, 'columnspan': 4} 15 | ]; 16 | let grid = dashGridGlobal('#grid', {boxes: boxes}); 17 | 18 | // Tests. 19 | t.plan(4); 20 | 21 | prevState = deepcopy(grid.grid); 22 | grid.updateBox(boxes[0], {row: 1}); 23 | t.equal(boxes[0].row, 1, 'Should move.'); 24 | t.equal(boxes[1].row, 3, 'Should move.'); 25 | t.equal(boxes[2].row, 4, 'Should move.'); 26 | differences = diff(grid.grid, prevState); 27 | t.equal(differences.length, 3, 'Only 3 boxes moved.'); 28 | 29 | t.end(); 30 | }); 31 | 32 | test('Another simple collision', function (t) { 33 | // Mockup. 34 | let differences, prevState; 35 | 36 | let boxes = [ 37 | {'row': 0, 'column': 0, 'rowspan': 2, 'columnspan': 3}, 38 | {'row': 2, 'column': 0, 'rowspan': 1, 'columnspan': 4}, 39 | {'row': 3, 'column': 0, 'rowspan': 1, 'columnspan': 4} 40 | ]; 41 | let grid = dashGridGlobal('#grid', {boxes: boxes}); 42 | 43 | prevState = deepcopy(grid.grid); 44 | grid.updateBox(boxes[0], {row: 2}); 45 | differences = diff(grid.grid, prevState); 46 | 47 | // Tests. 48 | t.plan(4); 49 | 50 | t.equal(boxes[0].row, 2, 'Should move.'); 51 | t.equal(boxes[1].row, 4, 'Should move.'); 52 | t.equal(boxes[2].row, 5, 'Should move.'); 53 | t.equal(differences.length, 3, 'Should move.'); 54 | 55 | t.end(); 56 | }); 57 | 58 | test('Column collision', function (t) { 59 | let differences, prevState; 60 | 61 | // Mockup. 62 | let boxes = [ 63 | {'row': 0, 'column': 0, 'rowspan': 2, 'columnspan': 2}, 64 | {'row': 0, 'column': 2, 'rowspan': 2, 'columnspan': 1}, 65 | {'row': 1, 'column': 3, 'rowspan': 2, 'columnspan': 1} 66 | ]; 67 | let grid = dashGridGlobal('#grid', {boxes: boxes}); 68 | 69 | // Tests. 70 | prevState = deepcopy(grid.grid); 71 | grid.updateBox(boxes[0], {column: 2}); 72 | differences = diff(grid.grid, prevState); 73 | 74 | t.plan(4); 75 | 76 | t.equal(boxes[0].column, 2, 'Should move.'); 77 | t.equal(boxes[1].row, 2, 'Should move.'); 78 | t.equal(boxes[2].row, 2, 'Should move.'); 79 | t.equal(differences.length, 3, 'Should move.'); 80 | 81 | t.end(); 82 | }); 83 | 84 | test('Complete collision', function (t) { 85 | let differences, prevState; 86 | 87 | // Mockup. 88 | let boxes = [ 89 | {'row': 0, 'column': 0, 'rowspan': 2, 'columnspan': 2}, 90 | {'row': 2, 'column': 2, 'rowspan': 2, 'columnspan': 2} 91 | ]; 92 | 93 | let grid = dashGridGlobal('#grid', {boxes: boxes}); 94 | 95 | prevState = deepcopy(grid.grid); 96 | grid.updateBox(boxes[0], {row: 2, column: 2}); 97 | differences = diff(grid.grid, prevState); 98 | 99 | // Tests. 100 | t.plan(5); 101 | t.equal(boxes[0].row, 2, 'Should move.'); 102 | t.equal(boxes[0].column, 2, 'Should move.'); 103 | t.equal(boxes[1].row, 4, 'Should move.'); 104 | t.equal(boxes[1].column, 2, 'Should move.'); 105 | t.equal(differences.length, 3, 'Should move.'); 106 | t.end(); 107 | }); 108 | 109 | test('Collision outside boundary.', function (t) { 110 | let differences, prevState; 111 | 112 | // Mockup. 113 | let boxes = [ 114 | {'row': 0, 'column': 0, 'rowspan': 2, 'columnspan': 2}, 115 | {'row': 2, 'column': 0, 'rowspan': 4, 'columnspan': 2} 116 | ]; 117 | let grid = dashGridGlobal('#grid', {boxes: boxes, maxRows: 6}); 118 | 119 | prevState = deepcopy(grid.grid); 120 | grid.updateBox(boxes[0], {row: 1}); 121 | differences = diff(grid.grid, prevState); 122 | 123 | // Tests. 124 | t.plan(3); 125 | 126 | t.equal(boxes[0].row, 0, 'Should not move.'); 127 | t.equal(boxes[1].row, 2, 'Should not move.'); 128 | t.equal(differences, undefined, 'Should not move.'); 129 | 130 | t.end(); 131 | }); 132 | 133 | test('Collision from under.', function (t) { 134 | let differences, prevState; 135 | 136 | let boxes = [ 137 | {'row': 0, 'column': 0, 'rowspan': 2, 'columnspan': 2}, 138 | {'row': 2, 'column': 0, 'rowspan': 4, 'columnspan': 2} 139 | ]; 140 | 141 | let grid = dashGridGlobal('#grid', {boxes: boxes, maxRows: 6}); 142 | t.plan(12); 143 | 144 | prevState = deepcopy(grid.grid); 145 | grid.updateBox(boxes[1], {row: 1}); 146 | differences = diff(grid.grid, prevState); 147 | t.equal(boxes[0].row, 0, 'Should not move.'); 148 | t.equal(boxes[1].row, 2, 'Should not move.'); 149 | t.equal(differences, undefined, 'Should not move.'); 150 | 151 | prevState = deepcopy(grid.grid); 152 | grid.updateBox(boxes[1], {row: 0}); 153 | differences = diff(grid.grid, prevState); 154 | t.equal(boxes[0].row, 4, 'Should move.'); 155 | t.equal(boxes[1].row, 0, 'Should move.'); 156 | t.equal(differences.length, 2, 'Should not move.'); 157 | 158 | prevState = deepcopy(grid.grid); 159 | grid.updateBox(boxes[0], {row: 3}); 160 | differences = diff(grid.grid, prevState); 161 | t.equal(boxes[0].row, 4, 'Should not move.'); 162 | t.equal(boxes[1].row, 0, 'Should not move.'); 163 | t.equal(differences, undefined, 'Should not move.'); 164 | 165 | prevState = deepcopy(grid.grid); 166 | grid.updateBox(boxes[0], {row: 0}); 167 | differences = diff(grid.grid, prevState); 168 | t.equal(boxes[0].row, 0, 'Should not move.'); 169 | t.equal(boxes[1].row, 2, 'Should not move.'); 170 | t.equal(differences.length, 2, 'Should not move.'); 171 | 172 | t.end(); 173 | }); 174 | 175 | } -------------------------------------------------------------------------------- /specs/tests/boxMove.test.js: -------------------------------------------------------------------------------- 1 | var diff = require('deep-diff').diff; 2 | var deepcopy = require('deepcopy'); 3 | 4 | import {isNumber, arraysEqual} from '../util.js'; 5 | 6 | // TODO: move row AND column. 7 | 8 | export default function boxMove(dashGridGlobal, test) { 9 | 10 | test('Moving box outside visible window.', function (t) { 11 | // Mockup. 12 | let differences, prevState; 13 | let boxes = [ 14 | {'row': 0, 'column': 8, 'rowspan': 3, 'columnspan': 3} 15 | ]; 16 | let grid = dashGridGlobal(document.getElementById('grid'), {boxes: boxes}); 17 | 18 | t.plan(28); 19 | 20 | prevState = deepcopy(grid.grid); 21 | grid.updateBox(grid.grid.boxes[0], {column: grid.grid.boxes[0].column + 9999}); 22 | differences = diff(grid.grid, prevState); 23 | t.equal(grid.grid.boxes[0].column, 0, 'Move 1 step north outside boundary'); 24 | t.equal(differences, undefined, 'Move 1 step north outside boundary'); 25 | 26 | }) 27 | 28 | test('Move boxes', function (t) { 29 | 30 | // Mockup. 31 | let differences, prevState; 32 | let boxes = [ 33 | {'row': 0, 'column': 0, 'rowspan': 3, 'columnspan': 3} 34 | ]; 35 | let grid = dashGridGlobal('#grid', {boxes: boxes}); 36 | 37 | t.plan(28); 38 | /** 39 | * Moving inside boundary. 40 | */ 41 | // Move down 1 row. 42 | prevState = deepcopy(grid.grid); 43 | grid.updateBox(grid.grid.boxes[0], {row: grid.grid.boxes[0].row + 1}); 44 | differences = diff(grid.grid, prevState); 45 | t.equal(grid.grid.boxes[0].row, 1, 'Move down 1 step'); 46 | t.equal(differences.length, 1, 'Move down 1 step'); 47 | 48 | // Move up 1 row. 49 | prevState = deepcopy(grid.grid); 50 | grid.updateBox(grid.grid.boxes[0], {row: grid.grid.boxes[0].row - 1}); 51 | differences = diff(grid.grid, prevState); 52 | t.equal(grid.grid.boxes[0].row, 0, 'Move up 1 step'); 53 | t.equal(differences.length, 1, 'Move down 1 step'); 54 | 55 | // Move down 2 rows. 56 | prevState = deepcopy(grid.grid); 57 | grid.updateBox(grid.grid.boxes[0], {row: grid.grid.boxes[0].row + 2}); 58 | differences = diff(grid.grid, prevState); 59 | t.equal(grid.grid.boxes[0].row, 2, 'Move up 2 step'); 60 | t.equal(differences.length, 1, 'Move down 2 step'); 61 | 62 | // Move up 2 rows. 63 | prevState = deepcopy(grid.grid); 64 | grid.updateBox(grid.grid.boxes[0], {row: grid.grid.boxes[0].row - 2}); 65 | differences = diff(grid.grid, prevState); 66 | t.equal(grid.grid.boxes[0].row, 0, 'Move up 2 step'); 67 | t.equal(differences.length, 1, 'Move down 2 step'); 68 | 69 | // Move to right 1 column. 70 | prevState = deepcopy(grid.grid); 71 | grid.updateBox(grid.grid.boxes[0], {column: grid.grid.boxes[0].column + 1}); 72 | differences = diff(grid.grid, prevState); 73 | t.equal(grid.grid.boxes[0].column, 1, 'Move 1 step right'); 74 | t.equal(differences.length, 1, 'Move 1 step right'); 75 | 76 | // Move to left 1 column. 77 | prevState = deepcopy(grid.grid); 78 | grid.updateBox(grid.grid.boxes[0], {column: grid.grid.boxes[0].column - 1}); 79 | differences = diff(grid.grid, prevState); 80 | t.equal(grid.grid.boxes[0].column, 0, 'Move 1 step left'); 81 | t.equal(differences.length, 1, 'Move 1 step left'); 82 | 83 | // Move to right 2 columns. 84 | prevState = deepcopy(grid.grid); 85 | grid.updateBox(grid.grid.boxes[0], {column: grid.grid.boxes[0].column + 2}); 86 | differences = diff(grid.grid, prevState); 87 | t.equal(grid.grid.boxes[0].column, 2, 'Move 2 step right'); 88 | t.equal(differences.length, 1, 'Move 2 step right'); 89 | 90 | // Move to left 2 columns. 91 | prevState = deepcopy(grid.grid); 92 | grid.updateBox(grid.grid.boxes[0], {column: grid.grid.boxes[0].column - 2}); 93 | differences = diff(grid.grid, prevState); 94 | t.equal(grid.grid.boxes[0].column, 0, 'Move 2 step left'); 95 | t.equal(differences.length, 1, 'Move 2 step left'); 96 | 97 | /** 98 | * Out-of-bound up-down left-right 99 | */ 100 | // Attempt to move part of box outside top border. 101 | prevState = deepcopy(grid.grid); 102 | grid.updateBox(grid.grid.boxes[0], {row: grid.grid.boxes[0].row - 1}); 103 | differences = diff(grid.grid, prevState); 104 | t.equal(grid.grid.boxes[0].row, 0, 'Move 1 step north outside boundary'); 105 | t.equal(differences, undefined, 'Move 1 step north outside boundary'); 106 | 107 | prevState = deepcopy(grid.grid); 108 | grid.updateBox(grid.grid.boxes[0], {row: grid.grid.boxes[0].row - 9999}); 109 | differences = diff(grid.grid, prevState); 110 | t.equal(grid.grid.boxes[0].row, 0, 'Move 1 step north outside boundary'); 111 | t.equal(differences, undefined, 'Move 1 step north outside boundary'); 112 | 113 | // Attempt to move out of bound row-wise (+). 114 | prevState = deepcopy(grid.grid); 115 | grid.updateBox(grid.grid.boxes[0], {row: grid.grid.boxes[0].row + 9999}); 116 | differences = diff(grid.grid, prevState); 117 | t.equal(grid.grid.boxes[0].row, 0, 'Move 1 step north outside boundary'); 118 | t.equal(differences, undefined, 'Move 1 step north outside boundary'); 119 | 120 | // Attempt to move part of box outside left border. 121 | prevState = deepcopy(grid.grid); 122 | grid.updateBox(grid.grid.boxes[0], {column: grid.grid.boxes[0].column - 1}); 123 | differences = diff(grid.grid, prevState); 124 | t.equal(grid.grid.boxes[0].column, 0, 'Move 1 step north outside boundary'); 125 | t.equal(differences, undefined, 'Move 1 step north outside boundary'); 126 | 127 | // Attempt to move whole box outside left border. 128 | prevState = deepcopy(grid.grid); 129 | grid.updateBox(grid.grid.boxes[0], {column: grid.grid.boxes[0].column - 9999}); 130 | differences = diff(grid.grid, prevState); 131 | t.equal(grid.grid.boxes[0].column, 0, 'Move 1 step north outside boundary'); 132 | t.equal(differences, undefined, 'Move 1 step north outside boundary'); 133 | 134 | // Attempt to move whole box outside right border. 135 | prevState = deepcopy(grid.grid); 136 | grid.updateBox(grid.grid.boxes[0], {column: grid.grid.boxes[0].column + 9999}); 137 | differences = diff(grid.grid, prevState); 138 | t.equal(grid.grid.boxes[0].column, 0, 'Move 1 step north outside boundary'); 139 | t.equal(differences, undefined, 'Move 1 step north outside boundary'); 140 | 141 | t.end(); 142 | }); 143 | 144 | } 145 | -------------------------------------------------------------------------------- /specs/tests/boxResize.test.js: -------------------------------------------------------------------------------- 1 | var diff = require('deep-diff').diff; 2 | var deepcopy = require('deepcopy'); 3 | 4 | import {isNumber, arraysEqual} from '../util.js'; 5 | 6 | // TODO: resize columnspan AND rowspan test. 7 | export default function boxResize(dashGridGlobal, test) { 8 | test('Resize boxes', function (t) { 9 | // Mockup. 10 | let differences, prevState; 11 | let boxes = [ 12 | {'row': 0, 'column': 0, 'rowspan': 3, 'columnspan': 3} 13 | ]; 14 | let grid = dashGridGlobal('#grid', {boxes: boxes}); 15 | 16 | t.plan(44); 17 | /** 18 | * VALID MOVES. 19 | */ 20 | 21 | // Resize down 1 row. 22 | prevState = deepcopy(grid.grid); 23 | grid.updateBox(grid.grid.boxes[0], {rowspan: grid.grid.boxes[0].rowspan + 1}); 24 | differences = diff(grid.grid, prevState); 25 | t.equal(grid.grid.boxes[0].rowspan, 4, 'Positive rowspan resize'); 26 | t.equal(differences.length, 1, 'Positive rowspan resize'); 27 | 28 | // Resize up 1 row. 29 | prevState = deepcopy(grid.grid); 30 | grid.updateBox(grid.grid.boxes[0], {rowspan: grid.grid.boxes[0].rowspan - 1}); 31 | differences = diff(grid.grid, prevState); 32 | t.equal(grid.grid.boxes[0].rowspan, 3, 'Negative rowspan resize'); 33 | t.equal(differences.length, 1, 'Negative rowspan resize'); 34 | 35 | // Resize down 2 row. 36 | prevState = deepcopy(grid.grid); 37 | grid.updateBox(grid.grid.boxes[0], {rowspan: grid.grid.boxes[0].rowspan + 2}); 38 | differences = diff(grid.grid, prevState); 39 | t.equal(grid.grid.boxes[0].rowspan, 5, 'Positive rowspan resize'); 40 | t.equal(differences.length, 1, 'Positive rowspan resize'); 41 | 42 | // Resize up 2 row. 43 | prevState = deepcopy(grid.grid); 44 | grid.updateBox(grid.grid.boxes[0], {rowspan: grid.grid.boxes[0].rowspan - 2}); 45 | differences = diff(grid.grid, prevState); 46 | t.equal(grid.grid.boxes[0].rowspan, 3, 'Negative rowspan resize'); 47 | t.equal(differences.length, 1, 'Negative rowspan resize'); 48 | 49 | // Resize to right 1 columnspan. 50 | prevState = deepcopy(grid.grid); 51 | grid.updateBox(grid.grid.boxes[0], {columnspan: grid.grid.boxes[0].columnspan + 1}); 52 | differences = diff(grid.grid, prevState); 53 | t.equal(grid.grid.boxes[0].columnspan, 4, 'Resize 1 step columnspan'); 54 | t.equal(differences.length, 1, 'Resize 1 step columnspan'); 55 | 56 | // Resize to left 1 columnspan. 57 | prevState = deepcopy(grid.grid); 58 | grid.updateBox(grid.grid.boxes[0], {columnspan: grid.grid.boxes[0].columnspan - 1}); 59 | differences = diff(grid.grid, prevState); 60 | t.equal(grid.grid.boxes[0].columnspan, 3, 'Resize 1 step left'); 61 | t.equal(differences.length, 1, 'Resize 1 step left'); 62 | 63 | // Resize to columnspan 2 columns. 64 | prevState = deepcopy(grid.grid); 65 | grid.updateBox(grid.grid.boxes[0], {columnspan: grid.grid.boxes[0].columnspan + 2}); 66 | differences = diff(grid.grid, prevState); 67 | t.equal(grid.grid.boxes[0].columnspan, 5, 'Resize 2 step columnspan'); 68 | t.equal(differences.length, 1, 'Resize 2 step columnspan'); 69 | 70 | // Resize to left 2 columns. 71 | prevState = deepcopy(grid.grid); 72 | grid.updateBox(grid.grid.boxes[0], {columnspan: grid.grid.boxes[0].columnspan - 2}); 73 | differences = diff(grid.grid, prevState); 74 | t.equal(grid.grid.boxes[0].columnspan, 3, 'Resize 2 step columnspan'); 75 | t.equal(differences.length, 1, 'Resize 2 step columnspan'); 76 | 77 | /** 78 | * NONE VALID MOVES. 79 | */ 80 | // Attempt to Resize part of box outside top border. 81 | prevState = deepcopy(grid.grid); 82 | grid.updateBox(grid.grid.boxes[0], {rowspan: grid.grid.boxes[0].rowspan - 3}); 83 | differences = diff(grid.grid, prevState); 84 | t.equal(grid.grid.boxes[0].rowspan, prevState.boxes[0].rowspan, 'Resize 0 rowspan'); 85 | t.equal(differences, undefined, 'Resize 0 rowspan'); 86 | 87 | prevState = deepcopy(grid.grid); 88 | grid.updateBox(grid.grid.boxes[0], {rowspan: grid.grid.boxes[0].rowspan - 9999}); 89 | differences = diff(grid.grid, prevState); 90 | t.equal(grid.grid.boxes[0].rowspan, prevState.boxes[0].rowspan, 'Resize - rowspan'); 91 | t.equal(differences, undefined, 'Resize - rowspan'); 92 | 93 | prevState = deepcopy(grid.grid); 94 | grid.updateBox(grid.grid.boxes[0], {rowspan: grid.grid.boxes[0].rowspan + 9999}); 95 | differences = diff(grid.grid, prevState); 96 | t.equal(grid.grid.boxes[0].rowspan, prevState.boxes[0].rowspan, 'Resize + rowspan'); 97 | t.equal(differences, undefined, 'Resize + rowspan'); 98 | 99 | prevState = deepcopy(grid.grid); 100 | grid.updateBox(grid.grid.boxes[0], {columnspan: grid.grid.boxes[0].columnspan - 3}); 101 | differences = diff(grid.grid, prevState); 102 | t.equal(grid.grid.boxes[0].columnspan, prevState.boxes[0].columnspan, 'Resize 0 columnspan'); 103 | t.equal(differences, undefined, 'Resize 0 columnspan'); 104 | 105 | prevState = deepcopy(grid.grid); 106 | grid.updateBox(grid.grid.boxes[0], {columnspan: grid.grid.boxes[0].columnspan - 9999}); 107 | differences = diff(grid.grid, prevState); 108 | t.equal(grid.grid.boxes[0].columnspan, prevState.boxes[0].columnspan, 'Resize - columnspan'); 109 | t.equal(differences, undefined, 'Resize - columnspan'); 110 | 111 | prevState = deepcopy(grid.grid); 112 | grid.updateBox(grid.grid.boxes[0], {columnspan: grid.grid.boxes[0].columnspan + 9999}); 113 | differences = diff(grid.grid, prevState); 114 | t.equal(grid.grid.boxes[0].columnspan, prevState.boxes[0].columnspan, 'Resize + columnspan'); 115 | t.equal(differences, undefined, 'Resize + columnspan'); 116 | 117 | /** 118 | * Testing min/max Columnspan and min/max Rowspan. 119 | */ 120 | prevState = deepcopy(grid.grid); 121 | grid.updateBox(grid.grid.boxes[0], {columnspan: grid.grid.boxes[0].columnspan - 9999}); 122 | differences = diff(grid.grid, prevState); 123 | t.equal(grid.grid.boxes[0].columnspan, prevState.boxes[0].columnspan, 'Resize - columnspan'); 124 | t.equal(differences, undefined, 'Resize - columnspan'); 125 | 126 | prevState = deepcopy(grid.grid); 127 | grid.updateBox(grid.grid.boxes[0], {columnspan: grid.grid.boxes[0].columnspan + 9999}); 128 | differences = diff(grid.grid, prevState); 129 | t.equal(grid.grid.boxes[0].columnspan, prevState.boxes[0].columnspan, 'Resize + columnspan'); 130 | t.equal(differences, undefined, 'Resize + columnspan'); 131 | 132 | prevState = deepcopy(grid.grid); 133 | grid.updateBox(grid.grid.boxes[0], {columnspan: grid.grid.boxes[0].columnspan - 9999}); 134 | differences = diff(grid.grid, prevState); 135 | t.equal(grid.grid.boxes[0].columnspan, prevState.boxes[0].columnspan, 'Resize - columnspan'); 136 | t.equal(differences, undefined, 'Resize - columnspan'); 137 | 138 | prevState = deepcopy(grid.grid); 139 | grid.updateBox(grid.grid.boxes[0], {columnspan: grid.grid.boxes[0].columnspan + 9999}); 140 | differences = diff(grid.grid, prevState); 141 | t.equal(grid.grid.boxes[0].columnspan, prevState.boxes[0].columnspan, 'Resize + columnspan'); 142 | t.equal(differences, undefined, 'Resize + columnspan'); 143 | 144 | boxes = [ 145 | {'row': 0, 'column': 0, 'rowspan': 3, 'columnspan': 3} 146 | ]; 147 | grid = dashGridGlobal('#grid', {boxes: boxes, 'minRowspan': 2, 'maxRowspan': 4, 'minColumnspan': 2, 'maxColumnspan': 4}); 148 | prevState = deepcopy(grid.grid); 149 | grid.updateBox(grid.grid.boxes[0], {rowspan: grid.grid.boxes[0].rowspan - 2}); 150 | differences = diff(grid.grid, prevState); 151 | t.equal(grid.grid.boxes[0].rowspan, prevState.boxes[0].rowspan, 'Resize - rowspan'); 152 | t.equal(differences, undefined, 'Resize - rowspan'); 153 | 154 | prevState = deepcopy(grid.grid); 155 | grid.updateBox(grid.grid.boxes[0], {rowspan: grid.grid.boxes[0].rowspan + 2}); 156 | differences = diff(grid.grid, prevState); 157 | t.equal(grid.grid.boxes[0].rowspan, prevState.boxes[0].rowspan, 'Resize - rowspan'); 158 | t.equal(differences, undefined, 'Resize - rowspan'); 159 | 160 | prevState = deepcopy(grid.grid); 161 | grid.updateBox(grid.grid.boxes[0], {columnspan: grid.grid.boxes[0].columnspan - 2}); 162 | differences = diff(grid.grid, prevState); 163 | t.equal(grid.grid.boxes[0].columnspan, prevState.boxes[0].columnspan, 'Resize - columnspan'); 164 | t.equal(differences, undefined, 'Resize - columnspan'); 165 | 166 | prevState = deepcopy(grid.grid); 167 | grid.updateBox(grid.grid.boxes[0], {columnspan: grid.grid.boxes[0].columnspan + 2}); 168 | differences = diff(grid.grid, prevState); 169 | t.equal(grid.grid.boxes[0].columnspan, prevState.boxes[0].columnspan, 'Resize - columnspan'); 170 | t.equal(differences, undefined, 'Resize - columnspan'); 171 | 172 | // Valid and invalid move at the same time 173 | 174 | // Valid and valid move at the same time 175 | 176 | // Invalid and invalid move at the same time 177 | 178 | t.end(); 179 | }); 180 | 181 | } 182 | -------------------------------------------------------------------------------- /src/lib/drag.js: -------------------------------------------------------------------------------- 1 | import * as Render from './render.js'; 2 | import * as GridMethod from './gridMethod.js'; 3 | 4 | export {start, dragging, end}; 5 | 6 | /** 7 | * Initialize Dragging. 8 | * any box is close to bottom / right 9 | * @param {Object} box 10 | * @param {Object} e 11 | */ 12 | function start({grid, box, e}) { 13 | // Initialize moving box. 14 | box.element.box.style.zIndex = 1004; 15 | box.element.box.style.transition = ''; 16 | 17 | // Initialize shadowBox. 18 | grid.element.shadowBox.style.left = box.element.box.style.left; 19 | grid.element.shadowBox.style.top = box.element.box.style.top; 20 | grid.element.shadowBox.style.width = box.element.box.style.width; 21 | grid.element.shadowBox.style.height = box.element.box.style.height; 22 | grid.element.shadowBox.style.display = 'block'; 23 | 24 | // Mouse values. 25 | grid.state.mouse.lastMouseX = e.pageX; 26 | grid.state.mouse.lastMouseY = e.pageY; 27 | grid.state.mouse.eX = parseInt(box.element.box.offsetLeft, 10); 28 | grid.state.mouse.eY = parseInt(box.element.box.offsetTop, 10); 29 | grid.state.mouse.eW = parseInt(box.element.box.offsetWidth, 10); 30 | grid.state.mouse.eH = parseInt(box.element.box.offsetHeight, 10); 31 | 32 | // grid.updateStart(box); 33 | // if (dashgrid.draggable.start) {dashgrid.draggable.start();} // user event. 34 | } 35 | 36 | /** 37 | * During dragging. 38 | * @param {Object} box 39 | * @param {Object} e 40 | */ 41 | function dragging({grid, box, e}) { 42 | updateMovingElement({grid, box, e}); 43 | 44 | if (grid.state.grid.liveChanges) { 45 | // Which cell to snap preview box to. 46 | // TODO: fix left / top, gives wrong value of -20. 47 | let left = box.element.box.offsetLeft; 48 | let right = box.element.box.offsetLeft + box.element.box.offsetWidth; 49 | let top = box.element.box.offsetTop; 50 | let bottom = box.element.box.offsetTop + box.element.box.offsetHeight; 51 | let {boxLeft, boxRight, boxTop, boxBottom} = Render.findIntersectedCells({ 52 | numRows: grid.state.grid.numRows, 53 | numColumns: grid.state.grid.numColumns, 54 | startRow: grid.state.render.startRow, 55 | startColumn: grid.state.render.startColumn, 56 | left, right, top, bottom}); 57 | 58 | grid.state.mouse.currentPosition = Render.getClosestCells({ 59 | xMargin: grid.state.grid.xMargin, 60 | yMargin: grid.state.grid.yMargin, 61 | startRow: grid.state.render.startRow, 62 | startColumn: grid.state.render.startColumn, 63 | boxLeft, boxRight, boxTop, boxBottom, left, right, top, bottom}); 64 | 65 | moveBox({grid, box, e}); 66 | } 67 | 68 | // if (dashgrid.draggable.dragging) {dashgrid.draggable.dragging();} // user event. 69 | } 70 | 71 | /** 72 | * On drag end. 73 | * @param {Object} box 74 | * @param {Object} e 75 | */ 76 | function end({grid, box, e}) { 77 | if (!grid.state.grid.liveChanges) { 78 | 79 | // Which cell to snap preview box to. 80 | grid.state.mouse.currentPosition = getClosestCells({ 81 | left: box.element.box.offsetLeft, 82 | right: box.element.box.offsetLeft + box.element.box.offsetWidth, 83 | top: box.element.box.offsetTop, 84 | bottom: box.element.box.offsetTop + box.element.box.offsetHeight 85 | }); 86 | 87 | moveBox(box, e); 88 | } 89 | 90 | // Set box style. 91 | box.element.box.style.transition = grid.state.grid.transition; 92 | box.element.box.style.left = grid.element.shadowBox.style.left; 93 | box.element.box.style.top = grid.element.shadowBox.style.top; 94 | 95 | // Give time for previewbox to snap back to tile. 96 | setTimeout(function () { 97 | box.element.box.style.zIndex = 1003; 98 | grid.element.shadowBox.style.display = 'none'; 99 | // grid.updateEnd(); 100 | }, grid.state.grid.snapBackTime); 101 | 102 | // if (grid.state.grid.draggable.dragEnd) {grid.state.grid.draggable.dragEnd();} // user event. 103 | } 104 | 105 | /** 106 | * Attempt to move box. 107 | * @param {Object} box 108 | * @param {Object} e 109 | */ 110 | function moveBox({grid, box, e}) { 111 | if (grid.state.mouse.currentPosition.row !== grid.state.mouse.previousPosition.row || 112 | grid.state.mouse.currentPosition.column !== grid.state.mouse.previousPosition.column) { 113 | 114 | let validMove = GridMethod.updateBox({grid, box, 115 | updateTo: grid.state.mouse.currentPosition 116 | }); 117 | 118 | // updateGridDimension preview box. 119 | if (validMove) { 120 | let prevScrollHeight = grid.element.shadowBox.offsetHeight - window.innerHeight; 121 | let prevScrollWidth = grid.element.shadowBox.offsetWidth - window.innerWidth 122 | 123 | grid.element.shadowBox.style.top = Render.getBoxElementYPosition({ 124 | row: grid.state.mouse.currentPosition.row, 125 | rowHeight: grid.state.render.rowHeight, 126 | yMargin: grid.state.grid.yMargin 127 | }); 128 | 129 | grid.element.shadowBox.style.left = Render.getBoxElementXPosition({ 130 | column: grid.state.mouse.currentPosition.column, 131 | columnWidth: grid.state.render.columnWidth, 132 | xMargin: grid.state.grid.xMargin 133 | }); 134 | 135 | let postScrollHeight = grid.element.shadowBox.offsetHeight - window.innerHeight; 136 | let postScrollWidth = grid.element.shadowBox.offsetWidth - window.innerWidth; 137 | 138 | // Account for minimizing scroll height when moving box upwards. 139 | // Otherwise bug happens where the dragged box is changed but directly 140 | // afterwards the dashgrid element dimension is changed. 141 | if (Math.abs(grid.element.shadowBox.offsetHeight - window.innerHeight - window.scrollY) < 30 && 142 | window.scrollY > 0 && 143 | prevScrollHeight !== postScrollHeight) { 144 | box.element.box.style.top = box.element.box.offsetTop - 100 + 'px'; 145 | } 146 | 147 | if (Math.abs(grid.element.shadowBox.offsetWidth - window.innerWidth - window.scrollX) < 30 && 148 | window.scrollX > 0 && 149 | prevScrollWidth !== postScrollWidth) { 150 | 151 | box.element.box.style.left = box.element.box.offsetLeft - 100 + 'px'; 152 | } 153 | } 154 | } 155 | 156 | // No point in attempting move if not switched to new cell. 157 | grid.state.mouse.previousPosition = {row: grid.state.mouse.currentPosition.row, column: grid.state.mouse.currentPosition.column}; 158 | } 159 | 160 | /** 161 | * Moves the moving box. 162 | * @param {Object} grid 163 | * @param {Object} box 164 | * @param {Object} e event 165 | */ 166 | function updateMovingElement({grid, box, e}) { 167 | Render.updateRenderBoundary(grid); 168 | 169 | // Get the current mouse position. 170 | grid.state.mouse.mouseX = e.pageX; 171 | grid.state.mouse.mouseY = e.pageY; 172 | 173 | // Get the deltas 174 | let diffX = grid.state.mouse.mouseX - grid.state.mouse.lastMouseX + grid.state.mouse.mOffX; 175 | let diffY = grid.state.mouse.mouseY - grid.state.mouse.lastMouseY + grid.state.mouse.mOffY; 176 | 177 | grid.state.mouse.mOffX = 0; 178 | grid.state.mouse.mOffY = 0; 179 | 180 | // Update last processed mouse positions. 181 | grid.state.mouse.lastMouseX = grid.state.mouse.mouseX; 182 | grid.state.mouse.lastMouseY = grid.state.mouse.mouseY; 183 | 184 | let dX = diffX; 185 | let dY = diffY; 186 | if (grid.state.mouse.eX + dX < grid.state.render.minLeft) { 187 | diffX = grid.state.render.minLeft - grid.state.mouse.eX; 188 | grid.state.mouse.mOffX = dX - diffX; 189 | } else if (grid.state.mouse.eX + grid.state.mouse.eW + dX > grid.state.render.maxLeft) { 190 | diffX = grid.state.render.maxLeft - grid.state.mouse.eX - grid.state.mouse.eW; 191 | grid.state.mouse.mOffX = dX - diffX; 192 | } 193 | 194 | if (grid.state.mouse.eY + dY < grid.state.render.minTop) { 195 | diffY = grid.state.render.minTop - grid.state.mouse.eY; 196 | grid.state.mouse.mOffY = dY - diffY; 197 | } else if (grid.state.mouse.eY + grid.state.mouse.eH + dY > grid.state.render.maxTop) { 198 | diffY = grid.state.render.maxTop - grid.state.mouse.eY - grid.state.mouse.eH; 199 | grid.state.mouse.mOffY = dY - diffY; 200 | } 201 | 202 | grid.state.mouse.eX += diffX; 203 | grid.state.mouse.eY += diffY; 204 | 205 | box.element.box.style.left = grid.state.mouse.eX + 'px'; 206 | box.element.box.style.top = grid.state.mouse.eY + 'px'; 207 | 208 | // Scrolling when close to bottom boundary. 209 | if (e.pageY - document.body.scrollTop < grid.state.grid.scrollSensitivity) { 210 | document.body.scrollTop = document.body.scrollTop - grid.state.grid.scrollSpeed; 211 | } else if (window.innerHeight - (e.pageY - document.body.scrollTop) < grid.state.grid.scrollSensitivity) { 212 | document.body.scrollTop = document.body.scrollTop + grid.state.grid.scrollSpeed; 213 | } 214 | 215 | // Scrolling when close to right boundary. 216 | if (e.pageX - document.body.scrollLeft < grid.state.grid.scrollSensitivity) { 217 | document.body.scrollLeft = document.body.scrollLeft - grid.state.grid.scrollSpeed; 218 | } else if (window.innerWidth - (e.pageX - document.body.scrollLeft) < grid.state.grid.scrollSensitivity) { 219 | document.body.scrollLeft = document.body.scrollLeft + grid.state.grid.scrollSpeed; 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/lib/resize.js: -------------------------------------------------------------------------------- 1 | export {Resize}; 2 | 3 | function Resize({render, grid}) { 4 | let minWidth, minHeight, elementLeft, elementTop, elementWidth, elementHeight, minTop, maxTop, minLeft, maxLeft, className, 5 | mouseX = 0, 6 | mouseY = 0, 7 | lastMouseX = 0, 8 | lastMouseY = 0, 9 | mOffX = 0, 10 | mOffY = 0, 11 | newState = {}, 12 | prevState = {}; 13 | 14 | /** 15 | * @param {Object} box 16 | * @param {Object} e 17 | */ 18 | let resizeStart = function (box, e) { 19 | className = e.target.className; 20 | 21 | // Removes transitions, displays and inits positions for preview box. 22 | box._element.style.zIndex = 1004; 23 | box._element.style.transition = ''; 24 | dashgrid._shadowBoxElement.style.left = box._element.style.left; 25 | dashgrid._shadowBoxElement.style.top = box._element.style.top; 26 | dashgrid._shadowBoxElement.style.width = box._element.style.width; 27 | dashgrid._shadowBoxElement.style.height = box._element.style.height; 28 | dashgrid._shadowBoxElement.style.display = ''; 29 | 30 | // Mouse values. 31 | minWidth = renderer.getColumnWidth(); 32 | minHeight = renderer.getRowHeight(); 33 | lastMouseX = e.pageX; 34 | lastMouseY = e.pageY; 35 | elementLeft = parseInt(box._element.style.left, 10); 36 | elementTop = parseInt(box._element.style.top, 10); 37 | elementWidth = box._element.offsetWidth; 38 | elementHeight = box._element.offsetHeight; 39 | 40 | grid.updateStart(box); 41 | 42 | if (dashgrid.resizable.resizeStart) {dashgrid.resizable.resizeStart();} // user cb. 43 | }; 44 | 45 | /** 46 | * 47 | * @param {Object} box 48 | * @param {Object} e 49 | */ 50 | let resize = function (box, e) { 51 | updateResizingElement(box, e); 52 | grid.updating(box); 53 | 54 | if (dashgrid.liveChanges) { 55 | // Which cell to snap shadowbox to. 56 | let {boxLeft, boxRight, boxTop, boxBottom} = renderer. 57 | findIntersectedCells({ 58 | left: box._element.offsetLeft, 59 | right: box._element.offsetLeft + box._element.offsetWidth, 60 | top: box._element.offsetTop, 61 | bottom: box._element.offsetTop + box._element.offsetHeight, 62 | }); 63 | newState = {row: boxTop, column: boxLeft, rowspan: boxBottom - boxTop + 1, columnspan: boxRight - boxLeft + 1}; 64 | 65 | resizeBox(box, e); 66 | } 67 | 68 | if (dashgrid.resizable.resizing) {dashgrid.resizable.resizing();} // user cb. 69 | }; 70 | 71 | /** 72 | * 73 | * @param {Object} box 74 | * @param {Object} e 75 | */ 76 | let resizeEnd = function (box, e) { 77 | if (!dashgrid.liveChanges) { 78 | let {boxLeft, boxRight, boxTop, boxBottom} = renderer. 79 | findIntersectedCells({ 80 | left: box._element.offsetLeft, 81 | right: box._element.offsetLeft + box._element.offsetWidth, 82 | top: box._element.offsetTop, 83 | bottom: box._element.offsetTop + box._element.offsetHeight, 84 | numRows: grid.getNumRows(), 85 | numColumns: grid.getNumColumns() 86 | }); 87 | newState = {row: boxTop, column: boxLeft, rowspan: boxBottom - boxTop + 1, columnspan: boxRight - boxLeft + 1}; 88 | resizeBox(box, e); 89 | } 90 | 91 | // Set box style. 92 | box._element.style.transition = dashgrid.transition; 93 | box._element.style.left = dashgrid._shadowBoxElement.style.left; 94 | box._element.style.top = dashgrid._shadowBoxElement.style.top; 95 | box._element.style.width = dashgrid._shadowBoxElement.style.width; 96 | box._element.style.height = dashgrid._shadowBoxElement.style.height; 97 | 98 | // Give time for previewbox to snap back to tile. 99 | setTimeout(function () { 100 | box._element.style.zIndex = 1003; 101 | dashgrid._shadowBoxElement.style.display = ''; 102 | grid.updateEnd(); 103 | }, dashgrid.snapBackTime); 104 | 105 | if (dashgrid.resizable.resizeEnd) {dashgrid.resizable.resizeEnd();} // user cb. 106 | }; 107 | 108 | /** 109 | * 110 | * @param {Object} box 111 | * @param {Object} e 112 | */ 113 | let resizeBox = function (box, e) { 114 | if (newState.row !== prevState.row || 115 | newState.column !== prevState.column || 116 | newState.rowspan !== prevState.rowspan || 117 | newState.columnspan !== prevState.columnspan ) { 118 | 119 | let update = grid.updateBox(box, newState, box); 120 | 121 | // updateGridDimension preview box. 122 | if (update) { 123 | renderer.setBoxElementXPosition(dashgrid._shadowBoxElement, newState.column); 124 | renderer.setBoxElementYPosition(dashgrid._shadowBoxElement, newState.row); 125 | renderer.setBoxElementWidth(dashgrid._shadowBoxElement, newState.columnspan); 126 | renderer.setBoxElementHeight(dashgrid._shadowBoxElement, newState.rowspan); 127 | } 128 | } 129 | 130 | // No point in attempting update if not switched to new cell. 131 | prevState.row = newState.row; 132 | prevState.column = newState.column; 133 | prevState.rowspan = newState.rowspan; 134 | prevState.columnspan = newState.columnspan; 135 | 136 | if (dashgrid.resizable.resizing) {dashgrid.resizable.resizing();} // user cb. 137 | }; 138 | 139 | /** 140 | * 141 | * @param {Object} box 142 | * @param {Object} e 143 | */ 144 | let updateResizingElement = function (box, e) { 145 | // Get the current mouse position. 146 | mouseX = e.pageX; 147 | mouseY = e.pageY; 148 | 149 | // Get the deltas 150 | let diffX = mouseX - lastMouseX + mOffX; 151 | let diffY = mouseY - lastMouseY + mOffY; 152 | mOffX = mOffY = 0; 153 | 154 | // Update last processed mouse positions. 155 | lastMouseX = mouseX; 156 | lastMouseY = mouseY; 157 | 158 | let dY = diffY; 159 | let dX = diffX; 160 | 161 | minTop = dashgrid.yMargin; 162 | maxTop = dashgrid._element.offsetHeight - dashgrid.yMargin; 163 | minLeft = dashgrid.xMargin; 164 | maxLeft = dashgrid._element.offsetWidth - dashgrid.xMargin; 165 | 166 | if (className.indexOf('dashgrid-box-resize-handle-w') > -1 || 167 | className.indexOf('dashgrid-box-resize-handle-nw') > -1 || 168 | className.indexOf('dashgrid-box-resize-handle-sw') > -1) { 169 | if (elementWidth - dX < minWidth) { 170 | diffX = elementWidth - minWidth; 171 | mOffX = dX - diffX; 172 | } else if (elementLeft + dX < minLeft) { 173 | diffX = minLeft - elementLeft; 174 | mOffX = dX - diffX; 175 | } 176 | elementLeft += diffX; 177 | elementWidth -= diffX; 178 | } 179 | 180 | if (className.indexOf('dashgrid-box-resize-handle-e') > -1 || 181 | className.indexOf('dashgrid-box-resize-handle-ne') > -1 || 182 | className.indexOf('dashgrid-box-resize-handle-se') > -1) { 183 | 184 | if (elementWidth + dX < minWidth) { 185 | diffX = minWidth - elementWidth; 186 | mOffX = dX - diffX; 187 | } else if (elementLeft + elementWidth + dX > maxLeft) { 188 | diffX = maxLeft - elementLeft - elementWidth; 189 | mOffX = dX - diffX; 190 | } 191 | elementWidth += diffX; 192 | } 193 | 194 | if (className.indexOf('dashgrid-box-resize-handle-n') > -1 || 195 | className.indexOf('dashgrid-box-resize-handle-nw') > -1 || 196 | className.indexOf('dashgrid-box-resize-handle-ne') > -1) { 197 | if (elementHeight - dY < minHeight) { 198 | diffY = elementHeight - minHeight; 199 | mOffY = dY - diffY; 200 | } else if (elementTop + dY < minTop) { 201 | diffY = minTop - elementTop; 202 | mOffY = dY - diffY; 203 | } 204 | elementTop += diffY; 205 | elementHeight -= diffY; 206 | } 207 | 208 | if (className.indexOf('dashgrid-box-resize-handle-s') > -1 || 209 | className.indexOf('dashgrid-box-resize-handle-sw') > -1 || 210 | className.indexOf('dashgrid-box-resize-handle-se') > -1) { 211 | if (elementHeight + dY < minHeight) { 212 | diffY = minHeight - elementHeight; 213 | mOffY = dY - diffY; 214 | } else if (elementTop + elementHeight + dY > maxTop) { 215 | diffY = maxTop - elementTop - elementHeight; 216 | mOffY = dY - diffY; 217 | } 218 | elementHeight += diffY; 219 | } 220 | 221 | box._element.style.top = elementTop + 'px'; 222 | box._element.style.left = elementLeft + 'px'; 223 | box._element.style.width = elementWidth + 'px'; 224 | box._element.style.height = elementHeight + 'px'; 225 | 226 | // Scrolling when close to bottom boundary. 227 | if (e.pageY - document.body.scrollTop < dashgrid.scrollSensitivity) { 228 | document.body.scrollTop = document.body.scrollTop - dashgrid.scrollSpeed; 229 | } else if (window.innerHeight - (e.pageY - document.body.scrollTop) < dashgrid.scrollSensitivity) { 230 | document.body.scrollTop = document.body.scrollTop + dashgrid.scrollSpeed; 231 | } 232 | 233 | // Scrolling when close to right boundary. 234 | if (e.pageX - document.body.scrollLeft < dashgrid.scrollSensitivity) { 235 | document.body.scrollLeft = document.body.scrollLeft - dashgrid.scrollSpeed; 236 | } else if (window.innerWidth - (e.pageX - document.body.scrollLeft) < dashgrid.scrollSensitivity) { 237 | document.body.scrollLeft = document.body.scrollLeft + dashgrid.scrollSpeed; 238 | } 239 | }; 240 | 241 | return Object.freeze({ 242 | resizeStart, 243 | resize, 244 | resizeEnd 245 | }); 246 | } 247 | -------------------------------------------------------------------------------- /src/lib/render.js: -------------------------------------------------------------------------------- 1 | import * as GridVisual from '../element/gridVisual.js'; 2 | 3 | export { 4 | renderGrid, 5 | renderBox, 6 | updateRenderBoundary, 7 | getColumnWidth, 8 | getRowHeight, 9 | findIntersectedCells, 10 | getCellCentroidPosition, 11 | getClosestCells, 12 | getGridElementWidth, 13 | getGridElementHeight, 14 | getBoxElementXPosition, 15 | getBoxElementYPosition, 16 | getBoxElementWidth, 17 | getBoxElementHeight 18 | }; 19 | 20 | /** 21 | * [getGridElementWidth description] 22 | * @param {[type]} {columnWidth [description] 23 | * @param {[type]} parentWidth [description] 24 | * @param {[type]} numColumns [description] 25 | * @param {[type]} xMargin} [description] 26 | * @return {[type]} [description] 27 | */ 28 | function getGridElementWidth({columnWidth, parentWidth, numColumns, xMargin}) { 29 | return (columnWidth) ? 30 | columnWidth * numColumns + (numColumns + 1) * xMargin + 'px' : parentWidth + 'px'; 31 | } 32 | 33 | /** 34 | * [getGridElementHeight description] 35 | * @param {[type]} {rowHeight [description] 36 | * @param {[type]} numRows [description] 37 | * @param {[type]} yMargin [description] 38 | * @param {[type]} parentHeight} [description] 39 | * @return {[type]} [description] 40 | */ 41 | function getGridElementHeight({rowHeight, numRows, yMargin, parentHeight}) { 42 | return (rowHeight) ? 43 | rowHeight * numRows + (numRows + 1) * yMargin + 'px' : parentHeight + 'px'; 44 | } 45 | 46 | /** 47 | * 48 | * @param {number} columnWidth, 49 | * @param {number} parentWidth, 50 | * @param {number} numColumns, 51 | * @param {number} xMargin 52 | * @returns {number} Column Width in pixels. 53 | */ 54 | function getColumnWidth({columnWidth, parentWidth, numColumns, xMargin}) { 55 | return (columnWidth !== 'auto') ? 56 | columnWidth : (parentWidth - (numColumns + 1) * xMargin) / numColumns; 57 | } 58 | 59 | /** 60 | * 61 | * @param {number} columnWidth, 62 | * @param {number} parentWidth, 63 | * @param {number} numColumns, 64 | * @param {number} xMargin 65 | * @returns {number} Row Width in pixels. 66 | */ 67 | function getRowHeight({rowHeight, parentHeight, numRows, yMargin}) { 68 | return (rowHeight !== 'auto') ? 69 | rowHeight : (parentHeight - (numRows + 1) * yMargin) / numRows; 70 | } 71 | 72 | /** 73 | * 74 | * @param {Number} column Column number. 75 | * @returns 76 | */ 77 | function getBoxElementXPosition({column, columnWidth, xMargin}) { 78 | return column * columnWidth + xMargin * (column + 1) + 'px'; 79 | } 80 | 81 | /** 82 | * 83 | * @param {Number} row Row number. 84 | * @returns 85 | */ 86 | function getBoxElementYPosition({row, rowHeight, yMargin}) { 87 | return row * rowHeight + yMargin * (row + 1) + 'px'; 88 | } 89 | 90 | /** 91 | * 92 | * @param {Number} columnspan Columnspan number. 93 | * @returns 94 | */ 95 | function getBoxElementWidth({columnspan, columnWidth, xMargin}) { 96 | return columnspan * columnWidth + xMargin * (columnspan - 1) + 'px'; 97 | } 98 | 99 | /** 100 | * 101 | * @param {Number} rowspan Rowspan number. 102 | * @returns 103 | */ 104 | function getBoxElementHeight({rowspan, rowHeight, yMargin}) { 105 | return rowspan * rowHeight + yMargin * (rowspan - 1) + 'px'; 106 | } 107 | 108 | /** 109 | * Initializes cell centroids which are used to compute closest cell 110 | * when dragging a box. 111 | * @param {Number} numRows The total number of rows. 112 | * @param {Number} numColumns The total number of rows. 113 | * @returns {Object.} startRow, startColumn. 114 | */ 115 | function getCellCentroidPosition({numRows, numColumns, yMargin, xMargin, 116 | rowHeight, columnWidth}) { 117 | let startRow = []; 118 | let startColumn = []; 119 | let start; 120 | let stop; 121 | 122 | for (let i = 0; i < numRows; i += 1) { 123 | start = i * (rowHeight + yMargin) + yMargin / 2; 124 | stop = start + rowHeight + yMargin; 125 | startRow.push([Math.floor(start), Math.ceil(stop)]); 126 | } 127 | 128 | for (let i = 0; i < numColumns; i += 1) { 129 | start = i * (columnWidth + xMargin) + xMargin / 2; 130 | stop = start + columnWidth + xMargin; 131 | startColumn.push([Math.floor(start), Math.ceil(stop)]); 132 | } 133 | 134 | return {startRow, startColumn}; 135 | } 136 | 137 | /** 138 | * Finds which cells box intersects with. 139 | * @param {Object} boxPosition Contains top/bottom/left/right box position 140 | * in px. 141 | * @param {Number} numRows How many rows the box spans. 142 | * @param {Number} numColumns How many rows the box spans. 143 | * @return {Object} The row or column which each side is found in. 144 | * For instance, boxLeft: column = 0, boxRight: column = 1, 145 | * BoxTop: row = 0, BoxBottom: row = 3. 146 | */ 147 | function findIntersectedCells({numRows, numColumns, startRow, startColumn, 148 | top, right, bottom, left, grid}) { 149 | // TODO: boxLeft returns undefined, because left is -20. 150 | let boxLeft, boxRight, boxTop, boxBottom; 151 | 152 | // Find top and bottom intersection cell row. 153 | for (let i = 0; i < numRows; i += 1) { 154 | if (top >= startRow[i][0] && 155 | top <= startRow[i][1]) { 156 | boxTop = i; 157 | } 158 | if (bottom >= startRow[i][0] && 159 | bottom <= startRow[i][1]) { 160 | boxBottom = i; 161 | } 162 | } 163 | 164 | // Find left and right intersection cell column. 165 | for (let j = 0; j < numColumns; j += 1) { 166 | if (left >= startColumn[j][0] && 167 | left <= startColumn[j][1]) { 168 | boxLeft = j; 169 | } 170 | if (right >= startColumn[j][0] && 171 | right <= startColumn[j][1]) { 172 | boxRight = j; 173 | } 174 | } 175 | 176 | return {boxLeft, boxRight, boxTop, boxBottom}; 177 | } 178 | 179 | /** 180 | * Get closest cell given (row, column) position in px. 181 | * @param {Object} boxPosition Contains top/bottom/left/right box position 182 | * in px. 183 | * @param {Number} numRows 184 | * @param {Number} numColumns 185 | * @returns {Object} 186 | */ 187 | function getClosestCells({xMargin, yMargin, startRow, startColumn, boxLeft, 188 | boxRight, boxTop, boxBottom, left, right, top, bottom}) { 189 | 190 | let column; 191 | let leftOverlap; 192 | let rightOverlap; 193 | // Determine if enough overlap for horizontal move. 194 | if (boxLeft !== undefined && boxRight !== undefined) { 195 | leftOverlap = Math.abs(left - startColumn[boxLeft][0]); 196 | rightOverlap = Math.abs(right - startColumn[boxRight][1] - xMargin); 197 | if (leftOverlap <= rightOverlap) {column = boxLeft;} 198 | else {column = boxLeft + 1;} 199 | } 200 | 201 | let row; 202 | let topOverlap; 203 | let bottomOverlap; 204 | // Determine if enough overlap for vertical move. 205 | if (boxTop !== undefined && boxBottom !== undefined) { 206 | topOverlap = Math.abs(top - startRow[boxTop][0]); 207 | bottomOverlap = Math.abs(bottom - startRow[boxBottom][1] - yMargin); 208 | if (topOverlap <= bottomOverlap) {row = boxTop;} 209 | else {row = boxTop + 1;} 210 | } 211 | 212 | return {row, column}; 213 | } 214 | 215 | function renderGrid(grid) { 216 | grid.state.render.columnWidth = getColumnWidth({ 217 | columnWidth: grid.state.grid.columnWidth, 218 | parentWidth: grid.element.grid.parentNode.offsetWidth, 219 | numColumns: grid.state.grid.numColumns, 220 | xMargin: grid.state.grid.xMargin 221 | }); 222 | 223 | grid.state.render.rowHeight = getRowHeight({ 224 | rowHeight: grid.state.grid.rowHeight, 225 | parentHeight: grid.element.grid.parentNode.offsetHeight, 226 | numRows: grid.state.grid.numRows, 227 | yMargin: grid.state.grid.yMargin 228 | }); 229 | 230 | grid.element.grid.style.height = getGridElementHeight({ 231 | rowHeight: grid.state.render.rowHeight, 232 | parentHeight: grid.element.grid.parentNode.offsetHeight, 233 | numRows: grid.state.grid.numRows, 234 | yMargin: grid.state.grid.yMargin 235 | }); 236 | 237 | grid.element.grid.style.width = getGridElementWidth({ 238 | columnWidth: grid.state.render.columnWidth, 239 | parentWidth: grid.element.grid.parentNode.offsetWidth, 240 | numColumns: grid.state.grid.numColumns, 241 | xMargin: grid.state.grid.xMargin 242 | }); 243 | 244 | if (grid.state.grid.showVerticalLine) { 245 | grid.element.vertical.innerHTML = ""; 246 | grid.element.vertical.appendChild(GridVisual.VerticalLines(grid)); 247 | } 248 | 249 | if (grid.state.grid.showHorizontalLine) { 250 | grid.element.horizontal.innerHTML = ""; 251 | grid.element.horizontal.appendChild(GridVisual.HorizontalLines(grid)); 252 | } 253 | 254 | if (grid.state.grid.showCentroid) { 255 | grid.element.centroid.innerHTML = ""; 256 | grid.element.centroid.appendChild(GridVisual.Centroids(grid)); 257 | } 258 | } 259 | 260 | 261 | function renderBox({grid, boxes, excludeBox}) { 262 | if (!Array.isArray(boxes)) { boxes = [boxes]; } 263 | 264 | boxes.forEach(function (box) { 265 | if (excludeBox !== box) { 266 | box.element.box.style.top = getBoxElementYPosition({ 267 | row: box.state.box.row, 268 | rowHeight: grid.state.render.rowHeight, 269 | yMargin: grid.state.grid.yMargin 270 | }); 271 | box.element.box.style.left = getBoxElementXPosition({ 272 | column: box.state.box.column, 273 | columnWidth: grid.state.render.columnWidth, 274 | xMargin: grid.state.grid.xMargin 275 | }); 276 | box.element.box.style.height = getBoxElementHeight({ 277 | rowspan: box.state.box.rowspan, 278 | rowHeight: grid.state.render.rowHeight, 279 | yMargin: grid.state.grid.yMargin 280 | }); 281 | box.element.box.style.width = getBoxElementWidth({ 282 | columnspan: box.state.box.columnspan, 283 | columnWidth: grid.state.render.columnWidth, 284 | xMargin: grid.state.grid.xMargin 285 | }); 286 | } 287 | } 288 | ); 289 | } 290 | 291 | /** 292 | * [updateRenderBoundary description] 293 | * @param {[type]} grid [description] 294 | * @return {[type]} [description] 295 | */ 296 | function updateRenderBoundary(grid) { 297 | // Left. 298 | grid.state.render.minLeft = grid.state.grid.xMargin; 299 | grid.state.render.maxLeft = grid.element.grid.offsetWidth - grid.state.grid.xMargin; 300 | 301 | // Top. 302 | grid.state.render.minTop = grid.state.grid.yMargin; 303 | grid.state.render.maxTop = grid.element.grid.offsetHeight - grid.state.grid.yMargin; 304 | } 305 | -------------------------------------------------------------------------------- /specs/tests/initGrid.test.js: -------------------------------------------------------------------------------- 1 | import {isNumber, arraysEqual} from '../util.js'; 2 | 3 | export default function initGrid(dashGridGlobal, test) { 4 | // Mockup. 5 | let boxes = [ 6 | {'row': 0, 'column': 0, 'rowspan': 3, 'columnspan': 3, 'floating': false, 'swapping': false, 'pushable': true, 'resizable': true, 'draggable': true} 7 | ]; 8 | 9 | test('Initialize Grid using default values', function (t) { 10 | let dashgrid = dashGridGlobal(document.getElementById('grid'), {}); 11 | 12 | // Check that grid object gets all properties. 13 | t.notEqual(dashgrid.dashgrid, undefined, 'Returns object'); 14 | t.notEqual(dashgrid.dashgrid, undefined, 'Has grid options'); 15 | t.notEqual(dashgrid.updateBox, undefined, 'Has API updateBox'); 16 | t.notEqual(dashgrid.insertBox, undefined, 'Has API insertBox'); 17 | t.notEqual(dashgrid.removeBox, undefined, 'Has API removeBox'); 18 | 19 | t.equal(dashgrid.dashgrid._element.nodeName, 'DIV', 'Grid Element initialized'); 20 | t.equal(Array.isArray(dashgrid.dashgrid.boxes), true, 'Boxes is array'); 21 | 22 | t.equal(isNumber(dashgrid.dashgrid.numRows), true, 'numRows initialized'); 23 | t.equal(isNumber(dashgrid.dashgrid.minRows), true, 'minRows initialized'); 24 | t.equal(isNumber(dashgrid.dashgrid.maxRows), true, 'maxRows initialized'); 25 | 26 | t.equal(isNumber(dashgrid.dashgrid.numColumns), true, 'numColumns initialized'); 27 | t.equal(isNumber(dashgrid.dashgrid.minColumns), true, 'minColumns initialized'); 28 | t.equal(isNumber(dashgrid.dashgrid.maxColumns), true, 'maxColumns initialized'); 29 | 30 | t.equal(isNumber(dashgrid.dashgrid.minRowspan), true, 'minRowspan initialized'); 31 | t.equal(isNumber(dashgrid.dashgrid.maxRowspan), true, 'maxRowspan initialized'); 32 | t.equal(isNumber(dashgrid.dashgrid.minColumnspan), true, 'minColumnspan initialized'); 33 | t.equal(isNumber(dashgrid.dashgrid.maxColumnspan), true, 'maxColumnspan initialized'); 34 | 35 | t.equal(isNumber(dashgrid.dashgrid.xMargin), true, 'xMargin initialized'); 36 | t.equal(isNumber(dashgrid.dashgrid.yMargin), true, 'yMargin initialized'); 37 | 38 | // t.equal(typeof grid.pushable, 'boolean', 'pushable initialized'); 39 | // t.equal(typeof grid.floating, 'boolean', 'floating initialized'); 40 | // t.equal(typeof grid.stacking, 'boolean', 'stacking initialized'); 41 | // t.equal(typeof grid.swapping, 'boolean', 'swapping initialized'); 42 | 43 | t.equal(typeof dashgrid.dashgrid.animate, 'boolean', 'animate initialized'); 44 | t.equal(typeof dashgrid.dashgrid.liveChanges, 'boolean', 'liveChanges initialized'); 45 | // t.equal(isNumber(dashgrid.dashgrid.mobileBreakPoint), true, 'mobileBreakPoint initialized'); 46 | // t.equal(typeof grid.mobileBreakPointEnabled, 'boolean', 'mobileBreakPointEnabled initialized'); 47 | 48 | t.equal(isNumber(dashgrid.dashgrid.scrollSensitivity), true, 'scrollSensitivity initialized'); 49 | t.equal(isNumber(dashgrid.dashgrid.scrollSpeed), true, 'scrollSpeed initialized'); 50 | 51 | t.equal(isNumber(dashgrid.dashgrid.snapBackTime), true, 'snapBackTime initialized'); 52 | t.equal(typeof dashgrid.dashgrid.showGridLines, 'boolean', 'showGridLines initialized'); 53 | t.equal(typeof dashgrid.dashgrid.showGridCentroids, 'boolean', 'showGridCentroids initialized'); 54 | 55 | t.equal(typeof dashgrid.dashgrid.draggable, 'object', 'draggable initialized'); 56 | t.equal(typeof dashgrid.dashgrid.draggable.enabled, 'boolean', 'draggable.enabled initialized'); 57 | t.equal(dashgrid.dashgrid.draggable.handle, 'dashgrid-box', 'draggable.handles initialized'); 58 | t.equal(dashgrid.dashgrid.draggable.dragStart, undefined, 'dragStart initialized'); 59 | t.equal(dashgrid.dashgrid.draggable.dragging, undefined, 'dragging initialized'); 60 | t.equal(dashgrid.dashgrid.draggable.dragEnd, undefined, 'dragEnd initialized'); 61 | 62 | t.equal(typeof dashgrid.dashgrid.resizable, 'object', 'resizable initialized'); 63 | t.equal(typeof dashgrid.dashgrid.resizable.enabled, 'boolean', 'enabled initialized'); 64 | t.equal(Array.isArray(dashgrid.dashgrid.resizable.handle), true, 'resizable handles initialized'); 65 | t.equal(isNumber(dashgrid.dashgrid.resizable.handleWidth), true, 'handleWidth initialized'); 66 | t.equal(dashgrid.dashgrid.resizable.resizeStart, undefined, 'resizeStart initialized'); 67 | t.equal(dashgrid.dashgrid.resizable.resizing, undefined, 'resizing initialized'); 68 | t.equal(dashgrid.dashgrid.resizable.resizeEnd, undefined, 'resize initialized'); 69 | 70 | t.end(); 71 | }); 72 | 73 | test('Initialize Grid using manually entered values', function (t) { 74 | 75 | let gs = { 76 | boxes: [], 77 | rowHeight: 80, 78 | numRows: 10, 79 | minRows: 10, 80 | maxRows: 10, 81 | columnWidth: 120, 82 | numColumns: 6, 83 | minColumns: 6, 84 | maxColumns: 10, 85 | xMargin: 20, 86 | yMargin: 10, 87 | draggable: { 88 | enabled: true, 89 | handle: undefined, 90 | dragStart: function () {}, 91 | dragging: function () {}, 92 | dragEnd: function () {} 93 | }, 94 | resizable: { 95 | enabled: true, 96 | handle: ['n', 'e', 's', 'w', 'ne', 'se', 'sw', 'nw'], 97 | handleWidth: 10, 98 | resizeStart: function () {}, 99 | resizing: function () {}, 100 | resizeEnd: function () {} 101 | }, 102 | minRowspan: 1, 103 | maxRowspan: 10, 104 | minColumnspan: 1, 105 | maxColumnspan: 10, 106 | pushable: true, 107 | floating: false, 108 | stacking: false, 109 | swapping: false, 110 | animate: true, 111 | liveChanges: true, 112 | mobileBreakPoint: 600, 113 | mobileBreakPointEnabled: false, 114 | scrollSensitivity: 20, 115 | scrollSpeed: 10, 116 | snapBackTime: 400, 117 | showGridLines: true, 118 | showGridCentroids: true 119 | }; 120 | let dashgrid = dashGridGlobal(document.getElementById('grid'), gs); 121 | 122 | // Check that grid object gets all properties. 123 | t.notEqual(dashgrid.dashgrid, undefined, 'Returns object'); 124 | t.notEqual(dashgrid.dashgrid, undefined, 'Has grid options'); 125 | t.notEqual(dashgrid.updateBox, undefined, 'Has API updateBox'); 126 | t.notEqual(dashgrid.insertBox, undefined, 'Has API insertBox'); 127 | t.notEqual(dashgrid.removeBox, undefined, 'Has API removeBox'); 128 | 129 | t.equal(dashgrid.dashgrid._element.nodeName, 'DIV', 'Grid Element initialized'); 130 | t.equal(Array.isArray(dashgrid.dashgrid.boxes), true, 'Boxes is array'); 131 | 132 | t.equal(dashgrid.dashgrid.rowHeight, gs.rowHeight, 'RowHeight initialized'); 133 | t.equal(dashgrid.dashgrid.numRows, gs.numRows, 'numRows initialized'); 134 | t.equal(dashgrid.dashgrid.minRows, gs.minRows, 'minRows initialized'); 135 | t.equal(dashgrid.dashgrid.maxRows, gs.maxRows, 'maxRows initialized'); 136 | 137 | t.equal(dashgrid.dashgrid.columnWidth, gs.columnWidth, 'columnWidth initialized'); 138 | t.equal(dashgrid.dashgrid.numColumns, gs.numColumns, 'numColumns initialized'); 139 | t.equal(dashgrid.dashgrid.minColumns, gs.minColumns, 'minColumns initialized'); 140 | t.equal(dashgrid.dashgrid.maxColumns, gs.maxColumns, 'maxColumns initialized'); 141 | 142 | t.equal(dashgrid.dashgrid.minRowspan, gs.minRowspan, 'minRowspan initialized'); 143 | t.equal(dashgrid.dashgrid.maxRowspan, gs.maxRowspan, 'maxRowspan initialized'); 144 | t.equal(dashgrid.dashgrid.minColumnspan, gs.minColumnspan, 'minColumnspan initialized'); 145 | t.equal(dashgrid.dashgrid.maxColumnspan, gs.maxColumnspan, 'maxColumnspan initialized'); 146 | 147 | t.equal(dashgrid.dashgrid.xMargin, gs.xMargin, 'xMargin initialized'); 148 | t.equal(dashgrid.dashgrid.yMargin, gs.yMargin, 'yMargin initialized'); 149 | 150 | t.equal(dashgrid.dashgrid.pushable, gs.pushable, 'pushable initialized'); 151 | t.equal(dashgrid.dashgrid.floating, gs.floating, 'floating initialized'); 152 | t.equal(dashgrid.dashgrid.stacking, gs.stacking, 'stacking initialized'); 153 | t.equal(dashgrid.dashgrid.swapping, gs.swapping, 'swapping initialized'); 154 | 155 | t.equal(dashgrid.dashgrid.animate, gs.animate, 'animate initialized'); 156 | t.equal(dashgrid.dashgrid.liveChanges, gs.liveChanges, 'liveChanges initialized'); 157 | 158 | // t.equal(dashgrid.dashgrid.mobileBreakPoint, gs.mobileBreakPoint, 'mobileBreakPoint initialized'); 159 | // t.equal(dashgrid.dashgrid.mobileBreakPointEnabled, gs.mobileBreakPointEnabled, 'mobileBreakPointEnabled initialized'); 160 | 161 | t.equal(dashgrid.dashgrid.scrollSensitivity, gs.scrollSensitivity, 'scrollSensitivity initialized'); 162 | t.equal(dashgrid.dashgrid.scrollSpeed, gs.scrollSpeed, 'scrollSpeed initialized'); 163 | 164 | t.equal(dashgrid.dashgrid.snapBackTime, gs.snapBackTime, 'snapBackTime initialized'); 165 | t.equal(dashgrid.dashgrid.showGridLines, gs.showGridLines, 'showGridLines initialized'); 166 | t.equal(dashgrid.dashgrid.showGridCentroids, gs.showGridCentroids, 'showGridCentroids initialized'); 167 | 168 | t.equal(typeof dashgrid.dashgrid.draggable, 'object', 'draggable initialized'); 169 | t.equal(dashgrid.dashgrid.draggable.enabled, true, 'draggable.enabled initialized'); 170 | t.equal(dashgrid.dashgrid.draggable.handle, 'dashgrid-box', 'draggable.handle initialized'); 171 | t.equal(typeof dashgrid.dashgrid.draggable.dragStart, 'function', 'dragStart initialized'); 172 | t.equal(typeof dashgrid.dashgrid.draggable.dragging, 'function', 'dragging initialized'); 173 | t.equal(typeof dashgrid.dashgrid.draggable.dragEnd, 'function', 'dragEnd initialized'); 174 | 175 | t.equal(typeof dashgrid.dashgrid.resizable, 'object', 'resizable initialized'); 176 | t.equal(dashgrid.dashgrid.resizable.enabled, gs.resizable.enabled, 'resizable enabled initialized'); 177 | t.equal(arraysEqual(dashgrid.dashgrid.resizable.handle, ['n', 'e', 's', 'w', 'ne', 'se', 'sw', 'nw']), true, 'resizable handles initialized'); 178 | t.equal(dashgrid.dashgrid.resizable.handleWidth, gs.resizable.handleWidth, 'handleWidth initialized'); 179 | t.equal(typeof dashgrid.dashgrid.resizable.resizeStart, 'function', 'resizeStart initialized'); 180 | t.equal(typeof dashgrid.dashgrid.resizable.resizing, 'function', 'resizing initialized'); 181 | t.equal(typeof dashgrid.dashgrid.resizable.resizeEnd, 'function', 'resize initialized'); 182 | 183 | t.end(); 184 | }); 185 | 186 | } 187 | -------------------------------------------------------------------------------- /src/lib/gridEngine.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Works in posterior fashion, akin to ask for forgiveness rather than for 3 | * permission. 4 | * Logic: 5 | * 6 | * 1. Is updateTo a valid state? 7 | * 1.1 No: Return false. 8 | * 2. Save positions. 9 | * 3. Move box. 10 | * 3.1. Is box outside border? 11 | * 3.1.1. Yes: Can border be pushed? 12 | * 3.1.1.1. Yes: Expand border. 13 | * 3.1.1.2. No: Return false. 14 | * 3.2. Does box collide? 15 | * 3.2.1. Yes: Calculate new box position and 16 | * go back to step 1 with the new collided box. 17 | * 3.2.2. No: Return true. 18 | * 4. Is move valid? 19 | * 4.1. Yes: Update number rows / columns. 20 | * 4.2. No: Revert to old positions. 21 | */ 22 | 23 | import * as Util from './utils.js'; 24 | 25 | export { 26 | updateBox, 27 | updateNumRows, 28 | increaseNumRows, 29 | decreaseNumRows, 30 | updateNumColumns, 31 | increaseNumColumns, 32 | decreaseNumColumns, 33 | insertBox, 34 | removeBox 35 | }; 36 | 37 | /** 38 | * Updates a position or size of box. 39 | * 40 | * @param {Object} box The box being updated. 41 | * @param {Object} updateTo The new state. 42 | * @returns {Array.} movedBoxes 43 | */ 44 | function updateBox({grid, box, updateTo}) { 45 | let movingBox = box; 46 | 47 | let previousBoxPositions = copyPositions(grid.component.boxes); 48 | Object.assign(box.state.box, updateTo); 49 | 50 | if (!isUpdateValid({grid, box})) { 51 | restoreOldPositions({grid, previousBoxPositions}); 52 | return false; 53 | } 54 | 55 | let movedBoxes = [box]; 56 | let validMove = moveBox({ 57 | box: box, 58 | excludeBox: box, 59 | movedBoxes, 60 | grid 61 | }); 62 | 63 | if (validMove) { 64 | updateNumRows({grid}); 65 | updateNumColumns({grid}); 66 | 67 | return movedBoxes; 68 | } 69 | 70 | restoreOldPositions({grid, previousBoxPositions}); 71 | 72 | return []; 73 | } 74 | 75 | /** 76 | * Checks if the board state is valid. 77 | */ 78 | function isBoardValid() { 79 | 80 | } 81 | 82 | /** 83 | * Copy box positions. 84 | * @returns {Array.} Previous box positions. 85 | */ 86 | function copyPositions(boxes) { 87 | let previousBoxPositions = []; 88 | boxes.forEach(function (box) { 89 | previousBoxPositions.push({ 90 | row: box.state.box.row, 91 | column: box.state.box.column, 92 | columnspan: box.state.box.columnspan, 93 | rowspan: box.state.box.rowspan 94 | }); 95 | }); 96 | 97 | return previousBoxPositions; 98 | } 99 | 100 | /** 101 | * Restore Old positions. 102 | * @param {Array.} Previous positions. 103 | */ 104 | function restoreOldPositions({grid, previousBoxPositions}) { 105 | for (let i = 0; i < grid.component.boxes.length; i++) { 106 | grid.component.boxes[i].state.box.row = previousBoxPositions[i].row, 107 | grid.component.boxes[i].state.box.column = previousBoxPositions[i].column, 108 | grid.component.boxes[i].state.box.columnspan = previousBoxPositions[i].columnspan, 109 | grid.component.boxes[i].state.box.rowspan = previousBoxPositions[i].rowspan 110 | }; 111 | } 112 | 113 | /** 114 | * Remove a box given its index in the boxes array. 115 | * @param {number} boxIndex. 116 | */ 117 | function removeBox(boxIndex) { 118 | let elem = boxes[boxIndex]._element; 119 | elem.parentNode.removeChild(elem); 120 | grid.component.boxes.splice(boxIndex, 1); 121 | 122 | // In case floating is on. 123 | updateNumRows(); 124 | updateNumColumns(); 125 | } 126 | 127 | /** 128 | * Insert a box. Box must contain at least the size and position of the box, 129 | * content element is optional. 130 | * @param {Object} box Box dimensions. 131 | * @returns {boolean} If insert was possible. 132 | */ 133 | function insertBox(grid, box) { 134 | movingBox = box; 135 | 136 | if (box.rows === undefined && box.column === undefined && 137 | box.rowspan === undefined && box.columnspan === undefined) { 138 | return false; 139 | } 140 | 141 | if (!isUpdateValid(grid, box)) { 142 | return false; 143 | } 144 | 145 | // let previousBoxPositions = copyPositions(grid); 146 | 147 | let movedBoxes = [box]; 148 | let validMove = moveBox({box, excludeBox: box, movedBoxes, grid}); 149 | movingBox = undefined; 150 | 151 | if (validMove) { 152 | boxHandler.createBox(box); 153 | grid.component.boxes.push(box); 154 | 155 | updateNumRows(); 156 | updateNumColumns(); 157 | return box; 158 | } 159 | 160 | restoreOldPositions({grid, previousBoxPositions}); 161 | 162 | return false; 163 | } 164 | 165 | 166 | /** 167 | * Checks and handles collisions with wall and grid.component.boxes. 168 | * Works as a tree, propagating moves down the collision tree and returns 169 | * true or false depending if the box infront is able to move. 170 | * @param {Object} box 171 | * @param {Array.} excludeBox 172 | * @param {Array.} movedBoxes 173 | * @return {boolean} true if move is possible, false otherwise. 174 | */ 175 | function moveBox({box, excludeBox, movedBoxes, grid}) { 176 | if (isBoxOutsideBoundary({box, grid})) {return false;} 177 | 178 | let intersectedBoxes = getIntersectedBoxes({ 179 | box, boxes: grid.component.boxes, excludeBox, movedBoxes 180 | }); 181 | 182 | // {box, boxes, excludeBox, movedBoxes} 183 | // Handle box Collision, recursive model. 184 | for (let i = 0, len = intersectedBoxes.length; i < len; i++) { 185 | if (!collisionHandler({box, boxB: intersectedBoxes[i], excludeBox, movedBoxes, grid})) { 186 | return false; 187 | } 188 | } 189 | 190 | return true; 191 | } 192 | 193 | /** 194 | * Propagates box collisions. 195 | * @param {Object} box 196 | * @param {Object} boxB 197 | * @param {Object} excludeBox 198 | * @param {Array.} movedBoxes 199 | * @return {boolean} If move is allowed 200 | */ 201 | function collisionHandler({box, boxB, excludeBox, movedBoxes, grid}) { 202 | boxB.state.box.row += incrementRow(box, boxB) 203 | return moveBox({box: boxB, grid, excludeBox, movedBoxes}); 204 | } 205 | 206 | /** 207 | * Calculates new box position based on the box that pushed it. 208 | * @param {Object} box Box which has moved. 209 | * @param {Object} boxB Box which is to be moved. 210 | */ 211 | function incrementRow(box, boxB) { 212 | return box.state.box.row + box.state.box.rowspan - boxB.state.box.row; 213 | } 214 | 215 | /** 216 | * Given a box, finds other boxes which intersect with it. 217 | * @param {Object} box 218 | * @param {Array.} excludeBox Array of grid.component.boxes. 219 | */ 220 | function getIntersectedBoxes({box, boxes, excludeBox, movedBoxes}) { 221 | let intersectedBoxes = []; 222 | 223 | console.log(boxes); 224 | boxes.forEach(function (boxB) { 225 | // Don't check moving box and the box itself. 226 | if (box !== boxB && boxB !== excludeBox) { 227 | if (doBoxesIntersect({box, boxB})) { 228 | console.log(9); 229 | movedBoxes.push(boxB); 230 | intersectedBoxes.push(boxB); 231 | } 232 | } 233 | }); 234 | Util.insertionSort(intersectedBoxes, 'row'); 235 | 236 | return intersectedBoxes; 237 | } 238 | 239 | /** 240 | * Checks whether 2 boxes intersect using bounding box method. 241 | * @param {Object} boxA 242 | * @param {Object} boxB 243 | * @returns boolean True if intersect else false. 244 | */ 245 | function doBoxesIntersect({box, boxB}) { 246 | return (box.state.box.column < boxB.state.box.column + boxB.state.box.columnspan && 247 | box.state.box.column + box.state.box.columnspan > boxB.state.box.column && 248 | box.state.box.row < boxB.state.box.row + boxB.state.box.rowspan && 249 | box.state.box.rowspan + box.state.box.row > boxB.state.box.row); 250 | } 251 | 252 | /** 253 | * Updates the number of columns. 254 | */ 255 | function updateNumColumns({grid}) { 256 | let newNumColumns = grid.state.grid.numColumns; 257 | let maxColumn = Util.getMaxNum(grid.component.boxes, 'column', 'columnspan'); 258 | 259 | if (maxColumn >= grid.state.grid.minColumns) { 260 | grid.state.grid.numColumns = maxColumn; 261 | } 262 | 263 | if (!grid.state.engine.movingBox) { 264 | return newNumColumns; 265 | } 266 | 267 | if (grid.state.grid.numColumns - grid.state.engine.movingBox.column - grid.state.engine.movingBox.columnspan === 0 && 268 | grid.state.grid.numColumns < grid.state.grid.maxColumns) { 269 | newNumColumns += 1; 270 | } else if (grid.state.grid.numColumns - grid.state.engine.movingBox.column- grid.state.engine.movingBox.columnspan > 1 && 271 | grid.state.engine.movingBox.column + grid.state.engine.movingBox.columnspan === maxColumn && 272 | grid.state.grid.numColumns > grid.state.grid.minColumns && 273 | grid.state.grid.numColumns < grid.state.grid.maxColumns) { 274 | newNumColumns = maxColumn + 1; 275 | } 276 | 277 | return newNumColumns; 278 | } 279 | 280 | /** 281 | * Increases number of grid.state.grid.numRows if box touches bottom of wall. 282 | * @param {Object} box 283 | * @param {number} numColumns 284 | * @returns {boolean} true if increase else false. 285 | */ 286 | function increaseNumColumns(box, numColumns) { 287 | // Determine when to add extra row to be able to move down: 288 | // 1. Anytime dragging starts. 289 | // 2. When dragging starts and moving box is close to bottom border. 290 | if ((box.column + box.columnspan) === grid.state.grid.numColumns && 291 | grid.state.grid.numColumns < grid.state.grid.maxColumns) { 292 | grid.state.grid.numColumns += 1; 293 | return true; 294 | } 295 | 296 | return false; 297 | } 298 | 299 | /** 300 | * Decreases number of grid.state.grid.numRows to furthest leftward box. 301 | * @returns boolean true if increase else false. 302 | */ 303 | let decreaseNumColumns = function () { 304 | let maxColumnNum = 0; 305 | 306 | grid.component.boxes.forEach(function (box) { 307 | if (maxColumnNum < (box.column + box.columnspan)) { 308 | maxColumnNum = box.column + box.columnspan; 309 | } 310 | }); 311 | 312 | if (maxColumnNum < grid.state.grid.numColumns) {grid.state.grid.numColumns = maxColumnNum;} 313 | if (maxColumnNum < grid.state.grid.minColumns) {grid.state.grid.numColumns = grid.state.grid.minColumns;} 314 | 315 | return true; 316 | } 317 | 318 | 319 | /** 320 | * Number rows depends on three things. 321 | *
    322 | *
  • Min / Max Rows.
  • 323 | *
  • Max Box.
  • 324 | *
  • Dragging box near bottom border.
  • 325 | *
326 | * 327 | */ 328 | function updateNumRows({grid}) { 329 | let newNumRows = grid.state.grid.numRows; 330 | let maxRow = Util.getMaxNum(grid.component.boxes, 'row', 'rowspan'); 331 | 332 | if (maxRow >= grid.state.grid.minRows) { 333 | grid.state.grid.numRows = maxRow; 334 | } 335 | 336 | if (!grid.state.engine.movingBox) { 337 | return newNumRows; 338 | } 339 | 340 | // Moving box when close to border. 341 | if (grid.state.grid.numRows - grid.state.engine.movingBox.row - grid.state.engine.movingBox.rowspan === 0 && 342 | grid.state.grid.numRows < grid.state.grid.maxRows) { 343 | newNumRows += 1; 344 | } else if (grid.state.grid.numRows - grid.state.engine.movingBox.row - grid.state.engine.movingBox.rowspan > 1 && 345 | grid.state.engine.movingBox.row + grid.state.engine.movingBox.rowspan === maxRow && 346 | grid.state.grid.numRows > grid.state.grid.minRows && 347 | grid.state.grid.numRows < grid.state.grid.maxRows) { 348 | newNumRows = maxRow + 1; 349 | } 350 | 351 | return newNumRows; 352 | } 353 | 354 | /** 355 | * Increases number of grid.state.grid.numRows if box touches bottom of wall. 356 | * @param box {Object} 357 | * @returns {boolean} true if increase else false. 358 | */ 359 | function increaseNumRows(box, numRows) { 360 | // Determine when to add extra row to be able to move down: 361 | // 1. Anytime dragging starts. 362 | // 2. When dragging starts AND moving box is close to bottom border. 363 | if ((box.row + box.rowspan) === grid.state.grid.numRows && 364 | grid.state.grid.numRows < grid.state.grid.maxRows) { 365 | grid.state.grid.numRows += 1; 366 | return true; 367 | } 368 | 369 | return false; 370 | } 371 | 372 | /** 373 | * Decreases number of grid.state.grid.numRows to furthest downward box. 374 | * @returns {boolean} true if increase else false. 375 | */ 376 | let decreaseNumRows = function () { 377 | let maxRowNum = 0; 378 | 379 | grid.component.boxes.forEach(function (box) { 380 | if (maxRowNum < (box.row + box.rowspan)) { 381 | maxRowNum = box.row + box.rowspan; 382 | } 383 | }); 384 | 385 | if (maxRowNum < grid.state.grid.numRows) {grid.state.grid.numRows = maxRowNum;} 386 | if (maxRowNum < grid.state.grid.minRows) {grid.state.grid.numRows = grid.state.grid.minRows;} 387 | 388 | return true; 389 | } 390 | 391 | /** 392 | * Checks min, max box-size. 393 | * @param {Object} box 394 | * @returns {boolean} 395 | */ 396 | function isUpdateValid({grid, box}) { 397 | if (box.state.box.rowspan < grid.state.grid.minRowspan || 398 | box.state.box.rowspan > grid.state.grid.maxRowspan || 399 | box.state.box.columnspan < grid.state.grid.minColumnspan || 400 | box.state.box.columnspan > grid.state.grid.maxColumnspan) { 401 | return false; 402 | } 403 | 404 | return true; 405 | } 406 | 407 | /** 408 | * Handles border collisions by reverting back to closest edge point. 409 | * @param {Object} box 410 | * @returns {boolean} True if collided and cannot move wall else false. 411 | */ 412 | function isBoxOutsideBoundary({box, grid}) { 413 | // Top and left border. 414 | if (box.state.box.column < 0 || 415 | box.state.box.row < 0) { 416 | return true; 417 | } 418 | 419 | // Right and bottom border. 420 | if (box.state.box.row + box.state.box.rowspan > grid.state.grid.maxRows || 421 | box.state.box.column + box.state.box.columnspan > grid.state.grid.maxColumns) { 422 | return true; 423 | } 424 | return false; 425 | } 426 | -------------------------------------------------------------------------------- /dist/dashgrid.min.js: -------------------------------------------------------------------------------- 1 | exports.dashGridGlobal=function(e){function t(o){if(n[o])return n[o].exports;var i=n[o]={exports:{},id:o,loaded:!1};return e[o].call(i.exports,i,i.exports,t),i.loaded=!0,i.exports}var n={};return t.m=e,t.c=n,t.p="dist",t(0)}([function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(6),r=o(i);t["default"]=r["default"],e.exports=t["default"]},function(e,t){"use strict";function n(e,t){for(var n=0,o=e.length-1;o>=0;o--)e[o][t]>=n&&(n=e[o][t]);return n}function o(e,t,n){var o=[];return Object.keys(n).forEach(function(r){i(e,t,n[r],o)}),o}function i(e,t,n,o){var i=o.length;if(0===i)o.push(n);else{for(var r=0;i>r;r+=1)if("desc"===e){if(n.row>o[r].row){o.splice(r,0,n);break}}else if(n.row0&&e[o-1][t]>e[o][t];)n=e[o],e[o]=e[o-1],e[o-1]=n,o+=1}function s(e){var t=0,n=void 0;for(n in e)e.hasOwnProperty(n)&&(t+=1);return t}function l(e,t,n){null!==e&&"undefined"!=typeof e&&(e.addEventListener?e.addEventListener(t,n,!1):e.attachEvent?e.attachEvent("on"+t,n):e["on"+t]=n)}function a(e){for(var t=JSON.parse(e.d),n=0;n-1)return e;e=e.parentNode}return!1}Object.defineProperty(t,"__esModule",{value:!0}),t.getMaxObj=n,t.getSortedArr=o,t.insertByOrder=i,t.insertionSort=r,t.ObjectLength=s,t.addEvent=l,t.parseArrayOfJSON=a,t.removeNodes=d,t.findParent=u},function(e,t){"use strict";function n(e){var t=e.grid,n=function(e){Object.assign(e,o(e,t)),e.content&&e.element.appendChild(e.content),t.element.appendChild(e.element)};return{createBox:n}}function o(e,t){return{element:function(){var e=document.createElement("div");return e.className="dashgridBox",e.style.position="absolute",e.style.cursor="move",e.style.transition="opacity .3s, left .3s, top .3s, width .3s, height .3s",e.style.zIndex="1002",i(e,t),e}(),row:e.row,column:e.column,rowspan:e.rowspan||1,columnspan:e.columnspan||1,draggable:e.draggable===!1?!1:!0,resizable:e.resizable===!1?!1:!0,pushable:e.pushable===!1?!1:!0,floating:e.floating===!0?!0:!1,stacking:e.stacking===!0?!0:!1,swapping:e.swapping===!0?!0:!1,inherit:e.inherit===!0?!0:!1}}function i(e,t){if(-1!==t.resizable.handles.indexOf("n")){var n=document.createElement("div");n.style.left="0px",n.style.top="0px",n.style.width="100%",n.style.height=t.resizable.handleWidth+"px",n.style.cursor="n-resize",n.style.position="absolute",n.style.display="block",n.className="grid-box-handle grid-box-handle-n",e.appendChild(n)}if(-1!==t.resizable.handles.indexOf("s")){var n=document.createElement("div");n.style.left="0px",n.style.bottom="0px",n.style.width="100%",n.style.height=t.resizable.handleWidth+"px",n.style.cursor="s-resize",n.style.position="absolute",n.style.display="block",n.className="grid-box-handle grid-box-handle-s",e.appendChild(n)}if(-1!==t.resizable.handles.indexOf("w")){var n=document.createElement("div");n.style.left="0px",n.style.top="0px",n.style.width=t.resizable.handleWidth+"px",n.style.height="100%",n.style.cursor="w-resize",n.style.position="absolute",n.style.display="block",n.className="grid-box-handle grid-box-handle-w",e.appendChild(n)}if(-1!==t.resizable.handles.indexOf("e")){var n=document.createElement("div");n.style.right="0px",n.style.top="0px",n.style.width=t.resizable.handleWidth+"px",n.style.height="100%",n.style.cursor="e-resize",n.style.position="absolute",n.style.display="block",n.className="grid-box-handle grid-box-handle-e",e.appendChild(n)}if(-1!==t.resizable.handles.indexOf("ne")){var n=document.createElement("div");n.style.right="0px",n.style.top="0px",n.style.width=t.resizable.handleWidth+"px",n.style.height=t.resizable.handleWidth+"px",n.style.cursor="ne-resize",n.style.position="absolute",n.style.display="block",n.className="grid-box-handle grid-box-handle-ne",e.appendChild(n)}if(-1!==t.resizable.handles.indexOf("se")){var n=document.createElement("div");n.style.right="0px",n.style.bottom="0px",n.style.width=t.resizable.handleWidth+"px",n.style.height=t.resizable.handleWidth+"px",n.style.cursor="se-resize",n.style.position="absolute",n.style.display="block",n.className="grid-box-handle grid-box-handle-se",e.appendChild(n)}if(-1!==t.resizable.handles.indexOf("sw")){var n=document.createElement("div");n.style.left="0px",n.style.bottom="0px",n.style.width=t.resizable.handleWidth+"px",n.style.height=t.resizable.handleWidth+"px",n.style.cursor="sw-resize",n.style.position="absolute",n.style.display="block",n.className="grid-box-handle grid-box-handle-sw",e.appendChild(n)}if(-1!==t.resizable.handles.indexOf("nw")){var n=document.createElement("div");n.style.left="0px",n.style.top="0px",n.style.width=t.resizable.handleWidth+"px",n.style.height=t.resizable.handleWidth+"px",n.style.cursor="nw-resize",n.style.position="absolute",n.style.display="block",n.className="grid-box-handle grid-box-handle-nw",e.appendChild(n)}}Object.defineProperty(t,"__esModule",{value:!0}),t["default"]=n,e.exports=t["default"]},function(e,t){"use strict";function n(e){var t=e.grid,n=e.renderer,o=e.engine,i=void 0,r=void 0,s=void 0,l=void 0,a=0,d=0,u=0,m=0,f=0,c=0,g=t.yMargin,h=9999,p=t.xMargin,x={},w={},b=function(e,n){e.element.style.transition="None",t.shadowBoxElement.style.left=e.element.style.left,t.shadowBoxElement.style.top=e.element.style.top,t.shadowBoxElement.style.width=e.element.style.width,t.shadowBoxElement.style.height=e.element.style.height,t.shadowBoxElement.style.display="",u=n.pageX,m=n.pageY,i=parseInt(e.element.offsetLeft,10),r=parseInt(e.element.offsetTop,10),s=parseInt(e.element.offsetWidth,10),l=parseInt(e.element.offsetHeight,10),t.draggable.dragStart&&t.draggable.dragStart()},v=function(e,o){window.requestAnimFrame(function(){B(e,o)}),t.liveChanges&&(x=n.getClosestCells({left:e.element.offsetLeft,right:e.element.offsetLeft+e.element.offsetWidth,top:e.element.offsetTop,bottom:e.element.offsetTop+e.element.offsetHeight}),z(e,o)),t.draggable.dragging&&t.draggable.dragging()},y=function(e,i){t.liveChanges||(x=n.getClosestCells({left:e.element.offsetLeft,right:e.element.offsetLeft+e.element.offsetWidth,top:e.element.offsetTop,bottom:e.element.offsetTop+e.element.offsetHeight}),z(e,i)),e.element.style.transition="opacity .3s, left .3s, top .3s, width .3s, height .3s",e.element.style.left=t.shadowBoxElement.style.left,e.element.style.top=t.shadowBoxElement.style.top,setTimeout(function(){t.shadowBoxElement.style.display="none"},t.snapbacktime),o.updateNumRows(!1),o.updateNumColumns(!1),t.draggable.dragEnd&&t.draggable.dragEnd()},z=function(e,i){var r=void 0;(x.row!==w.row||x.column!==w.column)&&(r=o.updateBox(e,x),r&&(n.setBoxYPosition(t.shadowBoxElement,r.row),n.setBoxXPosition(t.shadowBoxElement,r.column))),w={row:x.row,column:x.column}},B=function(e,n){var o=t.element.offsetWidth-t.xMargin;a=n.pageX,d=n.pageY;var x=a-u+f,w=d-m+c;f=0,c=0,u=a,m=d;var b=x,v=w;p>i+b?(x=p-i,f=b-x):i+s+b>o&&(x=o-i-s,f=b-x),g>r+v?(w=g-r,c=v-w):r+l+v>h&&(w=h-r-l,c=v-w),i+=x,r+=w,e.element.style.top=r+"px",e.element.style.left=i+"px"};return Object.freeze({dragStart:b,drag:v,dragEnd:y})}Object.defineProperty(t,"__esModule",{value:!0}),t["default"]=n,e.exports=t["default"]},function(e,t,n){"use strict";function o(e){var t=e.grid,n=e.renderer,o=void 0,r=function(){t.displayGrid&&s(),l()},s=function(){null===document.getElementById("draw-element")&&(o=document.createElement("div"),o.id="draw-element",t.element.appendChild(o))},l=function(){null===document.getElementById("shadow-box")&&(t.shadowBoxElement=document.createElement("div"),t.shadowBoxElement.id="shadow-box",t.shadowBoxElement.className="grid-shadow-box",t.shadowBoxElement.style.position="absolute",t.shadowBoxElement.style.display="block",t.shadowBoxElement.style.zIndex="1001",t.element.appendChild(t.shadowBoxElement))},a=function(){n.setRowHeight(),n.setColumnWidth(),n.setGridHeight(),n.setGridWidth(),n.setCellCentroids()},d=function(){n.setGridHeight(),n.setGridWidth()},u=function(e){n.setBoxYPosition(e.element,e.row),n.setBoxXPosition(e.element,e.column),n.setBoxHeight(e.element,e.rowspan),n.setBoxWidth(e.element,e.columnspan)},m=function(){(0,i.removeNodes)(o);for(var e="",n=0;n<=t.numRows;n+=1)e+="
\n
";for(var n=0;n<=t.numColumns;n+=1)e+="
\n
";for(var n=0;n\n ";o.innerHTML=e};return Object.freeze({initialize:r,renderGrid:a,createGridDraw:s,drawBox:u,drawGrid:m,updateGrid:d})}Object.defineProperty(t,"__esModule",{value:!0}),t["default"]=o;var i=n(1);e.exports=t["default"]},function(e,t,n){"use strict";function o(e){var t=e.grid,n=e.renderer,o=e.drawer,s=e.boxHandler,l=i({grid:t,drawer:o,renderer:n}),a=r({grid:t,boxHandler:s,engineView:l}),d=function(e){},u=function(e){};return Object.freeze({insertBoxAndRefresh:d,removeBoxAndRefresh:u,initialize:a.initialize,updateBox:a.updateBox,getBox:a.getBox,getNumRows:a.getNumRows,getNumColumns:a.getNumColumns,setActiveBox:a.setActiveBox,updateNumRows:a.updateNumRows,updateNumColumns:a.updateNumColumns})}function i(e){var t=e.grid,n=e.drawer,o=e.renderer,i=function(){n.renderGrid(),n.drawGrid(),s()},r=function(){o.setCellCentroids(),n.updateGrid(),n.drawGrid()},s=function(){for(var e=0,o=t.boxes.length;o>e;e++)n.drawBox(t.boxes[e])},l=function(e,t){window.requestAnimFrame(function(){for(var o=0,i=t.length;i>o;o++)e!==t[o].bref&&n.drawBox(t[o].bref)})};return{refreshGrid:i,updateDimensionState:r,updatePositions:l}}function r(e){var t=e.grid,n=e.boxHandler,o=e.engineView,i=void 0,r=void 0,l={},a=function(){i=t.boxes,d(),o.refreshGrid()},d=function(){for(var e=0,t=i.length;t>e;e++)n.createBox(i[e])},u=function(e){for(var t=i.length-1;t>=0;t-=1)if(i[t].element===e)return i[t]},m=function(e){return{bref:e,row:e.row,column:e.column,rowspan:e.rowspan,columnspan:e.columnspan}},f=function(e){l=e},c=function(e){for(var t=0,n=e.length;n>t;t++)e[t].bref.row=e[t].row,e[t].bref.column=e[t].column,e[t].bref.rowspan=e[t].rowspan,e[t].bref.columnspan=e[t].columnspan},g=function(e,t){r=e;var n=m(e);h(t,n);var i=[n],s=p(n,n,i);return s&&(c(i),o.updatePositions(l===e?e:{},i),z(!0)),r=null,n},h=function(e,t){void 0!==e.row&&(t.row=e.row),void 0!==e.column&&(t.column=e.column),void 0!==e.rowspan&&(t.rowspan=e.rowspan),void 0!==e.columnspan&&(t.columnspan=e.columnspan)},p=function(e,t,n){if(E(e))return!1;for(var o=w(e,t),i=0,r=o.length;r>i;i++)if(!x(e,o[i],t,n))return!1;return!0},x=function(e,t,n,o){return o.push(t),t.row+=e.row+e.rowspan-t.row,p(t,n,o)},w=function(e,t){for(var n=[],o=0,r=i.length;r>o;o++)e.bref!==i[o]&&i[o]!==t&&b(e,i[o])&&n.push(m(i[o]));return(0,s.insertionSort)(n),n},b=function(e,t){return e.columnt.column&&e.rowt.row},v=function(e){(0,s.getMaxObj)(i,"column"),y(),e===!0&&l.column+l.columnspan===t.numColumns&&t.numColumnst.numColumns||e.row<0)return!0;var n=e.row+e.rowspan,o=void 0;return n>t.numRows?(o=B(n),o?!1:!0):!1};return{initialize:a,updateBox:g,setActiveBox:f,updateNumRows:z,updateNumColumns:v,addBox:d,getBox:u}}Object.defineProperty(t,"__esModule",{value:!0});var s=n(1);t["default"]=o,e.exports=t["default"]},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function i(e,t){var n=Object.assign({},r(t,e)),o=(0,m["default"])({grid:n}),i=(0,d["default"])({grid:n}),s=(0,c["default"])({grid:n,renderer:o}),a=(0,l["default"])({grid:n,renderer:o,drawer:s,boxHandler:i}),u=(0,x["default"])({grid:n,renderer:o,engine:a}),f=(0,b["default"])({grid:n,renderer:o,engine:a}),g=(0,h["default"])({dragger:u,resizer:f,grid:n,engine:a});return s.initialize(),a.initialize(),g.initialize(),(0,v.addEvent)(window,"resize",a.refreshGrid),Object.freeze({updateBox:a.updateBox,insertBox:a.insertBoxAndRefresh,removeBox:a.removeBoxAndRefresh,getBoxes:a.getBoxes,grid:n})}function r(e,t){var n={element:function(){var e=document.getElementById(t.replace("#",""));return e.style.position="absolute",e.style.display="block",e.style.zIndex="1000",(0,v.removeNodes)(e),e}(),boxes:e.boxes||[],rowHeight:e.rowHeight,numRows:void 0!==e.numRows?e.numRows:6,minRows:void 0!==e.minRows?e.minRows:6,maxRows:void 0!==e.maxRows?e.maxRows:10,columnWidth:e.columnWidth,numColumns:void 0!==e.numColumns?e.numColumns:6,minColumns:void 0!==e.minColumns?e.minColumns:6,maxColumns:void 0!==e.maxColumns?e.maxColumns:10,xMargin:void 0!==e.xMargin?e.xMargin:20,yMargin:void 0!==e.yMargin?e.yMargin:20,pushable:e.pushable===!1?!1:!0,floating:e.floating===!0?!0:!1,stacking:e.stacking===!0?!0:!1,swapping:e.swapping===!0?!0:!1,animate:e.animate===!0?!0:!1,liveChanges:e.liveChanges===!1?!1:!0,mobileBreakPoint:600,mobileBreakPointEnabled:!1,draggable:{enabled:e.draggable&&e.draggable.enabled===!1?!1:!0,handles:e.draggable&&e.draggable.handles||void 0,dragStart:e.draggable&&e.draggable.dragStart,dragging:e.draggable&&e.draggable.dragging,dragEnd:e.draggable&&e.draggable.dragEnd},resizable:{enabled:e.draggable&&e.resizable.enabled===!1?!1:!0,handles:e.draggable&&e.resizable.handles||["n","e","s","w","ne","se","sw","nw"],handleWidth:e.draggable&&void 0!==e.draggable.handleWidth?e.draggable.handleWidth:10,resizeStart:e.draggable&&e.resizable.resizeStart,resizing:e.draggable&&e.resizable.resizing,resizeEnd:e.draggable&&e.resizable.resizeEnd},scrollSensitivity:20,scrollSpeed:10,snapbacktime:void 0===e.snapbacktime?300:e.snapbacktime,displayGrid:e.displayGrid===!1?!1:!0};return n}Object.defineProperty(t,"__esModule",{value:!0}),t["default"]=i,n(10);var s=n(5),l=o(s),a=n(2),d=o(a),u=n(8),m=o(u),f=n(4),c=o(f),g=n(7),h=o(g),p=n(3),x=o(p),w=n(9),b=o(w),v=n(1);e.exports=t["default"]},function(e,t,n){"use strict";function o(e){function t(){d.element.addEventListener("mousedown",function(e){n(e,d.element),e.preventDefault()},!1)}function n(e,t){var n=e.target;m.indexOf(n.nodeName.toLowerCase())>-1||n.hasAttribute("onclick")||2!==e.which&&3!==e.which&&(n.className.indexOf("handle")>-1?o(e,s):n.className.indexOf("dashgridBox")>-1?o(e,r):n.className.indexOf(d.draggable.handle)>-1&&o(e,r))}function o(e,t){var n=(0,i.findParent)(e.target,"dashgridBox"),o=u.getBox(n);o&&(u.setActiveBox(o),t(o,e))}function r(e,t){function n(t){console.log("drag"),l.drag(e,t),t.preventDefault()}function o(t){console.log("dragend"),l.dragEnd(e,t),t.preventDefault(),document.removeEventListener("mouseup",o,!1),document.removeEventListener("mousemove",n,!1),u.setActiveBox({})}d.draggable.enabled&&e.draggable&&(console.log("dragstart"),l.dragStart(e,t),document.addEventListener("mouseup",o,!1),document.addEventListener("mousemove",n,!1))}function s(e,t){function n(t){a.resize(e,t),t.preventDefault()}function o(t){document.removeEventListener("mouseup",o,!1),document.removeEventListener("mousemove",n,!1),a.resizeEnd(e,t),t.preventDefault()}d.resizable.enabled&&e.resizable&&(a.resizeStart(e,t),document.addEventListener("mouseup",o,!1),document.addEventListener("mousemove",n,!1))}var l=e.dragger,a=e.resizer,d=e.grid,u=e.engine,m=["select","input","textarea","button"];return Object.freeze({initialize:t})}Object.defineProperty(t,"__esModule",{value:!0}),t["default"]=o;var i=n(1);e.exports=t["default"]},function(e,t){"use strict";function n(e){var t=e.grid,n=[],o=[],i=function(){t.element.style.width=t.columnWidth?t.columnWidth*t.numColumns+(t.numColumns+1)*t.xMargin+"px":t.element.parentNode.offsetWidth+"px"},r=function(){t.columnWidth=t.columnWidth?t.columnWidth:(t.element.parentNode.offsetWidth-(t.numColumns+1)*t.xMargin)/t.numColumns},s=function(){t.element.style.height=t.rowHeight?t.rowHeight*t.numRows+(t.numRows+1)*t.yMargin+"px":t.element.parentNode.offsetHeight+"px"},l=function(){t.rowHeight=t.rowHeight?t.rowHeight:(t.element.parentNode.offsetHeight-(t.numRows+1)*t.yMargin)/t.numRows},a=function(e,n){e.style.left=n*t.columnWidth+t.xMargin*(n+1)+"px"},d=function(e,n){e.style.top=n*t.rowHeight+t.yMargin*(n+1)+"px"},u=function(e,n){e.style.width=n*t.columnWidth+t.xMargin*(n-1)+"px"},m=function(e,n){e.style.height=n*t.rowHeight+t.yMargin*(n-1)+"px"},f=function(){o=[],n=[];for(var e=void 0,i=void 0,r=0;r=o[f][0]&&i<=o[f][1]&&(u=f),s>=o[f][0]&&s<=o[f][1]&&(m=f);for(var c=0;c=n[c][0]&&l<=n[c][1]&&(a=c),r>=n[c][0]&&r<=n[c][1]&&(d=c);return{boxLeft:a,boxRight:d,boxTop:u,boxBottom:m}},g=function(e){var i=e.top,r=e.right,s=e.bottom,l=e.left,a=c(e),d=a.boxLeft,u=a.boxRight,m=a.boxTop,f=a.boxBottom,g=void 0,h=void 0,p=void 0;void 0!==d&&void 0!==u&&(h=Math.abs(l-n[d][0]),p=Math.abs(r-n[u][1]-t.xMargin),g=p>=h?d:d+1);var x=void 0,w=void 0,b=void 0;return void 0!==m&&void 0!==f&&(w=Math.abs(i-o[m][0]),b=Math.abs(s-o[f][1]-t.yMargin),x=b>=w?m:m+1),{row:x,column:g}};return Object.freeze({setCellCentroids:f,setColumnWidth:r,setRowHeight:l,setGridHeight:s,setGridWidth:i,setBoxXPosition:a,setBoxYPosition:d,setBoxWidth:u,setBoxHeight:m,findIntersectedCells:c,getClosestCells:g})}Object.defineProperty(t,"__esModule",{value:!0}),t["default"]=n,e.exports=t["default"]},function(e,t){"use strict";function n(e){var t=e.grid,n=e.renderer,o=e.engine,i=void 0,r=void 0,s=void 0,l=void 0,a=void 0,d=void 0,u=void 0,m=void 0,f=void 0,c=void 0,g=void 0,h=0,p=0,x=0,w=0,b=0,v=0,y={},z={},B=function(e,u){g=u.target.className,e.element.style.transition="None",t.shadowBoxElement.style.left=e.element.style.left,t.shadowBoxElement.style.top=e.element.style.top,t.shadowBoxElement.style.width=e.element.style.width,t.shadowBoxElement.style.height=e.element.style.height,t.shadowBoxElement.style.display="block",i=n.getWidthPerCell(),r=n.getHeightPerCell(),x=u.pageX,w=u.pageY,s=parseInt(e.element.style.left,10),l=parseInt(e.element.style.top,10),a=e.element.offsetWidth,d=e.element.offsetHeight,o.updateNumRows(!0),t.resizable.resizeStart&&t.resizable.resizeStart()},C=function(e,i){if(window.requestAnimFrame(function(){O(e,i)}),t.liveChanges){var r=n.findIntersectedCells({left:e.element.offsetLeft,right:e.element.offsetLeft+e.element.offsetWidth,top:e.element.offsetTop,bottom:e.element.offsetTop+e.element.offsetHeight,numRows:o.getNumRows(),numColumns:o.getNumColumns()}),s=r.boxLeft,l=r.boxRight,a=r.boxTop,d=r.boxBottom;y={row:a,column:s,rowspan:d-a+1,columnspan:l-s+1},M(e,i)}t.resizable.resizing&&t.resizable.resizing()},E=function(e,i){if(!t.liveChanges){var r=n.findIntersectedCells({left:e.element.offsetLeft,right:e.element.offsetLeft+e.element.offsetWidth,top:e.element.offsetTop,bottom:e.element.offsetTop+e.element.offsetHeight,numRows:o.getNumRows(),numColumns:o.getNumColumns()}),s=r.boxLeft,l=r.boxRight,a=r.boxTop,d=r.boxBottom;y={row:a,column:s,rowspan:d-a+1,columnspan:l-s+1},M(e,i)}e.element.style.transition="opacity .3s, left .3s, top .3s, width .3s, height .3s",e.element.style.left=t.shadowBoxElement.style.left,e.element.style.top=t.shadowBoxElement.style.top,e.element.style.width=t.shadowBoxElement.style.width,e.element.style.height=t.shadowBoxElement.style.height,setTimeout(function(){t.shadowBoxElement.style.display="none"},t.snapback),o.updateNumRows(!1),t.resizable.resizeEnd&&t.resizable.resizeEnd()},M=function(e,i){if(y.row!==z.row||y.column!==z.column||y.rowspan!==z.rowspan||y.columnspan!==z.columnspan){var r=o.updateBox(e,y);r&&(n.setBoxXPosition(t.shadowBoxElement,r.column),n.setBoxYPosition(t.shadowBoxElement,r.row),n.setBoxWidth(t.shadowBoxElement,r.columnspan),n.setBoxHeight(t.shadowBoxElement,r.rowspan))}z.row=y.row,z.column=y.column,z.rowspan=y.rowspan,z.columnspan=y.columnspan,t.resizable.resizing&&t.resizable.resizing()},O=function(e,n){h=n.pageX+window.scrollX,p=n.pageY+window.scrollY;var o=h-x+b,y=p-w+v;b=v=0,x=h,w=p;var z=y,B=o;u=t.yMargin,m=t.element.offsetHeight-t.yMargin,f=t.xMargin,c=t.element.offsetWidth-t.xMargin,(g.indexOf("grid-box-handle-w")>-1||g.indexOf("grid-box-handle-nw")>-1||g.indexOf("grid-box-handle-sw")>-1)&&(i>a-B?(o=a-i,b=B-o):f>s+B&&(o=f-s,b=B-o),s+=o,a-=o),(g.indexOf("grid-box-handle-e")>-1||g.indexOf("grid-box-handle-ne")>-1||g.indexOf("grid-box-handle-se")>-1)&&(i>a+B?(o=i-a,b=B-o):s+a+B>c&&(o=c-s-a,b=B-o),a+=o),(g.indexOf("grid-box-handle-n")>-1||g.indexOf("grid-box-handle-nw")>-1||g.indexOf("grid-box-handle-ne")>-1)&&(r>d-z?(y=d-r,v=z-y):u>l+z&&(y=u-l,v=z-y),l+=y,d-=y),(g.indexOf("grid-box-handle-s")>-1||g.indexOf("grid-box-handle-sw")>-1||g.indexOf("grid-box-handle-se")>-1)&&(r>d+z?(y=r-d,v=z-y):l+d+z>m&&(y=m-l-d,v=z-y),d+=y),e.element.style.top=l+"px",e.element.style.left=s+"px",e.element.style.width=a+"px",e.element.style.height=d+"px"};return Object.freeze({resizeStart:B,resize:C,resizeEnd:E})}Object.defineProperty(t,"__esModule",{value:!0}),t["default"]=n,e.exports=t["default"]},function(e,t){"use strict";window.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||function(e){e=e||function(){},window.setTimeout(e,1e3/60)}}()}]); --------------------------------------------------------------------------------