gridstack-8.x-2.5/src/GridStackAttribute.php
src/GridStackAttribute.php
<?php
namespace Drupal\gridstack;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\Html;
use Drupal\blazy\Blazy;
use Drupal\gridstack\Entity\GridStack;
/**
* Provides GridStack attribute utilities.
*/
class GridStackAttribute {
/**
* Returns the wrapper attributes.
*/
public static function prepare($optionset, array &$settings) {
$nameshort = $settings['nameshort'];
$attributes = empty($settings['attributes']) ? [] : self::parse($settings['attributes']);
Blazy::containerAttributes($attributes, $settings);
// ID is only relevant for JS layouts, not Bootstrap/Foundation ones.
if (empty($settings['use_framework'])) {
$attributes['id'] = $settings['id'];
}
else {
if (!empty($settings['vm'])) {
$attributes['class'][] = 'is-vm is-vm--' . $settings['vm'];
}
}
// Add debug class for admin usages.
if (!empty($settings['debug']) || !empty($settings['_access_ipe'])) {
$attributes['class'][] = 'gridstack--debug';
if (!empty($settings['_access_ipe'])) {
$attributes['class'][] = 'is-gs-lb';
}
}
// Adds wrapper classes for static grid Bootstrap/ Foundation, or js-driven.
if (!empty($settings['wrapper_classes']) && is_string($settings['wrapper_classes'])) {
self::parseClasses($attributes, $settings['wrapper_classes']);
}
// Empty it after being processed to not leak to children.
unset($settings['attributes'], $settings['wrapper_classes'], $settings['fw_classes'], $settings['vm']);
// Bail out if GridStack is being disabled such as for Isotope layouts.
if (!empty($settings['ungridstack'])) {
return $attributes;
}
// Only if using navice CSS Grid or original library layouts.
if ($settings['use_js']) {
// Adds attributes for js-driven layouts.
// Gets options.breakpoints.sm.[width, column, image_style, grids], etc.
$exclude_image_style = empty($settings['_admin']);
$columns = $optionset->getJson('breakpoints');
if ($responsives = array_filter($optionset->getBreakpoints())) {
$data = [];
foreach (GridStack::getConstantBreakpoints() as $breakpoint) {
$responsive_grids = $optionset->getJsonSummaryBreakpoints($breakpoint, $exclude_image_style);
$has_width = isset($responsives[$breakpoint]['width']) && $responsives[$breakpoint]['width'] > -1;
if ($has_width && $responsive_grids) {
$data[$responsives[$breakpoint]['width']] = Json::decode($responsive_grids);
}
}
$attributes['data-' . $nameshort . '-data'] = $data ? Json::encode($data) : '';
}
// Add the required configuration as JSON object.
// Supports dynamic like Isotope to have both native CSS and JS layouts.
// Cannot rely on the library grid-stack-static, since not always there.
// The .is-gs-layout can be used dynamically during Isotope filtering when
// native CSS Grid is enabled to support them both as needed.
$attributes['data-' . $nameshort . '-breakpoints'] = strpos($columns, '{"":12}') !== FALSE ? '' : $columns;
$attributes['data-' . $nameshort . '-config'] = $optionset->getJson('settings');
$attributes['data-' . $nameshort . '-column'] = (int) $settings['column'];
$attributes['class'][] = empty($settings['gridnative']) ? 'gridstack--gs is-gs-layout' : 'gridstack--native';
// Breakpoint related data-attributes helpers.
if ($optionset->getSetting('minWidth')) {
$attributes['data-min-width'] = (int) $optionset->getSetting('minWidth');
}
}
return $attributes;
}
/**
* Provides dynamic GridStack JS grid attributes.
*/
public static function jsBox($optionset, array &$settings, $current = 'grids') {
// The CSS BG rule is not good at .box, but .box__content for CSS framework
// due to overlapping containers unlike js-driven layout which have margins.
$attributes = empty($settings['use_framework']) ? self::regionAttributes($settings, $current) : [];
$nameshort = $settings['nameshort'];
$id = isset($settings['delta']) ? $settings['delta'] : 0;
$nid = isset($settings['nested_delta']) ? $settings['nested_delta'] : NULL;
$grids = $optionset->getEndBreakpointGrids($current);
$nodes = ['x', 'y', 'width', 'height'];
// Native CSS Grid layout doesn't need DOM rect positions, just dimensions.
if (!empty($settings['gridnative'])) {
unset($nodes[0], $nodes[1]);
}
// Nested grids.
if (isset($settings['nested_delta']) && $current == 'nested') {
foreach ($nodes as $key) {
if (!isset($grids[$id][$nid])) {
continue;
}
$attributes['data-' . $nameshort . '-' . $key] = isset($grids[$id][$nid][$key]) ? (int) $grids[$id][$nid][$key] : 0;
}
}
else {
// The root element grids.
foreach ($nodes as $key) {
$attributes['data-' . $nameshort . '-' . $key] = isset($grids[$id][$key]) ? (int) $grids[$id][$key] : 0;
}
}
return $attributes;
}
/**
* Provides static Bootstrap/ Foundation CSS grid attributes.
*/
public static function cssBox($optionset, array &$settings, $current = 'grids') {
// The CSS BG rule is not good at .box, but .box__content for CSS framework
// due to overlapping containers unlike js-driven layout which have margins.
$attributes = empty($settings['use_framework']) ? self::regionAttributes($settings, $current) : [];
$framework = $settings['framework'];
$points = GridStackDefault::breakpoints();
// Bootstrap 4 uses flexbox with `col` class, and has `xl` breakpoint.
if ($framework == 'bootstrap') {
$attributes['class'][] = 'col';
}
// @todo Foundation 6:
// https://get.foundation/sites/docs/xy-grid.html
// https://get.foundation/sites/docs/grid.html
// Top container: grid-container full|fluid.
// Container: grid-x grid-padding-x small-up-2 medium-up-4 large-up-6
// Cell: cell auto small-6 medium-8 large-2 large-auto.
elseif (strpos($framework, 'foundation') !== FALSE) {
unset($points['xs'], $points['xl']);
}
$unique = $optionset->optimizeGridWidths($settings, $current);
foreach ($points as $point => $label) {
if (!isset($unique[$point])) {
continue;
}
$prefix = $suffix = '';
if (strpos($framework, 'bootstrap') !== FALSE) {
// Specific to XS: Bootstrap 3: col-xs-*, Bootstrap 4: col-*.
$prefix = 'col-' . $point . '-';
if ($framework == 'bootstrap' && $point == 'xs') {
$prefix = 'col-';
}
}
elseif (strpos($framework, 'foundation') !== FALSE) {
$prefix = $label . '-';
$suffix = ' columns';
}
$attributes['class'][] = $prefix . $unique[$point] . $suffix;
}
return $attributes;
}
/**
* Provides both CSS grid and js-driven attributes configurable via UI.
*/
public static function regionAttributes(array &$settings, $current = 'grids') {
$id = isset($settings['delta']) ? $settings['delta'] : 0;
$nid = isset($settings['nested_delta']) ? $settings['nested_delta'] : NULL;
$regions = isset($settings['regions']) ? $settings['regions'] : [];
$rid = $current == 'nested' ? GridStackDefault::regionId($id . '_' . $nid) : GridStackDefault::regionId($id);
$region = empty($regions[$rid]) ? [] : $regions[$rid];
$attributes = [];
if ($region) {
if (isset($region['attributes']) && !empty($region['attributes'])) {
$attributes = self::parse($region['attributes']);
unset($settings['regions'][$rid]['attributes']);
}
if (!empty($region['wrapper_classes'])) {
self::parseClasses($attributes, $region['wrapper_classes']);
unset($settings['regions'][$rid]['wrapper_classes']);
}
}
return $attributes;
}
/**
* Parses the given string classes.
*/
private static function parseClasses(array &$attributes, $string = '') {
$classes = array_map('\Drupal\Component\Utility\Html::cleanCssIdentifier', explode(' ', $string));
$attributes['class'] = empty($attributes['class']) ? array_unique($classes) : array_unique(array_merge($attributes['class'], $classes));
}
/**
* Parses the given string attribute.
*/
private static function parse($string = '') {
$attributes = [];
// Given role|navigation,data-something|some value.
$layout_attributes = explode(',', $string);
foreach ($layout_attributes as $attribute) {
$replaced_attribute = $attribute;
// @nottodo: Token support.
// No need to whitelist as this already requires admin priviledges.
// With admin privileges, the site is already taken over before playing
// around with attributes. However provides few basic sanitizations to
// satisfy curious playful editors.
if (strpos($attribute, '|') !== FALSE) {
list($key, $value) = array_pad(array_map('trim', explode('|', $replaced_attribute, 2)), 2, NULL);
$key = mb_substr($key, 0, 2) === 'on' ? 'data-' . $key : $key;
$attributes[$key] = Html::cleanCssIdentifier(strip_tags($value));
}
}
return $attributes;
}
}
