Sortable and Resizable Dashboard Zones

Using DIVS to implement dashboard zones.

by humanzing

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui-touch-punch/0.2.3/jquery.ui.touch-punch.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.8.2/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css">
<div class="row">

  <div class="col-12">
    <div id="zone_div">
      <div class="flexgrid-container" style="width: 500px;">
        <div class="flexgrid-helper">
          <div class="add-row"></div>
          <div class="remove-row"></div>
          <button class="btn btn-sm clear-flexgrid">clear zone</button>
          <button class="btn btn-sm fg-add-widget">add zone</button>
          <button class="btn btn-sm save-flexgrid"><i class="fas fa-save"></i></button>
        </div>
        <div class="flexgrid-grid"></div>
      </div>
    </div>
  </div>

</div>
</div>

<label class="widget-holder-label">Widgets:</label>
<div class="widget-holder">
  <div class="fg-widget custom-widget"><i class="fa fa-chevron-right fg-resize-widget" aria-hidden="true"></i><i class="fa fa-times fg-remove-widget" title="remove this widget"></i><i class="fas fa-arrows-alt move-widget fg-widget-handle"></i>
    <div class="fg-widget-inner"><i class="fas fa-ad inner-icon"></i></div>
  </div>
  <div class="fg-widget custom-widget"><i class="fa fa-chevron-right fg-resize-widget" aria-hidden="true"></i><i class="fa fa-times fg-remove-widget" title="remove this widget"></i><i class="fas fa-arrows-alt move-widget fg-widget-handle"></i>
    <div class="fg-widget-inner"><i class="fas fa-ad inner-icon"></i></div>
  </div>
  <div class="fg-widget custom-widget"><i class="fa fa-chevron-right fg-resize-widget" aria-hidden="true"></i><i class="fa fa-times fg-remove-widget" title="remove this widget"></i><i class="fas fa-arrows-alt move-widget fg-widget-handle"></i>
    <div class="fg-widget-inner"><i class="fas fa-ad inner-icon"></i></div>
  </div>
  <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 {

}
.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 {
	background: #fff;
	color: #222;
	font-weight: bold;
	border: 2px solid #222;
	border-radius: 5px;
}
/* .absolute::after, .fake-col::after,...

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...