diff --git a/core/modules/layout/includes/layout.admin.inc b/core/modules/layout/includes/layout.admin.inc
index 489e8a3..2f137e9 100644
--- a/core/modules/layout/includes/layout.admin.inc
+++ b/core/modules/layout/includes/layout.admin.inc
@@ -6,6 +6,10 @@
*/
use Drupal\layout\Plugin\Core\Entity\Display;
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\layout\Form\BlockInstanceForm;
+use Drupal\layout\Ajax\OpenDialogCommand;
+
/**
* Page callback: Presents a list of layouts.
@@ -133,7 +137,8 @@ function layout_master_webservice(Display $display, $a=NULL, $b=NULL, $c=NULL) {
$block = $blockInstance['id'];
$blockInfo['block.' . $block] = array(
'region' => $region['id'],
- 'weight' => $blockInstance['weight'] * 100
+ 'weight' => $blockInstance['weight'] * 100,
+ 'label' => $blockInstance['label'],
);
}
}
@@ -193,3 +198,35 @@ function layout_master_break_lock_confirm_submit(&$form, &$form_state) {
$form_state['redirect'] = 'admin/structure/layouts/manage/' . $display->get('id') . '/edit';
drupal_set_message(t('The lock has been broken and you may now edit this display.'));
}
+
+
+/**
+ * Page callback for BlockInstance Configuration Form
+ *
+ * THIS IS ONLY FOR DEMONSTRATION PURPOSES!
+ *
+ * @param Drupal\layout\Plugin\Core\Entity\Display $display
+ * @param $region
+ * @param $block_id
+ * @return Drupal\Core\Ajax\AjaxResponse
+ */
+function layout_master_blockinstance_configure(Display $display, $block_id) {
+ $response = new AjaxResponse();
+ $form_state = array(
+ 'display' => $display,
+ 'block_id' => $block_id
+ );
+ if (!$display->getBlockInstanceInfo($block_id)) {
+ return MENU_NOT_FOUND;
+ }
+
+ $form = drupal_build_form('layout_master_blockinstance_configure_form', $form_state);
+ $html = drupal_render($form);
+ $response->addCommand(new OpenDialogCommand( t('Configure %block_id in layout %name', array('%block_id' => $block_id, '%name' => $display->get('label'))), $html));
+ return $response;
+}
+
+function layout_master_blockinstance_configure_form(array $form, array &$form_state) {
+ $form_handler = new BlockInstanceForm();
+ return $form_handler->build($form, $form_state);
+}
diff --git a/core/modules/layout/js/layout.admin.js b/core/modules/layout/js/layout.admin.js
index 43c1563..e91b5ab 100755
--- a/core/modules/layout/js/layout.admin.js
+++ b/core/modules/layout/js/layout.admin.js
@@ -4,6 +4,15 @@
var appView;
+Drupal.ajax.prototype.commands.layoutBlockReload = function (ajax, response, status) {
+ // Find the appropriate model by its id.
+ var m = Drupal.layout.getBlockInstanceModelById(response.data.id);
+ if (m) {
+ // The views will take care of all necessary updates (unbinding, rebinding).
+ m.set(response.data.info);
+ }
+};
+
/**
* Attach display editor functionality.
*/
@@ -20,6 +29,55 @@ Drupal.behaviors.displayEditor = {
}
/**
+ * Bind links to open in a dialog using Drupal.ajax.
+ * @param el
+ */
+ Drupal.layout.ajaxify = function(el) {
+ // Bind Ajax behaviors to all items showing the class.
+ $(el).find('.ajax').once('ajax', function () {
+ var element_settings = {};
+ element_settings.progress = { 'type': null };
+ element_settings.dialog = {
+ modal: true
+ };
+
+ if ($(this).attr('href')) {
+ element_settings.url = $(this).attr('href');
+ element_settings.event = 'click.layout';
+ }
+ var base = $(this).attr('id');
+ Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
+ });
+ }
+
+ /**
+ * Unbind links bound to Drupal.ajax.
+ * @param el
+ */
+ Drupal.layout.deajaxify = function(el) {
+ $(el).find('.ajax').each(function() {
+ var base = $(this).attr('id');
+ if (Drupal.ajax[base]) {
+ $(this).off('click.layout');
+ delete Drupal.ajax[base]
+ }
+ });
+ }
+
+ /**
+ * Retrieve the BlockInstanceModel
+ * @param id
+ * @return {*}
+ */
+ Drupal.layout.getBlockInstanceModelById = function(id) {
+ var m;
+ Drupal.layout.appModel.get('regions').each(function(region) {
+ m = m || region.get('blockInstances').get(id);
+ }, this);
+ return m;
+ }
+
+ /**
* Helper function generating a BlocksCollection populated with randomly
* named items.
*
diff --git a/core/modules/layout/js/theme.js b/core/modules/layout/js/theme.js
index b0e24b1..4a5623b 100644
--- a/core/modules/layout/js/theme.js
+++ b/core/modules/layout/js/theme.js
@@ -9,7 +9,7 @@
* @param label
* @return {String}
*/
- Drupal.theme.layoutRegion = function (id, label) {
+ Drupal.theme.layoutRegion = function (id, label, attributes) {
var html =
'
' +
'
' +
@@ -39,9 +39,13 @@
'
' +
'
' +
'M' +
- '' + id + ' block' +
+ '' + label + ' block' +
+ '
' +
+ '
' +
+ '
' +
+ // this is only for demonstration purpose and so that we can have full blockInstance-CRUD.
+ '
'
'
' +
- '
' +
'
' +
'
';
};
diff --git a/core/modules/layout/js/views/blockinstance-view.js b/core/modules/layout/js/views/blockinstance-view.js
index aded8a8..ec8f15c 100644
--- a/core/modules/layout/js/views/blockinstance-view.js
+++ b/core/modules/layout/js/views/blockinstance-view.js
@@ -10,27 +10,50 @@
Drupal.layout.BlockInstanceView = Backbone.View.extend({
events:{
- 'click':'onClick',
+ 'click [name="block"][value="delete"]': 'onClickDelete',
'drop':'onDrop'
},
+ initialize: function() {
+ this.model.on('change', this.render, this);
+ },
onDrop:function (event, index) {
// Trigger reorder, will be handled in Drupal.layout.RegionView.
this.$el.trigger('reorder', [this.model, index]);
- // @todo: handle dropping onto enpty region.
event.preventDefault();
event.stopPropagation();
return ;
},
- onClick:function () {
+ onClickDelete:function (event) {
this.dialogView = new Drupal.layout.BlockInstanceModalView({
model: this.model,
title: this.model.get('label')
});
this.dialogView.render();
+ event.preventDefault();
+ event.stopPropagation();
+ return ;
+ },
+ getConfigureURL: function() {
+ return Drupal.url('admin/structure/layouts/manage/' + drupalSettings.layout.id + '/blockinstances/' + this.model.get('id') + '/configure' );
},
render:function () {
- this.setElement($(Drupal.theme('layoutBlock', this.model.get('id'), this.model.get('label'), this.model.attributes)));
+ // Remove any existent Drupal.ajax.
+ Drupal.layout.deajaxify(this.$el);
+ // If you want to have the template render the "top" element of your view
+ // you need to do this.
+ // @see http://stackoverflow.com/questions/11594961/backbone-not-this-el-wrapping
+ var old = this.$el;
+ this.setElement(Drupal.theme('layoutBlock', this.model.get('id'), this.model.get('label'), {
+ 'configureURL': this.getConfigureURL()
+ }));
+ old.replaceWith(this.$el);
+ // Rewire Drupal.ajax.
+ Drupal.layout.ajaxify(this.$el);
return this;
+ },
+ remove: function() {
+ Drupal.layout.deajaxify(this.$el);
+ this.$el.remove();
}
});
diff --git a/core/modules/layout/js/views/region-view.js b/core/modules/layout/js/views/region-view.js
index d5d1995..2e765ab 100644
--- a/core/modules/layout/js/views/region-view.js
+++ b/core/modules/layout/js/views/region-view.js
@@ -65,7 +65,7 @@
},
render:function () {
- this.$el.html(Drupal.theme.layoutRegion(this.model.get('id'), this.model.get('label')));
+ this.$el.html(Drupal.theme.layoutRegion(this.model.get('id'), this.model.get('label'), this.model.toJSON()));
this._collectionView.render();
// Making the whole layout-region-element sortable provides a larger area
// to drop block instances on and allows for dropping on empty regions.
diff --git a/core/modules/layout/layout.module b/core/modules/layout/layout.module
index 73861d4..5df14b8 100755
--- a/core/modules/layout/layout.module
+++ b/core/modules/layout/layout.module
@@ -64,6 +64,15 @@ function layout_menu() {
'file' => 'includes/layout.admin.inc',
);
+ $items['admin/structure/layouts/manage/%layout_master_cache/blockinstances/%/configure'] = array(
+ 'title' => 'Configure',
+ 'page callback' => 'layout_master_blockinstance_configure',
+ 'page arguments' => array(4, 6),
+ 'access callback' => 'user_access',
+ 'access arguments' => array('administer layouts'),
+ 'file' => 'includes/layout.admin.inc',
+ );
+
return $items;
}
@@ -221,8 +230,11 @@ function layout_library_info() {
// Drupal's pseudo-templates
$path . '/js/theme.js' => array('defer' => TRUE),
+ // Has to be JS_DEFAULT otherwise Drupal.ajax.prototype.commands can't
+ // be added to apparently.
+ $path . '/js/layout.admin.js' => array('group' => JS_DEFAULT),
+
// Models
- $path . '/js/layout.admin.js' => array('defer' => TRUE),
$path . '/js/models/app-model.js' => array('defer' => TRUE),
$path . '/js/models/block-model.js' => array('defer' => TRUE),
$path . '/js/models/blockinstance-model.js' => array('defer' => TRUE),
diff --git a/core/modules/layout/lib/Drupal/layout/Ajax/OpenDialogCommand.php b/core/modules/layout/lib/Drupal/layout/Ajax/OpenDialogCommand.php
new file mode 100644
index 0000000..4a8362d
--- /dev/null
+++ b/core/modules/layout/lib/Drupal/layout/Ajax/OpenDialogCommand.php
@@ -0,0 +1,52 @@
+title = $title;
+ $this->html = $html;
+ $this->settings = $settings;
+ }
+
+ /**
+ * Implements Drupal\Core\Ajax\CommandInterface:render().
+ */
+ public function render() {
+ return array(
+ 'command' => 'insert',
+ 'method' => NULL,
+ 'title' => $this->title,
+ 'selector' => '#drupal-modal',
+ 'data' => $this->html,
+ 'settings' => $this->settings,
+ );
+ }
+}
diff --git a/core/modules/layout/lib/Drupal/layout/DisplayFormController.php b/core/modules/layout/lib/Drupal/layout/DisplayFormController.php
index 802cacb..d826557 100755
--- a/core/modules/layout/lib/Drupal/layout/DisplayFormController.php
+++ b/core/modules/layout/lib/Drupal/layout/DisplayFormController.php
@@ -87,6 +87,10 @@ public function form(array $form, array &$form_state, EntityInterface $display)
if (!isset($display->layout) || ($form_state['values']['layout'] != $display->layout)) {
// @todo: clean this up - this is highly likely to be the wrong place
// to alter the TempStore.
+ // Figured out where this problem comes from form cache and tempstore
+ // compete obviously - see also ViewEditFormController where they use
+ // $form_state['no_cache'] = TRUE; but that again requires going through
+ // views_ui_ajax_get_form instead of ajax_get_form.
// But if i *don't* reload the blockInfo property of the Display, it can
// be "stale" and overwrite changes made via the webservice.
@@ -127,6 +131,7 @@ private function layoutDemonstration(EntityInterface $display) {
$build['#attached']['js'][] =array(
'data' => array(
'layout' => array(
+ 'id' => $display->id,
'webserviceURL' => url('admin/structure/layouts/manage/' . $display->id . '/webservice'),
'locked' => $locked,
'layoutData' => $display->exportGroupedByRegion()
@@ -134,6 +139,7 @@ private function layoutDemonstration(EntityInterface $display) {
),
'type' => 'setting',
);
+
return $build;
}
diff --git a/core/modules/layout/lib/Drupal/layout/Form/BlockInstanceForm.php b/core/modules/layout/lib/Drupal/layout/Form/BlockInstanceForm.php
new file mode 100644
index 0000000..9c45750
--- /dev/null
+++ b/core/modules/layout/lib/Drupal/layout/Form/BlockInstanceForm.php
@@ -0,0 +1,112 @@
+display = $form_state['display'];
+ $this->blockId = $form_state['block_id'];
+ $block_info = $this->display->getBlockInstanceInfo($this->blockId);
+
+ $form = array();
+ $form['error-placeholder'] = array(
+ '#markup' => '
',
+ );
+ $form['label'] = array(
+ '#title' => t('Enter a label'),
+ '#type' => 'textfield',
+ '#default_value' => isset($block_info['label']) ? $block_info['label'] : NULL
+ );
+ $form['save'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ '#ajax' => array(
+ 'dialog' => array(),
+ 'callback' => array($this, 'ajaxSave')
+ ),
+ );
+ $form['cancel'] = array(
+ '#type' => 'link',
+ '#title' => t('Cancel'),
+ '#href' => '#',
+ '#attributes' => array(
+ // This is a special class to which JavaScript assigns dialog closing
+ // behavior.
+ 'class' => array('dialog-cancel'),
+ ),
+ );
+ // Add validation and submission handlers.
+ $form['#validate'][] = array($this, 'validate');
+ $form['#submit'][] = array($this, 'submit');
+ return $form;
+ }
+
+ function ajaxSave($form, $form_state) {
+ // @todo: use proper AjaxResponse commands.
+ if ($form_state['executed'] && $form_state['submitted']) {
+ $commands = array();
+ $commands[] = array('command'=>'layoutBlockReload', 'data' => array(
+ 'info' => $this->display->getBlockInstanceInfo($this->blockId),
+ 'id' => $this->blockId
+ ));
+ $commands[] = array('command'=>'remove', 'selector' => '#drupal-modal', 'data' => NULL);
+ return array('#type' => 'ajax', '#commands' => $commands);
+ }
+ $commands = array();
+ $errors = form_get_errors();
+ if (count($errors)) {
+ $commands[] = array(
+ 'command'=>'insert',
+ 'method' => 'replaceWith',
+ 'selector' => '#drupal-modal .error-placeholder',
+ 'data' => theme('status_messages', $errors)
+ );
+ }
+ return array('#type' => 'ajax', '#commands' => $commands);
+ }
+
+ /**
+ * Validates the form.
+ */
+ public function validate(array $form, array &$form_state) {
+ $values = & $form_state['values'];
+ if (strlen($values['label'])>10) {
+ form_set_error('label', t('Please chose a shorter label (for the sake testing validation!)'));
+ }
+ }
+
+ /**
+ * Store the values by updating the TempStored Display.
+ */
+ public function submit(array $form, array &$form_state) {
+ $values = $form_state['values'];
+ // Update the instance info
+ $this->display->setBlockInstanceInfo($this->blockId, array(
+ 'label'=>$values['label']
+ ));
+ // Store changes in TempStore.
+ layout_master_cache_set($this->display);
+ }
+}
diff --git a/core/modules/layout/lib/Drupal/layout/Plugin/Core/Entity/Display.php b/core/modules/layout/lib/Drupal/layout/Plugin/Core/Entity/Display.php
index 7a2daca..a461479 100644
--- a/core/modules/layout/lib/Drupal/layout/Plugin/Core/Entity/Display.php
+++ b/core/modules/layout/lib/Drupal/layout/Plugin/Core/Entity/Display.php
@@ -186,7 +186,7 @@ public function getLayoutInstance() {
/**
* Returns an array representation grouped for json-serialisation.
- * @todo: this is a bad name (and should probably)
+ * @todo: this is a bad name (and should probably change)
*
* @return array
*/
@@ -202,7 +202,7 @@ public function exportGroupedByRegion() {
$region_data = array(
'id' => $region,
'label' => $info['label'],
- 'blockInstances' => array()
+ 'blockInstances' => array(),
);
$existing_blocks = $this->getSortedBlocksByRegion($region);
foreach ($existing_blocks as $block => $placement) {
@@ -211,7 +211,8 @@ public function exportGroupedByRegion() {
$block_id = str_replace('block.', '', $block);
$region_data['blockInstances'][] = array(
'id' => $block_id,
- 'label' => $block_id,
+ // we are adding a rather meaningless label for CRUD-demo purposes.
+ 'label' => isset($placement['label']) ? $placement['label'] : $block_id,
'blockId' => 'default',
'weight' => $placement['weight'],
'region' => $placement['region'],
@@ -221,4 +222,24 @@ public function exportGroupedByRegion() {
}
return $data;
}
+
+ public function getBlockInstanceInfo($block_id) {
+ $block_info = $this->getAllBlockInfo();
+ return isset($block_info['block.' . $block_id]) ? $block_info['block.' . $block_id] : NULL;
+ }
+
+ public function setBlockInstanceInfo($block_id, array $info, $merge=TRUE) {
+ $block_info = $this->getBlockInstanceInfo($block_id);
+ if (!isset($block_info)) {
+ return FALSE;
+ }
+ // @todo: does it make sense to invalidate this?
+ unset($this->blocksInRegions);
+ if ($merge) {
+ $info = array_merge($block_info, $info);
+ }
+ $this->blockInfo['block.' . $block_id] = $info;
+ return $this->blockInfo['block.' . $block_id];
+ }
+
}