Commit 2c861ec1 authored by Cecilia Vela Gurovic's avatar Cecilia Vela Gurovic Committed by Robert Lyon

Bug 1813987: Creating a new page with gristack layout

add a new block, place it anywhere in the grid, edit configuration
and delete it.

It will save it in the db on table block_instance_dimension
but not display it in view or edit mode yet

Also included a small fix in xmldb postgres class generator
to handle reserved words in getAlterFieldSQL function

Failing tests:

- most of them are failing when adding a new block to the page
because they expect to have a modal to choose the position
where to place block. That modal was removed for this patch but
there will be a similar one in patch:
 https://reviews.mahara.org/#/c/9952
and those tests will be fixed there

- a few tests failed when they couldn't find text inside the
blocks because they are not expanded to fit the content yet.
This is added in patch:
 https://reviews.mahara.org/#/c/9986
and they will be fixed there

behatnotneeded

Change-Id: If4521a6315f6e8cc5d88693f536946dace359288
parent 2a9211b3
......@@ -844,6 +844,10 @@ class BlockInstance {
private $temp = array();
private $tags = array();
private $inedit = false;
private $positionx;
private $positiony;
private $width;
private $height;
public function __construct($id=0, $data=null) {
if (!empty($id)) {
......@@ -869,6 +873,30 @@ class BlockInstance {
$this->{$field} = $value;
}
}
$dimensiontable_exists = true;
if (defined('INSTALLER')) {
// Check to see if the table exists yet
require_once('ddl.php');
$dimensiontable_exists = table_exists(new XMLDBTable('block_instance_dimension'));
}
if ($dimensiontable_exists) {
$dimension = get_records_array('block_instance_dimension', 'block', $id);
if (is_array($dimension) && isset($dimension[0])) {
$this->positionx = $dimension[0]->positionx;
$this->positiony = $dimension[0]->positiony;
$this->width = $dimension[0]->width;
$this->height = $dimension[0]->height;
}
}
else {
$this->positionx = 0;
$this->positiony = 0;
$this->width = 4;
$this->height = 3;
}
$this->artefactplugin = blocktype_artefactplugin($this->blocktype);
}
......@@ -1200,6 +1228,12 @@ class BlockInstance {
$smarty->assign('row', $this->get('row'));
$smarty->assign('column', $this->get('column'));
$smarty->assign('order', $this->get('order'));
$smarty->assign('positionx', $this->get('positionx'));
$smarty->assign('positiony', $this->get('positiony'));
$smarty->assign('width', $this->get('width'));
$smarty->assign('height', $this->get('height'));
$smarty->assign('blocktype', $this->get('blocktype'));
$smarty->assign('configurable', call_static_method($blocktypeclass, 'has_instance_config'));
$smarty->assign('configure', $configure); // Used by the javascript to rewrite the block, wider.
......@@ -1725,6 +1759,7 @@ class BlockInstance {
call_static_method($classname, 'delete_instance', $this);
}
delete_records('view_artefact', 'block', $this->id);
delete_records('block_instance_dimension', 'block', $this->id);
delete_records('block_instance', 'id', $this->id);
delete_records('tag', 'resourcetype', 'blocktype', 'resourceid', $this->id);
db_commit();
......@@ -2084,6 +2119,23 @@ class BlockInstance {
public static function group_tabs($groupid, $role) {
return array();
}
public function set_block_dimensions($positionx, $positiony, $width, $height) {
$obj = new StdClass();
$obj->block = $this->id;
$obj->positionx = $positionx;
$obj->positiony = $positiony;
$obj->height = $height;
$obj->width = $width;
//TODO: move this inside of the commit
ensure_record_exists('block_instance_dimension', (object) array('block' => $this->id), $obj);
$this->set('positionx', $positionx);
$this->set('positiony', $positiony);
$this->set('height', $height);
$this->set('width', $width);
}
}
function require_blocktype_plugins() {
......
......@@ -290,6 +290,31 @@
location.reload();
});
var serializeWidgetMap = function(items) {
// conseguir el id del bloque
// json call to update new position and/or dimension
var i;
if (typeof(items) != 'undefined') {
for (i=0; i<items.length; i++) {
if (typeof(items[i].id) != 'undefined') {
var blockid = items[i].id,
destination = {
'newx': items[i].x,
'newy': items[i].y,
'newheight': items[i].height,
'newwidth': items[i].width,
}
moveBlock(blockid, destination);
}
}
}
};
$('.grid-stack').on('change', function(event, items) {
serializeWidgetMap(items);
})
// images need time to load before height can be properly calculated
window.setTimeout(function(){
$(window).trigger('colresize');
......@@ -352,8 +377,6 @@
$(category).html(data.data);
makeNewBlocksDraggable();
// the column has changed size, pass on to listeners
$(window).trigger('colresize');
});
return false;
}
......@@ -422,7 +445,6 @@
$('.blocktype-drag').draggable({
start: function(event, ui) {
$(window).trigger('colresize');
},
helper: function(event) {
var original = $(this),
......@@ -448,37 +470,14 @@
if (isHit(e) && !$('#addblock').hasClass('in')) {
e.stopPropagation();
e.preventDefault();
startAddBlock($(this));
if (!addblockstarted) {
addblockstarted = true;
addNewBlock($(this).find('.blocktype-radio').val());
}
}
});
}
var addblockstarted = false; // To stop the double clicking of add block button causing multiple saving problem
function startAddBlock(element) {
var addblockdialog = $('#addblock');
addblockdialog.modal('show');
if (!addblockstarted) {
addblockstarted = true;
addblockdialog.one('dialog.end', function(event, options) {
if (options.saved) {
addNewBlock(options, element.find('.blocktype-radio').val());
}
else {
element.trigger("focus");
}
});
addblockdialog.find('h4.modal-title').text(get_string('addnewblock', 'view'));
computeColumnInputs(addblockdialog);
addblockdialog.find('.block-inner').removeClass('d-none');
addblockdialog.find('.cell-chooser input:first').prop('checked', true);
addblockdialog.find('.cell-chooser input:first').parent().addClass('focused active');
addblockdialog.find('.deletebutton').trigger("focus");
keytabbinginadialog(addblockdialog, addblockdialog.find('.deletebutton'), addblockdialog.find('.cancel'));
}
}
function cellChanged() {
$(this).closest('.js-cell-chooser').find('.active').removeClass('active');
......@@ -497,9 +496,8 @@
selectbox.html('<option>' + options.join('</option><option>') + '</option>');
}
function addNewBlock(whereTo, blocktype) {
addblockstarted = false;
var addblockstarted = false; // To stop the double clicking of add block button causing multiple saving problem
function addNewBlock(blocktype) {
var pd = {
'id': $('#viewid').val(),
'change': 1,
......@@ -509,23 +507,17 @@
if (config.blockeditormaxwidth) {
pd['cfheight'] = $(window).height() - 100;
}
pd['action_addblocktype_row_' + whereTo['row'] + '_column_' + whereTo['column'] + '_order_' + whereTo['order']] = true;
pd['action_addblocktype_positionx_0_positiony_0_width_3_height_3'] = true;// The default 3x3 block at position 0,0
sendjsonrequest(config['wwwroot'] + 'view/blocks.json.php', pd, 'POST', function(data) {
var div = $('<div>').html(data.data.display.html),
blockinstance = div.find('div.blockinstance'),
blockinstance = div.find('div.grid-stack-item'),
configureButton = blockinstance.find('.configurebutton');
addBlockCss(data.css);
// Make configure button clickable, but disabled as blocks are rendered in configure mode by default
if (configureButton) {
rewriteConfigureButton(configureButton);
$('#action-dummy').attr('name', 'action_addblocktype_row_' + whereTo['row'] + '_column_' + whereTo['column'] + '_order_' + whereTo['order']);
}
insertBlockStub(blockinstance, whereTo);
var grid = $('.grid-stack').data('gridstack');
addNewWidget(blockinstance, grid);
if (data.data.configure) {
showDock($('#configureblock'), true);
......@@ -535,10 +527,12 @@
rewriteDeleteButton(blockinstance.find('.deletebutton'));
blockinstance.find('.deletebutton').trigger("focus");
}
addblockstarted = false;
},
function() {
// On error callback we need to reset the Dock
hideDock();
addblockstarted = false;
});
}
......@@ -550,26 +544,6 @@
});
}
function insertBlockStub(newblock, whereTo) {
var columnContent = $('#row_'+whereTo['row']+'_column_'+whereTo['column']).find('div.column-content');
if (whereTo['order'] == 1) {
$(columnContent).prepend(newblock);
}
else {
var count = 1;
columnContent.children().each(function() {
count++;
if (count == whereTo['order']) {
$(this).after(newblock);
return false;
}
});
if (whereTo['order'] > count) {
columnContent.append(newblock);
}
}
}
/**
* Rewrites the blockinstance configure buttons to be AJAX
*/
......@@ -630,10 +604,14 @@
pd[self.attr('name')] = 1;
sendjsonrequest(config['wwwroot'] + 'view/blocks.json.php', pd, 'POST', function(data) {
if (blockinstanceId !== undefined && blockinstanceId !== null) {
$('#blockinstance_' + blockinstanceId).remove();
}
var grid = $('.grid-stack').data('gridstack');
grid.removeWidget($('#block_' + blockinstanceId));
if (!$('#configureblock').hasClass('d-none')) {
hideDock();
showMediaPlayers();
......@@ -722,20 +700,15 @@
}
function moveBlock(whereTo, instanceId) {
function moveBlock(id, whereTo) {
var pd = {
'id': $('#viewid').val(),
'change': 1
};
if (config.blockeditormaxwidth) {
pd['cfheight'] = $(window).height() - 100;
}
pd['action_moveblockinstance_id_' + instanceId + '_row_' + whereTo['row'] + '_column_' + whereTo['column'] + '_order_' + whereTo['order']] = true;
sendjsonrequest(config['wwwroot'] + 'view/blocks.json.php', pd, 'POST', function(data) {
if (data.data.html) {
$('#blockinstance_' + instanceId + ' .blockinstance-content').html(data.data.html);
}
});
pd['action_moveblockinstance_id_' + id + '_newx_' + whereTo['newx'] + '_newy_' + whereTo['newy'] + '_newheight_' + whereTo['newheight'] + '_newwidth_' + whereTo['newwidth']] = true;
sendjsonrequest(config['wwwroot'] + 'view/blocks.json.php', pd, 'POST');
}
/**
......@@ -753,7 +726,9 @@
sendjsonrequest(config['wwwroot'] + 'view/blocks.json.php', pd, 'POST', function(data) {
$('#blockinstance_' + blockinstanceId).remove();
var grid = $('.grid-stack').data('gridstack'),
item = $('#block_' + blockinstanceId);
grid.removeWidget(item);
if (!$('#configureblock').hasClass('d-none')) {
hideDock();
......@@ -1076,3 +1051,15 @@ function blockConfigError(form, data) {
function wire_blockoptions() {
return ViewManager.blockOptions();
}
/* GRIDSTACK functions */
function addNewWidget(blockContent, grid) {
var node = {
x: 0,
y: 0,
width: 3,
height: 3
};
grid.addWidget(blockContent, node.x, node.y, node.width, node.height);
return false;
}
......@@ -1392,21 +1392,38 @@ function xmldb_core_upgrade($oldversion=0) {
if ($oldversion < 2019080600) {
log_debug('create block dimension table for gridstack layout');
$table = new XMLDBTable('block_instance_dimension');
$table->addFieldInfo('block', XMLDB_TYPE_INTEGER, 10, false, XMLDB_NOTNULL);
$table->addFieldInfo('positionx', XMLDB_TYPE_INTEGER, 2, false, XMLDB_NOTNULL, null, null, null, 0);
$table->addFieldInfo('positiony', XMLDB_TYPE_INTEGER, 10, false, XMLDB_NOTNULL, null, null, null, 0);
$table->addFieldInfo('width', XMLDB_TYPE_INTEGER, 2, false, XMLDB_NOTNULL, null, null, null, 4);
$table->addFieldInfo('height', XMLDB_TYPE_INTEGER, 2, false, XMLDB_NOTNULL, null, null, null, 4);
$table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('block'));
$table->addKeyInfo('blockfk', XMLDB_KEY_FOREIGN, array('block'), 'block_instance', array('id'));
create_table($table);
if (!table_exists($table)) {
$table->addFieldInfo('block', XMLDB_TYPE_INTEGER, 10, false, XMLDB_NOTNULL);
$table->addFieldInfo('positionx', XMLDB_TYPE_INTEGER, 2, false, XMLDB_NOTNULL, null, null, null, 0);
$table->addFieldInfo('positiony', XMLDB_TYPE_INTEGER, 10, false, XMLDB_NOTNULL, null, null, null, 0);
$table->addFieldInfo('width', XMLDB_TYPE_INTEGER, 2, false, XMLDB_NOTNULL, null, null, null, 4);
$table->addFieldInfo('height', XMLDB_TYPE_INTEGER, 2, false, XMLDB_NOTNULL, null, null, null, 4);
$table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('block'));
$table->addKeyInfo('blockfk', XMLDB_KEY_FOREIGN, array('block'), 'block_instance', array('id'));
create_table($table);
}
log_debug('drop constraint from block_instance table in row, column and order');
$table = new XMLDBTable('block_instance');
$key = new XMLDBKey('viewrowcolumnorderuk');
$key->setAttributes(XMLDB_KEY_UNIQUE, array('view', 'row', 'column', 'order'));
drop_key($table, $key);
log_debug('remove NOT NULL modifier from row, column, order in block_instance table');
$table = new XMLDBTable('block_instance');
$field = new XMLDBField('row');
$field->setAttributes(XMLDB_TYPE_INTEGER, 2, null, null, null, null, null, null);
change_field_notnull($table, $field);
$field = new XMLDBField('column');
$field->setAttributes(XMLDB_TYPE_INTEGER, 2, null, null, null, null, null, null);
change_field_notnull($table, $field);
$field = new XMLDBField('order');
$field->setAttributes(XMLDB_TYPE_INTEGER, 2, null, null, null, null, null, null);
change_field_notnull($table, $field);
}
return $status;
......
......@@ -63,6 +63,7 @@ class View {
private $lockblocks = 0;
private $instructions;
private $instructionscollapsed=0;
private $newlayout = 1;
const UNSUBMITTED = 0;
const SUBMITTED = 1;
......@@ -307,21 +308,47 @@ class View {
}
$this->atime = time();
$this->rows = array();
$this->columns = array();
$this->oldcolumnsperrow = $this->get('columnsperrow');
// set only for existing views - _create provides default value
// Ignore if the constructor is called with deleted set to true
if (empty($this->deleted)) {
if ($this->columnsperrow === false || ($this->numrows > 0 && count($this->columnsperrow) != $this->numrows)) {
// if we are missing the info for some reason we will give the page it's layout back
// this can happen in MySQL when many users are copying the same page
if ($this->layout) {
if ($rowscols = get_records_sql_array("
SELECT vlrc.row, vlc.columns
FROM {view_layout_rows_columns} vlrc
JOIN {view_layout_columns} vlc ON vlc.id = vlrc.columns
WHERE viewlayout = ?", array($this->layout))) {
if ($this->newlayout) {
$this->grid = array();
}
else {
$this->rows = array();
$this->columns = array();
$this->oldcolumnsperrow = $this->get('columnsperrow');
// set only for existing views - _create provides default value
// Ignore if the constructor is called with deleted set to true
if (empty($this->deleted)) {
if ($this->columnsperrow === false || ($this->numrows > 0 && count($this->columnsperrow) != $this->numrows)) {
// if we are missing the info for some reason we will give the page it's layout back
// this can happen in MySQL when many users are copying the same page
if ($this->layout) {
if ($rowscols = get_records_sql_array("
SELECT vlrc.row, vlc.columns
FROM {view_layout_rows_columns} vlrc
JOIN {view_layout_columns} vlc ON vlc.id = vlrc.columns
WHERE viewlayout = ?", array($this->layout))) {
$default = array();
foreach ($rowscols as $row) {
if ($this->get('id')) {
$vrc = (object) array(
'view' => $this->get('id'),
'row' => $row->row,
'columns' => $row->columns
);
ensure_record_exists('view_rows_columns', $vrc, $vrc);
}
$default[$row->row] = $row;
}
}
}
else if ($rowscols = get_records_sql_array("
SELECT vrc.row, vrc.columns
FROM {view} v
JOIN {view_rows_columns} vrc ON vrc.view = v.id
WHERE v.template = ?
AND v.type = ?", array(self::SITE_TEMPLATE, $this->type))) {
// Layout not specified so use the view type default layout
$default = array();
foreach ($rowscols as $row) {
if ($this->get('id')) {
......@@ -335,37 +362,17 @@ class View {
$default[$row->row] = $row;
}
}
}
else if ($rowscols = get_records_sql_array("
SELECT vrc.row, vrc.columns
FROM {view} v
JOIN {view_rows_columns} vrc ON vrc.view = v.id
WHERE v.template = ?
AND v.type = ?", array(self::SITE_TEMPLATE, $this->type))) {
// Layout not specified so use the view type default layout
$default = array();
foreach ($rowscols as $row) {
if ($this->get('id')) {
$vrc = (object) array(
'view' => $this->get('id'),
'row' => $row->row,
'columns' => $row->columns
);
ensure_record_exists('view_rows_columns', $vrc, $vrc);
}
$default[$row->row] = $row;
else {
// Layout not known so make it 1 row / 3 cols
if ($this->get('id')) {
insert_record('view_rows_columns', (object) array(
'view' => $this->get('id'),
'row' => 1, 'columns' => 3));
}
}
else {
// Layout not known so make it 1 row / 3 cols
if ($this->get('id')) {
insert_record('view_rows_columns', (object) array(
'view' => $this->get('id'),
'row' => 1, 'columns' => 3));
$default = self::default_columnsperrow();
}
$default = self::default_columnsperrow();
$this->columnsperrow = $default;
}
$this->columnsperrow = $default;
}
}
}
......@@ -2164,7 +2171,6 @@ class View {
foreach (explode(',', $layout->rows[$row]['widths']) as $width) {
$this->columns[$row][++$i]['width'] = $width;
}
foreach ($data as $block) {
require_once(get_config('docroot') . 'blocktype/lib.php');
$block->view_obj = $this;
......@@ -2174,6 +2180,43 @@ class View {
}
public function get_grid_datastructure() {
$sql = '
SELECT bi.id, bi.view,positionx, positiony, width, height, blocktype, title, configdata
FROM {block_instance_dimension} bd
INNER JOIN {block_instance} bi
ON bd.block = bi.id
WHERE bi.view = ?
';
$blocks = get_records_sql_array($sql, array($this->get('id')));
if (is_array($blocks) || is_object($blocks)) {
foreach ($blocks as $block) {
require_once(get_config('docroot') . 'blocktype/lib.php');
$block->view_obj = $this;
$b = new BlockInstance($block->id, (array)$block);
$b->set('positionx', $block->positionx);
$b->set('positiony', $block->positiony);
$b->set('width', $block->width);
$b->set('height', $block->height);
$this->grid[]=$b;
}
}
$blockcontent = '';
foreach($this->grid as $blockinstance) {
$result = $blockinstance->render_editing();
$smarty = smarty_core();
$smarty->assign('blockcontent', $result['html']);
$smarty->assign('id', $blockinstance->get('id'));
$smarty->assign('width', $blockinstance->get('width'));
$smarty->assign('height', $blockinstance->get('height'));
$smarty->assign('positionx', $blockinstance->get('positionx'));
$smarty->assign('positiony', $blockinstance->get('positiony'));
$blockcontent .= $smarty->fetch('view/gridcell.tpl');
}
return $blockcontent;
}
/*
*
* wrapper around get_column_datastructure
......@@ -2221,10 +2264,16 @@ class View {
* Returns the HTML for the rows of this view
*/
public function build_rows($editing=false, $exporting=false, $versioning=false) {
$numrows = $this->get('numrows');
$result = '';
for ($i = 1; $i <= $numrows; $i++) {
$result .= $this->build_columns($i, $editing, $exporting, $versioning);
if (get_config('new_layout')) {
$result = $this->get_grid_datastructure();
}
else {
$numrows = $this->get('numrows');
$result = '';
for ($i = 1; $i <= $numrows; $i++) {
$result .= $this->build_columns($i, $editing, $exporting, $versioning);
}
}
return $result;
}
......@@ -2328,6 +2377,15 @@ class View {
if (isset($data['width'])) {
$smarty->assign('width', $data['width']);
}
if (isset($data['positionx'])) {
$smarty->assign('positionx', $data['positionx']);
}
if (isset($data['height'])) {
$smarty->assign('height', $data['height']);
}
if (isset($data['positiony'])) {
$smarty->assign('positiony', $data['positiony']);
}
if ($editing) {
return $smarty->fetch('view/columnediting.tpl');
......@@ -2347,7 +2405,7 @@ class View {
*
*/
public function addblocktype($values) {
$requires = array('blocktype', 'row', 'column', 'order');
$requires = array('blocktype');
foreach ($requires as $require) {
if (!array_key_exists($require, $values) || empty($values[$require])) {
throw new ParamOutOfRangeException(get_string('missingparam'. $require, 'error'));
......@@ -2376,20 +2434,34 @@ class View {
'title' => $newtitle,
'view' => $this->get('id'),
'view_obj' => $this,
'row' => $values['row'],
'column' => $values['column'],
'order' => $values['order'],
'row' => $values['positiony'],
'column' => $values['positionx'],
'order' => 1,
)
);
$bi->commit();
// add the dimension of the block
$bi->set_block_dimensions($values['positionx'], $values['positiony'], $values['width'], $values['height']);
if ($values['returndata'] === 'id') {
return $bi->get('id');
}
else if ($values['returndata']) {
// Return new block rendered in both configure mode and (editing) display mode
$display = $bi->render_editing(false, true);
$smarty = smarty_core();
$smarty->assign('blockcontent', $display['html']);
$smarty->assign('id', $bi->get('id'));
$smarty->assign('width', $bi->get('width'));
$smarty->assign('height', $bi->get('height'));
$smarty->assign('positionx', $bi->get('positionx'));
$smarty->assign('positiony', $bi->get('positiony'));
$display['html'] = $smarty->fetch('view/gridcell.tpl');
$result = array(
'display' => $bi->render_editing(false, true),
'display' => $display,
);
if (call_static_method(generate_class_name('blocktype', $values['blocktype']), 'has_instance_config')) {
$result['configure'] = $bi->render_editing(true, true);
......@@ -2583,9 +2655,9 @@ class View {
* order => position in new column to insert at
*/
public function moveblockinstance($values) {
$requires = array('id', 'row', 'column', 'order');
$requires = array('id', 'newx', 'newy', 'newheight', 'newwidth');
foreach ($requires as $require) {
if (!array_key_exists($require, $values) || empty($values[$require])) {
if (!array_key_exists($require, $values)) {
throw new ParamOutOfRangeException(get_string('missingparam' . $require, 'error'));
}
}
......@@ -2596,8 +2668,10 @@ class View {
throw new AccessDeniedException(get_string('blocknotinview', 'view', $bi->get('id')));
}
$bi->set_block_dimensions($values['newx'], $values['newy'], $values['newwidth'], $values['newheight']);
$bi->commit();
//TODO: check if code down here is still needed
// Because embedly externalvideo blocks have their original content changed
// by the cdn.embedly.com/widgets/platform.js file to use iframe data the info
// is lost on block move so we need to referesh the block with its original content
......@@ -2618,7 +2692,9 @@ class View {
$javascriptfiles = array();
$initjavascripts = array();
$view_data = $this->get_row_datastructure();
$loadajax = false;
foreach ($view_data as $row_data) {
foreach($row_data as $column) {
......
......@@ -255,7 +255,7 @@ class XMLDBpostgres extends XMLDBgenerator {
/// Take a look to field metadata
$meta = array_change_key_case($db->MetaColumns($tablename));
$metac = $meta[$fieldname];
$metac = $meta[$xmldb_field->getName()];
$oldtype = strtolower($metac->type);
$oldmetatype = column_type($xmldb_table->getName(), $fieldname);
$oldlength = $metac->max_length;
......
......@@ -15,6 +15,7 @@ $animation_speed: .3s !default;
.grid-stack {
position: relative;
min-height: 80vh;
&.grid-stack-rtl {
direction: ltr;
......
......@@ -45,7 +45,9 @@
<div class="col">
<div id="bottom-pane" data-role="workspace">
<div id="column-container" class="user-page-content">
{$columns|safe}
<div class="grid-stack">
{$columns|safe}
</div>
</div>
</div>
</div>
......
<div id="row_{$row}_column_{$column}" class="column column-layout columns{$numcolumns}{if $column == 1} firstcolumn{/if}{if $column == $numcolumns} lastcolumn{/if} {if $width}col-width-{$width}{/if}" {if $width}style="width:{if $width == 100}{$width }%;{else}{$width - 2}%;{/if}"{/if}>
<div id="row_{$row}_column_{$column}" >
<div class="grid-stack-item-content ui-draggable-handle">
<div class="column-header-empty"></div>
<div class="column-content">
{$blockcontent|safe}
</div>
</div>