diff --git a/core/modules/gridbuilder/config/grid.ninesixty_12.yml b/core/modules/gridbuilder/config/grid.ninesixty_12.yml
new file mode 100644
index 0000000..5299ec8
--- /dev/null
+++ b/core/modules/gridbuilder/config/grid.ninesixty_12.yml
@@ -0,0 +1,7 @@
+id: ninesixty_12
+label: '960px wide, 12 column grid'
+type: 0
+width: 960
+columns: 12
+padding_width: 20
+gutter_width: 10
diff --git a/core/modules/gridbuilder/config/grid.ninesixty_16.yml b/core/modules/gridbuilder/config/grid.ninesixty_16.yml
new file mode 100644
index 0000000..bbe86be
--- /dev/null
+++ b/core/modules/gridbuilder/config/grid.ninesixty_16.yml
@@ -0,0 +1,7 @@
+id: ninesixty_16
+label: '960px wide, 16 column grid'
+type: 0
+width: 960
+columns: 16
+padding_width: 20
+gutter_width: 10
diff --git a/core/modules/gridbuilder/config/grid.six_column_fluid.yml b/core/modules/gridbuilder/config/grid.six_column_fluid.yml
new file mode 100644
index 0000000..106c7e2
--- /dev/null
+++ b/core/modules/gridbuilder/config/grid.six_column_fluid.yml
@@ -0,0 +1,9 @@
+id: six_column_fluid
+label: Six column fluid
+type: 1
+width: 100
+columns: 6
+padding_width: 1.5
+gutter_width: 2
+breakpoints:
+ - 'module.gridbuilder.tablet'
diff --git a/core/modules/gridbuilder/config/grid.three_column_fluid.yml b/core/modules/gridbuilder/config/grid.three_column_fluid.yml
new file mode 100644
index 0000000..6b1a5c4
--- /dev/null
+++ b/core/modules/gridbuilder/config/grid.three_column_fluid.yml
@@ -0,0 +1,9 @@
+id: three_column_fluid
+label: Three column fluid
+type: 1
+width: 100
+columns: 3
+padding_width: 1.5
+gutter_width: 2
+breakpoints:
+ - 'module.gridbuilder.smartphone'
diff --git a/core/modules/gridbuilder/config/grid.twelve_column_fluid.yml b/core/modules/gridbuilder/config/grid.twelve_column_fluid.yml
new file mode 100644
index 0000000..348246a
--- /dev/null
+++ b/core/modules/gridbuilder/config/grid.twelve_column_fluid.yml
@@ -0,0 +1,9 @@
+id: twelve_column_fluid
+label: Twelve column fluid
+type: 1
+width: 100
+columns: 12
+padding_width: 1.5
+gutter_width: 2
+breakpoints:
+ - 'module.gridbuilder.standard'
diff --git a/core/modules/gridbuilder/config/gridbuilder.breakpoints.yml b/core/modules/gridbuilder/config/gridbuilder.breakpoints.yml
new file mode 100644
index 0000000..d8f354c
--- /dev/null
+++ b/core/modules/gridbuilder/config/gridbuilder.breakpoints.yml
@@ -0,0 +1,3 @@
+smartphone: '(min-width: 0px)'
+tablet: 'all and (min-width: 321px) and (max-width: 760px)'
+standard: 'all and (min-width: 761px)'
diff --git a/core/modules/gridbuilder/gridbuilder-admin.css b/core/modules/gridbuilder/gridbuilder-admin.css
new file mode 100644
index 0000000..30229f8
--- /dev/null
+++ b/core/modules/gridbuilder/gridbuilder-admin.css
@@ -0,0 +1,32 @@
+#grid-form {
+ width: 350px;
+ border-right: 1px solid #ADADAD;
+ padding: 0.5em;
+ float: left;
+ margin-right: -400px;
+}
+#griddemonstrator {
+ float: left;
+ height: 300px;
+ margin-left: 400px;
+}
+#griddemonstrator .col {
+ float: left;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ -moz-background-clip: padding-box !important;
+ -webkit-background-clip: padding-box !important;
+ background-clip: padding-box !important;
+ height: 300px;
+ background-color: #C5D6DB;
+}
+#griddemonstrator .col .inner {
+ background-color: #4B6E78;
+ width: 100%;
+ height: 100%;
+ display: block;
+}
+#griddemonstrator .col.first {
+ margin-left: 0;
+}
diff --git a/core/modules/gridbuilder/gridbuilder-admin.js b/core/modules/gridbuilder/gridbuilder-admin.js
new file mode 100644
index 0000000..b37e67c
--- /dev/null
+++ b/core/modules/gridbuilder/gridbuilder-admin.js
@@ -0,0 +1,119 @@
+(function ($) {
+
+Drupal.behaviors.gridBuilder = {
+ attach: function(context) {
+ // Initialize responsive layout editor.
+ Drupal.gridBuilder.init();
+ }
+}
+
+Drupal.gridBuilder = Drupal.gridBuilder || {};
+
+/**
+ * Initialize gridbuilder behavior.
+ */
+Drupal.gridBuilder.init = function() {
+ // Set up container for grid demonstration.
+ $('form').parent().append('
');
+ $('input[name="type"]').change(Drupal.gridBuilder.typeChange);
+
+ Drupal.gridBuilder.gridType = $('input[name="type"]:checked').val() == 1 ? '%' : 'px';
+ if (Drupal.gridBuilder.gridType == '%') {
+ // Fake a sensible previous value for fixed grid.
+ Drupal.gridBuilder.prevValue = {
+ 'fullWidth': '960',
+ 'columns': '12',
+ 'paddingWidth': '20',
+ 'gutterWidth': '10',
+ };
+ }
+ else {
+ // Fake a sensible previous value for fluid grid.
+ Drupal.gridBuilder.prevValue = {
+ 'fullWidth': '100',
+ 'columns': '12',
+ 'paddingWidth': '1.5',
+ 'gutterWidth': '2',
+ };
+ }
+
+ // When any of the parameter values change, rebuild the demo.
+ $('#edit-width').change(Drupal.gridBuilder.reBuildDemonstration);
+ $('#edit-columns').change(Drupal.gridBuilder.reBuildDemonstration);
+ $('#edit-padding-width').change(Drupal.gridBuilder.reBuildDemonstration);
+ $('#edit-gutter-width').change(Drupal.gridBuilder.reBuildDemonstration);
+
+ // Build the initial version of the demo.
+ Drupal.gridBuilder.reBuildDemonstration();
+}
+
+/**
+ * React to grid type changes.
+ */
+Drupal.gridBuilder.typeChange = function(event) {
+ // Remember previous value, and restore the earlier value for this size.
+ var prevValue = {
+ 'fullWidth': $('#edit-width').val(),
+ 'columns': $('#edit-columns').val(),
+ 'paddingWidth': $('#edit-padding-width').val(),
+ 'gutterWidth': $('#edit-gutter-width').val(),
+ };
+ $('#edit-width').val(Drupal.gridBuilder.prevValue.fullWidth);
+ $('#edit-columns').val(Drupal.gridBuilder.prevValue.columns);
+ $('#edit-padding-width').val(Drupal.gridBuilder.prevValue.paddingWidth);
+ $('#edit-gutter-width').val(Drupal.gridBuilder.prevValue.gutterWidth);
+ Drupal.gridBuilder.prevValue = prevValue;
+
+ var newVal = 'px';
+ if ($(this).val() == 1) {
+ // User selected fluid layout, switch to % based sizes.
+ newVal = '%';
+ }
+ Drupal.gridBuilder.gridType = newVal;
+
+ // Switch the suffix helper field to proper value.
+ $('.form-item-width .field-suffix, .form-item-padding-width .field-suffix, .form-item-gutter-width .field-suffix').html(newVal);
+
+ // Rebuild our demonstration grid.
+ Drupal.gridBuilder.reBuildDemonstration();
+}
+
+Drupal.gridBuilder.reBuildDemonstration = function() {
+ // Retrieve all numeric values. All of them should be ints.
+ var width = '100';
+ if (Drupal.gridBuilder.gridType == 'px') {
+ var width = parseInt($('#edit-width').val());
+ }
+ var columns = parseInt($('#edit-columns').val());
+ var paddingWidth = parseInt($('#edit-padding-width').val());
+ var gutterWidth = parseInt($('#edit-gutter-width').val());
+
+ // Compute column widths.
+ var coloumnWidth = (width - ((columns - 1) * gutterWidth)) / columns;
+ var innerWidth = coloumnWidth - (paddingWidth * 2);
+
+ // Add CSS style generated for this setup.
+ var columnHTML =
+ '';
+
+ // Add number of columns as needed.
+ for (var i = 0; i < columns; i++) {
+ columnHTML += '';
+ }
+
+ $('#griddemonstrator').html(columnHTML);
+}
+
+})(jQuery);
diff --git a/core/modules/gridbuilder/gridbuilder.admin.inc b/core/modules/gridbuilder/gridbuilder.admin.inc
new file mode 100644
index 0000000..78f1aad
--- /dev/null
+++ b/core/modules/gridbuilder/gridbuilder.admin.inc
@@ -0,0 +1,67 @@
+render();
+}
+
+/**
+ * Page callback: Presents the grid editing form.
+ *
+ * @see gridbuilder_menu()
+ */
+function gridbuilder_page_edit(Grid $grid) {
+ drupal_set_title(t('Edit grid @label', array('@label' => $grid->label())), PASS_THROUGH);
+ return entity_get_form($grid);
+}
+
+/**
+ * Page callback: Provides the new grid addition form.
+ *
+ * @see gridbuilder_menu()
+ */
+function gridbuilder_page_add() {
+ $grid = entity_create('grid', array());
+ return entity_get_form($grid);
+}
+
+/**
+ * Page callback: Form constructor for grid deletion confirmation form.
+ *
+ * @see gridbuilder_menu()
+ */
+function gridbuilder_delete_confirm($form, &$form_state, Grid $grid) {
+ // Always provide entity id in the same form key as in the entity edit form.
+ $form['id'] = array('#type' => 'value', '#value' => $grid->id());
+ $form_state['grid'] = $grid;
+ return confirm_form($form,
+ t('Are you sure you want to remove the grid %title?', array('%title' => $grid->label())),
+ 'admin/structure/grids',
+ t('This action cannot be undone.'),
+ t('Delete'),
+ t('Cancel')
+ );
+}
+
+/**
+ * Form submission handler for gridbuilder_delete_confirm().
+ */
+function gridbuilder_delete_confirm_submit($form, &$form_state) {
+ $grid = $form_state['grid'];
+ $grid->delete();
+ drupal_set_message(t('Grid %label has been deleted.', array('%label' => $grid->label())));
+ watchdog('gridbuilder', 'Grid %label has been deleted.', array('%label' => $grid->label()), WATCHDOG_NOTICE);
+ $form_state['redirect'] = 'admin/structure/grids';
+}
diff --git a/core/modules/gridbuilder/gridbuilder.info b/core/modules/gridbuilder/gridbuilder.info
new file mode 100644
index 0000000..43d5f2f
--- /dev/null
+++ b/core/modules/gridbuilder/gridbuilder.info
@@ -0,0 +1,9 @@
+name = Grid builder
+description = Simple grid system builder.
+package = Core
+version = VERSION
+core = 8.x
+dependencies[] = config
+dependencies[] = breakpoint
+configure = admin/structure/grids
+
diff --git a/core/modules/gridbuilder/gridbuilder.module b/core/modules/gridbuilder/gridbuilder.module
new file mode 100644
index 0000000..1c85848
--- /dev/null
+++ b/core/modules/gridbuilder/gridbuilder.module
@@ -0,0 +1,242 @@
+ 'Grids',
+ 'description' => 'Manage list of grids which can be used with layouts.',
+ 'page callback' => 'gridbuilder_page_list',
+ 'access callback' => 'user_access',
+ 'access arguments' => array('administer gridbuilder'),
+ 'file' => 'gridbuilder.admin.inc',
+ );
+ $items['admin/structure/grids/add'] = array(
+ 'title' => 'Add grid',
+ 'page callback' => 'gridbuilder_page_add',
+ 'access callback' => 'user_access',
+ 'access arguments' => array('administer gridbuilder'),
+ 'type' => MENU_LOCAL_ACTION,
+ 'file' => 'gridbuilder.admin.inc',
+ );
+ $items['admin/structure/grids/grid/%gridbuilder/edit'] = array(
+ 'title' => 'Edit grid',
+ 'page callback' => 'gridbuilder_page_edit',
+ 'page arguments' => array(4),
+ 'access callback' => 'user_access',
+ 'access arguments' => array('administer gridbuilder'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'gridbuilder.admin.inc',
+ );
+ $items['admin/structure/grids/grid/%gridbuilder/delete'] = array(
+ 'title' => 'Delete',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('gridbuilder_delete_confirm', 4),
+ 'access callback' => 'user_access',
+ 'access arguments' => array('administer gridbuilder'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'gridbuilder.admin.inc',
+ );
+ return $items;
+}
+
+/**
+ * Implements hook_permission().
+ */
+function gridbuilder_permission() {
+ return array(
+ 'administer gridbuilder' => array(
+ 'title' => t('Administer grid builder grids'),
+ 'description' => t('Administer grids created with the grid builder.'),
+ ),
+ );
+}
+
+/**
+ * Implements hook_entity_info().
+ */
+function gridbuilder_entity_info() {
+ $types['grid'] = array(
+ 'label' => 'Grid',
+ 'entity class' => 'Drupal\gridbuilder\Grid',
+ 'controller class' => 'Drupal\Core\Config\Entity\ConfigStorageController',
+ 'form controller class' => array(
+ 'default' => 'Drupal\gridbuilder\GridFormController',
+ ),
+ 'list controller class' => 'Drupal\Core\Config\Entity\ConfigEntityListController',
+ 'list path' => 'admin/structure/grids',
+ 'uri callback' => 'gridbuilder_uri',
+ 'config prefix' => 'grid',
+ 'entity keys' => array(
+ 'id' => 'id',
+ 'label' => 'label',
+ 'uuid' => 'uuid',
+ ),
+ );
+ return $types;
+}
+
+/**
+ * Entity URI callback.
+ *
+ * @param Drupal\gridbuilder\Grid $grid
+ * Grid configuration entity instance.
+ *
+ * @return array
+ * Entity URI information.
+ */
+function gridbuilder_uri(Grid $grid) {
+ return array(
+ 'path' => 'admin/structure/grids/grid/' . $grid->id(),
+ );
+}
+
+/**
+ * Look up one grid setup based on machine name.
+ *
+ * @return Drupal\gridbuilder\Grid
+ * Grid configuration entity instance.
+ */
+function gridbuilder_load($id) {
+ return entity_load('grid', $id);
+}
+
+/**
+ * API function to get all grids on the site.
+ *
+ * @return array
+ * List of Drupal\gridbuilder\Grid instances keyed by id.
+ */
+function gridbuilder_load_all() {
+ return entity_load_multiple('grid');
+}
+
+/**
+ * Return generated CSS for a given grid.
+ *
+ * @param (string) $name
+ * Machine name of the grid.
+ * @param (string) $wrapper_selector
+ * (optional) Wrapper CSS selector to use to scope the CSS.
+ * @param (string) $span_selector_prefix
+ * (optional) Column span selector prefix to scope the CSS.
+ * @param (boolean) $skip_spacing
+ * Whether we should skip including spacing in the output. Useful for tight
+ * layout demonstration presentation.
+ *
+ * @return
+ * Fully assembled CSS string.
+ */
+function gridbuilder_get_css($name, $wrapper_selector = NULL, $span_selector_prefix = NULL, $skip_spacing = FALSE) {
+ // First attempt to let other modules provide CSS for this grid. If users are
+ // not happy with the CSS generated here, they can provide their own and skip
+ // our CSS generation.
+ $css = module_invoke_all('gridbuilder_get_css', $name);
+ if (!empty($css)) {
+ return join('', $css);
+ }
+
+ $grid = gridbuilder_load($name);
+ $css = '';
+
+ // If the wrapper selector was not provided, generate one. This is useful for
+ // specific administration use cases when we scope the classes by grids.
+ if (empty($wrapper_selector)) {
+ $wrapper_selector = '.rld-container-' . $grid->id();
+ }
+
+ // If the span selector was not provided, generate one. This is useful for
+ // the front end to apply varying span widths under different names.
+ if (empty($span_selector_prefix)) {
+ $span_selector_prefix = '.rld-span_';
+ }
+
+ // If spacing is to be skipped, override the gutter and padding temporarily.
+ if ($skip_spacing) {
+ $grid->gutter_width = $grid->padding_width = 0;
+ }
+
+ switch ($grid->type) {
+ case GRIDBUILDER_FLUID:
+ $size_suffix = '%';
+ // Override to 100% whatever it was.
+ $grid->width = '100';
+ break;
+ case GRIDBUILDER_FIXED:
+ $size_suffix = 'px';
+ break;
+ }
+
+
+ // Because we use the border-box box model, we only need to substract the
+ // size of margins from the full width and divide the rest by number of
+ // columns to get a value for column size.
+ $colwidth = ($grid->width - (($grid->columns - 1) * $grid->gutter_width)) / $grid->columns;
+
+ $css = $wrapper_selector . ' .rld-col {
+ border: 0px solid rgba(0,0,0,0);
+ float: left;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ -moz-background-clip: padding-box !important;
+ -webkit-background-clip: padding-box !important;
+ background-clip: padding-box !important;
+ margin-left: ' . $grid->gutter_width . $size_suffix . ';
+ padding: 0 ' . $grid->padding_width . $size_suffix . ';
+}
+' . $wrapper_selector . ' .rld-col' . $span_selector_prefix .'first {
+ margin-left: 0;
+ clear: both;
+}
+';
+ for ($i = 1; $i <= $grid->columns; $i++) {
+ $css .= $wrapper_selector . ' ' . $span_selector_prefix . $i . " {\n";
+ if ($i == 1) {
+ // The first column does not yet have any margins.
+ $css .= ' width: ' . $colwidth * $i . $size_suffix . ";\n";
+ }
+ elseif ($i == $grid->columns) {
+ // The full width column always spans 100%.
+ $css .= " width: " . $grid->width . $size_suffix . ";\n margin-left: 0;\n";
+ }
+ else {
+ // Other columns absorb all columns that they need to include and one
+ // less margin before them.
+ $css .= ' width: ' . (($colwidth * $i) + ($grid->gutter_width * ($i -1))) . $size_suffix . ";\n";
+ }
+ $css .= "}\n";
+ }
+
+ return $css;
+}
+
+/**
+ * Get the grid plugin manager instance.
+ *
+ * @return Drupal\layout\Plugin\Type\GridBuilderManager
+ * The grid plugin manager instance.
+ */
+function gridbuilder_manager() {
+ return drupal_container()->get('plugin.manager.gridbuilder');
+}
+
diff --git a/core/modules/gridbuilder/lib/Drupal/gridbuilder/Grid.php b/core/modules/gridbuilder/lib/Drupal/gridbuilder/Grid.php
new file mode 100644
index 0000000..ebfe963
--- /dev/null
+++ b/core/modules/gridbuilder/lib/Drupal/gridbuilder/Grid.php
@@ -0,0 +1,80 @@
+register('plugin.manager.gridbuilder', 'Drupal\gridbuilder\Plugin\Type\GridBuilderManager');
+ }
+}
diff --git a/core/modules/gridbuilder/lib/Drupal/gridbuilder/GridFormController.php b/core/modules/gridbuilder/lib/Drupal/gridbuilder/GridFormController.php
new file mode 100644
index 0000000..d092874
--- /dev/null
+++ b/core/modules/gridbuilder/lib/Drupal/gridbuilder/GridFormController.php
@@ -0,0 +1,173 @@
+width)) {
+ // Set some defaults for the user if this is a new grid.
+ $grid->type = GRIDBUILDER_FLUID;
+ $grid->width = 100;
+ $grid->columns = 12;
+ $grid->padding_width = 1.5;
+ $grid->gutter_width = 2;
+ }
+ }
+
+ /**
+ * Overrides Drupal\Core\Entity\EntityFormController::form().
+ */
+ public function form(array $form, array &$form_state, EntityInterface $grid) {
+ $form['label'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Label'),
+ '#maxlength' => 255,
+ '#default_value' => $grid->label(),
+ '#required' => TRUE,
+ );
+ $form['id'] = array(
+ '#type' => 'machine_name',
+ '#default_value' => $grid->id(),
+ '#machine_name' => array(
+ 'exists' => 'gridbuilder_load',
+ 'source' => array('label'),
+ ),
+ '#disabled' => !$grid->isNew(),
+ );
+
+ // Master grid configuration.
+ $form['type'] = array(
+ '#type' => 'radios',
+ '#title' => t('Type of grid'),
+ '#options' => array(GRIDBUILDER_FLUID => t('Fluid'), GRIDBUILDER_FIXED => t('Fixed to specific width')),
+ '#default_value' => $grid->type,
+ );
+ $form['width'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Grid width'),
+ '#description' => t('Only meaningful if using a fixed grid. Enter a pixel size (eg. 960).'),
+ '#default_value' => $grid->width,
+ '#states' => array(
+ 'visible' => array('input[name="type"]' => array('value' => GRIDBUILDER_FIXED)),
+ ),
+ '#field_suffix' => $grid->type == GRIDBUILDER_FIXED ? 'px' : '%',
+ );
+
+ // Grid detail configuration.
+ $form['columns'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Number of grid columns'),
+ '#default_value' => $grid->columns,
+ );
+ $form['padding_width'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Column padding'),
+ '#description' => t('Column padding in pixels (for fixed grids, eg. 10) or percentages (for fluid grids, eg. 1.5). Enter 0 for no padding.'),
+ '#default_value' => $grid->padding_width,
+ '#field_suffix' => $grid->type == GRIDBUILDER_FIXED ? 'px' : '%',
+ );
+
+ $form['gutter_width'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Gutter width'),
+ '#description' => t('Gutter width in pixels (for fixed grids, eg. 20) or percentages (for fluid grids, eg. 2). Enter 0 for no gutter.'),
+ '#default_value' => $grid->gutter_width,
+ '#field_suffix' => $grid->type == GRIDBUILDER_FIXED ? 'px' : '%',
+ );
+
+ $grid_breakpoints = entity_load('breakpoint_group', 'module.gridbuilder.gridbuilder');
+ $breakpoint_options = array();
+ foreach ($grid_breakpoints->breakpoints as $key => $breakpoint) {
+ $breakpoint_options[$key] = $breakpoint->label();
+ }
+ $form['breakpoints'] = array(
+ '#title' => t('Breakpoints where this grid will apply'),
+ '#type' => 'checkboxes',
+ '#options' => $breakpoint_options,
+ '#default_value' => (array) $grid->breakpoints,
+ );
+
+ $form['#attached']['css'][] = drupal_get_path('module', 'gridbuilder') . '/gridbuilder-admin.css';
+ $form['#attached']['js'][] = drupal_get_path('module', 'gridbuilder') . '/gridbuilder-admin.js';
+
+ return parent::form($form, $form_state, $grid);
+ }
+
+ /**
+ * Overrides Drupal\Core\Entity\EntityFormController::validate().
+ */
+ public function validate(array $form, array &$form_state) {
+ // Force width to 100 if fluid width. That is in percentages.
+ if ($form_state['values']['type'] == GRIDBUILDER_FLUID) {
+ $form_state['values']['width'] = 100;
+ }
+
+ if ((intval($form_state['values']['width']) != $form_state['values']['width']) || $form_state['values']['width'] == 0) {
+ // Width should be a positive integer.
+ form_set_error('columns', t('The width should be a positive number.'));
+ }
+ if ((intval($form_state['values']['columns']) != $form_state['values']['columns']) || $form_state['values']['columns'] == 0) {
+ // Columns should be a positive integer.
+ form_set_error('columns', t('The number of columns should be a positive number.'));
+ }
+ if (!is_numeric($form_state['values']['padding_width'])) {
+ // Padding can be float as well (eg. 1.5 for 1.5% for fluid grids).
+ form_set_error('padding_width', t('The column padding should be a number. Enter 0 (zero) for no padding.' . $form_state['values']['padding_width']));
+ }
+ if (!is_numeric($form_state['values']['gutter_width'])) {
+ // Gutter can be float too (eg. 1.5 for 1.5% for fluid grids).
+ form_set_error('gutter_width', t('The gutter width should be a number. Enter 0 (zero) for no gutter.'));
+ }
+ }
+
+ /**
+ * Overrides Drupal\Core\Entity\EntityFormController::actions().
+ */
+ protected function actions(array $form, array &$form_state) {
+ // Only includes a Save action for the entity, no direct Delete button.
+ return array(
+ 'submit' => array(
+ '#value' => t('Save'),
+ '#validate' => array(
+ array($this, 'validate'),
+ ),
+ '#submit' => array(
+ array($this, 'submit'),
+ array($this, 'save'),
+ ),
+ ),
+ );
+ }
+
+ /**
+ * Overrides Drupal\Core\Entity\EntityFormController::save().
+ */
+ public function save(array $form, array &$form_state) {
+ $grid = $this->getEntity($form_state);
+ $grid->save();
+
+ watchdog('gridbuilder', 'Grid @label saved.', array('@label' => $grid->label()), WATCHDOG_NOTICE);
+ drupal_set_message(t('Grid %label saved.', array('%label' => $grid->label())));
+
+ $form_state['redirect'] = 'admin/structure/grids';
+ }
+
+}
+
diff --git a/core/modules/gridbuilder/lib/Drupal/gridbuilder/Plugin/Derivative/GridBuilder.php b/core/modules/gridbuilder/lib/Drupal/gridbuilder/Plugin/Derivative/GridBuilder.php
new file mode 100644
index 0000000..e5187d1
--- /dev/null
+++ b/core/modules/gridbuilder/lib/Drupal/gridbuilder/Plugin/Derivative/GridBuilder.php
@@ -0,0 +1,57 @@
+derivatives) && !empty($this->derivatives[$derivative_id])) {
+ return $this->derivatives[$derivative_id];
+ }
+ $this->getDerivativeDefinitions($base_plugin_definition);
+ return $this->derivatives[$derivative_id];
+ }
+
+ /**
+ * Implements DerivativeInterface::getDerivativeDefinitions().
+ */
+ public function getDerivativeDefinitions(array $base_plugin_definition) {
+ // Use module_invoke() because plugins are active even if the module is not
+ // enabled.
+ $this->derivatives = array();
+ $grids = module_invoke('gridbuilder', 'load_all');
+ if (!empty($grids)) {
+ foreach ($grids as $key => $grid) {
+ $this->derivatives[$key] = array(
+ 'grid' => $grid,
+ 'class' => 'Drupal\gridbuilder\Plugin\gridbuilder\gridbuilder\EqualColumnGrid',
+ );
+ }
+ }
+ return $this->derivatives;
+ }
+
+}
diff --git a/core/modules/gridbuilder/lib/Drupal/gridbuilder/Plugin/GridBuilderInterface.php b/core/modules/gridbuilder/lib/Drupal/gridbuilder/Plugin/GridBuilderInterface.php
new file mode 100644
index 0000000..cbde3af
--- /dev/null
+++ b/core/modules/gridbuilder/lib/Drupal/gridbuilder/Plugin/GridBuilderInterface.php
@@ -0,0 +1,30 @@
+ 'Drupal\gridbuilder\Plugin\gridbuilder\gridbuilder\EqualColumnGrid',
+ );
+
+ /**
+ * Overrides Drupal\Component\Plugin\PluginManagerBase::__construct().
+ */
+ public function __construct() {
+ // Create gridbuilder plugin derivatives from declaratively defined grids.
+ $this->discovery = new DerivativeDiscoveryDecorator(new AnnotatedClassDiscovery('gridbuilder', 'gridbuilder'));
+ $this->factory = new ReflectionFactory($this);
+ }
+}
diff --git a/core/modules/gridbuilder/lib/Drupal/gridbuilder/Plugin/gridbuilder/gridbuilder/EqualColumnGrid.php b/core/modules/gridbuilder/lib/Drupal/gridbuilder/Plugin/gridbuilder/gridbuilder/EqualColumnGrid.php
new file mode 100644
index 0000000..a365aaa
--- /dev/null
+++ b/core/modules/gridbuilder/lib/Drupal/gridbuilder/Plugin/gridbuilder/gridbuilder/EqualColumnGrid.php
@@ -0,0 +1,114 @@
+getDefinition($plugin_id);
+ parent::__construct($configuration, $plugin_id, $discovery);
+ }
+
+
+ /**
+ * Implements Drupal\gridbuilder\Plugin\GridBuilderInterface::getGridCss().
+ */
+ public function getGridCss($wrapper_selector = NULL, $col_selector_prefix = NULL, $skip_spacing = FALSE) {
+ $definition = $this->getDefinition();
+ $grid = $definition['grid'];
+ $css = '';
+
+ // If the wrapper selector was not provided, generate one. This is useful for
+ // specific administration use cases when we scope the classes by grids.
+ // @todo update legacy selector (in concert with rlayout module).
+ if (empty($wrapper_selector)) {
+ $wrapper_selector = '.rld-container-' . $grid->id;
+ }
+
+ // If the span selector was not provided, generate one. This is useful for
+ // the front end to apply varying span widths under different names.
+ if (empty($col_selector_prefix)) {
+ $col_selector_prefix = '.rld-span_';
+ }
+
+ // If spacing is to be skipped, override the gutter and padding temporarily.
+ if ($skip_spacing) {
+ $grid->gutter_width = $grid->padding_width = 0;
+ }
+
+ // @todo: type constants are in the module.
+ switch ($grid->type) {
+ case 1:
+ $size_suffix = '%';
+ // Override to 100% whatever it was.
+ $grid->width = '100';
+ break;
+ case 0:
+ $size_suffix = 'px';
+ break;
+ }
+
+ // Because we use the border-box box model, we only need to substract the
+ // size of margins from the full width and divide the rest by number of
+ // columns to get a value for column size.
+ $colwidth = ($grid->width - (($grid->columns - 1) * $grid->gutter_width)) / $grid->columns;
+
+ $css = $wrapper_selector . ' .rld-col {
+ border: 0px solid rgba(0,0,0,0);
+ float: left;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ -moz-background-clip: padding-box !important;
+ -webkit-background-clip: padding-box !important;
+ background-clip: padding-box !important;
+ margin-left: ' . $grid->gutter_width . $size_suffix . ';
+ padding: 0 ' . $grid->padding_width . $size_suffix . ';
+}
+' . $wrapper_selector . ' .rld-col' . $span_selector_prefix .'first {
+ margin-left: 0;
+ clear: both;
+}
+';
+ for ($i = 1; $i <= $grid->columns; $i++) {
+ $css .= $wrapper_selector . ' ' . $span_selector_prefix . $i . " {\n";
+ if ($i == 1) {
+ // The first column does not yet have any margins.
+ $css .= ' width: ' . $colwidth * $i . $size_suffix . ";\n";
+ }
+ elseif ($i == $grid->columns) {
+ // The full width column always spans 100%.
+ $css .= " width: " . $grid->width . $size_suffix . ";\n margin-left: 0;\n";
+ }
+ else {
+ // Other columns absorb all columns that they need to include and one
+ // less margin before them.
+ $css .= ' width: ' . (($colwidth * $i) + ($grid->gutter_width * ($i -1))) . $size_suffix . ";\n";
+ }
+ $css .= "}\n";
+ }
+
+ return $css;
+ }
+
+}