diff --git a/core/modules/layout/grunt.js b/core/modules/layout/grunt.js index f566742..79bb9e6 100644 --- a/core/modules/layout/grunt.js +++ b/core/modules/layout/grunt.js @@ -1,3 +1,9 @@ +/** + * @file + * Grunt file for the layout JS app. Allows for linting and build processes. + * + * @see http://gruntjs.com/ + */ module.exports = function(grunt) { // Project configuration. grunt.initConfig({ @@ -44,6 +50,7 @@ module.exports = function(grunt) { jQuery: true, Backbone: true, Drupal: true, + drupalSettings: true, VIE: true, _: true } diff --git a/core/modules/layout/js/collections/collections.js b/core/modules/layout/js/collections/collections.js index 9020725..b71c783 100644 --- a/core/modules/layout/js/collections/collections.js +++ b/core/modules/layout/js/collections/collections.js @@ -1,5 +1,8 @@ /** * @file + * This file contains the collections of models for the layout js-app. + * + * @todo: split into separate files. */ (function ($, _, Backbone, Drupal, drupalSettings) { "use strict"; diff --git a/core/modules/layout/js/models/app-model.js b/core/modules/layout/js/models/app-model.js index 8830e6c..b7535b4 100644 --- a/core/modules/layout/js/models/app-model.js +++ b/core/modules/layout/js/models/app-model.js @@ -1,5 +1,9 @@ /** * @file + * This model hold application state and corresponds to the layout containing + * regions (and block instances). + * + * @todo: probably split this AppModel into AppModel and LayoutModel. */ (function ($, _, Backbone, Drupal) { "use strict"; diff --git a/core/modules/layout/js/models/block-model.js b/core/modules/layout/js/models/block-model.js index 167b030..e746ef5 100644 --- a/core/modules/layout/js/models/block-model.js +++ b/core/modules/layout/js/models/block-model.js @@ -1,5 +1,6 @@ /** * @file + * This model corresponds to a block that can be placed */ (function ($, _, Backbone, Drupal, drupalSettings) { "use strict"; @@ -14,10 +15,10 @@ 'description': '', 'config': {} }, + // @todo: create a new BlockInstanceModel instance via a webservice (or + // another way of ensuring that the id of the BlockInstanceMdoel is unique). createBlockInstance: function() { return new Drupal.layout.BlockInstanceModel({ - // in real life this would need to come from somewhere else .. - // maybe time for UUID.js 'id': this.get('id'), 'label': this.get('id'), 'blockId': this.get('id'), @@ -26,5 +27,4 @@ } }); - })(jQuery, _, Backbone, Drupal, drupalSettings); diff --git a/core/modules/layout/js/models/blockinstance-model.js b/core/modules/layout/js/models/blockinstance-model.js index 26d3587..d39caf1 100644 --- a/core/modules/layout/js/models/blockinstance-model.js +++ b/core/modules/layout/js/models/blockinstance-model.js @@ -1,5 +1,7 @@ /** * @file + * This model corresponds to the instance of a block placed in a region of a + * layout. */ (function ($, _, Backbone, Drupal, drupalSettings) { "use strict"; @@ -11,14 +13,14 @@ return drupalSettings.layout.webserviceURL + '/block'; }, defaults: { + // Unique id of the block instance. 'id': null, 'weight': null, + // Unique id of the block (e.g CMI key). 'blockId': null, 'region': '', 'config': {} } }); - - })(jQuery, _, Backbone, Drupal, drupalSettings); diff --git a/core/modules/layout/js/models/region-model.js b/core/modules/layout/js/models/region-model.js index 6e0b22d..0cb35e3 100644 --- a/core/modules/layout/js/models/region-model.js +++ b/core/modules/layout/js/models/region-model.js @@ -1,5 +1,6 @@ /** * @file + * This model corresponds to a region in a layout. */ (function ($, _, Backbone, Drupal) { "use strict"; @@ -17,5 +18,4 @@ } }); - })(jQuery, _, Backbone, Drupal); diff --git a/core/modules/layout/js/theme.js b/core/modules/layout/js/theme.js index f4af36c..7e76446 100644 --- a/core/modules/layout/js/theme.js +++ b/core/modules/layout/js/theme.js @@ -1,5 +1,6 @@ /** * @file + * Theme functions for the layout js-app. */ (function ($) { /** @@ -29,9 +30,9 @@ /** * Theme function to get the html for a block instance. - * Poor man's _.template ... - * @return - * The corresponding HTML. + * @param id + * @param label + * @return {String} */ Drupal.theme.layoutBlock = function (id, label, attributes) { return '
' + diff --git a/core/modules/layout/js/views/app-view.js b/core/modules/layout/js/views/app-view.js index 8fcaca9..79f9b58 100644 --- a/core/modules/layout/js/views/app-view.js +++ b/core/modules/layout/js/views/app-view.js @@ -1,27 +1,32 @@ +/** + * @file + * This file holds the master view for the layout js app. + */ (function ($, _, Backbone, Drupal) { "use strict"; - // sad panda. - Backbone.emulateJSON = true; + + // @todo: be RESTful. Not sure whether the router/rest.module are in place. + // Instead of using REST verbs, POST will be used and $_POST['_method'] will + // contain the verb. Backbone.emulateHTTP = true; + // Payloads will be sent as application/x-www-form-urlencoded, in + // $_POST['model']. + Backbone.emulateJSON = true; Drupal.layout = Drupal.layout || {}; Drupal.layout.AppView = Backbone.View.extend({ - events: { - 'change': 'onChange' - }, initialize: function(options) { this.regionsView = new Drupal.layout.RegionsView({ collection: this.model.get('regions'), el: this.$el.find('.layout-display') }); - this.model.get('regions').on('change', function() { - console.log('Change!'); - }); this.settings = options.settings; }, render: function() { - // @todo: let's do this in a better way. + // @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) { return false; } @@ -40,9 +45,6 @@ this.$el.sortable('destroy'); this.regionsView.remove(); this.$el.remove(); - }, - onChange: function() { - } }); diff --git a/core/modules/layout/js/views/blockinstance-view.js b/core/modules/layout/js/views/blockinstance-view.js index a407c3e..aded8a8 100644 --- a/core/modules/layout/js/views/blockinstance-view.js +++ b/core/modules/layout/js/views/blockinstance-view.js @@ -1,3 +1,7 @@ +/** + * @file + * This view controls a single BlockInstance. + */ (function ($, _, Backbone, Drupal) { "use strict"; @@ -10,7 +14,7 @@ 'drop':'onDrop' }, onDrop:function (event, index) { - // Trigger reorder, will be handle in Drupal.layout.RegionView + // Trigger reorder, will be handled in Drupal.layout.RegionView. this.$el.trigger('reorder', [this.model, index]); // @todo: handle dropping onto enpty region. event.preventDefault(); diff --git a/core/modules/layout/js/views/blockinstancemodal-view.js b/core/modules/layout/js/views/blockinstancemodal-view.js index ee3a521..7746d03 100644 --- a/core/modules/layout/js/views/blockinstancemodal-view.js +++ b/core/modules/layout/js/views/blockinstancemodal-view.js @@ -1,5 +1,6 @@ /** * @file + * This view controls and instantiates the dialog configuring a BlockInstance. */ (function ($, _, Backbone, Drupal) { diff --git a/core/modules/layout/js/views/blockselectormodal-view.js b/core/modules/layout/js/views/blockselectormodal-view.js index 56d12d3..5e6a3c4 100644 --- a/core/modules/layout/js/views/blockselectormodal-view.js +++ b/core/modules/layout/js/views/blockselectormodal-view.js @@ -1,5 +1,11 @@ /** * @file + * BlockSelectorModalView displays Blocks available for placement in a given + * region, creates BlockInstance from a selected Block adds it to the region. + * + * @todo: use a form/server-side generated listing or a proper webservice to + * retrieve the "placeable" Blocks. + * @todo: split of Drupal.layout.BlockListItemView into separate file. */ (function ($, _, Backbone, Drupal) { diff --git a/core/modules/layout/js/views/modal-view.js b/core/modules/layout/js/views/modal-view.js index ea38807..26eba39 100644 --- a/core/modules/layout/js/views/modal-view.js +++ b/core/modules/layout/js/views/modal-view.js @@ -1,3 +1,9 @@ +/** + * @file + * Base view wrapping a dialog.js based dialog. + * + * @todo: maybe provide meaningful form-loading? + */ (function ($, _, Backbone, Drupal) { "use strict"; diff --git a/core/modules/layout/js/views/region-view.js b/core/modules/layout/js/views/region-view.js index 705f68b..f79ce05 100644 --- a/core/modules/layout/js/views/region-view.js +++ b/core/modules/layout/js/views/region-view.js @@ -1,3 +1,8 @@ +/** + * @file + * This view controls a region and the blockInstances contained in it. Opens + * the BlockSelectorModalView on request. + */ (function ($, _, Backbone, Drupal) { "use strict"; @@ -31,7 +36,8 @@ e.stopPropagation(); return ; }, - // @todo: this should be on app-view.js but for now ... + + // @todo: listen to collection events in app-view.js instead/propagate event. saveFullLayout: function() { // Show the "changed" notice. $('.display-changed').removeClass('js-hide'); @@ -46,12 +52,11 @@ nestedViewTagName:'div' }); - - // make sure we gather a bunch (300ms) of change events before we push the - // last to the server. @todo: make sure that the submit button is disabled - // so that we don't have crazy race condition thingies. + // @todo: be selective about what change-events trigger requests to the + // server. + // "Debounce" rapid sequences of change events to avoid unnecessary requests. blockInstances.on('change', _.debounce(function() { - Drupal.layout.appModel.save(); + this.saveFullLayout(); }, 100), this); blockInstances.on('add', this.saveFullLayout, this); blockInstances.on('remove', this.saveFullLayout, this); @@ -63,10 +68,12 @@ this._collectionView.render(); return this; }, + remove:function () { this.$el.empty(); this._collectionView.remove(); }, + reorderInstances:function (event, model, position) { var collection = this.model.get('blockInstances'); // handle cross collection. @@ -93,5 +100,4 @@ } }); - })(jQuery, _, Backbone, Drupal); diff --git a/core/modules/layout/layout.admin.inc b/core/modules/layout/layout.admin.inc index 20b6f15..8bc2270 100644 --- a/core/modules/layout/layout.admin.inc +++ b/core/modules/layout/layout.admin.inc @@ -97,7 +97,16 @@ function layout_master_edit(Display $display) { } /** - * Page callback: temporary webservice. + * Page callback: webservice handling RESTful(-ish) requests from the JS app. + * Currently it only updates the *whole* + * + * @todo: convert this to use the routing system (currently we use + * Backbone.emulateHTTP). + * @todo: allow this to consume application/json-payloads (currently we need + * Backbone.emulateJSON) + * @todo: protect against CSRF. We should use a session-based token. This issue + * will reoccur with all RESTful services, because a per-request token (à la + * form-token just is not RESTful). * * @param Drupal\layout\Plugin\Core\Entity\Display $display * @param null $a @@ -160,17 +169,27 @@ function layout_master_break_lock_confirm($form, &$form_state, Display $display) $cancel = drupal_container()->get('request')->query->get('cancel'); if (empty($cancel)) { - $cancel = 'admin/structure/layouts/' . $display->get('name') . '/edit'; + $cancel = 'admin/structure/layouts/manage/' . $display->get('id') . '/edit'; } $account = user_load($display->locked->owner); $form = confirm_form($form, t('Do you want to break the lock on display %name?', - array('%name' => $display->get('name'))), + array('%name' => $display->get('label'))), $cancel, t('By breaking this lock, any unsaved changes made by !user will be lost.', array('!user' => theme('username', array('account' => $account)))), t('Break lock'), t('Cancel')); - $form['actions']['submit']['#submit'][] = array($display, 'submitBreakLock'); + $form['actions']['submit']['#submit'][] = 'layout_master_break_lock_confirm_submit'; return $form; } + +/** + * Form submit handler to break_lock on a display. + */ +function layout_master_break_lock_confirm_submit(&$form, &$form_state) { + $display = $form_state['display']; + drupal_container()->get('user.tempstore')->get('layout')->delete($display->get('id')); + $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.')); +} diff --git a/core/modules/layout/lib/Drupal/layout/DisplayFormController.php b/core/modules/layout/lib/Drupal/layout/DisplayFormController.php index 05306a4..f505d69 100644 --- a/core/modules/layout/lib/Drupal/layout/DisplayFormController.php +++ b/core/modules/layout/lib/Drupal/layout/DisplayFormController.php @@ -54,12 +54,12 @@ public function form(array $form, array &$form_state, EntityInterface $display) $form['locked'] = array( '#type' => 'container', '#attributes' => array('class' => array('view-locked', 'messages', 'warning')), - '#children' => t('This display is being edited by user !user, and is therefore locked from editing by others. This lock is !age old. Click here to break this lock.', + '#children' => t('This display is being edited by user !user, and is therefore locked from editing by others. This lock is @age old. Click here to break this lock.', // @todo: add callback to break lock. array( '!user' => theme('username', array('account' => user_load($display->locked->owner))), - '!age' => format_interval(REQUEST_TIME - $display->locked->updated), - '!break' => url('admin/structure/layouts/manage/' . $display->get('id') . '/break-lock') + '@age' => format_interval(REQUEST_TIME - $display->locked->updated), + '@break' => url('admin/structure/layouts/manage/' . $display->get('id') . '/break-lock') ) ), '#weight' => -10, @@ -195,14 +195,9 @@ protected function actions(array $form, array &$form_state) { public function save(array $form, array &$form_state) { $display = $this->getEntity($form_state); $cached_display = layout_master_cache_load($display->id); - // @todo: properly handle storing template / other form values - // this is a all bohemian villages to me ... - $cached_display->set('layout', $form_state['values']['layout']); - - // As the changes are cached in TempStore loading the cached instance and - // saving it should be enough. + // All changes are already in TempStore. So a save commit all changes. $cached_display->save(); - // Cache busting. Remove this view from cache so we can edit it properly. + // Bust the TempStore. Remove the display form TempStore so it will be reloaded. drupal_container()->get('user.tempstore')->get('layout')->delete($cached_display->id); watchdog('display', 'Layout @label saved.', array('@label' => $display->label()), WATCHDOG_NOTICE); 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 4875c1e..bd3e52d 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,14 +183,4 @@ public function getLayoutInstance() { return $this->layoutInstance; } - - /** - * Submit handler to break_lock on a display. - * @todo: is this the best place? copied from ViewsUI. - */ - public function submitBreakLock(&$form, &$form_state) { - drupal_container()->get('user.tempstore')->get('layout')->delete($this->get('id')); - $form_state['redirect'] = 'admin/structure/layouts/manage/' . $this->get('id') . '/edit'; - drupal_set_message(t('The lock has been broken and you may now edit this display.')); - } }