Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save idahopotato1/e73f2f8372fdb64d056c821ec90ac76c to your computer and use it in GitHub Desktop.
Save idahopotato1/e73f2f8372fdb64d056c821ec90ac76c to your computer and use it in GitHub Desktop.
Dynamic Grid System - FlexGrid

Dynamic Grid System - FlexGrid

A Dynamic Grid System.

I wanted to use GridStack.js but it had a few outstanding bugs that made it impossible for me to use in my web app.

Methods:

  • setOption: set an option after initialization by FlexGrid.setOption(option)

  • addWidget: add a widget to specified grid by FlexGrid.addWidget(grid)

  • removeWidget: remove specified widget by FlexGrid.removeWidget(widget)

  • clearGrid: remove all widgets from specified grid by FlexGrid.clearGrid(grid)

  • toggleGridlines: toggle specified grids gridlines by FlexGrid.toggleGridlines(grid)

  • addRow: add number of rows to specified grid by FlexGrid.addRow(grid, val)

  • removeRow: remove number of rows from specified grid by FlexGrid.removeRow(grid, val). Once the end of the grid meets the end of a widget, no more rows can be removed.

A Pen by Souleste on CodePen.

License.

<div class="col-sm-12 form-group">
</div>
<div class="col-sm-12">
<div class="row">
<div class="grid-settings" style="max-width: 300px;">
<!-- <button class="btn button display-inline new_widget">new widget at </button> -->
<div class="form-check display-inline view-gridlines-div">
<label class="view-gridlines-label">Display gridlines?</label>
<div class="toggle">
<input type="checkbox" id="toggle1" class="togglegridlines" checked>
<label for="toggle1"></label>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-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 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>
<label class="nested-holder-label">Nested Widgets: (WIP)</label>
<div class="nested-holder">
<div class="nested-widget cloner">
<div class="nested-widget-inner">
<div class="remove-nested-widget"><i class="fas fa-times"></i></div>
<div class="nested-widget-txt">cloner</div>
</div>
</div>
<div class="nested-widget" data-cs-height="300">
<div class="nested-widget-inner">
<div class="remove-nested-widget"><i class="fas fa-times"></i></div>
<div class="nested-widget-txt">1</div>
</div>
</div>
<div class="nested-widget" data-cs-height="400">
<div class="nested-widget-inner">
<div class="remove-nested-widget"><i class="fas fa-times"></i></div>
<div class="nested-widget-txt">2</div>
</div>
</div>
<div class="nested-widget">
<div class="nested-widget-inner">
<div class="remove-nested-widget"><i class="fas fa-times"></i></div>
<div class="nested-widget-txt">3</div>
</div>
</div>
<div class="nested-widget">
<div class="nested-widget-inner">
<div class="remove-nested-widget"><i class="fas fa-times"></i></div>
<div class="nested-widget-txt">4</div>
</div>
</div>
</div>
$(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 red
};
options = $.extend(defaults, options);
var zoneInner = $(this);
// var zoneInner = zone.find('.flexgrid-grid');
// maintain a reference to the existing function
var old_width = $.fn.width;
// ...before overwriting the jQuery extension point
$.fn.width = function(type, value) {
// original behavior - use function.apply to preserve context
var ret = old_width.apply(this, arguments);
// stuff I will be extending that doesn't change the way .width() works
if ($.type(type) == 'string' && type != undefined && type != null && type != '') {
var item = $(this);
switch(type) {
case 'minWidth':
if (value != undefined) { // set min-width
item.css('min-width', value);
} else { // get min-width
return parseInt(item.css('min-width'));
}
break;
case 'maxWidth':
if (value != undefined) { // set max-width
item.css('max-width', value);
} else { // get max-width
return parseInt(item.css('max-width'));
}
break;
}
}
// preserve return value (probably the jQuery object...)
return ret;
};
// maintain a reference to the existing function
var old_height = $.fn.height;
// ...before overwriting the jQuery extension point
$.fn.height = function(type, value) {
// original behavior - use function.apply to preserve context
var ret = old_height.apply(this, arguments);
// stuff I will be extending that doesn't change the way .height() works
if ($.type(type) == 'string' && type != undefined && type != null && type != '') {
var item = $(this);
switch(type) {
case 'minHeight':
if (value != undefined) { // set min-height
item.css('min-height', value);
} else { // get min-height
return parseInt(item.css('min-height'));
}
break;
case 'maxHeight':
if (value != undefined) { // set max-height
item.css('max-height', value);
} else { // get max-height
return parseInt(item.css('max-height'));
}
break;
}
}
// preserve return value (probably the jQuery object...)
return ret;
};
// setup variables to be used in calculations in multiple functions
$.fn.resetVars = function() {
// reset variables since we perfected the zone-inner width / height
var zoneInner = $(this); // we will need to reintialize this variable in every function where the following variables are used
var zone = zoneInner.closest('.flexgrid-container');
var zoneWidth = zoneInner.outerWidth();
var zoneHeight = zoneInner.outerHeight();
var colWidth = Math.floor(zoneWidth / options.cols); // width of each column
var rowHeight = options.rowHeight > 1 ? options.rowHeight * colWidth : colWidth; // height of each row
var res = { zone: zone, zW: zoneWidth, zH: zoneHeight, cW: colWidth, rH: rowHeight };
return res;
}
var re = zoneInner.resetVars(); // reset zone variables
// CREATE COLS
$.fn.buildGrid = function() { // create rows of columns
var zoneInner = $(this);
var re = zoneInner.resetVars(); // reset zone variables
var colAmount = options.cols; // number of columns in each row
var rowAmount = options.rows; // number of rows in a zone
var gridlines = options.showGridlines ? 'fg-gridlines' : '';
for (var y = 0; y < rowAmount; y++) {
for (var x = 0; x < colAmount; x++) {
zoneInner.append('<div class="fg-enabled-col fg-col '+gridlines+'" data-fg-eq="'+x+'" data-fg-row="'+y+'" style="min-width: '+ re.cW +'px; min-height: '+ re.rH +'px; top:'+(re.rH * y)+'px; left: '+(re.cW * x)+'px; "></div>');
var appended = zoneInner.find('.fg-col[data-fg-row="'+y+'"][data-fg-eq="'+x+'"]');
var i = zoneInner.find('.fg-col').index(appended);
appended.attr('data-fg-index', i);
}
}
var lastCol = zoneInner.find('.fg-col').last();
var rowCount = parseInt(lastCol.attr('data-fg-row'));
zoneInner.css({ // reset the zone-inner width / height to perfect it.
'height': (rowCount + 1) * re.rH,
'width': (re.cW * options.cols)
});
re.zone.css({ // reset the zone width / height to perfect it.
'height': (rowCount + 1) * re.rH + 55, // add some pixels to allow space for the zone-helper
'width': (re.cW * options.cols) + 15 // add a little bezzel
});
enableSortable();
}
var re = zoneInner.resetVars(); // reset zone variables
// set data attributes to find position of widget
$.fn.setData = function() {
var widget = $(this);
var zoneInner = widget.closest('.flexgrid-grid');
var zoneCol = widget.closest('.fg-col');
var wigW = Math.floor(widget.width()); // widget width
var wigH = Math.floor(widget.height()); // widget height
var re = zoneInner.resetVars(); // reset zone variables
var data_minWidth = parseFloat(Math.round(widget.width('minWidth') / re.cW));
var data_minHeight = parseFloat(Math.round(widget.height('minHeight') / re.rH));
var data_width = parseFloat(Math.round(wigW / re.cW)) < data_minWidth ? data_minWidth : parseFloat(Math.round(wigW / re.cW)); // number of columns a widget spans
var data_height = parseFloat(Math.round(wigH / re.rH)) < data_minHeight ? data_minHeight : parseFloat(Math.round(wigH / re.rH)); // number of rows a widget spans
var data_maxWidth = parseFloat(Math.round(widget.width('maxWidth') / re.cW));
var data_maxHeight = parseFloat(Math.round(widget.height('maxHeight') / re.rH));
var data_y = zoneCol.attr('data-fg-row'); // row the widget starts on ~ 0 indexed
var data_x = zoneInner.find('.fg-col[data-fg-row="'+data_y+'"]').index(widget.closest('.fg-col[data-fg-row="'+data_y+'"]')); // column # the widget starts on ~ 0 indexed
widget.attr({ 'data-fg-width': data_width, 'data-fg-height': data_height, 'data-fg-x': data_x, 'data-fg-y': data_y, 'data-fg-minwidth': data_minWidth, 'data-fg-minheight': data_minHeight, 'data-fg-maxwidth': data_maxWidth, 'data-fg-maxheight': data_maxHeight }); // set these new attributes
}
$.fn.setOption = function(option, val) {
var widget = $(this);
var grid = widget.closest('.flexgrid-grid');
var re = grid.resetVars(); // reset zone variables
var toggle;
switch(option) {
case 'height':
toggle = 'data-fg-height';
widget.css('height', val * re.rH);
break;
case 'width':
toggle = 'data-fg-width';
widget.css('width', val * re.cW);
break;
case 'minHeight':
toggle = 'data-fg-minheight';
widget.css('min-height', val * re.rH);
break;
case 'minWidth':
toggle = 'data-fg-minwidth';
widget.css('min-width', val * re.cW);
break;
case 'maxHeight':
toggle = 'data-fg-maxheight';
widget.css('max-height', val * re.rH);
break;
case 'maxWidth':
toggle = 'data-fg-maxwidth';
widget.css('max-width', val * re.cW);
break;
case 'x':
toggle = 'data-fg-x';
var y = widget.attr('data-fg-y');
var col = grid.find('.fg-col[data-fg-eq="'+val+'"][data-fg-row="'+y+'"]');
widget.detach();
col.append(widget);
break;
case 'y':
toggle = 'data-fg-y';
var x = widget.attr('data-fg-x');
var col = grid.find('.fg-col[data-fg-eq="'+x+'"][data-fg-row="'+val+'"]');
widget.detach();
col.append(widget);
break;
}
widget.attr(toggle, val);
widget.setData();
}
// enable or disable columns depending on the function parameter...
$.fn.modCols = function(modifier) {
var zoneCol = $(this);
var zoneInner = zoneCol.closest('.flexgrid-grid');
var widget = zoneCol.find('.fg-widget');
if (modifier == 'disable') widget.setData();
var rowStart = parseInt(zoneCol.attr('data-fg-row')); // row the widget starts on
var rowEnd = rowStart + parseInt(widget.attr('data-fg-height')); // row the widget ends on
var colStart = parseInt(zoneCol.attr('data-fg-eq')); // col the widget starts on
var colEnd = colStart + parseInt(widget.attr('data-fg-width')); // col the widget ends on
for (var r = rowStart; r < rowEnd; r++) {
for (var c = colStart; c < colEnd; c++) {
var self = zoneInner.find('.fg-col[data-fg-row="'+r+'"][data-fg-eq="'+c+'"]');
if (modifier == 'enable') { self.addClass('fg-enabled-col').removeClass('fg-disabled-col'); }
if (modifier == 'disable') { self.removeClass('fg-enabled-col').addClass('fg-disabled-col'); }
}
}
}
$.fn.zoneOverflow = function() {
var col = $(this);
var widget = col.find('.fg-widget');
var zoneInner = col.closest('.flexgrid-grid');
var ogParent = widget.data('ogParent'); // original parent column
var re = zoneInner.resetVars(); // reset zone variables
var res = {obj: col};
widget.setData();
var data_x = parseInt(widget.attr('data-fg-x'));
var data_y = parseInt(widget.attr('data-fg-y'));
var data_width = parseInt(widget.attr('data-fg-width'));
var data_height = parseInt(widget.attr('data-fg-height'));
var xCon = data_width + data_x > options.cols;
var yCon = parseInt(col.attr('data-fg-row')) + data_height > parseInt(zoneInner.find('.fg-col').last().attr('data-fg-row')) + 1;
var dif = ((data_width + data_x) - options.cols) * re.cW;
if ($(ogParent).length > 0) { // if we dropped widget we should have an 'ogParent'
if ((options.cols - data_x) * re.cW < widget.width('minWidth')) { // if overflowing zone on x-axis
var de = col.find('.fg-widget').detach();
$(ogParent).append(de);
res = {obj: $(ogParent)};
} else if (xCon) { // if widget is overflowing zone on x-axis but is not at min-width
widget.css('width', widget.width() - dif);
}
res = {obj: col};
} else if ($(ogParent).length <= 0) { // if we added new widget
var goHere = col;
while (xCon) { // detach the widget and append to the next open fg-enabled-col until it no longer overflows the zone width
var de = goHere.find('.fg-widget').detach();
goHere = goHere.nextAll('.fg-enabled-col').first();
if (goHere.length == 0 || goHere == undefined || goHere == null) {
zoneInner.createRow();
goHere = zoneInner.find('.fg-widget').last().closest('.fg-col').nextAll('.fg-enabled-col').first();
}
goHere.append(de);
de.setData(); // reset widget attributes
// reset position parameters to be used in next if statement / loop ...
data_x = parseInt(widget.attr('data-fg-x'));
data_y = parseInt(widget.attr('data-fg-y'));
data_width = parseInt(widget.attr('data-fg-width'));
data_height = parseInt(widget.attr('data-fg-height'));
yCon = yCon = parseInt(goHere.attr('data-fg-row')) + data_height > parseInt(zoneInner.find('.fg-col').last().attr('data-fg-row')) + 1;
xCon = data_width + data_x > options.cols;
if (!xCon) {
break;
}
}
col = goHere; // reset col so it can be used in the next if statment...
res = {obj: col};
}
if (yCon) { // if widget is overflowing zone height but is not at min-height
moreHeight(col); // add more rows...
}
data_x = parseInt(col.attr('data-fg-eq'));
data_y = parseInt(col.attr('data-fg-row'));
// tried to only target siblings that shared columns, but was much slower....
var sibs = col.siblings('.fg-col');
for (var a = 0; a < sibs.length; a++) {
var colSib = sibs.eq(a);
if (colSib.find('.fg-widget').length > 0) {
var dc = dropCollision(colSib, col);
col = dc.obj;
sibs = col.siblings('.fg-col');
}
}
res = {obj: col};
return res;
}
// pass in parameters such as: x, y, width, height to position and size the widget
$.fn.addWidget = function(params) {
console.log('options: ', options);
var zoneInner = $(this);
var re = zoneInner.resetVars(); // reset zone variables
var goHere;
var width, height, minWidth, minHeight, maxWidth, maxHeight, nextAxis;
// if no parameters are passed, use the default value. else check if the parameter has a value, if so use that value, else use default value
var noParams = params == undefined || params == null ? true : false;
width = noParams ? options.defaultWidth * re.cW : (params.width != undefined || params.width != null) ? params.width * re.cW : options.defaultWidth * re.cW;
height = noParams ? options.defaultHeight * re.rH : (params.height != undefined || params.height != null) ? params.height * re.rH : options.defaultHeight * re.rH;
minWidth = noParams ? options.minWidth * re.cW : (params.minWidth != undefined || params.minWidth != null) ? params.minWidth * re.cW : options.minWidth * re.cW;
minHeight = noParams ? options.minHeight * re.rH : (params.minHeight != undefined || params.minHeight != null) ? params.minHeight * re.rH : options.minHeight * re.rH;
maxWidth = noParams ? options.maxWidth * re.cW : (params.maxWidth != undefined || params.maxWidth != null) ? params.maxWidth * re.cW : options.maxWidth * re.cW;
maxWidth = (maxWidth == 0) ? 'unset' : maxWidth;
maxHeight = noParams ? options.maxHeight * re.rH : (params.maxHeight != undefined || params.maxHeight != null) ? params.maxHeight * re.rH : options.maxHeight * re.rH;
maxHeight = (maxHeight == 0) ? 'unset' : maxHeight;
nextAxis = noParams ? options.nextAxis : (params.nextAxis != undefined || params.nextAxis != null) ? params.nextAxis : options.nextAxis;
var amountNeeded = ((options.rowHeight * re.rH) * options.defaultHeight) / re.rH;
amountNeeded = options.rowHeight > 1 ? amountNeeded : options.defaultHeight;
if ( zoneInner.find('.fg-enabled-col').length < 1 ) {
for (l = 0; l < amountNeeded; l++) {
zoneInner.createRow();
}
}
// if we aren't given parameters or positions, then go to the first open column.
if (noParams || (params.y === undefined || params.y === null || params.y === '') || (params.x === undefined || params.x === null || params.x === '')) {
var findNext = findNextColumn('x', zoneInner, params.x, params.y);
goHere = findNext.goHere;
} else {
var findNext = findNextColumn(nextAxis, zoneInner, params.x, params.y);
goHere = findNext.goHere;
}
if (goHere.length == 0) console.error('Parent column does not exist.');
var nested = options.nested == true ? $('<div class="fg-nested-container"></div>') : $('');
var widget = noParams ? $('<div class="fg-widget"><i class="fa fa-times fg-remove-widget" title="remove this widget."></i><div class="fg-widget-inner fg-widget-handle"><div class="zone-txt text-center"></div></div></div>') : params.widget === undefined || params.widget === null ? $('<div class="fg-widget"><i class="fa fa-times remove-widget" title="remove this widget"></i><div class="fg-widget-inner fg-widget-handle"><div class="zone-txt text-center"></div></div></div>') : params.widget;
widget.find('.fg-widget-inner').append(nested);
widget.css({ // set widget style options
'width': width,
'min-width': minWidth,
'max-width': maxWidth,
'height': height,
'min-height': minHeight,
'max-height': maxHeight
});
goHere.append(widget); // append the widget
widget.setData(); // reset the widget attributes
// DETECT ZONE OVERFLOW
var zo = goHere.zoneOverflow();
goHere = zo.obj;
sibs = goHere.siblings('.fg-col');
options.animate ? widget.animateWidget() : null;
widget.setData();
goHere.modCols('disable'); // disable overlapped columns
resize(widget);
enableSortable();
}
function findNextColumn(axis, zoneInner, x, y) {
var goHere;
switch(axis) {
case 'x':
zoneInner.find('.fg-col').each(function() {
goHere = $(this);
if ( !goHere.hasClass('fg-disabled-col') && goHere.find('.fg-widget').length == 0 ) {
goHere = $(this);
return false; // break when we find an open column
}
});
break;
case 'y':
if (x === undefined || x === null || x === '') { // if we still aren't passed an x
findNextColumn('x', zoneInner);
}
goHere = zoneInner.find('.fg-col[data-fg-row="'+y+'"][data-fg-eq="'+x+'"]');
while (goHere.hasClass('fg-disabled-col')) {
y++;
goHere = zoneInner.find('.fg-col[data-fg-row="'+y+'"][data-fg-eq="'+x+'"]');
if (!goHere.hasClass('fg-disabled-col')) {
break;
}
}
if (goHere.length == 0) {
var rowCount = zoneInner.find('.fg-col').last().attr('data-fg-row');
zoneInner.addRow(y - rowCount); // add as many rows as needed for placing the widget...
goHere = zoneInner.find('.fg-col[data-fg-row="'+y+'"][data-fg-eq="'+x+'"]');
goHere = goHere.hasClass('fg-disabled-col') ? goHere.nextAll('.fg-enabled-col[data-fg-row="'+ (y) +'"][data-fg-eq="'+x+'"]').first() : goHere;
}
break;
}
var result = res = {goHere: goHere};
return result;
}
$.fn.removeWidget = function(widget) {
var zoneCol = widget.closest('.fg-col');
zoneCol.modCols('enable');
var originalContainer = widget.data('originalContainer') != undefined || widget.data('originalContainer') != null ? widget.data('originalContainer') : zoneCol;
if (!originalContainer.hasClass('fg-col')) { // if the widget came from a different container
var de = widget.detach();
originalContainer.append(de);
de.css({'width':widget.data('ogWidth'), 'height':widget.data('ogHeight'), 'min-width':'', 'min-height':'', 'max-width':'', 'max-height':''});
} else {
widget.remove();
}
zoneCol.sortable('refresh');
}
$.fn.createRow = function() { // add a row to the grid ~ used internally
var zoneInner = $(this);
var re = zoneInner.resetVars(); // reset zone variables
var lastCol = zoneInner.find('.fg-col').last(); // last column in zone
var appendHere = lastCol[0].offsetTop + re.rH; // new row position
var rowCount = parseInt(lastCol.attr('data-fg-row')) + 1; // number of rows found within zone
var colAmount = options.cols; // number of columns spanning a row
var gridlines = options.showGridlines ? 'fg-gridlines' : ''; // is show gridlines checked?
for (var i = 0; i < colAmount; i++) {
zoneInner.append('<div class="fg-enabled-col fg-col ui-sortable '+gridlines+'" data-fg-eq="'+i+'" data-fg-row="'+ rowCount +'" style="min-width: '+re.cW+'px; min-height: '+re.rH+'px; top:'+appendHere+'px; left: '+(re.cW * i)+'px; "></div>');
var blah = zoneInner.find('.fg-col[data-fg-row="'+ rowCount +'"][data-fg-eq="'+i+'"]');
var n = zoneInner.find('.fg-col').index(blah);
blah.attr('data-fg-index', n);
}
lastCol = zoneInner.find('.fg-col').last(); // reset since we added rows
rowCount = parseInt(lastCol.attr('data-fg-row')) + 1; // reset since we added rows
zoneInner.height(rowCount * re.rH); // reset since we added rows
re.zone.height( zoneInner.height() + 50 ); // reset since we added rows
enableSortable();
}
$.fn.addRow = function(val) { // add specified number of rows outside of plugin...
var zoneInner = $(this);
val = val === undefined || val === null ? 1 : val;
for (var i = 0; i < val; i++) {
zoneInner.createRow();
}
}
$.fn.addRowHere = function(goHere, rowsNeeded) { // rows will be appended after specified columns row
var zoneInner = $(this);
var re = zoneInner.resetVars(); // reset zone variables
var appendHere = goHere[0].offsetTop + re.rH; // new row position
var rowCount = parseInt(goHere.attr('data-fg-row')) + 1; // number of rows found within zone
var colAmount = options.cols; // number of columns spanning a row
var gridlines = options.showGridlines ? 'fg-gridlines' : ''; // is show gridlines checked?
for (la = 0; la < rowsNeeded; la++) {
for (var i = 0; i < colAmount; i++) {
zoneInner.append('<div class="fg-enabled-col fg-col ui-sortable '+gridlines+'" data-fg-eq="'+i+'" data-fg-row="'+rowCount+'" style="min-width: '+re.cW+'px; min-height: '+re.rH+'px; top:'+appendHere+'px; left: '+(re.cW * i)+'px;"></div>');
var blah = zoneInner.find('.fg-col[data-fg-row="'+(rowCount + 1)+'"][data-fg-eq="'+i+'"]');
var n = zoneInner.find('.fg-col').index(blah);
blah.attr('data-fg-index', n);
}
appendHere = goHere[0].offsetTop + (re.rH * (la + 2));
rowCount = rowCount + 1;
}
// reset since we added rows
lastCol = zoneInner.find('.fg-col').last(); // reset since we added rows
rowCount = parseInt(lastCol.attr('data-fg-row')) + 1; // reset since we added rows
zoneInner.height(rowCount * re.rH); // reset since we added rows
re.zone.height( zoneInner.height() + 50 ); // reset since we added rows
enableSortable();
}
$.fn.removeThisRow = function() { // remove a row from the grid
var zoneInner = $(this);
var re = zoneInner.resetVars(); // reset zone variables
var zoneCol = zoneInner.find('.fg-col');
var rowCount = parseInt(zoneInner.find('.fg-col').last().attr('data-fg-row')) + 1; // number of rows found in the zone
if (zoneCol.length > options.cols * options.rows) {
zoneInner.find('.fg-col[data-fg-row="'+(rowCount - 1)+'"]').remove();
rowCount = parseInt(zoneInner.find('.fg-col').last().attr('data-fg-row')) + 1;
zoneInner.css({ 'height': rowCount * re.rH });
re.zone.css({ 'height': rowCount * re.rH + 55 });
}
// check if any widgets are overflowing the zone after we remove rows
var wigs = zoneInner.find('.fg-widget');
for (var i = 0; i < wigs.length; i++) {
var widget = wigs.eq(i);
var zoneCol = widget.closest('.fg-col');
widget.setData();
var data_y = parseInt(widget.attr('data-fg-y'));
var data_height = parseInt(widget.attr('data-fg-height'));
var yCon = data_height + data_y > rowCount;
var dif = ((data_height + data_y) - rowCount) * re.rH;
// if (yCon) { // if widget is overflowing zone, resize it down to fit
// widget.height(widget.height() - dif);
// widget.setData();
// }
if (yCon) {
moreHeight(widget.closest('.fg-col'));
widget.setData();
zoneCol.modCols('disable');
}
// if (yCon && widget.height() == options.minHeight * re.rH) { // if we resized down but the widget is as short as it can get, don't remove rows
// moreHeight(widget.closest('.fg-col'));
// }
}
rowCount = parseInt(zoneInner.find('.fg-col').last().attr('data-fg-row')) + 1;
re.zone.css({ 'height': rowCount * re.rH + 55 });
}
$.fn.removeRow = function(val) { // remove specified number of rows outside of plugin...
var zoneInner = $(this);
val = val === undefined || val === null ? 1 : val;
for (var i = 0; i < val; i++) {
zoneInner.removeThisRow();
}
}
function enableSortable() {
$('.fg-col').sortable({
items: '.fg-widget',
connectWith: '.fg-enabled-col',
handle: '.fg-widget-handle',
cursor: 'move',
placeholder: 'fg-widget-placeholder',
tolerance: 'intersect',
start: function(event, ui) {
var zoneCol = $(this);
var zoneInner = zoneCol.closest('.flexgrid-grid')
var sibs = zoneCol.siblings('.fg-col');
var widget = zoneCol.find('.fg-widget');
zoneCol.modCols('enable'); // enable overlapped columns
var wig = $(this).find('.fg-widget-inner'); // set the placeholder's height and width equal to this widget.
var wigW = wig[0].offsetWidth;
var wigH = wig[0].offsetHeight;
zoneInner.find('.fg-widget-placeholder').css({
'width': wigW,
'height': wigH
});
// set data so we can see if it changes on stop
ui.item.data({
'ogIndex': ui.item.closest('.fg-col').index($(this)), // original index value of the widget
'ogParent': ui.item.closest('.fg-col') // original parent column of the widget
});
},
over: function(event, ui) {
// refresh sortable columns only when we are dragging over them
// previously called in sortable start and that caused a lot of lag with multiple widgets...
var zoneCol = $(this);
zoneCol.sortable('refresh');
var sibs = zoneCol.siblings('.fg-col');
var widget = ui.item;
// this is for widgets coming from outside of a grid...
if (!widget.data('sortableItem').bindings.hasClass('.fg-col')) {
var originalContainer = widget.data('sortableItem').bindings;
var ogWidth = widget.data('sortableItem').helperProportions.width;
var ogHeight = widget.data('sortableItem').helperProportions.height;
$(this).find('.ui-sortable-placeholder').addClass('fg-widget-placeholder');
// var minWidth = ui.item.attr('data-fg-minwidth') != options.minWidth ? ui.item.attr('data-fg-minwidth') : options.minWidth * re.cW;
// var minHeight = ui.item.attr('data-fg-minheight') != options.minHeight ? ui.item.attr('data-fg-minheight') : options.minHeight * re.rH;
// $(this).find('.ui-sortable-placeholder').css({ 'width': (Math.ceil((ogWidth / re.cW)) * re.cW) - 10, 'height': (Math.ceil((ogHeight / re.rH)) * re.rH) - 10, 'min-width': minWidth - 10, 'min-height':minHeight - 10, 'max-width': options.maxWidth != null && options.maxWidth != undefined ? options.maxWidth * re.cW - 10 : 'unset', 'max-height': options.maxWidth != null && options.maxWidth != undefined ? options.maxWidth * re.cW - 10 : 'unset', 'visibility': 'visible', 'position':'absolute' });
// ui.item.css({ 'width': Math.ceil((ogWidth / re.cW)) * re.cW, 'height': Math.ceil((ogHeight / re.rH)) * re.rH, 'min-width': minWidth, 'min-height': minHeight, 'max-width': options.maxWidth != null && options.maxWidth != undefined ? options.maxWidth * re.cW : 'unset', 'max-height': options.maxWidth != null && options.maxWidth != undefined ? options.maxWidth * re.cW : 'unset' });
}
if (options.checkRevert) {
// check if widget can be dropped here or if it will revert to it's original position
widget.checkCollision('sort', sibs); // check for collision
}
},
receive: function(event, ui) {
var zoneCol = $(this);
var zoneInner = zoneCol.closest('.flexgrid-grid');
var re = zoneInner.resetVars();
var sibs = zoneCol.siblings('.fg-col');
var zone = zoneCol.closest('.flexgrid-container');
ui.item.setData(); // reset here so that zoneOverflow can use the attributes
if (!ui.item.data('sortableItem').bindings.hasClass('fg-col')) {
var originalContainer = ui.item.data('sortableItem').bindings;
var ogWidth = ui.item.data('sortableItem').helperProportions.width;
var ogHeight = ui.item.data('sortableItem').helperProportions.height;
ui.item.data({
'originalContainer': originalContainer,
'ogWidth': ogWidth,
'ogHeight': ogHeight
}); // set the original container data
ui.item.css({
'width': Math.ceil((ogWidth / re.cW)) * re.cW,
'height': Math.ceil((ogHeight / re.rH)) * re.rH,
'minWidth': options.minWidth * re.cW,
'minHeight': options.minHeight * re.rH,
'maxWidth': options.maxWidth != null && options.maxWidth != undefined ? options.maxWidth * re.cW : 'unset',
'maxHeight': options.maxWidth != null && options.maxWidth != undefined ? options.maxWidth * re.cW : 'unset'
});
}
var zo = zoneCol.zoneOverflow(); // check if the widget is overflowing the zone
zoneCol = zo.obj;
sibs = zoneCol.siblings('.fg-col');
zoneCol.modCols('disable');
ui.item.setData(); // reset widget attributes
options.animate ? ui.item.animateWidget() : null;
resize(ui.item);
},
stop: function(event, ui) {
var zoneCol = $(this);
var widget = zoneCol.find('.fg-widget');
var sibs = zoneCol.siblings('.fg-col');
var ogParent = ui.item.data('ogParent');
var zo = zoneCol.zoneOverflow(); // check if the widget is overflowing the zone
zoneCol = zo.obj;
sibs = zoneCol.siblings('.fg-col');
zoneCol.modCols('disable'); // disable overlapped columns
// ui.item.setData(); // set data attributes
options.animate ? ui.item.animateWidget() : null;
resize(ui.item);
// detect if the item position has changed so that we can remind the user to save...
if (ui.item.closest('.fg-col').index(zoneCol) != ui.item.data('ogIndex')) {
console.log('position has changed');
}
}
});
} enableSortable();
var re = zoneInner.resetVars(); // reset zone variables
// Resize function
function resize(widget) {
widget.resizable({
grid: [re.cW, (options.rowHeight > 1 ? options.rowHeight * re.cW : re.cW)],
handles: options.resizeHandles,
containment: zoneInner,
start: function(event, ui) {
console.log(ui, event);
var widget = ui.element;
var zoneCol = widget.closest('.fg-col');
var sibs = zoneCol.siblings('.fg-col');
zoneCol.modCols('enable'); // enable overlapped columns
for (var i = 0; i < sibs.length; i++) {
var colSib = sibs.eq(i);
if (colSib.children('.fg-widget').length > 0 && (colSib.offset().left == zoneCol.offset().left + zoneCol.width() || colSib.offset().top == zoneCol.offset().top + zoneCol.height()) ) {
zoneCol.checkCollision('resize', colSib); // check for collision when resizing
}
}
ui.element.data({
'ogSize': { width: ui.element[0].offsetWidth, height: ui.element[0].offsetHeight }, // find the original size of item so we can later detect if it has changed.
'ogParent': ui.element.closest('.fg-col') // original parent column of the widget
});
},
resize: function(event, ui) {
// DETECT COLLISION
var widget = ui.element;
var zoneCol = widget.parent();
var sibs = zoneCol.siblings('.fg-col');
// so that we can continue to resize once there is no collision
ui.element.resizable("option", "maxHeight", null);
ui.element.resizable("option", "maxWidth", null);
for (var i = 0; i < sibs.length; i++) {
var colSib = sibs.eq(i);
if (colSib.children('.fg-widget').length > 0 && (colSib.offset().left == zoneCol.offset().left + zoneCol.width() || colSib.offset().top == zoneCol.offset().top + zoneCol.height()) ) {
zoneCol.checkCollision('resize', colSib); // check for collision when resizing
}
}
widget.setData(); // reset widget attributes
},
stop: function(event, ui) {
var widget = ui.element;
var zoneCol = widget.closest('.fg-col');
var sibs = zoneCol.siblings('.fg-col');
for (var i = 0; i < sibs.length; i++) {
var colSib = sibs.eq(i);
if (colSib.find('.fg-widget').length > 0) {
zoneCol.checkCollision('resize', colSib); // check for collision when resizing
dropCollision(colSib, zoneCol); // check for collision on stop, just in case...
}
}
widget.setData(); // reset widget attributes
zoneCol.modCols('disable'); // disable overlapped columns
options.animate ? widget.animateWidget() : null;
// reset max height and width
widget.resizable( "option", "maxHeight", null );
widget.resizable( "option", "maxWidth", null );
// detect if the item size has changed so that we can remind the user to save...
if (widget[0].offsetWidth != widget.data('ogSize').width || widget[0].offsetHeight != widget.data('ogSize').height) {
console.log('Size has changed.');
}
}
});
}
$.fn.checkCollision = function(type, colSib) {
var el = $(this);
var x1 = el[0].offsetLeft; // widget left position
var y1 = el[0].offsetTop; // widget top position
var b1 = y1 + el[0].offsetHeight; // widget bottom position
var r1 = x1 + el[0].offsetWidth; // widget right position
var x2 = colSib[0].offsetLeft; // collided widget left position
var y2 = colSib[0].offsetTop; // collided widget top position
var b2 = y2 + colSib[0].offsetHeight; // collided widget bottom position
var r2 = x2 + colSib[0].offsetWidth; // collided widget right position
// detect when a widget has collided with another during resize, then prevent resizing through that widget...
if (type == 'resize') {
// X-AXIS
if ( (r1 == x2 && y1 == y2 || r1 == x2 && b1 == b2)
|| (y2 < y1 && b2 > b1 && r2 > r1)
|| (y1 < y2 && b1 > b2 && r2 > r1)
|| (y2 < b1 && b2 > b1 && r1 == x2)
|| (y1 < b2 && b1 >= b2 && y1 >= y2 && r1 >= x2 && x1 < x2))
{
$('.ui-resizable-resizing').resizable('option', 'maxWidth', (x2 - x1));
}
// Y-AXIS
if ( (b1 == y2 && x1 == x2 || b1 == y2 && r1 == r2)
|| (x1 < x2 && r1 > r2 && b1 == y2)
|| (x2 < x1 && r2 > r1 && b1 == y2)
|| (r2 > r1 && x2 < r1 && b1 == y2)
|| (r1 > r2 && x1 < r2 && b1 == y2) )
{
$('.ui-resizable-resizing').resizable('option', 'maxHeight', (y2 - y1));
}
} else if (type == 'sort') {
var widget = el;
var placeholder = widget.data().sortableItem.placeholder;
placeholder.removeClass('danger-placeholder');
var zoneCol = placeholder.parent();
var zoneInner = zoneCol.closest('.flexgrid-grid');
var re = zoneInner.resetVars(); // reset zone variables
var width = parseInt(widget.attr('data-fg-width'));
var height = parseInt(widget.attr('data-fg-height'));
var minWidth = parseInt(widget.attr('data-fg-minwidth'));
var minHeight = parseInt(widget.attr('data-fg-minheight'));
var rowStart = parseInt(zoneCol.attr('data-fg-row'));
var rowEnd = rowStart + minHeight - 1;
var colStart = parseInt(zoneCol.attr('data-fg-eq'));
var colEnd = colStart + minWidth - 1;
var danger = [];
for (var i = 0; i < colSib.length; i++) {
var item = colSib.eq(i);
if (item.hasClass('fg-disabled-col')
&& (item.attr('data-fg-eq') >= colStart && item.attr('data-fg-eq') <= colEnd)
&& (item.attr('data-fg-row') >= rowStart && item.attr('data-fg-row') <= rowEnd)
) {
danger.push('');
}
}
if ((options.cols - colStart) * re.cW < widget.width('minWidth')) {
danger.push('');
}
if (danger.length) {
placeholder.addClass('danger-placeholder');
}
}
};
// check for collision when dropping a widget, if it overlaps another widget.
function dropCollision(colSib, col) {
var widget = col.find('.fg-widget');
var zoneInner = col.closest('.flexgrid-grid');
var re = zoneInner.resetVars(); // reset zone variables
var ogParent = widget.data('ogParent'); // original parent column
res = {obj: col};
// column postions
var x1 = col.offset().left;
var y1 = col.offset().top;
var b1 = y1 + col.height();
var r1 = x1 + col.width();
// current sibling column positions
var x2 = colSib.offset().left;
var y2 = colSib.offset().top;
var b2 = y2 + colSib.height();
var r2 = x2 + colSib.width();
// if the column is colliding with the sibling column at any position
var con = (r1 > x2 && x1 < x2 && y1 < y2 && b1 > y2) || (r1 > x2 && x1 < x2 && y1 == y2 && b1 == b2) || (r1 > x2 && x1 < x2 && y1 <= y2 && b1 >= b2) || (r1 > x2 && x1 < x2 && y1 >= y2 && b1 <= b2) || (r1 > x2 && x1 < x2 && y1 < b2 && b1 > b2) || (x1 < r2 && r1 > r2 && y1 < y2 && b1 > y2) || (x1 >= x2 && r1 <= r2 && y1 < y2 && b1 > y2);
// if the columns x-axis is colliding with the sibling columns x-axis
var xCon = (y1 <= y2 && x1 < x2 && r1 > x2 && b1 > y2) || (y1 < y2 && b1 > b2 && x1 < x2 && r1 > x2) || (b1 >= b2 && y1 < b2 && x1 < x2 && r1 > x2) || (y1 > y2 && b1 < b2 && x1 < x2 && r1 > x2) || (y1 == y1 && b1 == b2 && x1 < x2 && r1 > x2);
// if the columns y-axis is colliding with the sibling columns y-axis
var yCon = (x1 < r2 && r1 > r2 && y1 < y2 && b1 > y2) || (x1 >= x2 && r1 <= r2 && y1 < y2 && b1 > y2) || (x1 <= x2 && r1 >= r2 && y1 < y2 && b1 > y2) || (r1 > x2 && x1 < x2 && y1 < y2 && b1 > y2);
if ($(ogParent).length > 0) { // if we dragged from another column
if (xCon && yCon) { // if both the x-axis and y-axis have collision
var xConVal = x2 - x1;
var yConVal = y2 - y1;
if (xConVal > yConVal) { // find which axis has more collision and resize for that one
xCollision();
} else {
yCollision();
}
} else if (xCon) { // if only x-axis collision, resize for x-axis
xCollision();
} else if (yCon) { // if only y-axis collision, resize for y-axis
yCollision();
}
function xCollision() { // x-axis collision
var saveWidth = widget.width(); // save the width
widget.width(x2 - x1);
if (x2 - x1 < widget.width('minWidth')) { // if new width is less than the minWidth ~
var de = widget.detach();
de.width(saveWidth); // ~ resize back to saveWidth
$(ogParent).append(de); // ~ and revert to ogParent
}
}
function yCollision() { // y-axis collision
var saveHeight = widget.height(); // save the height
widget.height(y2 - y1);
if (y2 - y1 < widget.height('minHeight')) { // if new height is less than the minHeight ~
var de = widget.detach();
de.height(saveHeight); // ~ resize back to saveHeight
$(ogParent).append(de); // ~ and revert to ogParent
}
}
} else if ($(ogParent).length <= 0) { // if we added a new widget
var goHere = col;
while (con) { // detach the widget and append to the next open fg-enabled-col until there is no collision
var de = goHere.find('.fg-widget').detach();
goHere = goHere.nextAll('.fg-enabled-col').first();
if (goHere.length == 0 || goHere == undefined || goHere == null) {
zoneInner.createRow();
goHere = zoneInner.find('.fg-widget').last().closest('.fg-col').nextAll('.fg-enabled-col').first();
}
goHere.append(de);
de.setData();
// check for zone overflow, if true head the widget will head for the next column and check for collision again
var zo = goHere.zoneOverflow();
goHere = zo.obj;
// reset position parameters
x1 = goHere.offset().left;
y1 = goHere.offset().top;
b1 = y1 + goHere.height();
r1 = x1 + goHere.width();
// reset condition, otherwise the position variables will still be attached to "el" rather than "goHere"
con = (r1 > x2 && x1 < x2 && y1 < y2 && b1 > y2) || (r1 > x2 && x1 < x2 && y1 == y2 && b1 == b2) || (r1 > x2 && x1 < x2 && y1 <= y2 && b1 >= b2) || (r1 > x2 && x1 < x2 && y1 >= y2 && b1 <= b2) || (r1 > x2 && x1 < x2 && y1 < b2 && b1 > b2) || (x1 < r2 && r1 > r2 && y1 < y2 && b1 > y2) || (x1 >= x2 && r1 <= r2 && y1 < y2 && b1 > y2);
if (!con) {
break;
}
}
res = {obj: goHere};
}
return res;
}
function moreHeight(el) { // when a widget is appended but it overflows the zone height, append more rows until the height + the data-fg-row == the last rows 'data-fg-row'
var zoneInner = el.closest('.flexgrid-grid');
var widget = el.find('.fg-widget');
var row_and_height = parseInt(el.attr('data-fg-row')) + parseInt(widget.attr('data-fg-height'));
var last_row_num = parseInt(zoneInner.find('.fg-col').last().attr('data-fg-row'));
if (row_and_height != last_row_num) {
var amountNeeded = (row_and_height - last_row_num) - 1;
for (n = 0; n < amountNeeded; n++) {
zoneInner.createRow();
}
}
widget.setData();
widget.modCols('disable');
}
$.fn.animateWidget = function() {
var widget = $(this).find('.fg-widget-inner');
widget.queue('fx', function(next) {
$(this).addClass('animate');
next();
});
widget.delay(400).queue('fx', function(next) {
$(this).removeClass('animate');
next();
});
}
$.fn.clearGrid = function() {
var zoneInner = $(this);
var widgets = zoneInner.find('.fg-widget');
for (var i = 0; i < widgets.length; i++) {
var widget = widgets.eq(i);
var originalContainer = widget.data('originalContainer') != undefined || widget.data('originalContainer') != null ? widget.data('originalContainer') : widget.closest('.fg-col');
if (!originalContainer.hasClass('fg-col')) { // if the widget came from a different container
var de = widget.detach();
originalContainer.append(de);
de.css({'width':widget.data('ogWidth'), 'height':widget.data('ogHeight'), 'min-width':'', 'min-height':'', 'max-width':'', 'max-height':''});
} else { // if the widget was appended by `.addWidget()`
widget.remove();
}
}
zoneInner.html('');
zoneInner.buildGrid();
}
$.fn.getOption = function(option) {
var res = option === undefined || option === null ? options : options[option];
return res;
}
$.fn.toggleGridlines = function() {
var zoneInner = $(this);
options.showGridlines = !options.showGridlines;
if (!options.showGridlines) {
$('.fg-gridlines').removeClass('fg-gridlines');
} else {
zoneInner.find('.fg-col').addClass('fg-gridlines');
}
}
$.fn.saveGrid = function() {
var zoneInner = $(this);
var array = [
{
cols: options.cols,
rows: options.rows,
widgets: []
}
];
var widgets = zoneInner.find('.fg-widget');
$(widgets).each(function() {
var widget = $(this);
array[0]['widgets'].push({
data_x: widget.attr('data-fg-x'),
data_y: widget.attr('data-fg-y'),
data_width: widget.attr('data-fg-width'),
data_height: widget.attr('data-fg-height'),
data_minWidth: widget.attr('data-fg-minwidth'),
data_minHeight: widget.attr('data-fg-minheight'),
data_maxWidth: widget.attr('data-fg-maxwidth'),
data_maxHeight: widget.attr('data-fg-maxheight'),
innerHtml: widget.find('.fg-widget-inner').html()
});
});
return array;
}
// end
}
var zone = $('.flexgrid-container');
var zoneInner = zone.find('.flexgrid-grid');
zoneInner.setFlexGrid({
cols: 6,
rows: 6,
defaultHeight: 2,
defaultWidth: 2,
minWidth: 1,
minHeight: 1,
});
// console.log(zoneInner.getOption());
zoneInner.buildGrid();
$(document).on('click', '.add-row', function() {
zoneInner.addRow();
});
$(document).on('click', '.remove-row', function() {
zoneInner.removeRow();
});
$(document).on('click', '.fg-add-widget', function() {
// widget.find('.fg-widget-inner').css('background', options.background != null ? options.background[Math.floor(Math.random() * options.background.length)] : '');
var widget = $('<div class="fg-widget"><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" style="background: #406fff !important;"></div></div>');
zoneInner.addWidget({widget:widget});
});
var widget = $('<div class="fg-widget"><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" style="background: #406fff !important;"></div></div>');
// for(var i = 0; i < 100; i++) {
// zoneInner.addWidget({widget:widget});
// }
$(document).on('click', '.fg-remove-widget', function() {
var widget = $(this).closest('.fg-widget');
zoneInner.removeWidget(widget);
});
$(document).on('click', '.togglegridlines', function() {
zoneInner.toggleGridlines();
});
$(document).on('click', '.clear-flexgrid', function() {
zoneInner.clearGrid();
});
$(document).on('click', '.save-flexgrid', function() {
var grid = zoneInner.saveGrid();
var widgets = grid[0]['widgets'];
console.log(widgets);
console.log(grid);
});
// add an array of widgets
var widgets = [
{
width: 2,
height: 2,
minHeight: 2,
minWidth: 2,
maxWidth: 2,
maxHeight: 2,
x: 0,
y: 0
},
{
width: 3,
height: 3,
x: 0,
y: 0
},
{
width: 1,
height: 2,
x: 2,
y: 0
}
];
for (var i = 0; i < widgets.length; i++) {
var x = widgets[i].x;
var y = widgets[i].y;
var width = widgets[i].width;
var minWidth = widgets[i].minWidth;
var maxWidth = widgets[i].maxWidth;
var height = widgets[i].height;
var minHeight = widgets[i].minHeight;
var maxHeight = widgets[i].maxHeight;
// var inner = widgets[i].inner ? widgets[i].inner : '';
var inner = '<p class="inner-icon">'+width+'x'+height+'</p>';
var widget = $('<div class="fg-widget custom-blue-widget"><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" style="background: #406fff !important;">'+inner+'</div></div>');
zoneInner.addWidget({
widget: widget,
x:x, y:y,
width:width, height:height,
minWidth:minWidth, minHeight: minHeight,
maxWidth:maxWidth, maxHeight: maxHeight
});
}
$(document).on('resizestop', '.custom-blue-widget', function() {
var text = $(this).find('.inner-icon');
var width = $(this).attr('data-fg-width');
var height = $(this).attr('data-fg-height');
text.text(width+'x'+height);
});
$('.widget-holder').sortable({
connectWith: '.fg-enabled-col', // connect to the grid columns
items: '.fg-widget', // make sure the only sortable items are the widgets
handle: '.fg-widget-handle', // include sortable handle
helper: 'clone',
appendTo: 'body'
});
// nested stuff - not done
$('.nested-holder').sortable({
connectWith: '.fg-nested-container',
items: '.nested-widget',
placeholder: 'nested-placeholder',
handle: '.nested-widget-inner',
zIndex: 9999,
start: function(event, ui) {
if ( ui.item.hasClass('cloner') ) {
clone = ui.item.clone();
return clone;
}
},
stop: function(event, ui) {
if ( ui.item.parent().hasClass('nested-holder') ) {
ui.item.css({'width':'100px', 'height':'100px', 'min-width':'100px', 'min-height':'100px', 'position':'relative !important'});
}
if (ui.item.hasClass('cloner') && ! ui.item.parent('.nested-holder').length ) {
$(this).append(clone);
}
}
});
$('.fg-nested-container').sortable({
items: '.nested-widget',
handle: '.nested-widget-inner',
placeholder: 'nested-placeholder',
connectWith: '.fg-nested-container'
});
});
// stuff I'm keeping for reference just in case
// $.fn.minWidth = function(minWidth) { // get the min-width value of something
// var item = $(this);
// var minWidth = parseInt(item.css('min-width'));
// return minWidth;
// }
// $.fn.minHeight = function(minHeight) { // get the min-height value of something
// var item = $(this);
// var minHeight = parseInt(item.css('min-height'));
// return minHeight;
// }
// $.fn.maxWidth = function(maxWidth) { // get the min-width value of something
// var item = $(this);
// var maxWidth = parseInt(item.css('max-width'));
// return maxWidth;
// }
// $.fn.maxHeight = function(maxHeight) { // get the min-height value of something
// var item = $(this);
// var maxHeight = parseInt(item.css('max-height'));
// return maxHeight;
// }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui-touch-punch/0.2.3/jquery.ui.touch-punch.min.js"></script>
@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 {
background: #fff;
color: #222;
font-weight: bold;
border: 2px solid #222;
border-radius: 5px;
}
/* .absolute::after, .fake-col::after, .absolute::before, .fake-col::before {
content: '';
height: 1px;
background: rgba(0,0,0,0.2);
position: absolute;
top: 50%;
left: 0;
right: 0;
}
.zone-inner[data-cols="4"] .absolute::before, .zone-inner[data-cols="4"] .fake-col::before {
top: 33%;
}
.zone-inner[data-cols="4"] .absolute::after, .zone-inner[data-cols="4"] .fake-col::after {
top: unset;
bottom: 33%;
}
.zone-inner[data-cols="3"] .absolute::before, .zone-inner[data-cols="3"] .fake-col::before {
top: 25%;
}
.zone-inner[data-cols="3"] .absolute::after, .zone-inner[data-cols="3"] .fake-col::after {
top: unset;
bottom: 25%;
}
.zone-inner[data-cols="3"] .absolute > span, .zone-inner[data-cols="3"] .fake-col > span {
position: absolute !important;
top: 50%;
left: 0;
right: 0;
height: 1px;
background: rgba(0,0,0,0.1);
} */
.fg-col {
position: absolute !important;
}
.fg-col.fg-gridlines {
outline: 1px solid rgba(0,0,0,0.1);
}
.relative {
position: relative !important;
}
.flexgrid-container, .zone-out {
position: relative;
display: inline-block;
float: left;
border-radius: 5px;
}
.flexgrid-helper {
display: block;
position: sticky;
top: 0px;
height: 40px;
padding: 5px;
background: transparent;
z-index: 100000;
}
.clear-flexgrid, .fg-add-widget, .save-flexgrid {
float: right;
margin: 5px;
transition: 0.3s;
}
.btn:active {
transform-origin: 50% 50%;
transform: scale(0.9);
}
.flexgrid-grid, .zone-out-inner {
position: absolute;
top: 45px;
right: 5px;
bottom: 5px;
left: 5px;
height: 100%;
}
.fg-enabled-col {
height: auto;
width: auto;
}
.flexgrid-container .fg-widget {
position: relative;
float: left;
}
.fg-widget-inner {
position: absolute;
background: var(--widget-bg);
z-index: 8;
border-radius: 5px;
}
.ui-helper {
position: absolute;
background: var(--widget-bg);
z-index: 8;
box-shadow: 2px 2px 5px rgba(0,0,0,0.5);
border-radius: 5px;
}
.zone-txt {
font-family: 'Baloo Da', cursive;
color: var(--wig-color);
font-size: 18px;
position: absolute;
left: 5px;
}
#holder .fg-widget-inner {
top: 5px;
right: 5px;
bottom: 5px;
left: 5px;
}
.fg-widget-inner {
top: 5px;
right: 5px;
bottom: 5px;
left: 5px;
box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
}
.fg-widget {
border-radius: 5px;
}
.flexgrid-grid .fg-widget:hover .ui-resizable-se {
opacity: 1;
}
.flexgrid-grid .fg-widget:hover .fg-remove-widget {
display: block;
}
.fg-widget-placeholder {
border: 1px dashed rgba(0,0,0,0.5) !important;
border-radius: 5px;
background: rgba(0,0,0,0.3);
top: 5px;
left: 5px;
bottom: 5px;
z-index: 3;
position: absolute;
}
.danger-placeholder {
background: rgba(214, 68, 58, 0.5) !important;
border: 1px dashed #54100b !important;
}
.hold-col .fg-widget-placeholder {
opacity: 0;
}
.ui-resizable-n, .ui-resizable-e, .ui-resizable-s, .ui-resizable-w, .ui-resizable-ne, .ui-resizable-se, .ui-resizable-sw, .ui-resizable-nw {
height: 20px;
width: 20px;
position: absolute;
/* opacity: 0; */
}
.ui-resizable-ne::after, .ui-resizable-se::after, .ui-resizable-sw::after, .ui-resizable-nw::after {
display: block;
color: var(--wig-color);
content: '\f054';
font-size: 15px;
font-family: "Font Awesome 5 Free";
font-weight: 600;
position: absolute;
z-index: 9;
}
.ui-resizable-n::after, .ui-resizable-e::after, .ui-resizable-s::after, .ui-resizable-w::after, .ui-resizable-n::before, .ui-resizable-e::before, .ui-resizable-s::before, .ui-resizable-w::before {
content: '';
position: absolute;
z-index: 9;
display: block;
background: var(--wig-color);
border-radius: 4px;
pointer-events: none;
}
.ui-resizable-n::after, .ui-resizable-s::after, .ui-resizable-n::before, .ui-resizable-s::before {
height: 2px;
width: 15px;
}
.ui-resizable-n, .ui-resizable-s {
left: calc(50% + 3px);
transform: translate(-50%, 0);
cursor: n-resize;
}
.ui-resizable-n { top: 5px; }
.ui-resizable-n::before { top: 5px; }
.ui-resizable-n::after { top: 9px; }
.ui-resizable-s { bottom: 5px; }
.ui-resizable-s::before { bottom: 9px; }
.ui-resizable-s::after { bottom: 5px; }
.ui-resizable-e::after, .ui-resizable-w::after, .ui-resizable-e::before, .ui-resizable-w::before {
width: 2px;
height: 15px;
}
.ui-resizable-e, .ui-resizable-w {
top: calc(50% + 3px);
transform: translate(0, -50%);
cursor: e-resize;
}
.ui-resizable-e { right: 5px; }
.ui-resizable-e::before { right: 6px; }
.ui-resizable-e::after { right: 10px; }
.ui-resizable-w { left: 5px; }
.ui-resizable-w::before { left: 6px; }
.ui-resizable-w::after { left: 10px; }
.ui-resizable-se {
bottom: 5px;
right: 5px;
cursor: se-resize;
}
.ui-resizable-se::after {
transform: rotate(45deg);
left: 6px;
top: 1px;
}
.ui-resizable-ne {
top: 5px;
right: 5px;
cursor: ne-resize;
}
.ui-resizable-ne::after {
transform: rotate(-45deg);
right: 3px;
top: -1px;
}
.ui-resizable-sw {
bottom: 5px;
left: 5px;
cursor: sw-resize;
}
.ui-resizable-sw::after {
transform: rotate(135deg);
left: 3px;
top: 0px;
}
.ui-resizable-nw {
top: 5px;
left: 5px;
cursor: nw-resize;
}
.ui-resizable-nw::after {
transform: rotate(-135deg);
left: 4px;
top: -2px;
}
.fg-remove-widget {
color: var(--wig-color);
position: absolute;
top: 10px;
right: 11px;
font-size: 15px;
cursor: pointer;
display: none;
z-index: 9;
}
.ui-resizable-resizing {
border: 1px dashed var(--widget-bg) !important;
/* background: var(--placeholder); */
/* border-radius: 5px; */
}
.grab {
cursor: grab !important;
}
.grabbing {
cursor: grabbing !important;
}
.dragging {
z-index: 9999;
}
.not-dragging {
background: var(--no-drag);
}
.fg-disabled-col {
/* background: red !important; */
}
@media screen and (max-width: 700px) {
.fg-widget .fg-remove-widget {
display: block;
}
.fg-widget .ui-resizable-se {
opacity: 1;
}
}
.animate {
animation-name: pop;
animation-iteration-count: 1;
animation-duration: 0.3s;
animation-timing-function: ease-in-out;
z-index: 9999 !important;
}
@keyframes pop {
0% {
top: 5px;
right: 5px;
bottom: 5px;
left: 5px;
}
50% {
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
}
100% {
top: 5px;
right: 5px;
bottom: 5px;
left: 5px;
}
}
/* width */
.widget-holder::-webkit-scrollbar {
width: 8px;
}
/* Track */
.widget-holder::-webkit-scrollbar-track {
background: transparent;
}
/* Handle */
.widget-holder::-webkit-scrollbar-thumb {
background: var(--zone-bord);
border-radius: 4px;
}
/* Handle on hover */
.widget-holder::-webkit-scrollbar-thumb:hover {
background: #111;
}
.something-changed {
color: #b81825;
font-weight: bold;
display: none;
}
.grid-settings {
position: sticky;
top: 0;
}
/* toggle gridlines */
.view-gridlines-div {
width: fit-content;
display: inline-block;
}
.view-gridlines-label {
padding-right: 10px;
color: white;
}
.toggle {
display: inline-block;
width: fit-content;
padding-left: 10px;
border-radius: 50px;
transition: 0.3s;
cursor: pointer;
background: #fff;
border: 2px solid #111;
position: relative;
}
.toggle input[type="checkbox"] {
opacity: 0;
z-index: 1;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.toggle label {
min-height: 20px;
margin-bottom: 0;
font-weight: normal;
cursor: pointer;
width: 40px;
height: 20px;
color: #91a6ff;
}
.toggle label::after {
content: "";
display: inline-block;
position: absolute;
width: 20px;
height: 20px;
left: 0px;
margin-left: 5px;
margin-top: 3px;
border: 2px solid #222;
border-radius: 50%;
background-color: #fff;
transition: 0.3s;
}
.toggle input[type="checkbox"]:checked+label::after {
left: 20px;
background-color: #222;
}
.display-inline {
display: inline-block;
}
/* nested */
.nested-holder-label {
position: absolute;
color: white;
font-weight: bold;
top: 235px;
right: 10px;
width: 208px;
}
.nested-holder {
height: 200px;
width: 208px;
background: #333;
position: absolute;
top: 260px;
right: 10px;
border-radius: 5px;
overflow: hidden;
border: 1px solid #000;
}
.fg-nested-container {
width: 100%;
height: calc(100% - 10px);
top: 10px;
position: absolute;
}
.nested-holder .nested-widget {
position: relative !important;
float: left;
height: 100px !important;
width: 100px !important;
}
.nested-widget {
/* position: relative !important; */
/* float: left !important; */
width: 100%;
}
.fg-nested-container .nested-widget:not(:first-child) {
margin-top: 10px !important;
}
.nested-holder .nested-widget-inner {
top: 5px;
right: 5px;
bottom: 5px;
left: 5px;
box-shadow: 1px 1px 3px rgba(0,0,0,0.5);
}
.fg-nested-container .nested-widget-inner {
top: 15px;
right: 20px;
bottom: 15px;
left: 20px;
}
.nested-widget-inner {
position: absolute;
background: #51D682;
border-radius: 5px;
z-index: 9000 !important;
}
.remove-nested-widget {
position: absolute;
right: 5px;
top: 0px;
font-size: 13px;
color: white;
cursor: pointer;
}
.fg-nested-container .remove-nested-widget {
display: none;
}
.fg-nested-container .nested-widget-inner:hover .remove-nested-widget {
display: block;
}
.nested-holder .remove-nested-widget {
display: none;
}
.nested-widget-txt {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
color: #222;
font-weight: bold;
}
.nested-placeholder {
margin-left: 20px;
width: calc(100% - 40px);
border: 1px dashed #111;
background: rgba(0,0,0,0.2);
border-radius: 5px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.8.2/css/all.min.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment