diff --git a/core/modules/layout/js/layout.admin.js b/core/modules/layout/js/layout.admin.js index 48e457f..79fe84e 100755 --- a/core/modules/layout/js/layout.admin.js +++ b/core/modules/layout/js/layout.admin.js @@ -8,42 +8,7 @@ Drupal.behaviors.displayEditor = { attach: function (context, settings) { - var appModel; - - function ExtractModelsFromDOM() { - var regions = new Drupal.layout.RegionsCollection(); - // Retrieve regions from DOM. - $('.layout-region').each(function() { - var $region = $(this); - var blockInstances = new Drupal.layout.BlockInstancesCollection(); - var weight = 0; - // wow, this is awkward but will go away. - var classes = $region.attr('class').split(' '); - var pattern = new RegExp('layout-region-.+'); - var region_id = _.filter(classes, function(v) { - return pattern.test(v); - }).toString().replace('layout-region-', ''); - - $region.find('.block').each(function() { - var block = new Drupal.layout.BlockInstanceModel({ - id: $(this).attr('id').replace(/block-/, ''), - blockId: 'default', - label: $(this).find('.label').text(), - region: region_id, - weight: weight - }); - weight++; - blockInstances.add(block, {silent: true}); - }); - var region = new Drupal.layout.RegionModel({ - id: region_id, - label: $region.find('.region-label').text(), - blockInstances: blockInstances - }); - regions.add(region); - }); - return regions; - } + var appModel, appView; function randomId() { var chars = "abcdefghiklmnopqrstuvwxyz"; @@ -55,6 +20,12 @@ Drupal.behaviors.displayEditor = { return randomString; } + /** + * Helper function generating a BlocksCollection populated with randomly + * named items. + * + * @return {Drupal.layout.BlocksCollection} + */ Drupal.layout.getBlocksCollection = function() { var blocks = []; // Generate a bunch of randomly named blocks. @@ -69,16 +40,33 @@ Drupal.behaviors.displayEditor = { return new Drupal.layout.BlocksCollection(blocks); } - // @todo: make this work on template change, i.e. Drupal.ajax/behaviour compatible. + /** + * Generates the required Backbone Collections and Models. + * @param layoutData + * @return {Drupal.layout.RegionsCollection} + */ + function generateRegionCollections(layoutData) { + var regions = new Drupal.layout.RegionsCollection(); + _(layoutData.regions).each(function(region) { + regions.add(new Drupal.layout.RegionModel({ + id: region.id, + label: region.label, + blockInstances: + new Drupal.layout.BlockInstancesCollection().reset(region.blockInstances, {silent: true}) + })); + }); + return regions; + } + + // Populate the appModel Drupal.layout.appModel = new Drupal.layout.AppModel({ - id: drupalSettings.layout.id + id: drupalSettings.layout.id, + regions: generateRegionCollections(drupalSettings.layout.layoutData) }); - // let's grab the stuff from DOM - it would be useful to have JSON ... - Drupal.layout.appModel.set('regions', ExtractModelsFromDOM() ); var appView = new Drupal.layout.AppView({ model: Drupal.layout.appModel, el: $('#block-system-main'), - settings: drupalSettings.layout + locked: drupalSettings.layout.locked }); appView.render(); } diff --git a/core/modules/layout/js/views/app-view.js b/core/modules/layout/js/views/app-view.js index 79f9b58..4b8985a 100644 --- a/core/modules/layout/js/views/app-view.js +++ b/core/modules/layout/js/views/app-view.js @@ -17,17 +17,18 @@ Drupal.layout = Drupal.layout || {}; Drupal.layout.AppView = Backbone.View.extend({ initialize: function(options) { - this.regionsView = new Drupal.layout.RegionsView({ + this.regionsView = new Drupal.layout.UpdatingCollectionView({ + el: this.$el.find('.layout-display'), collection: this.model.get('regions'), - el: this.$el.find('.layout-display') + nestedViewConstructor:Drupal.layout.RegionView, + nestedViewTagName:'div' }); - this.settings = options.settings; }, render: function() { // @todo: this should move to layout.admin.js and provide better handling. // Do not setup the js app if another user is currently operating on this // layout (locked on the server via TempStore). - if (this.settings.locked) { + if (this.options.locked) { return false; } this.regionsView.render(); diff --git a/core/modules/layout/js/views/region-view.js b/core/modules/layout/js/views/region-view.js index 8382977..8a8834a 100644 --- a/core/modules/layout/js/views/region-view.js +++ b/core/modules/layout/js/views/region-view.js @@ -22,6 +22,8 @@ title: this.model.get('label') }); this.dialogView.render(); + e.preventDefault(); + e.stopPropagation(); }, onClickAdd:function (e) { @@ -49,7 +51,9 @@ this._collectionView = new Drupal.layout.UpdatingCollectionView({ collection:blockInstances, nestedViewConstructor:Drupal.layout.BlockInstanceView, - nestedViewTagName:'div' + nestedViewTagName:'div', + el: this.$el, + nestedViewContainerSelector: '.blocks .row' }); // @todo: be selective about what change-events trigger requests to the @@ -64,7 +68,6 @@ render:function () { this.$el.html(Drupal.theme.layoutRegion(this.model.get('id'), this.model.get('label'))); - this._collectionView.setElement(this.$el.find('.row', '.blocks')); this._collectionView.render(); return this; }, diff --git a/core/modules/layout/js/views/regions-view.js b/core/modules/layout/js/views/regions-view.js deleted file mode 100644 index 234d0c8..0000000 --- a/core/modules/layout/js/views/regions-view.js +++ /dev/null @@ -1,30 +0,0 @@ -(function ($, _, Backbone, Drupal) { - - "use strict"; - - Drupal.layout = Drupal.layout || {}; - - Drupal.layout.RegionsView = Backbone.View.extend({ - initialize:function () { - this._collectionView = new Drupal.layout.UpdatingCollectionView({ - collection:this.collection, - nestedViewConstructor:Drupal.layout.RegionView, - nestedViewTagName:'div' - }); - }, - - render:function () { - this.$el.empty(); - this._collectionView.setElement(this.$el); - this._collectionView.render(); - return this; - }, - - remove:function () { - this._collectionView.remove(); - this.$el.empty(); - } - }); - - -})(jQuery, _, Backbone, Drupal); diff --git a/core/modules/layout/js/views/updatingcollection-view.js b/core/modules/layout/js/views/updatingcollection-view.js index c62929e..3568a27 100644 --- a/core/modules/layout/js/views/updatingcollection-view.js +++ b/core/modules/layout/js/views/updatingcollection-view.js @@ -15,17 +15,18 @@ throw "no child view tag name provided"; } - this._nestedViewConstructor = options.nestedViewConstructor; - this._nestedViewTagName = options.nestedViewTagName; - this._nestedViews = []; - - this.collection.each(this.addModel, this); - - this.collection.bind('add', this.addModel, this); - this.collection.bind('remove', this.removeModel, this); + this.collection.each(this._addModel, this); + this.collection.bind('add', this._addModel, this); + this.collection.bind('remove', this._removeModel, this); }, + /** + * Retrieves a nested Backbone.View by its Backbone.Model + * @param model + * @return {Backbone.Model} + * @private + */ _getViewByModel: function(model) { // @todo this probably should be cached/tracked. var vs = _(this._nestedViews).select(function (nv) { @@ -34,34 +35,68 @@ return vs.length ? vs[0] : false; }, - addModel:function (model) { - var nv = new this._nestedViewConstructor({ - tagName:this._nestedViewTagName, + /** + * Return either this.$el or if a nestedViewContainerSelector-option was + * given the element that matches this.$(nestedViewContainerSelector). + * + * @return {jQuery} + * @private + */ + _getContainerElement: function() { + if (this.options.nestedViewContainerSelector) { + return this.$(this.options.nestedViewContainerSelector); + } + else { + return this.$el; + } + }, + + /** + * Called when a new model is added to the collection. + * + * @param {Backbone.Model} model + * @private + */ + _addModel:function (model) { + var nv = new this.options.nestedViewConstructor({ + tagName:this.options.nestedViewTagName, model:model }); this._nestedViews.push(nv); if (this._rendered) { - this.$el.append(nv.render().$el); + this._getContainerElement().append(nv.render().$el); } }, - removeModel:function (model) { + /** + * Called when a new model is removed from the collection. + * + * @param {Backbone.Model} model + * @private + */ + _removeModel:function (model) { var viewToRemove = this._getViewByModel(model); this._nestedViews = _(this._nestedViews).without(viewToRemove); - if (this._rendered) { + if (this._rendered && viewToRemove) { viewToRemove.$el.remove(); } }, + /** + * Renders all nested views (one per model in the view's collection). + * + * @return {Drupal.layout.UpdatingCollectionView} + */ render:function () { this._rendered = true; - this.$el.empty(); + var $el = this._getContainerElement(); + $el.empty(); // Use the collection to make sure the order of the rendered views is // up-to-date. this.collection.each(function(m) { var nv = this._getViewByModel(m); - this.$el.append(nv.render().$el); + $el.append(nv.render().$el); }, this); return this; } diff --git a/core/modules/layout/layout.module b/core/modules/layout/layout.module index 68133de..73861d4 100755 --- a/core/modules/layout/layout.module +++ b/core/modules/layout/layout.module @@ -236,7 +236,6 @@ function layout_library_info() { $path . '/js/views/modal-view.js' => array('defer' => TRUE), $path . '/js/views/region-view.js' => array('defer' => TRUE), - $path . '/js/views/regions-view.js' => array('defer' => TRUE), $path . '/js/views/blockinstance-view.js' => array('defer' => TRUE), $path . '/js/views/blockinstancemodal-view.js' => array('defer' => TRUE), diff --git a/core/modules/layout/lib/Drupal/layout/DisplayFormController.php b/core/modules/layout/lib/Drupal/layout/DisplayFormController.php index f547d71..3d0cc3f 100755 --- a/core/modules/layout/lib/Drupal/layout/DisplayFormController.php +++ b/core/modules/layout/lib/Drupal/layout/DisplayFormController.php @@ -49,8 +49,9 @@ public function form(array $form, array &$form_state, EntityInterface $display) ); } + $locked = isset($display->locked) && is_object($display->locked) && $display->locked->owner != $GLOBALS['user']->uid; // Copied from ViewsEditFormController - if (isset($display->locked) && is_object($display->locked) && $display->locked->owner != $GLOBALS['user']->uid) { + if ($locked) { $form['locked'] = array( '#type' => 'container', '#attributes' => array('class' => array('view-locked', 'messages', 'warning')), @@ -116,36 +117,22 @@ public function form(array $form, array &$form_state, EntityInterface $display) * Produces a render array demonstration form of the display. */ private function layoutDemonstration(EntityInterface $display) { - // Render the layout in an admin context with region demonstrations. $layout = layout_manager()->createInstance($display->layout, array()); - $regions = $layout->getRegions(); - foreach ($regions as $region => $info) { - $region_text = '
' . check_plain($info['label']) . '
'; - $region_text .= '
'; - $existing_blocks = $display->getSortedBlocksByRegion($region); - foreach ($existing_blocks as $block => $placement) { - $region_text .= $this->layoutBlock($block); - } - $region_text .= '
'; - $regions[$region] = $region_text; - } $build['demonstration'] = array( '#type' => 'markup', - '#markup' => $layout->renderLayout(TRUE, $regions), + '#markup' => $layout->renderLayout(TRUE, array()), ); // Add the backbone app. $build['#attached']['library'][] = array('layout', 'layout.admin'); - $locked = isset($display->locked) && is_object($display->locked) && $display->locked->owner != $GLOBALS['user']->uid; - // Add the webservice URL and display id. $build['#attached']['js'][] =array( 'data' => array( 'layout' => array( 'webserviceURL' => url('admin/structure/layouts/manage/' . $display->id . '/webservice'), - 'id' => $display->id, - 'locked' => $locked + 'locked' => $locked, + 'layoutData' => $display->exportGroupedByRegion() ) ), 'type' => 'setting', 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 bd3e52d..7a2daca 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 @@ -183,4 +183,42 @@ public function getLayoutInstance() { return $this->layoutInstance; } + + /** + * Returns an array representation grouped for json-serialisation. + * @todo: this is a bad name (and should probably) + * + * @return array + */ + public function exportGroupedByRegion() { + // Render the layout in an admin context with region demonstrations. + $layout = layout_manager()->createInstance($this->layout, array()); + $regions = $layout->getRegions(); + $data = array( + 'id' => $this->id, + 'layout' => $this->layout + ); + foreach ($regions as $region => $info) { + $region_data = array( + 'id' => $region, + 'label' => $info['label'], + 'blockInstances' => array() + ); + $existing_blocks = $this->getSortedBlocksByRegion($region); + foreach ($existing_blocks as $block => $placement) { + // @todo: this should be proper data. Block instances should maybe + // be classed objects as well. + $block_id = str_replace('block.', '', $block); + $region_data['blockInstances'][] = array( + 'id' => $block_id, + 'label' => $block_id, + 'blockId' => 'default', + 'weight' => $placement['weight'], + 'region' => $placement['region'], + ); + } + $data['regions'][] = $region_data; + } + return $data; + } }