gridstack-8.x-2.5/js/admin/gridstack.admin.backbone.crud.js
js/admin/gridstack.admin.backbone.crud.js
/**
* @file
* Provides GridStack Backbone.Crud.
*/
(function ($, Drupal, Backbone, _, _db) {
'use strict';
Drupal.gridstack = Drupal.gridstack || {};
_.each(['base', 'models', 'views'], function (key) {
Drupal.gridstack[key] = Drupal.gridstack[key] || {};
});
/**
* The GridStack nested methods.
*/
Drupal.gridstack.nested = {
onNestedRemoveMultiple: function (e, box, $box) {
var me = this;
var delta = $box.index();
$box = $box.length ? $box : $($box);
box = me.isValidModel(box) ? box : me.collection.at(delta);
// Content box is not a model, no need to remove box model.
me.removeWidget($box, 'nested');
// Manually trigger change since nested are not backbone models.
me.collection.trigger('change', box);
},
onNestedAddMultiple: function (e, box, $box) {
var me = this;
var delta = $box.index();
box = me.isValidModel(box) ? box : me.collection.at(delta);
me.isRendered = true;
if (me.isValidModel(box)) {
var el = $box.find('.gridstack:first');
if (el.length) {
me.addNestedWidget(box, el, true);
}
// Manually trigger change since nested are not backbone models.
me.collection.trigger('change', box);
}
},
// See ::addWidget(node, el, isDefault, gridStack)
addNestedWidget: function (box, el, isDefault, data) {
var me = this;
var index = box.get('index');
var gridStack = GridStack.init(me.nestedOptions, el);
var len = gridStack.engine.nodes.length || 0;
var addNestedItem = function (node, i) {
node = node || box.defaults;
var indexNested = isDefault ? len + 1 : i + 1;
// Nested box is not a backbone model, only has similar attributes.
node = me.getFakeModel(node, index, indexNested);
var $box = me.addWidget(node, el, isDefault, gridStack);
me.updateBoxNested($box, node);
};
if (isDefault) {
addNestedItem();
}
else if (data.length) {
gridStack.batchUpdate();
_.each(data, function (node, i) {
addNestedItem(node, i);
}, this);
gridStack.commit();
}
me.collection.trigger('change', box);
// Nested boxes are not backbone models, hook into gridstack events.
// @todo no need gridStack.on('added', _.bind(me.onNestedAdd, me));
return gridStack;
},
addNestedWidgetMultiple: function (box, i) {
var me = this;
var data = _.isUndefined(me.nestedNodes[i]) ? [] : me.nestedNodes[i];
var id = box.get('id');
var el = $('> .box[data-gs-bid="' + id + '"] .gridstack:first', me.$el);
return el.length ? me.addNestedWidget(box, el, false, data) : null;
},
updateBoxNestedMultiple: function (box, $box) {
var me = this;
$box = $box.length ? $box : $($box);
var delta = $box.index();
box = me.isValidModel(box) ? box : me.collection.at(delta);
if (!me.isValidModel(box)) {
return;
}
var index = box.get('index');
var nested = [];
var $nested = $('.gridstack:first', $box);
var $boxes = $('> .box:not(.gridstack__box--placeholder)', $nested);
// Might be empty, even if not empty here due to sequence, or delay.
if ($boxes.length) {
_.each($boxes, function (el, i) {
var $el = $(el);
var node = $el.data('_gridstack_node');
if (me.isValid(node)) {
var data = me.getFakeModel(node, index, (i + 1));
nested.push(data);
me.updateBoxNested($el, data);
}
});
}
box.set('nested', nested, {silent: false});
},
updateBoxNested: function ($box, node) {
$box = $box.length ? $box : $($box);
var indexNested = node.indexNested || ($box.index() + 1);
$box.attr('data-gs-bid', node.id);
$box.attr('data-gs-index', indexNested);
$box.addClass('is-box-nested');
$box.find('.btn').attr('data-gs-bid', node.id);
if (!_.isUndefined(node.mid)) {
$box.find('.btn').attr('data-gs-mid', node.mid);
}
}
};
/**
* The GridStack crud methods.
*/
Drupal.gridstack.crud = _.extend(Drupal.gridstack.nested, {
events: {
resizestop: 'onResizeStop',
drag: 'onDrag'
},
onChange: function () {
this.save();
},
onSave: function () {
this.options.updateIcon = true;
this.save();
},
onDragResize: function (e) {
var me = this;
var $box = $(e.target);
var box = me.getModel($box);
// This is GridStack library selector.
if (me.$el[0] === e.delegateTarget) {
// Only a nested box needs manual update.
// @todo move it to view along with the model if you can.
if ($box.hasClass('is-box-nested')) {
me.updateBoxDimensions($box);
}
else {
var node = $box.data('_gridstack_node');
if (me.isValidModel(box) && me.isValidNode(node)) {
box.set('width', node.width, {silent: false});
box.set('height', node.height, {silent: false});
}
}
}
// Manually trigger change since this event is not backbone's.
me.collection.trigger('change', box);
},
onDrag: function (e) {
_db.throttle(this.onDragResize(e), 200, this);
},
onResizeStop: function (e) {
_db.throttle(this.onDragResize(e), 200, this);
},
onAdd: function (box) {
this.addWidget(box, null, true);
},
widgetOptions: function (node, extra) {
// v0.5.2, https://github.com/gridstack/gridstack.js/issues/907
return _.extend({
x: node.x,
y: node.y,
width: node.width,
height: node.height,
// Without autoPosition, new widgets are prepended, not appended.
autoPosition: true,
// Dont! The box can not get smaller than 12 columns.
// minWidth: 1
// This can get absurd to 225+, add a max.
// minHeight: 1,
maxHeight: 12
}, extra || {});
},
widgetHtml: function (box, el, type) {
var me = this;
if (type === 'root') {
var view = me.getCurrentView(box);
if (me.isValid(view)) {
return view.render().el.outerHTML;
}
}
// Disable infinite nests.
return Drupal.theme('gridStackBox', {
isNested: me.isNested && !el.hasClass('gridstack--nested')
});
},
addWidget: function (box, el, isDefault, gridStack) {
var me = this;
var node = box;
var extra = {};
var widget;
var isFakeModel = false;
el = el || me.$el;
isDefault = isDefault || false;
gridStack = gridStack || me.getGridStack(el);
if (!me.isValid(gridStack)) {
return;
}
// Unlike nested, the root boxes are valid backbone models.
if (me.isValidModel(box)) {
// @todo, not functional, yet. For future nested models.
box.set('gid', me.$el.attr('id'), {silent: true});
node = box.attributes;
if (me.isNested) {
extra.minWidth = 12;
}
if (me.column < 12 && me.column > 4) {
extra.maxWidth = me.column;
}
widget = me.widgetHtml(box, el, 'root');
}
else {
isFakeModel = true;
widget = me.widgetHtml(box, el, 'nested');
}
if (isDefault) {
node.height = 2;
node.width = 2;
}
if (!gridStack.willItFit(node.x, node.y, node.width, node.height, true)) {
return;
}
widget = gridStack.addWidget(widget, me.widgetOptions(node, extra));
if (!_.isUndefined(el) && _.isUndefined(widget.context)) {
widget.context = el.length ? el[0] : el;
}
// The ::addWidget returns non-jquery object without length.
var $box = $(widget);
node = $box.data('_gridstack_node') || node;
if (isFakeModel) {
// Cannot move it to model, _yet, since this is rendered later.
me.updateBoxDimensions($box);
}
// Disable movable, as too complex to handle with breakpoints for now.
if (me.isNested && $box.parent('.gridstack--root').length) {
gridStack.movable($box[0], false);
}
// Must trigger manually to apply attributes on added widgets.
me.collection.trigger('change', box);
return $box;
},
onRemove: function (box) {
var me = this;
var view = me.getCurrentView(box);
if (me.isValid(view)) {
view.remove();
view.stopListening(box);
}
},
shouldRemove: function (box) {
return box.get('deleted') === true;
},
removeWidget: function ($box, context) {
var me = this;
$box = $box.length ? $box : $($box);
context = context === 'undefined' ? 'root' : context;
if (!$box.length) {
return;
}
var box = me.getModel($box);
var el = $box.closest('.gridstack');
if (me.isValidModel(box) && context === 'root') {
// Do not remove directly, instead set properties, and collect it later
// to avoid potential memory leaks.
box.set('deleted', true);
}
// @todo move it model if you can.
var gridStack = me.getGridStack(el);
if (me.isValid(gridStack)) {
gridStack.removeWidget($box, true);
}
},
onRootRemoveMultiple: function (e, box, $box) {
var me = this;
$box = $box.length ? $box : $($box);
// Destroy the box including all nested instances.
me.removeWidget($box, 'root');
},
onChangeImageStyle: function (e) {
var me = this;
var $el = $(e.currentTarget);
var $box = $el.closest('.box');
var i = $box.index();
var box = me.collection.at(i);
var stored = me.getStoredImageStyle(i);
var v = $el.val() || stored || $box.data('imageStyle');
$el.val(v).attr('data-imageid', v);
$el.find('option:selected').prop('selected', true).siblings('option').prop('selected', false);
// Pass it to Backbone model.
if (me.isValidModel(box)) {
box.set('image_style', v);
}
if (me.isRendered) {
me.collection.trigger('change', box);
}
},
onClickImageStyle: function (e) {
var $el = $(e.currentTarget);
var $box = $el.closest('.box');
var v = $box.data('imageStyle');
if ($el.children().length < 2) {
$el.html(Drupal.gridstack.base.imageStyleOptions());
if (v !== '') {
$el.val(v).attr('data-imageid', v);
}
}
},
updateBoxDimensions: function ($box, node) {
Drupal.gridstack.base.updateBoxDimensions($box, node);
},
updateBoxModel: function (box, i, reIndex) {
var me = this;
var index = (i + 1);
box = me.isValidModel(box) ? box : me.collection.at(i);
reIndex = reIndex || false;
if (!me.isValidModel(box)) {
return;
}
var id = box.get('id');
var $box = me.getBoxById(id);
var node = $box.data('_gridstack_node');
var data = me.getParsedNode(node);
if (reIndex) {
data.index = index;
}
if (!_.isEmpty(data)) {
_.each(data, function (value, key) {
box.set(key, value, {silent: false});
});
}
if (reIndex) {
$box.attr('data-gs-index', index);
}
me.updateBoxDimensions($box, data);
// Update nested boxes if required.
if (me.isNested) {
me.updateBoxNestedMultiple(box, $box);
}
me.collection.trigger('change', box);
},
updateBoxModelMultiple: function () {
var me = this;
var remove = [];
var reIndex = false;
me.collection.each(function (box) {
if (me.shouldRemove(box)) {
remove.push(box);
}
}, me);
if (remove.length) {
reIndex = true;
me.collection.remove(remove);
}
me.collection.each(function (box, i) {
me.updateBoxModel(box, i, reIndex);
}, me);
Drupal.gridstack.base.lastBoxIndex = me.collection.length;
},
save: function () {
var me = this;
me.updateBoxModelMultiple();
if (!_.isEmpty(me.saveCallback) && me.isRendered) {
var o = me.options || {};
me.saveCallback.callback(me.collection, o);
}
me.options.updateIcon = false;
}
});
})(jQuery, Drupal, Backbone, _, dBlazy);
