features-8.x-3.11/modules/features_ui/js/features_ui.admin.js
modules/features_ui/js/features_ui.admin.js
/** * @file * JQuery.fn.sortElements * --------------. * @param Function comparator: * Exactly the same behaviour as [1,2,3].sort(comparator) * @param Function getSortable * A function that should return the element that is * to be sorted. The comparator will run on the * current collection, but you may want the actual * resulting sort to occur on a parent or another * associated element. * * E.g. $('td').sortElements(comparator, function(){ * return this.parentNode; * }) * * The <td>'s parent (<tr>) will be sorted instead * of the <td> itself. * * Credit: http://james.padolsey.com/javascript/sorting-elements-with-jquery/ */ jQuery.fn.sortElements = (function () { "use strict"; var sort = [].sort; return function (comparator, getSortable) { getSortable = getSortable || function () {return this;}; var placements = this.map(function () { var sortElement = getSortable.call(this); var parentNode = sortElement.parentNode; // Since the element itself will change position, we have // to have some way of storing its original position in // the DOM. The easiest way is to have a 'flag' node: var nextSibling = parentNode.insertBefore( document.createTextNode(''), sortElement.nextSibling ); return function () { if (parentNode === this) { throw new Error( "You can't sort elements if any one is a descendant of another." ); } // Insert before flag: parentNode.insertBefore(this, nextSibling); // Remove flag: parentNode.removeChild(nextSibling); }; }); return sort.call(this, comparator).each(function (i) { placements[i].call(getSortable.call(this)); }); }; })(); (function ($, Drupal) { "use strict"; Drupal.behaviors.features = { attach: function (context) { // Mark any conflicts with a class. if ((typeof drupalSettings.features !== 'undefined') && (typeof drupalSettings.features.conflicts !== 'undefined')) { // For (var configType in drupalSettings.features.conflicts) {. if (drupalSettings.features.conflicts) { var configConflicts = drupalSettings.features.conflicts; $('.js-features-export-wrapper .features-export-parent input[type=checkbox]:not(.js-features-filter)', context).each(function () { var key = $(this).attr('name'); var matches = key.match(/^([^\[]+)(\[.+\])?\[(.+)\]\[(.+)\]$/); var component = matches[1]; var item = matches[4]; if ((component in configConflicts) && (item in configConflicts[component])) { $(this).parent().addClass('component-conflict'); } }); } // } } function _checkAll(value) { if (value) { $('.js-components-select input[type=checkbox]:visible', context).each(function () { var move_id = $(this).attr('id'); $(this).click(); $('#' + move_id).prop('checked', true); }); } else { $('.js-components-added input[type=checkbox]:visible', context).each(function () { var move_id = $(this).attr('id'); $(this).click(); $('#' + move_id).prop('checked', false); }); } } function updateComponentCountInfo(item, section) { var parent; switch (section) { case 'select': parent = $(item).closest('.js-features-export-list').siblings('.js-features-export-component'); $('.js-component-count', parent).text(function (index, text) { return +text + 1; } ); break; case 'added': case 'detected': parent = $(item).closest('.js-features-export-component'); $('.js-component-count', parent).text(function (index, text) { return text - 1; }); } } function moveCheckbox(item, section, value) { updateComponentCountInfo(item, section); var curParent = item; if ($(item).hasClass('js-form-type-checkbox')) { item = $(item).children('input[type=checkbox]'); } else { curParent = $(item).parents('.js-form-type-checkbox'); } var newParent = $(curParent).parents('.js-features-export-parent').find('.js-components-' + section + ' .form-checkboxes'); $(curParent).detach(); $(curParent).appendTo(newParent); var list = ['select', 'added', 'detected', 'included']; for (var i in list) { if (list[i]) { $(curParent).removeClass('component-' + list[i]); $(item).removeClass('component-' + list[i]); } } $(curParent).addClass('component-' + section); $(item).addClass('component-' + section); if (value) { $(item).attr('checked', 'checked'); } else { $(item).removeAttr('checked'); } var $newParents = $(newParent); $newParents.parents('.js-features-export-list').removeClass('features-export-empty'); // Unhide the config type group. $newParents.parents('.features-export-parent').removeClass('features-filter-hidden'); // re-sort new list of checkboxes based on labels. $newParents.find('label').sortElements( function (a, b) { return $(a).text() > $(b).text() ? 1 : -1; }, function () { return this.parentNode; } ); } // Provide timer for auto-refresh trigger. var timeoutID = 0; var inTimeout = 0; function _triggerTimeout() { timeoutID = 0; _updateDetected(); } function _resetTimeout() { inTimeout++; // If timeout is already active, reset it. if (timeoutID !== 0) { window.clearTimeout(timeoutID); if (inTimeout > 0) { inTimeout--; } } timeoutID = window.setTimeout(_triggerTimeout, 500); } function _updateDetected() { if (!drupalSettings.features.autodetect) { return; } // Query the server for a list of components/items in the feature and update // the auto-detected items. var items = []; // Will contain a list of selected items exported to feature. var components = {}; // Contains object of component names that have checked items. $('.js-features-export-wrapper .features-export-parent input[type=checkbox]:not(.js-features-filter):checked', context).each(function () { var key = $(this).attr('name'); var matches = key.match(/^([^\[]+)(\[.+\])?\[(.+)\]\[(.+)\]$/); components[matches[1]] = matches[1]; if (!$(this).hasClass('component-detected')) { items.push(key); } }); var featureName = $('#edit-machine-name').val(); if (featureName === '') { featureName = '*'; } var url = Drupal.url('features/api/detect/' + featureName); var excluded = drupalSettings.features.excluded; var required = drupalSettings.features.required; var postData = {'items': items, 'excluded': excluded, 'required': required}; jQuery.post(url, postData, function (data) { if (inTimeout > 0) { inTimeout--; } // If we have triggered another timeout then don't update with old results. if (inTimeout === 0) { // Data is an object keyed by component listing the exports of the feature. for (var component in data) { if (data[component]) { var itemList = data[component]; $('.js-component--name-' + component + ' input[type=checkbox]', context).each(function () { var key = $(this).attr('value'); // First remove any auto-detected items that are no longer in component. if ($(this).hasClass('component-detected')) { if (!(key in itemList)) { moveCheckbox(this, 'select', false); } } // Next, add any new auto-detected items. else if ($(this).hasClass('component-select')) { if (key in itemList) { moveCheckbox(this, 'detected', itemList[key]); $(this).prop('checked', true); $(this).parent().show(); // Make sure it's not hidden from filter. } } }); } } // Loop over all selected components and check for any that have been completely removed. for (var selectedComponent in components) { if ((data == null) || !(selectedComponent in data)) { $('.js-component--name-' + selectedComponent + ' input[type=checkbox].component-detected', context).each(function () { moveCheckbox(this, 'select', false); }); } } } }, "json"); } // Handle component selection UI. $('.js-features-export-wrapper .features-export-parent input[type=checkbox]', context).click(function () { _resetTimeout(); if ($(this).hasClass('component-select')) { moveCheckbox(this, 'added', true); } else if ($(this).hasClass('component-included')) { moveCheckbox(this, 'added', false); } else if ($(this).hasClass('component-added')) { if ($(this).is(':checked')) { moveCheckbox(this, 'included', true); } else { moveCheckbox(this, 'select', false); } } }); // Handle select/unselect all. $('.js-features-checkall', context).click(function () { let $text = $(this).next(); if ($(this).prop('checked')) { _checkAll(true); $text.text(Drupal.t('Deselect all')) .attr('title', Drupal.t('Deselect all currently expanded configurations')); } else { _checkAll(false); $text.text(Drupal.t('Select all')) .attr('title', Drupal.t('Select all currently expanded configurations')); } _resetTimeout(); }); // Handle hide/show components. $('.js-features-filter .features-hide-component.form-select', context).change(function () { var $exportWrapper = $('.js-features-export-wrapper', context); var componentType = $(this).val(); $exportWrapper .find('.js-features-filter-hidden') .removeClass('js-features-filter-hidden'); if (componentType) { if (componentType === 'included+groups') { componentType = 'included'; // Hide empty config components. $exportWrapper.find('.js-component-count').filter(function() { return $(this).text() === '0'; }).parents('.features-export-parent').addClass('js-features-filter-hidden'); } $exportWrapper.find('.js-features-export-parent .js-components-' + componentType).addClass('js-features-filter-hidden'); } }); // Collapse/Expand components. $('.js-features-filter .features-toggle-components', context).click(function (e) { e.preventDefault(); e.stopPropagation(); var expandAll = Drupal.t('Expand all'); var collapseAll = Drupal.t('Collapse all'); var $this = $(this); var $components = $('.features-export-component', context); if (expandAll == $this.text()) { $components.attr('open', true); $this.text(collapseAll); } else { $components.attr('open', false); $this.text(expandAll); } }); // Handle filtering. // Provide timer for auto-refresh trigger. var filterTimeoutID = 0; function _triggerFilterTimeout() { filterTimeoutID = 0; _updateFilter(); } function _resetFilterTimeout() { // If timeout is already active, reset it. if (filterTimeoutID !== 0) { window.clearTimeout(filterTimeoutID); filterTimeoutID = null; } filterTimeoutID = window.setTimeout(_triggerFilterTimeout, 200); } function _updateFilter() { var filter = $('.js-features-filter-input').val(); var regex = new RegExp(filter, 'i'); // Collapse fieldsets. var newState = {}; var currentState = {}; $('.js-features-export-component', context).each(function () { // Expand parent fieldset. var section = $(this).attr('id'); var details = $(this); currentState[section] = details.prop('open'); if (!(section in newState)) { newState[section] = false; } details.find('.form-checkboxes label').each(function () { if (filter === '') { // Collapse the section, but make checkbox visible. if (currentState[section]) { details.prop('open', false); currentState[section] = false; } $(this).parent().show(); } else if ($(this).text().match(regex)) { $(this).parent().show(); newState[section] = true; } else { $(this).parent().hide(); } }); }); for (var section in newState) { if (currentState[section] !== newState[section]) { if (newState[section]) { $('#' + section).prop('open', true); } else { $('#' + section).prop('open', false); } } } } $('.js-features-filter-input', context).bind("input", function () { _resetFilterTimeout(); }); $('.js-features-filter-clear', context).click(function () { $('.js-features-filter-input').val(''); _updateFilter(); }); // Show the filter bar. $('.js-features-filter', context).removeClass('visually-hidden'); // Handle Package selection checkboxes in the Differences page. $('.features-diff-listing .features-diff-header input.form-checkbox', context).click(function () { var value = $(this).prop('checked'); $('.features-diff-listing .diff-' + $(this).prop('value') + ' input.form-checkbox', context).each(function () { $(this).prop('checked', value); if (value) { $(this).parents('tr').addClass('selected'); } else { $(this).parents('tr').removeClass('selected'); } }); }); // Handle special theming of headers in tableselect. $('td.features-export-header-row', context).each(function () { var row = $(this).parent('tr'); row.addClass('features-export-header-row'); var checkbox = row.find('td input:checkbox'); if (checkbox.length) { checkbox.hide(); } }); // Handle clicking anywhere in row on Differences page. $('.features-diff-listing tr td:nth-child(2)', context).click(function () { var checkbox = $(this).parent().find('td input:checkbox'); checkbox.prop('checked', !checkbox.prop('checked')).triggerHandler('click'); if (checkbox.prop('checked')) { $(this).parents('tr').addClass('selected'); } else { $(this).parents('tr').removeClass('selected'); } }); // Show/Hide components. $('.features-diff-header-action-link', context).click(function (e) { e.preventDefault(); e.stopPropagation(); var showAll = Drupal.t('Show all'); var hideAll = Drupal.t('Hide all'); var $this = $(this); var $checkbox = $this.closest('tr').find('td:nth-child(1) input:checkbox'); var $elements = $this.closest('table').find('tr.diff-' + $checkbox.prop('value')); if (hideAll == $this.text()) { $this.text(showAll); $elements.addClass('js-features-diff-hidden'); } else { $this.text(hideAll); $elements.removeClass('js-features-diff-hidden'); } }); $('.features-diff-listing thead th:nth-child(2)', context).click(function () { var checkbox = $(this).parent().find('th input:checkbox'); checkbox.click(); }); } }; })(jQuery, Drupal);