diff --git a/.eslintignore b/.eslintignore index 0948100b4106f..5a82bdeb3c28e 100644 --- a/.eslintignore +++ b/.eslintignore @@ -12,5 +12,4 @@ joomla-field-permissions.w-c.es6.js joomla-field-send-test-mail.w-c.es6.js joomla-field-simple-color.w-c.es6.js joomla-field-subform.w-c.es6.js -joomla-field-switcher.w-c.es6.js # End of TODO diff --git a/administrator/templates/atum/scss/blocks/_form.scss b/administrator/templates/atum/scss/blocks/_form.scss index 2b8cf5abd6b84..7c52ca887f4f2 100644 --- a/administrator/templates/atum/scss/blocks/_form.scss +++ b/administrator/templates/atum/scss/blocks/_form.scss @@ -37,6 +37,9 @@ .controls { margin-left: 0; + .switcher__legend { + float:none; + } } .control-label { diff --git a/administrator/templates/atum/scss/pages/_com_config.scss b/administrator/templates/atum/scss/pages/_com_config.scss index 65deef99f9456..78bac875ebd47 100644 --- a/administrator/templates/atum/scss/pages/_com_config.scss +++ b/administrator/templates/atum/scss/pages/_com_config.scss @@ -22,4 +22,8 @@ .revert-controls .controls { margin-left: 0; } + + .switcher__legend { + margin-left: -220px; + } } diff --git a/build/media_source/system/joomla.asset.json b/build/media_source/system/joomla.asset.json index a2b3c9b371780..1cc953dc4693e 100644 --- a/build/media_source/system/joomla.asset.json +++ b/build/media_source/system/joomla.asset.json @@ -49,6 +49,12 @@ "media/system/js/showon.min.js" ] }, + "switcher": { + "name": "switcher", + "css": [ + "media/system/css/fields/switcher.min.css" + ] + }, "fields.validate": { "name": "fields.validate", "dependencies": [ diff --git a/build/media_source/system/js/fields/joomla-field-switcher.w-c.es6.js b/build/media_source/system/js/fields/joomla-field-switcher.w-c.es6.js deleted file mode 100644 index cc2a876d49afa..0000000000000 --- a/build/media_source/system/js/fields/joomla-field-switcher.w-c.es6.js +++ /dev/null @@ -1,261 +0,0 @@ -((customElements) => { - // Keycodes - const KEYCODE = { - ENTER: 13, - SPACE: 32, - }; - - class JoomlaSwitcherElement extends HTMLElement { - /* Attributes to monitor */ - static get observedAttributes() { return ['type', 'off-text', 'on-text']; } - - get type() { return this.getAttribute('type'); } - - set type(value) { return this.setAttribute('type', value); } - - get offText() { return this.getAttribute('off-text') || 'Off'; } - - get onText() { return this.getAttribute('on-text') || 'On'; } - - // attributeChangedCallback(attr, oldValue, newValue) {} - - constructor() { - super(); - - this.inputs = []; - this.spans = []; - this.initialized = false; - this.inputsContainer = ''; - this.newActive = ''; - this.inputLabel = ''; - this.inputLabelText = ''; - - // Let's bind some functions so we always have the same context - this.createMarkup = this.createMarkup.bind(this); - this.addListeners = this.addListeners.bind(this); - this.removeListeners = this.removeListeners.bind(this); - this.switch = this.switch.bind(this); - this.toggle = this.toggle.bind(this); - this.keyEvents = this.keyEvents.bind(this); - this.onFocus = this.onFocus.bind(this); - } - - /* Lifecycle, element appended to the DOM */ - connectedCallback() { - // Element was moved so we need to re add the event listeners - if (this.initialized && this.inputs.length > 0) { - this.addListeners(); - return; - } - - this.inputs = [].slice.call(this.querySelectorAll('input')); - - if (this.inputs.length !== 2 || this.inputs[0].type !== 'radio') { - throw new Error('`Joomla-switcher` requires two inputs type="radio"'); - } - - // this.inputLabel = document.querySelector(`[for="${this.id}"]`); - // - // if (this.inputLabel) { - // this.inputLabelText = this.inputLabel.innerText; - // } - - // Create the markup - this.createMarkup(); - - this.inputsContainer = this.inputs[0].parentNode; - - this.inputsContainer.setAttribute('role', 'switch'); - - if (this.inputs[1].checked) { - this.inputs[1].parentNode.classList.add('active'); - this.spans[1].classList.add('active'); - - // Aria-label ONLY in the container span! - this.inputsContainer.setAttribute('aria-labelledby', `${this.id}-lbl`); // this.spans[1].innerHTML); - } else { - this.spans[0].classList.add('active'); - - // Aria-label ONLY in the container span! - this.inputsContainer.setAttribute('aria-label', this.spans[0].innerHTML); - } - - this.addListeners(); - } - - /* Lifecycle, element removed from the DOM */ - disconnectedCallback() { - this.removeListeners(); - } - - /* Method to dispatch events */ - dispatchCustomEvent(eventName) { - const OriginalCustomEvent = new CustomEvent(eventName, { bubbles: true, cancelable: true }); - OriginalCustomEvent.relatedTarget = this; - this.dispatchEvent(OriginalCustomEvent); - this.removeEventListener(eventName, this); - } - - /** Method to build the switch */ - createMarkup() { - let checked = 0; - - // If no type has been defined, the default as "success" - if (!this.type) { - this.setAttribute('type', 'success'); - } - - // Create the first 'span' wrapper - const spanFirst = document.createElement('fieldset'); - spanFirst.classList.add('switcher'); - spanFirst.classList.add(this.type); - spanFirst.setAttribute('tabindex', '0'); - - // Set the id to the fieldset - spanFirst.id = this.id; - // Remove the id from the custom Element - // this.removeAttribute('id'); - - const switchEl = document.createElement('span'); - switchEl.classList.add('switch'); - switchEl.classList.add(this.type); - - this.inputs.forEach((input, index) => { - // Remove the tab focus from the inputs - input.setAttribute('tabindex', '-1'); - - if (input.checked) { - spanFirst.setAttribute('aria-checked', true); - } - - spanFirst.appendChild(input); - - if (index === 1 && input.checked) { - checked = 1; - } - }); - - spanFirst.appendChild(switchEl); - - // Create the second 'span' wrapper - const spanSecond = document.createElement('span'); - spanSecond.classList.add('switcher-labels'); - - const labelFirst = document.createElement('span'); - labelFirst.classList.add('switcher-label-0'); - labelFirst.innerHTML = `${this.offText}`; - - const labelSecond = document.createElement('span'); - labelSecond.classList.add('switcher-label-1'); - labelSecond.innerHTML = `${this.onText}`; - - if (checked === 0) { - labelFirst.classList.add('active'); - } else { - labelSecond.classList.add('active'); - } - - this.spans.push(labelFirst); - this.spans.push(labelSecond); - spanSecond.appendChild(labelFirst); - spanSecond.appendChild(labelSecond); - - // Append everything back to the main element - this.appendChild(spanFirst); - this.appendChild(spanSecond); - - this.initialized = true; - } - - /** Method to toggle the switch */ - switch() { - this.spans.forEach((span) => { - span.classList.remove('active'); - }); - - if (this.inputsContainer.classList.contains('active')) { - this.inputsContainer.classList.remove('active'); - } else { - this.inputsContainer.classList.add('active'); - } - - // Remove active class from all inputs - this.inputs.forEach((input) => { - input.classList.remove('active'); - }); - - // Check if active - if (this.newActive === 1) { - this.inputs[this.newActive].classList.add('active'); - this.inputs[1].setAttribute('checked', ''); - this.inputs[0].removeAttribute('checked'); - this.inputsContainer.setAttribute('aria-checked', true); - - // Aria-label ONLY in the container span! - this.inputsContainer.setAttribute('aria-label', `${this.inputLabelText} ${this.spans[1].innerHTML}`); - - // Dispatch the "joomla.switcher.on" event - this.dispatchCustomEvent('joomla.switcher.on'); - } else { - this.inputs[1].removeAttribute('checked'); - this.inputs[0].setAttribute('checked', ''); - this.inputs[0].classList.add('active'); - this.inputsContainer.setAttribute('aria-checked', false); - - // Aria-label ONLY in the container span! - this.inputsContainer.setAttribute('aria-label', `${this.inputLabelText} ${this.spans[0].innerHTML}`); - - // Dispatch the "joomla.switcher.off" event - this.dispatchCustomEvent('joomla.switcher.off'); - } - - this.spans[this.newActive].classList.add('active'); - } - - /** Method to toggle the switch */ - toggle() { - this.newActive = this.inputs[1].classList.contains('active') ? 0 : 1; - this.switch(); - } - - keyEvents(event) { - if (event.keyCode === KEYCODE.ENTER || event.keyCode === KEYCODE.SPACE) { - event.preventDefault(); - this.newActive = this.inputs[1].classList.contains('active') ? 0 : 1; - this.switch(); - } - } - - onFocus() { - this.inputsContainer.focus(); - } - - addListeners() { - if (this.inputLabel) { - this.inputLabel.addEventListener('click', this.onFocus); - } - - this.inputs.forEach((switchEl) => { - // Add the active class on click - switchEl.addEventListener('click', this.toggle); - }); - - this.inputsContainer.addEventListener('keydown', this.keyEvents); - } - - removeListeners() { - if (this.inputLabel) { - this.inputLabel.removeEventListener('click', this.onFocus); - } - - this.inputs.forEach((switchEl) => { - // Add the active class on click - switchEl.removeEventListener('click', this.toggle); - }); - - this.inputsContainer.removeEventListener('keydown', this.keyEvents); - } - } - - customElements.define('joomla-field-switcher', JoomlaSwitcherElement); -})(customElements); diff --git a/build/media_source/system/scss/fields/joomla-field-switcher.scss b/build/media_source/system/scss/fields/joomla-field-switcher.scss deleted file mode 100644 index 593915096a8f0..0000000000000 --- a/build/media_source/system/scss/fields/joomla-field-switcher.scss +++ /dev/null @@ -1,176 +0,0 @@ -// Switcher - -// -// Functions -// - -// Retrieve color Sass maps -@function color($key: "blue") { - @return map-get($colors, $key); -} - -@function theme-color($key: "primary") { - @return map-get($theme-colors, $key); -} - -@function gray($key: "100") { - @return map-get($grays, $key); -} - -// Request a theme color level -@function theme-color-level($color-name: "primary", $level: 0) { - $color: theme-color($color-name); - $color-base: if($level > 0, $black, $white); - $level: abs($level); - - @return mix($color-base, $color, $level * $theme-color-interval); -} - - -// -// Variables -// - -$switcher-width: 62px; -$switcher-height: 28px; -$border-width: 1px; -$border-radius: 0; - -$white: #fff; -$gray-600: #868e96; - -$blue: #1e87f0; -$red: #f0506e; -$yellow: #faa05a; -$green: #2f7d32; - -$theme-colors: (); -$theme-colors: map-merge(( - primary: $blue, - secondary: $gray-600, - success: $green, - warning: $yellow, - danger: $red -), $theme-colors); - -$theme-color-interval: 8%; - - -// -// Base styles -// - -joomla-field-switcher { - box-sizing: border-box; - display: block; - height: $switcher-height; - - .switcher { - position: relative; - box-sizing: border-box; - display: inline-block; - width: $switcher-width; - height: $switcher-height; - vertical-align: middle; - cursor: pointer; - user-select: none; - background-color: darken($white, 5%); - background-clip: content-box; - border: $border-width solid rgba(0,0,0,.18); - border-radius: $border-radius; - box-shadow: 0 0 0 0 rgb(223,223,223) inset; - transition: border .4s ease 0s, box-shadow .4s ease 0s; - - &.active { - transition: border .4s ease 0s, box-shadow .4s ease 0s, background-color 1.2s ease 0s; - - .switch { - left: calc((#{$switcher-width} / 2) - (#{$border-width} * 2)); - } - } - - } - - input { - position: absolute; - top: 0; - left: 0; - z-index: 2; - width: $switcher-width; - height: $switcher-height; - padding: 0; - margin: 0; - cursor: pointer; - opacity: 0; - } - - .switch { - position: absolute; - top: 0; - left: 0; - width: calc(#{$switcher-width} / 2); - height: calc(#{$switcher-height} - (#{$border-width} * 2)); - background: $white; - border-radius: $border-radius; - box-shadow: 0 1px 3px rgba(0,0,0,.15); - transition: left .2s ease 0s; - } - - .switcher:focus .switch { - animation: switcherPulsate 1.5s infinite; - } - - input:checked { - z-index: 0; - } - - .switcher-labels { - position: relative; - - span { - position: absolute; - top: 0; - color: var(--dark); - visibility: hidden; - opacity: 0; - transition: all .2s ease-in-out; - - &.active { - visibility: visible; - opacity: 1; - transition: all .2s ease-in-out; - } - - } - - } - -} - -[dir="rtl"] joomla-field-switcher .switcher-labels span { - right: 10px; -} -[dir="ltr"] joomla-field-switcher .switcher-labels span { - left: 10px; -} - -// Alternate styles -@each $color, $value in $theme-colors { - joomla-field-switcher[type="#{$color}"] .switcher.active { - background-color: theme-color-level($color); - border-color: theme-color-level($color); - box-shadow: 0 0 0 calc(#{$switcher-height} / 2) theme-color-level($color) inset; - } -} - -@keyframes switcherPulsate { - 0% { - box-shadow: 0 0 0 0 rgba(66,133,244,.55); - } - 70% { - box-shadow: 0 0 0 10px rgba(66,133,244,0); - } - 100% { - box-shadow: 0 0 0 0 rgba(66,133,244,0); - } -} diff --git a/build/media_source/system/scss/fields/switcher.scss b/build/media_source/system/scss/fields/switcher.scss new file mode 100644 index 0000000000000..3a17f201a45cf --- /dev/null +++ b/build/media_source/system/scss/fields/switcher.scss @@ -0,0 +1,80 @@ +.switcher { + position: relative; + width: 18rem; + height: 28px; +} +.switcher input { + position: absolute; + top: 0; + z-index: 2; + opacity: 0; + cursor: pointer; + height: 28px; + width: 62px; + margin: 0; +} +.switcher input:checked { + z-index: 1; +} +.switcher input:checked + label { + opacity: 1; +} +.switcher input:not(:checked) + label { + opacity: 0; +} +.switcher label { + line-height: 28px; + display: inline-block; + width: 6rem; + height: 100%; + margin-left: 70px; + text-align: left; + position: absolute; + transition: opacity 0.25s ease; + margin-bottom: 0; +} +.switcher .toggle-outside { + height: 100%; + padding: 0.25rem; + overflow: hidden; + transition: 0.25s ease all; + background: grey; + position: absolute; + width: 62px; + box-sizing: border-box; +} + +.switcher input ~ input:checked ~ .toggle-outside{ + background: green; +} + +.switcher .toggle-inside { + height: 20px; + width: 20px; + background: white; + position: absolute; + transition: 0.25s ease all; +} +.switcher input:checked ~ .toggle-outside .toggle-inside { +left: 0.25rem; +} +.switcher input ~ input:checked ~ .toggle-outside .toggle-inside { +left: 38px; +} +.switcher__legend { +margin-bottom: 1rem; +font-size: 1rem; +font-weight: 400; +float: left; +width: 220px; +padding-top: 5px; +padding-right: 5px; +text-align: left; +} +.col-md-3 .switcher__legend { + margin-left: 0; +} + +.col-md-9 .switcher__legend { + margin-left: -220px; +} diff --git a/layouts/joomla/content/options_default.php b/layouts/joomla/content/options_default.php index b372e314075d5..4d0251d6f134d 100644 --- a/layouts/joomla/content/options_default.php +++ b/layouts/joomla/content/options_default.php @@ -28,13 +28,14 @@ 'auto', 'relative' => true)); ?> showon, $field->formControl, $field->group)) . '\''; ?> -
> + showlabel)) : ?> +
>
input; ?>
+
renderField(); ?> -
diff --git a/layouts/joomla/form/field/radio/switcher.php b/layouts/joomla/form/field/radio/switcher.php index ebb2872fe64df..9198742d376b0 100644 --- a/layouts/joomla/form/field/radio/switcher.php +++ b/layouts/joomla/form/field/radio/switcher.php @@ -9,6 +9,7 @@ defined('JPATH_BASE') or die; +use Joomla\CMS\Factory; use Joomla\CMS\HTML\HTMLHelper; extract($displayData, null); @@ -48,6 +49,9 @@ return ''; } +// Load the css files +Factory::getApplication()->getDocument()->getWebAssetManager()->enableAsset('switcher'); + /** * The format of the input tag to be filled in using sprintf. * %1 - id @@ -55,55 +59,27 @@ * %3 - value * %4 = any other attributes */ -$format = ''; -$alt = preg_replace('/[^a-zA-Z0-9_\-]/', '_', $name); - -HTMLHelper::_('webcomponent', 'system/fields/joomla-field-switcher.min.js', ['version' => 'auto', 'relative' => true]); - -// Set the type of switcher -$type = ''; - -if ($pos = strpos($class, 'switcher-')) -{ - $type = 'type="' . strtok(substr($class, $pos + 9), ' ') . '"'; -} - -// Add the attributes of the fieldset in an array -$attribs = [ - 'id="' . $id . '"', - $type, - 'off-text="' . $options[0]->text . '"', - 'on-text="' . $options[1]->text . '"', -]; - -if (!empty($disabled)) -{ - $attribs[] = 'disabled'; -} - -if (!empty($onclick)) -{ - $attribs[] = 'onclick="' . $onclick . '()"'; -} - -if (!empty($onchange)) -{ - $attribs[] = 'onchange="' . $onchange . '()"'; -} +$input = ''; ?> -> +
+ + + +
$option) : ?> value == $value) ? 'checked="checked"' : ''; - $active = ((string) $option->value == $value) ? 'class="active"' : ''; - - // Initialize some JavaScript option attributes. - $oid = $id . $i; - $ovalue = htmlspecialchars($option->value, ENT_COMPAT, 'UTF-8'); - $attributes = array_filter(array($checked, $active, null)); + $checked = ((string) $option->value == $value) ? 'checked="checked"' : ''; + $active = ((string) $option->value == $value) ? 'class="active"' : ''; + $oid = $id . $i; + $ovalue = htmlspecialchars($option->value, ENT_COMPAT, 'UTF-8'); + $attributes = array_filter(array($checked, $active, null)); + $text = $options[$i]->text; ?> - + + ' . $text . ''; ?> - + +
+
diff --git a/libraries/src/Form/FormField.php b/libraries/src/Form/FormField.php index 4ce7d5b10ce52..c7dd92429f9cd 100644 --- a/libraries/src/Form/FormField.php +++ b/libraries/src/Form/FormField.php @@ -948,7 +948,7 @@ public function renderField($options = array()) $options['rel'] = ''; - if (empty($options['hiddenLabel']) && $this->getAttribute('hiddenLabel')) + if (empty($options['hiddenLabel']) && $this->getAttribute('hiddenLabel') || $this->class === 'switcher') { $options['hiddenLabel'] = true; } diff --git a/templates/cassiopeia/scss/blocks/_form.scss b/templates/cassiopeia/scss/blocks/_form.scss index b5cc5fd8a3ea1..76ee6f24a9421 100644 --- a/templates/cassiopeia/scss/blocks/_form.scss +++ b/templates/cassiopeia/scss/blocks/_form.scss @@ -45,20 +45,21 @@ form:not(.form-no-margin) { } -form .form-no-margin { +.form-no-margin { .control-group { .controls { margin-left: 0; + .switcher__legend { + float:none; + } } .control-label { float: none; } - } - } .spacer hr { diff --git a/tests/javascript/joomla-switcher/fixtures/fixture.html b/tests/javascript/joomla-switcher/fixtures/fixture.html deleted file mode 100644 index c10a6afa1688c..0000000000000 --- a/tests/javascript/joomla-switcher/fixtures/fixture.html +++ /dev/null @@ -1,6 +0,0 @@ -
- - - - -
diff --git a/tests/javascript/joomla-switcher/joomla-switcher.spec.js b/tests/javascript/joomla-switcher/joomla-switcher.spec.js deleted file mode 100644 index 32f24f12df7f6..0000000000000 --- a/tests/javascript/joomla-switcher/joomla-switcher.spec.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * @package Joomla.Tests - * @subpackage JavaScript Tests - * - * @copyright Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved. - * @license GNU General Public License version 2 or later; see LICENSE.txt - * - * @since 4.0.0 - * @version 1.0.0 - */ -describe('Joomla switcher tests', () => { - // Set up the script - beforeEach(() => { - fixture.setBase('tests/javascript/joomla-switcher') - html_fixture = fixture.load('fixtures/fixture.html'); - window.fix = html_fixture[0]; - }); - - afterEach(() => { - fixture.cleanup(); - }); - - describe('Joomla switcher first option preselected', () => { - it('Should have a constructor name: JoomlaSwitcherElement', () => { - const switcher = fix.querySelector('joomla-field-switcher'); - expect(switcher.constructor.name).toBe('JoomlaSwitcherElement'); - }); - - it('Joomla switcher have 5 spans inside', () => { - console.log(fix) - const switcher = fix.querySelector('joomla-field-switcher'); - const container = switcher.querySelector('fieldset.switcher'); - const spans = [].slice.call(switcher.querySelectorAll('span')); - expect(spans.length).toBe(4) && expect(container.length).toBe(1); - }); - - it('Joomla switcher have 1st span with aria-checked true', () => { - const switcher = fix.querySelector('joomla-field-switcher'); - const container = switcher.querySelector('fieldset.switcher'); - expect(container.getAttribute('aria-checked')).toBe('true'); - }); - - it('Joomla switcher have 1st span to have class active', () => { - const switcher = fix.querySelector('joomla-field-switcher'); - const span = switcher.querySelector('span.switcher-label-0'); - expect(span.classList.contains('active')).toBe(true); - }); - - it('Joomla switcher have 2nd span not to have class active', () => { - const switcher = fix.querySelector('joomla-field-switcher'); - const span = switcher.querySelector('span.switcher-label-1'); - expect(span.classList.contains('active')).toBe(false); - }); - - it('Joomla switcher on click change state', () => { - const clickable = fix.querySelector('input#sw1'); - const switcher = fix.querySelector('joomla-field-switcher'); - const span0 = switcher.querySelector('span.switcher-label-0'); - const span1 = switcher.querySelector('span.switcher-label-1'); - const container = switcher.querySelector('fieldset.switcher'); - clickable.click(); - expect(container.getAttribute('aria-checked')).toBe('true'); - expect(span0.classList.contains('active')).toBe(false); - expect(span1.classList.contains('active')).toBe(true); - }); - }); - -}); diff --git a/tests/javascript/karma.conf.js b/tests/javascript/karma.conf.js index 0f4213473088d..e1b0a1b7f3e27 100644 --- a/tests/javascript/karma.conf.js +++ b/tests/javascript/karma.conf.js @@ -19,14 +19,6 @@ module.exports = function (config) { // Load web components polyfill { pattern: 'media/vendor/webcomponentsjs/js/webcomponents-sd-ce-pf.js', loaded: true, served: true, watch: false }, - // Load the files to test against - { pattern: 'media/system/css/fields/joomla-field-switcher.css', loaded: true, served: true, watch: false }, - { pattern: 'media/system/js/fields/joomla-field-switcher-es5.js', loaded: true, served: true, watch: false }, - - - // Load the tests definitions files - 'tests/javascript/joomla-switcher/*.spec.js', - ], // preprocess matching files before serving them to the browser @@ -69,6 +61,9 @@ module.exports = function (config) { // if true, Karma captures browsers, runs the tests and exits singleRun: false, + // Currently we have no tests as we've moved switcher away. Once we add tests change this back to true + failOnEmptyTestSuite: false, + // list of plugins plugins: [ 'karma-fixture',