Sortable and Resizable Dashboard Zones

Using DIVS to implement dashboard zones.

by humanzing

HTML

<div id="outer">
    <table id="sortable">
        <tr>
            <td>
                <div class="span6 zone">a</div>
                <div class="span6 zone">bb</div>
                <div class="span6 zone">ccc</div>
                <div class="span6 zone">ddd</div>
                <div class="span6 zone">eeee</div>
                <div class="span6 zone">fffff</div>
                <div class="span6 zone">gggggg</div>
            </td>
        </tr>
    </table>
</div>

CSS

@import url('https://fonts.googleapis.com/css?family=Baloo+Da&display=swap');
:root {
	--widget-bg: #777;
	--zone-bg: #252525;
	--zone-bord: #000;
	--placeholder: rgba(0, 0, 0, 0.5);
	--no-drag: rgba(100, 100, 100, 0.2);
	--wig-color: #222;
}
.widget-holder-label {
	position: absolute;
	color: white;
	font-weight: bold;
	top: 5px;
	right: 10px;
	width: 208px;
}
.widget-holder {
	position: absolute;
	right: 10px;
	top: 30px;
	width: 208px;
	height: 200px;
	background: #333;
	border-radius: 5px;
	overflow-y: scroll;
}
.widget-holder .ui-sortable-placeholder {
	visibility: hidden !important;
}
.widget-holder .fg-widget {
	height: 100px;
	width: 100px;
	position: relative;
	display: inline-block;
	float: left;
}
.custom-widget .fg-widget-inner {
	background: #e04c8a;
}
.inner-icon {
	position: absolute;
	left: 50%;
	top: 50%;
	transform: translate(-50%, -50%);
	font-size: 30px;
}
.fg-widget-handle {
	cursor: move;
}
.fg-nested-container {
	width: 100%;
	height: 100%;
}
.move-widget {
	position: absolute;
	left: 10px;
	top: 10px;
	color: #222;
	z-index: 9;
}
.line1, .line2 {
	position: absolute;
	top: 0;
	bottom: 0;
	width: 1px;
	background: red;
}
.line2 {
	background: blue;
}
.zone_div {
	width: 100%;
}
.flexgrid-container {
	position: absolute;
	left: 50%;
	top: 0;
	transform: translate(-50%, 0);
	background: var(--zone-bg);
  border: 2px solid var(--zone-bord);
}
.flexgrid-container * {
	user-select: none;
}
.add-row, .remove-row {
	position: absolute;
	left: 10px;
	top: 10px;
	cursor: pointer;
	height: 30px;
	width: 30px;
	border-radius: 50%;
	background: #fff;
	border: 2px solid #222;
}
.remove-row {
	left: 50px;
}
.add-row::after, .remove-row::after {
	font-family: 'Font Awesome 5 Free';
	font-weight: 900;
	content: "\f067";
	display: inline-block;
	color: #222;
	z-index: 1;
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%,-50%);
}
.remove-row::after {
	content: "\f068";
}
body {
	background: #111;
	padding-bottom: 50px;
}
.btn...

JavaScript

$(function() {
	// grid/zone refers to the container 
	// widget refers to the blocks within a grid/zone
	
	// TODO:
	// - resize axis
	// - redo save widget functionality to fit plugin system
	// - fix placeholder position, currently follows mouse rather than widget
	// - optional size / position values passed with widgets from widget holder
	// - add nested functionality
	// - - optional height passed with nested widget to push other widgets down
	// - widgets from html

	$.fn.setFlexGrid = function(options) {
		var defaults = { // default options for grid
			cols: 12, // starting amount of columns across a grid
			rows: 12, // starting amount of rows in a grid
			fixedGrid: false, // determines whether the grid can be resized or not (adding more rows)
			defaultHeight: 3, // amount of rows a widget will span upon creation
			defaultWidth: 3, // amount of columns a widget will span upon creation
			minHeight: 1, // minimum number of rows a widget can span ~ should be <= defaultHeight
			maxHeight: null, // if set to numeric value: maximum number of rows a widget can span
			minWidth: 1, // minimum number of columns a widget can span ~ should be <= defaultWidth
			maxWidth: null, // if set to numeric value: maximum number of columns a widget can span
			rowHeight: 1, // value times column width
			nested: true, // if true, widgets will allow nested widget to be dropped into them
			showGridlines: true, // if true, show gridlines on initialization
			animate: true, // determines wether or not the widgets should be animated
			nextAxis: 'x', // determines which axis the widget should find an open column. When X: widget will go to the first open column. When Y: widget will go to the first row that has an open column of x.
			resizeHandles: 'se', // determines resize handles (n, e, s, w, ne, se, sw, nw, all) ~ default is "se"
			checkRevert: false // if widget cannot be dropped in pos (going to revert) because of minWidth / minHeight, placeholder will turn...