Commit c97d0958 authored by webchick's avatar webchick

Issue #2786459 by tedbow, nod_, martin107, tkoleary, droplet, drpal:...

Issue #2786459 by tedbow, nod_, martin107, tkoleary, droplet, drpal: "Offcanvas" tray should be using the existing dialog system
parent 0d74926e
......@@ -4,10 +4,10 @@
*/
/* Position the offcanvas tray container outside the right of the viewport. */
#offcanvas {
.ui-dialog.ui-dialog-offcanvas {
box-sizing: border-box;
height: 100%;
overflow-y: auto;
overflow: hidden;
z-index: 501;
}
......@@ -16,49 +16,28 @@
right: 0;
}
/* Position the button that closes the offcanvas tray. */
#offcanvas > button.offcanvasClose {
position: static;
float: right; /* LTR */
height: 52px;
width: 40px;
border: 0;
border-radius: 0;
background: url(../../../misc/icons/bebebe/ex.svg) center center no-repeat;
color: transparent;
cursor: pointer;
z-index: 501;
}
#offcanvas > button.offcanvasClose:focus {
outline: none;
}
[dir="rtl"] #offcanvas > button.offcanvasClose {
float: left;
}
/* Create a place to name the tray. */
#offcanvas h1 {
.ui-dialog.ui-dialog-offcanvas h1 {
padding: 15px 25% 15px 15px; /* LTR */
margin-top: 0;
margin-bottom: 0;
font-size: 120%;
}
[dir="rtl"] #offcanvas h1 {
[dir="rtl"] .ui-dialog.ui-dialog-offcanvas h1 {
text-align: right;
padding-right: 0;
padding-left: 25%;
}
/* Wrap the form that's inside the offcanvas tray. */
#offcanvas > .offcanvas-content {
height: 10000px;
.ui-dialog.ui-dialog-offcanvas > .ui-dialog-content {
padding: 0 15px;
}
[dir="rtl"] #offcanvas .offcanvas-content {
[dir="rtl"] .ui-dialog.ui-dialog-offcanvas .ui-dialog-content {
text-align: right;
}
#offcanvas > .form-item,
#offcanvas > .form-item .form-item {
.ui-dialog.ui-dialog-offcanvas > .form-item,
.ui-dialog.ui-dialog-offcanvas > .form-item .form-item {
width: 100%;
}
......@@ -71,132 +50,32 @@
float: left;
}
/* Media queries. */
/* @todo Rework breakpoints: https://www.drupal.org/node/2784599. */
@media (max-width: 700px) {
#offcanvas {
position: absolute;
display: block;
right: 0;
top: 0;
width: 300px;
margin-right: -300px;
padding-top: 39px;
}
/* Wrap the rest of the site so we can control its width. */
#main-canvas-wrapper #main-canvas {
display: inline-block;
width: 100%;
}
#main-canvas-wrapper.js-tray-open #offcanvas {
margin-right: 0;
right: 0;
top: 0;
}
#main-canvas-wrapper.js-tray-open #main-canvas {
position: static;
width: 100%;
}
}
@media (min-width: 700px) {
/* Position the offcanvas tray container outside the right of the viewport. */
#offcanvas {
position: fixed;
display: inline-block;
width: 35%;
-webkit-transform: translateX(100%);
-moz-transform: translateX(100%);
-o-transform: translateX(100%);
-ms-transform: translateX(100%);
transform: translateX(100%);
}
[dir="rtl"] #offcanvas {
text-align: right;
-webkit-transform: translateX(-100%);
-moz-transform: translateX(-100%);
-o-transform: translateX(-100%);
-ms-transform: translateX(-100%);
transform: translateX(-100%);
}
/* Wrap the rest of the site so we can control its width. */
#main-canvas-wrapper #main-canvas {
display: inline-block;
width: 100%;
}
/* Move the offcanvas tray on canvas. */
#main-canvas-wrapper.js-tray-open #offcanvas {
-webkit-transform: translateX(0);
-moz-transform: translateX(0);
-o-transform: translateX(0);
-ms-transform: translateX(0);
transform: translateX(0);
}
/* Reduce the width of the main canvas to provide space for the offcanvas tray. */
#main-canvas-wrapper.js-tray-open #main-canvas {
width: 65%;
}
}
@media (min-width: 900px) {
/* Position the offcanvas tray container outside the right of the viewport. */
#offcanvas {
position: fixed;
display: inline-block;
width: 30%;
}
/* Wrap the rest of the site so we can control its width. */
#main-canvas-wrapper #main-canvas {
display: inline-block;
width: 100%;
}
/* Reduce the width of the main canvas to provide space for the offcanvas tray. */
#main-canvas-wrapper.js-tray-open #main-canvas {
width: 70%;
}
}
@media (min-width: 1000px) {
/* Position the offcanvas tray container outside the right of the viewport. */
#offcanvas {
position: fixed;
display: inline-block;
width: 25%;
}
/* Wrap the rest of the site so we can control its width. */
#main-canvas-wrapper #main-canvas {
display: inline-block;
width: 100%;
}
/* Reduce the width of the main canvas to provide space for the offcanvas tray. */
#main-canvas-wrapper.js-tray-open #main-canvas {
width: 75%;
}
}
/*
* Form layout changes, mostly specific to Bartik theme and menu.
* @todo Remove when more general form styling is done:
* https://www.drupal.org/node/2784437.
*/
#offcanvas td {
.ui-dialog.ui-dialog-offcanvas td {
width: auto;
}
#offcanvas .menu-enabled {
.ui-dialog.ui-dialog-offcanvas .menu-enabled {
width: auto;
}
#offcanvas table#menu-overview th {
.ui-dialog.ui-dialog-offcanvas table#menu-overview th {
display: none;
}
#offcanvas table#menu-overview tr td:first-child {
.ui-dialog.ui-dialog-offcanvas table#menu-overview tr td:first-child {
min-width: 110px;
}
#offcanvas details > .details-wrapper {
.ui-dialog.ui-dialog-offcanvas details > .details-wrapper {
padding: 5px;
overflow: scroll;
}
#offcanvas .tabledrag-toggle-weight {
.ui-dialog.ui-dialog-offcanvas .tabledrag-toggle-weight {
font-size: 80%;
}
#offcanvas input:focus,
#offcanvas summary:focus {
.ui-dialog.ui-dialog-offcanvas input:focus,
.ui-dialog.ui-dialog-offcanvas summary:focus {
outline: none;
box-shadow: 2px 2px #ddd;
}
......
......@@ -92,18 +92,18 @@ button.toolbar-icon.toolbar-icon-edit.toolbar-item:hover > .toolbar-icon-edit:be
* @todo Move Off-canvas css into core Off-canvas library:
* https://www.drupal.org/node/2784443.
*/
#offcanvas {
.ui-dialog.ui-dialog-offcanvas {
background: #fff;
border-left: 1px solid #ddd; /* LTR */
box-shadow: -2px 2px 1px 1px rgba(0, 0, 0, 0.1); /* LTR */
}
[dir="rtl"] #offcanvas {
[dir="rtl"] .ui-dialog.ui-dialog-offcanvas {
border-right: 1px solid #ddd;
box-shadow: 2px 2px 1px 1px rgba(0, 0, 0, 0.1);
}
/* Style the tray header. */
#offcanvas h1 {
.ui-dialog.ui-dialog-offcanvas h1 {
font-size: 120%;
border-bottom: 1px solid #ddd;
}
/**
* @file
* Drupal's off-canvas library.
*
* @todo This functionality should extracted into a new core library or a part
* of the current drupal.dialog.ajax library.
* https://www.drupal.org/node/2784443
*/
(function ($, Drupal) {
(function ($, Drupal, debounce, displace) {
'use strict';
// Set the initial state of the off-canvas element.
// If the state has been set previously, use it.
Drupal.offCanvas = {
visible: (Drupal.offCanvas ? Drupal.offCanvas.visible : false)
};
/**
* Create a wrapper container for the off-canvas element.
* The edge of the screen that the dialog should appear on.
*
* @return {jQuery}
* jQuery object that is the off-canvas wrapper element.
* @type {string}
*/
Drupal.theme.createOffCanvasWrapper = function createOffCanvasWrapper() {
return $('<div id="offcanvas" ' + (document.dir === 'ltr' ? 'data-offset-right' : 'data-offset-left') + ' role="region" aria-labelledby="offcanvas-header"></div>');
};
var edge = document.documentElement.dir === 'rtl' ? 'left' : 'right';
/**
* Create the title element for the off-canvas element.
*
* @param {string} title
* The title string.
* Resets the size of the dialog.
*
* @return {object}
* jQuery object that is the off-canvas title element.
* @param {jQuery.Event} event
* The event triggered.
*/
Drupal.theme.createTitle = function createTitle(title) {
return $('<h1 id="offcanvas-header">' + title + '</h1>');
};
function resetSize(event) {
var offsets = displace.offsets;
var $element = event.data.$element;
var $widget = $element.dialog('widget');
/**
* Create the actual off-canvas content.
*
* @param {string} data
* This is fully rendered HTML from Drupal.
*
* @return {object}
* jQuery object that is the off-canvas content element.
*/
Drupal.theme.createOffCanvasContent = function createOffCanvasContent(data) {
return $('<div class="offcanvas-content">' + data + '</div>');
};
var adjustedOptions = {
// @see http://api.jqueryui.com/position/
position: {
my: edge + ' top',
at: edge + ' top' + (offsets.top !== 0 ? '+' + offsets.top : ''),
of: window
}
};
$widget.css({
position: 'fixed',
height: ($(window).height() - (offsets.top + offsets.bottom)) + 'px'
});
$element
.dialog('option', adjustedOptions)
.trigger('dialogContentResize.outsidein');
}
/**
* Create the off-canvas close element.
*
* @param {object} offCanvasWrapper
* The jQuery off-canvas wrapper element
* @param {object} pageWrapper
* The jQuery off page wrapper element
* Adjusts the dialog on resize.
*
* @return {jQuery}
* jQuery object that is the off-canvas close element.
* @param {jQuery.Event} event
* The event triggered.
*/
Drupal.theme.createOffCanvasClose = function createOffCanvasClose(offCanvasWrapper, pageWrapper) {
return $([
'<button class="offcanvasClose" aria-label="',
Drupal.t('Close configuration tray.'),
'"><span class="visually-hidden">',
Drupal.t('Close'),
'</span></button>'
].join(''))
.on('click', function () {
pageWrapper
.removeClass('js-tray-open')
.one('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', function () {
Drupal.offCanvas.visible = false;
offCanvasWrapper.remove();
Drupal.announce(Drupal.t('Configuration tray closed.'));
}
);
});
};
function handleDialogResize(event) {
var $element = event.data.$element;
var $widget = $element.dialog('widget');
var $offsets = $widget.find('> :not(#drupal-offcanvas, .ui-resizable-handle)');
var offset = 0;
var modalHeight;
// Let scroll element take all the height available.
$element.css({height: 'auto'});
modalHeight = $widget.height();
$offsets.each(function () { offset += $(this).outerHeight(); });
// Take internal padding into account.
var scrollOffset = $element.outerHeight() - $element.height();
$element.height(modalHeight - offset - scrollOffset);
}
/**
* Command to open an off-canvas element.
* Adjusts the body padding when the dialog is resized.
*
* @param {Drupal.Ajax} ajax
* The Drupal Ajax object.
* @param {object} response
* Object holding the server response.
* @param {number} [status]
* The HTTP status code.
* @param {jQuery.Event} event
* The event triggered.
*/
Drupal.AjaxCommands.prototype.openOffCanvas = function (ajax, response, status) {
// Discover display/viewport size.
// @todo Work on breakpoints for tray size:
// https://www.drupal.org/node/2784599.
var $pageWrapper = $('#main-canvas-wrapper');
// var pageWidth = $pageWrapper.width();
// Construct off-canvas wrapper
var $offcanvasWrapper = Drupal.theme('createOffCanvasWrapper');
// Construct off-canvas internal elements.
var $offcanvasClose = Drupal.theme('createOffCanvasClose', $offcanvasWrapper, $pageWrapper);
var $title = Drupal.theme('createTitle', response.dialogOptions.title);
var $offcanvasContent = Drupal.theme('createOffCanvasContent', response.data);
// Put everything together.
$offcanvasWrapper.append([$offcanvasClose, $title, $offcanvasContent]);
// Handle opening or updating tray with content.
var existingTray = false;
if (Drupal.offCanvas.visible) {
// Remove previous content then append new content.
$pageWrapper.find('#offcanvas').remove();
existingTray = true;
}
$pageWrapper.addClass('js-tray-open');
Drupal.offCanvas.visible = true;
$pageWrapper.append($offcanvasWrapper);
if (existingTray) {
Drupal.announce(Drupal.t('Configuration tray content has been updated.'));
function bodyPadding(event) {
var $element = event.data.$element;
var $widget = $element.dialog('widget');
var $body = $('body');
var width = $widget.outerWidth();
var bodyPadding = $body.css('padding-' + edge);
if (width !== bodyPadding) {
$body.css('padding-' + edge, width + 'px');
$widget.attr('data-offset-' + edge, width);
displace();
}
else {
Drupal.announce(Drupal.t('Configuration tray opened.'));
}
$(window).on({
'dialog:aftercreate': function (event, dialog, $element, settings) {
if ($element.is('#drupal-offcanvas')) {
var eventData = {settings: settings, $element: $element};
$('.ui-dialog-offcanvas, .ui-dialog-offcanvas .ui-dialog-titlebar').toggleClass('ui-dialog-empty-title', !settings.title);
$element
.on('dialogresize.outsidein', eventData, debounce(bodyPadding, 100))
.on('dialogContentResize.outsidein', eventData, handleDialogResize)
.trigger('dialogresize.outsidein');
$element.dialog('widget').attr('data-offset-' + edge, '');
$(window)
.on('resize.outsidein scroll.outsidein', eventData, debounce(resetSize, 100))
.trigger('resize.outsidein');
}
},
'dialog:beforecreate': function (event, dialog, $element, settings) {
if ($element.is('#drupal-offcanvas')) {
// @see http://api.jqueryui.com/position/
settings.position = {
my: 'left top',
at: edge + ' top',
of: window
};
settings.dialogClass = 'ui-dialog-offcanvas';
}
},
'dialog:beforeclose': function (event, dialog, $element) {
if ($element.is('#drupal-offcanvas')) {
$(document).off('.outsidein');
$(window).off('.outsidein');
$('body').css('padding-' + edge, 0);
}
}
Drupal.attachBehaviors(document.querySelector('#offcanvas'), drupalSettings);
};
});
})(jQuery, Drupal);
})(jQuery, Drupal, Drupal.debounce, Drupal.displace);
......@@ -26,10 +26,10 @@
if (!localStorage.getItem('Drupal.contextualToolbar.isViewing')) {
return;
}
var editLink = $(e.target).find('li.outside-inblock-configure a')[0];
var editLink = $(e.target).find('a[data-dialog-renderer="offcanvas"]')[0];
if (!editLink) {
var closest = $(e.target).closest('.outside-in-editable');
editLink = closest.find('li.outside-inblock-configure a')[0];
editLink = closest.find('li a[data-dialog-renderer="offcanvas"]')[0];
}
editLink.click();
});
......@@ -48,25 +48,7 @@
// Bind Ajax behaviors to all items showing the class.
// @todo Fix contextual links to work with use-ajax links in
// https://www.drupal.org/node/2764931.
data.$el.find('.use-ajax').once('ajax').each(function () {
// Below is copied directly from ajax.js to keep behavior the same.
var element_settings = {};
// Clicked links look better with the throbber than the progress bar.
element_settings.progress = {type: 'throbber'};
// For anchor tags, these will go to the target of the anchor rather
// than the usual location.
var href = $(this).attr('href');
if (href) {
element_settings.url = href;
element_settings.event = 'click';
}
element_settings.dialogType = $(this).data('dialog-type');
element_settings.dialog = $(this).data('dialog-options');
element_settings.base = $(this).attr('id');
element_settings.element = this;
Drupal.ajax(element_settings);
});
Drupal.attachBehaviors(data.$el[0]);
// Bind a listener to all 'Quick edit' links for blocks
// Click "Edit" button in toolbar to force Contextual Edit which starts
......@@ -141,6 +123,27 @@
$('.contextual-toolbar-tab.toolbar-tab button').on('click', function () {
setToggleActiveMode();
});
var search = Drupal.ajax.WRAPPER_FORMAT + '=drupal_dialog';
var replace = Drupal.ajax.WRAPPER_FORMAT + '=drupal_dialog_offcanvas';
// Loop through all Ajax links and change the format to offcanvas when
// needed.
Drupal.ajax.instances
.filter(function (instance) {
var hasElement = instance && !!instance.element;
var rendererOffcanvas = false;
var wrapperOffcanvas = false;
if (hasElement) {
rendererOffcanvas = $(instance.element).attr('data-dialog-renderer') === 'offcanvas';
wrapperOffcanvas = instance.options.url.indexOf('drupal_dialog_offcanvas') === -1;
}
return hasElement && rendererOffcanvas && wrapperOffcanvas;
})
.forEach(function (instance) {
// @todo Move logic for data-dialog-renderer attribute into ajax.js
// https://www.drupal.org/node/2784443
instance.options.url = instance.options.url.replace(search, replace);
});
}
};
......
......@@ -10,6 +10,7 @@ drupal.outside_in:
dependencies:
- core/jquery
- core/drupal
- core/drupal.ajax
drupal.off_canvas:
version: VERSION
js:
......
......@@ -32,7 +32,8 @@ function outside_in_contextual_links_view_alter(&$element, $items) {
if (isset($element['#links']['outside-inblock-configure'])) {
$element['#links']['outside-inblock-configure']['attributes'] = [
'class' => ['use-ajax'],
'data-dialog-type' => 'offcanvas',
'data-dialog-type' => 'dialog',
'data-dialog-renderer' => 'offcanvas',
];
$element['#attached']['library'][] = 'outside_in/drupal.off_canvas';
......
......@@ -3,7 +3,7 @@ services:
class: Drupal\outside_in\Render\MainContent\OffCanvasRender
arguments: ['@title_resolver', '@renderer']
tags:
- { name: render.main_content_renderer, format: drupal_offcanvas }
- { name: render.main_content_renderer, format: drupal_dialog_offcanvas }
outside_in.manager:
class: Drupal\outside_in\OutsideInManager
......
......@@ -31,18 +31,21 @@ class OpenOffCanvasDialogCommand extends OpenDialogCommand {
* populated automatically from the current request.
*/
public function __construct($title, $content, array $dialog_options = [], $settings = NULL) {
$dialog_options['modal'] = FALSE;
parent::__construct('#drupal-offcanvas', $title, $content, $dialog_options, $settings);
$this->dialogOptions['modal'] = FALSE;
$this->dialogOptions['autoResize'] = FALSE;
$this->dialogOptions['resizable'] = 'w';
$this->dialogOptions['draggable'] = FALSE;
$this->dialogOptions['drupalAutoButtons'] = FALSE;
}
/**
* {@inheritdoc}
*/
public function render() {
$this->dialogOptions['modal'] = FALSE;
return [
'command' => 'openOffCanvas',
'selector' => $this->selector,
'command' => 'openDialog',
'selector' => '#drupal-offcanvas',
'settings' => $this->settings,
'data' => $this->getRenderedContent(),
'dialogOptions' => $this->dialogOptions,
......
......@@ -33,18 +33,23 @@ public function testDialog() {
$dialog_contents = \Drupal::service('renderer')->renderRoot($dialog_renderable);
$offcanvas_expected_response = [
'command' => 'openOffCanvas',
'command' => 'openDialog',
'selector' => '#drupal-offcanvas',
'settings' => NULL,
'data' => $dialog_contents,
'dialogOptions' => [
'modal' => FALSE,
'title' => 'AJAX Dialog contents',
],
'dialogOptions' =>
[
'title' => 'AJAX Dialog contents',
'modal' => FALSE,
'autoResize' => FALSE,
'resizable' => 'w',
'draggable' => FALSE,
'drupalAutoButtons' => FALSE,
],
];
// Emulate going to the JS version of the page and check the JSON response.
$ajax_result = $this->drupalGetAjax('ajax-test/dialog-contents', ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_offcanvas']]);
$ajax_result = $this->drupalGetAjax('ajax-test/dialog-contents', ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_dialog_offcanvas']]);
$this->assertEqual($offcanvas_expected_response, $ajax_result[3], 'Off-canvas dialog JSON response matches.');
}
......
......@@ -18,6 +18,12 @@ offcanvas_test.thing2:
path: '/offcanvas-thing2'
defaults:
_controller: '\Drupal\offcanvas_test\Controller\TestController::thing2'
_title: 'Thing 2'
requirements:
_access: 'TRUE'
offcanvas_test.dialog_links:
path: '/offcanvas-dialog-links'
defaults:
_controller: '\Drupal\offcanvas_test\Controller\TestController::otherDialogLinks'
requirements:
_access: 'TRUE'
......@@ -49,11 +49,12 @@ public function linksDisplay() {
'#url' => Url::fromRoute('offcanvas_test.thing1'),
'#attributes' => [
'class' => ['use-ajax'],
'data-dialog-type' => 'offcanvas',
'data-dialog-type' => 'dialog',
'data-dialog-renderer' => 'offcanvas',
],
'#attached' => [
'library' => [
'outside_in/drupal.off_canvas',
'outside_in/drupal.outside_in',
],
],
],
......@@ -63,15 +64,72 @@ public function linksDisplay() {
'#url' => Url::fromRoute('offcanvas_test.thing2'),
'#attributes' => [
'class' => ['use-ajax'],
'data-dialog-type' => 'offcanvas',
'data-dialog-type' => 'dialog',
'data-dialog-renderer' => 'offcanvas',
],
'#attached' => [
'library' => [
'outside_in/drupal.off_canvas',
'outside_in/drupal.outside_in',
],
],
],
'other_dialog_links' => [
'#title' => 'Display more links!',
'#type' => 'link',
'#url' => Url::fromRoute('offcanvas_test.dialog_links'),
'#attributes' => [
'class' => ['use-ajax'],
'data-dialog-type' => 'dialog',
'data-dialog-renderer' => 'offcanvas',
],
'#attached' => [
'library' => [
'outside_in/drupal.outside_in',
],
],
],
];
}
/**
* Displays dialogs links to be displayed inside the offcanvas tray.
*
* This links are used to test opening a modal and another offcanvas link from
* inside the offcanvas tray.
*
* @todo Update tests to check these links work in the offcanvas tray.
* https://www.drupal.org/node/2790073
*
* @return array
* Render array with links.
*/
public function otherDialogLinks() {
return [
'#theme' => 'links',
'#links' => [
'modal_link' => [
'title' => 'Open modal!',
'url' => Url::fromRoute('offcanvas_test.thing2'),