123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591 |
- /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
- define([
- 'jquery',
- 'jquery/ui',
- 'jquery/jquery-storageapi',
- 'mage/mage'
- ], function ($) {
- 'use strict';
- var hideProps = {},
- showProps = {};
- hideProps.height = 'hide';
- showProps.height = 'show';
- $.widget('mage.collapsible', {
- options: {
- active: false,
- disabled: false,
- collapsible: true,
- header: '[data-role=title]',
- content: '[data-role=content]',
- trigger: '[data-role=trigger]',
- closedState: null,
- openedState: null,
- disabledState: null,
- ajaxUrlElement: '[data-ajax=true]',
- ajaxContent: false,
- loadingClass: null,
- saveState: false,
- animate: false,
- icons: {
- activeHeader: null,
- header: null
- },
- collateral: {
- element: null,
- openedState: null
- }
- },
- /**
- * @private
- */
- _create: function () {
- this.storage = $.localStorage;
- this.icons = false;
- if (typeof this.options.icons === 'string') {
- this.options.icons = $.parseJSON(this.options.icons);
- }
- this._processPanels();
- this._processState();
- this._refresh();
- if (this.options.icons.header && this.options.icons.activeHeader) {
- this._createIcons();
- this.icons = true;
- }
- this.element.on('dimensionsChanged', function (e) {
- if (e.target && e.target.classList.contains('active')) {
- this._scrollToTopIfVisible(e.target);
- }
- }.bind(this));
- this._bind('click');
- this._trigger('created');
- },
- /**
- * @private
- */
- _refresh: function () {
- this.trigger.attr('tabIndex', 0);
- if (this.options.active && !this.options.disabled) {
- if (this.options.openedState) {
- this.element.addClass(this.options.openedState);
- }
- if (this.options.collateral.element && this.options.collateral.openedState) {
- $(this.options.collateral.element).addClass(this.options.collateral.openedState);
- }
- if (this.options.ajaxContent) {
- this._loadContent();
- }
- // ARIA (updates aria attributes)
- this.header.attr({
- 'aria-selected': false
- });
- } else if (this.options.disabled) {
- this.disable();
- } else {
- this.content.hide();
- if (this.options.closedState) {
- this.element.addClass(this.options.closedState);
- }
- }
- },
- /**
- * Processing the state:
- * If deep linking is used and the anchor is the id of the content or the content contains this id,
- * and the collapsible element is a nested one having collapsible parents, in order to see the content,
- * all the parents must be expanded.
- * @private
- */
- _processState: function () {
- var anchor = window.location.hash,
- isValid = $.mage.isValidSelector(anchor),
- urlPath = window.location.pathname.replace(/\./g, ''),
- state;
- this.stateKey = encodeURIComponent(urlPath + this.element.attr('id'));
- if (isValid &&
- ($(this.content.find(anchor)).length > 0 || this.content.attr('id') === anchor.replace('#', ''))
- ) {
- this.element.parents('[data-collapsible=true]').collapsible('forceActivate');
- if (!this.options.disabled) {
- this.options.active = true;
- if (this.options.saveState) { //eslint-disable-line max-depth
- this.storage.set(this.stateKey, true);
- }
- }
- } else if (this.options.saveState && !this.options.disabled) {
- state = this.storage.get(this.stateKey);
- if (typeof state === 'undefined' || state === null) {
- this.storage.set(this.stateKey, this.options.active);
- } else if (state === true) {
- this.options.active = true;
- } else if (state === false) {
- this.options.active = false;
- }
- }
- },
- /**
- * @private
- */
- _createIcons: function () {
- var icons = this.options.icons;
- if (icons) {
- $('<span>')
- .addClass(icons.header)
- .attr('data-role', 'icons')
- .prependTo(this.header);
- if (this.options.active && !this.options.disabled) {
- this.header.children('[data-role=icons]')
- .removeClass(icons.header)
- .addClass(icons.activeHeader);
- }
- }
- },
- /**
- * @private
- */
- _destroyIcons: function () {
- this.header
- .children('[data-role=icons]')
- .remove();
- },
- /**
- * @private
- */
- _destroy: function () {
- var options = this.options;
- this.element.removeAttr('data-collapsible');
- this.trigger.removeAttr('tabIndex');
- if (options.openedState) {
- this.element.removeClass(options.openedState);
- }
- if (this.options.collateral.element && this.options.collateral.openedState) {
- $(this.options.collateral.element).removeClass(this.options.collateral.openedState);
- }
- if (options.closedState) {
- this.element.removeClass(options.closedState);
- }
- if (options.disabledState) {
- this.element.removeClass(options.disabledState);
- }
- if (this.icons) {
- this._destroyIcons();
- }
- },
- /**
- * @private
- */
- _processPanels: function () {
- var headers, triggers;
- this.element.attr('data-collapsible', 'true');
- if (typeof this.options.header === 'object') {
- this.header = this.options.header;
- } else {
- headers = this.element.find(this.options.header);
- if (headers.length > 0) {
- this.header = headers.eq(0);
- } else {
- this.header = this.element;
- }
- }
- if (typeof this.options.content === 'object') {
- this.content = this.options.content;
- } else {
- this.content = this.header.next(this.options.content).eq(0);
- }
- // ARIA (init aria attributes)
- if (this.header.attr('id')) {
- this.content.attr('aria-labelledby', this.header.attr('id'));
- }
- if (this.content.attr('id')) {
- this.header.attr('aria-controls', this.content.attr('id'));
- }
- this.header
- .attr({
- 'role': 'tab',
- 'aria-selected': this.options.active,
- 'aria-expanded': this.options.active
- });
- // For collapsible widget only (not tabs or accordion)
- if (this.header.parent().attr('role') !== 'presentation') {
- this.header
- .parent()
- .attr('role', 'tablist');
- }
- this.content.attr({
- 'role': 'tabpanel',
- 'aria-hidden': !this.options.active
- });
- if (typeof this.options.trigger === 'object') {
- this.trigger = this.options.trigger;
- } else {
- triggers = this.header.find(this.options.trigger);
- if (triggers.length > 0) {
- this.trigger = triggers.eq(0);
- } else {
- this.trigger = this.header;
- }
- }
- },
- /**
- * @param {jQuery.Event} event
- * @private
- */
- _keydown: function (event) {
- var keyCode;
- if (event.altKey || event.ctrlKey) {
- return;
- }
- keyCode = $.ui.keyCode;
- switch (event.keyCode) {
- case keyCode.SPACE:
- case keyCode.ENTER:
- this._eventHandler(event);
- break;
- }
- },
- /**
- * @param {jQuery.Event} event
- * @private
- */
- _bind: function (event) {
- var self = this;
- this.events = {
- keydown: '_keydown'
- };
- if (event) {
- $.each(event.split(' '), function (index, eventName) {
- self.events[ eventName ] = '_eventHandler';
- });
- }
- this._off(this.trigger);
- if (!this.options.disabled) {
- this._on(this.trigger, this.events);
- }
- },
- /**
- * Disable.
- */
- disable: function () {
- this.options.disabled = true;
- this._off(this.trigger);
- this.forceDeactivate();
- if (this.options.disabledState) {
- this.element.addClass(this.options.disabledState);
- }
- this.trigger.attr('tabIndex', -1);
- },
- /**
- * Enable.
- */
- enable: function () {
- this.options.disabled = false;
- this._on(this.trigger, this.events);
- this.forceActivate();
- if (this.options.disabledState) {
- this.element.removeClass(this.options.disabledState);
- }
- this.trigger.attr('tabIndex', 0);
- },
- /**
- * @param {jQuery.Event} event
- * @private
- */
- _eventHandler: function (event) {
- if (this.options.active && this.options.collapsible) {
- this.deactivate();
- } else {
- this.activate();
- }
- event.preventDefault();
- },
- /**
- * @param {*} prop
- * @private
- */
- _animate: function (prop) {
- var duration,
- easing,
- animate = this.options.animate;
- if (typeof animate === 'number') {
- duration = animate;
- }
- if (typeof animate === 'string') {
- animate = $.parseJSON(animate);
- }
- duration = duration || animate.duration;
- easing = animate.easing;
- this.content.animate(prop, duration, easing);
- },
- /**
- * Deactivate.
- */
- deactivate: function () {
- if (this.options.animate) {
- this._animate(hideProps);
- } else {
- this.content.hide();
- }
- this._close();
- },
- /**
- * Force deactivate.
- */
- forceDeactivate: function () {
- this.content.hide();
- this._close();
- },
- /**
- * @private
- */
- _close: function () {
- this.options.active = false;
- if (this.options.saveState) {
- this.storage.set(this.stateKey, false);
- }
- if (this.options.openedState) {
- this.element.removeClass(this.options.openedState);
- }
- if (this.options.collateral.element && this.options.collateral.openedState) {
- $(this.options.collateral.element).removeClass(this.options.collateral.openedState);
- }
- if (this.options.closedState) {
- this.element.addClass(this.options.closedState);
- }
- if (this.icons) {
- this.header.children('[data-role=icons]')
- .removeClass(this.options.icons.activeHeader)
- .addClass(this.options.icons.header);
- }
- // ARIA (updates aria attributes)
- this.header.attr({
- 'aria-selected': 'false',
- 'aria-expanded': 'false'
- });
- this.content.attr({
- 'aria-hidden': 'true'
- });
- this.element.trigger('dimensionsChanged', {
- opened: false
- });
- },
- /**
- * Activate.
- *
- * @return void;
- */
- activate: function () {
- if (this.options.disabled) {
- return;
- }
- if (this.options.animate) {
- this._animate(showProps);
- } else {
- this.content.show();
- }
- this._open();
- },
- /**
- * Force activate.
- */
- forceActivate: function () {
- if (!this.options.disabled) {
- this.content.show();
- this._open();
- }
- },
- /**
- * @private
- */
- _open: function () {
- this.element.trigger('beforeOpen');
- this.options.active = true;
- if (this.options.ajaxContent) {
- this._loadContent();
- }
- if (this.options.saveState) {
- this.storage.set(this.stateKey, true);
- }
- if (this.options.openedState) {
- this.element.addClass(this.options.openedState);
- }
- if (this.options.collateral.element && this.options.collateral.openedState) {
- $(this.options.collateral.element).addClass(this.options.collateral.openedState);
- }
- if (this.options.closedState) {
- this.element.removeClass(this.options.closedState);
- }
- if (this.icons) {
- this.header.children('[data-role=icons]')
- .removeClass(this.options.icons.header)
- .addClass(this.options.icons.activeHeader);
- }
- // ARIA (updates aria attributes)
- this.header.attr({
- 'aria-selected': 'true',
- 'aria-expanded': 'true'
- });
- this.content.attr({
- 'aria-hidden': 'false'
- });
- this.element.trigger('dimensionsChanged', {
- opened: true
- });
- },
- /**
- * @private
- */
- _loadContent: function () {
- var url = this.element.find(this.options.ajaxUrlElement).attr('href'),
- that = this;
- if (url) {
- that.xhr = $.get({
- url: url,
- dataType: 'html'
- }, function () {
- });
- }
- if (that.xhr && that.xhr.statusText !== 'canceled') {
- if (that.options.loadingClass) {
- that.element.addClass(that.options.loadingClass);
- }
- that.content.attr('aria-busy', 'true');
- that.xhr.done(function (response) {
- setTimeout(function () {
- that.content.html(response);
- }, 1);
- });
- that.xhr.always(function (jqXHR, status) {
- setTimeout(function () {
- if (status === 'abort') {
- that.content.stop(false, true);
- }
- if (that.options.loadingClass) {
- that.element.removeClass(that.options.loadingClass);
- }
- that.content.removeAttr('aria-busy');
- if (jqXHR === that.xhr) {
- delete that.xhr;
- }
- }, 1);
- });
- }
- },
- /**
- * @param {HTMLElement} elem
- * @private
- */
- _scrollToTopIfVisible: function (elem) {
- if (this._isElementOutOfViewport(elem)) {
- elem.scrollIntoView();
- }
- },
- /**
- * @param {HTMLElement} elem
- * @private
- * @return {Boolean}
- */
- _isElementOutOfViewport: function (elem) {
- var rect = elem.getBoundingClientRect();
- return rect.bottom < 0 || rect.right < 0 || rect.left > window.innerWidth || rect.top > window.innerHeight;
- }
- });
- return $.mage.collapsible;
- });
|