/** * 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 's parent () will be sorted instead * of the itself. * * Credit: http://james.padolsey.com/javascript/sorting-elements-with-jquery/ * */ jQuery.fn.sortElements = (function(){ var sort = [].sort; return function(comparator, getSortable) { getSortable = getSortable || function(){return this;}; var placements = this.map(function(){ var sortElement = getSortable.call(this), 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: 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.behaviors.features = { attach: function(context, settings) { // Features management form $('table.features:not(.processed)', context).each(function() { $(this).addClass('processed'); // Check the overridden status of each feature Drupal.features.checkStatus(); // Add some nicer row hilighting when checkboxes change values $('input', this).bind('change', function() { if (!$(this).attr('checked')) { $(this).parents('tr').removeClass('enabled').addClass('disabled'); } else { $(this).parents('tr').addClass('enabled').removeClass('disabled'); } }); }); // Export form component selector $('form.features-export-form select.features-select-components:not(.processed)', context).each(function() { $(this) .addClass('processed') .change(function() { var target = $(this).val(); $('div.features-select').hide(); $('div.features-select-' + target).show(); return false; }).trigger('change'); }); //View info dialog var infoDialog = $('#features-info-file'); if (infoDialog.length != 0) { infoDialog.dialog({ autoOpen: false, modal: true, draggable: false, resizable: false, width: 600, height: 480 }); } if ((Drupal.settings.features != undefined) && (Drupal.settings.features.info != undefined)) { $('#features-info-file textarea').val(Drupal.settings.features.info); $('#features-info-file').dialog('open'); //To be reset by the button click ajax Drupal.settings.features.info = undefined; } // mark any conflicts with a class if ((Drupal.settings.features != undefined) && (Drupal.settings.features.conflicts != undefined)) { for (var moduleName in Drupal.settings.features.conflicts) { moduleConflicts = Drupal.settings.features.conflicts[moduleName]; $('#features-export-wrapper input[type=checkbox]', context).each(function() { if (!$(this).hasClass('features-checkall')) { var key = $(this).attr('name'); var matches = key.match(/^([^\[]+)(\[.+\])?\[(.+)\]\[(.+)\]$/); if (matches != null) { var component = matches[1]; var item = matches[4]; if ((component in moduleConflicts) && (moduleConflicts[component].indexOf(item) != -1)) { $(this).parent().addClass('features-conflict'); } } } }); } } function _checkAll(value) { if (value) { $('#features-export-wrapper .component-select input[type=checkbox]:visible', context).each(function() { var move_id = $(this).attr('id'); $(this).click(); $('#'+ move_id).attr('checked', 'checked'); }); } else { $('#features-export-wrapper .component-added input[type=checkbox]:visible', context).each(function() { var move_id = $(this).attr('id'); $('#'+ move_id).removeAttr('checked'); $(this).click(); $('#'+ move_id).removeAttr('checked'); }); } } function updateComponentCountInfo(item, section) { switch (section) { case 'select': var parent = $(item).closest('.features-export-list').siblings('.features-export-component'); $('.component-count', parent).text(function (index, text) { return +text + 1; } ); break; case 'added': case 'detected': var parent = $(item).closest('.features-export-component'); $('.component-count', parent).text(function (index, text) { return text - 1; }); } } function moveCheckbox(item, section, value) { updateComponentCountInfo(item, section); var curParent = item; if ($(item).hasClass('form-type-checkbox')) { item = $(item).children('input[type=checkbox]'); } else { curParent = $(item).parents('.form-type-checkbox'); } var newParent = $(curParent).parents('.features-export-parent').find('.form-checkboxes.component-'+section); $(curParent).detach(); $(curParent).appendTo(newParent); var list = ['select', 'added', 'detected', 'included']; for (i in list) { $(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') } $(newParent).parent().removeClass('features-export-empty'); // re-sort new list of checkboxes based on labels $(newParent).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() { var autodetect = $('#features-autodetect input[type=checkbox]'); if ((autodetect.length > 0) && (!autodetect.is(':checked'))) 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 $('#features-export-wrapper input[type=checkbox]:checked', context).each(function() { if (!$(this).hasClass('features-checkall')) { 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-module-name').val(); if (featureName == '') { featureName = '*'; } var url = Drupal.settings.basePath + 'features/ajaxcallback/' + featureName; var excluded = Drupal.settings.features.excluded; var postData = {'items': items, 'excluded': excluded}; 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) { var itemList = data[component]; $('#features-export-wrapper .component-' + 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).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 component in components) { if ((data == null) || !(component in data)) { $('#features-export-wrapper .component-' + component + ' input[type=checkbox].component-detected', context).each(function() { moveCheckbox(this, 'select', false); }); } } } }, "json"); } // Handle component selection UI $('#features-export-wrapper input[type=checkbox]:not(.processed)', context).addClass('processed').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 $('#features-filter .features-checkall', context).click(function() { if ($(this).attr('checked')) { _checkAll(true); $(this).next().html(Drupal.t('Deselect all')); } else { _checkAll(false); $(this).next().html(Drupal.t('Select all')); } _resetTimeout(); }); // Handle filtering // provide timer for auto-refresh trigger var filterTimeoutID = 0; var inFilterTimeout = 0; function _triggerFilterTimeout() { filterTimeoutID = 0; _updateFilter(); } function _resetFilterTimeout() { inFilterTimeout++; // if timeout is already active, reset it if (filterTimeoutID != 0) { window.clearTimeout(filterTimeoutID); if (inFilterTimeout > 0) inFilterTimeout--; } filterTimeoutID = window.setTimeout(_triggerFilterTimeout, 200); } function _updateFilter() { var filter = $('#features-filter input').val(); var regex = new RegExp(filter, 'i'); // collapse fieldsets var newState = {}; var currentState = {}; $('#features-export-wrapper fieldset.features-export-component', context).each(function() { // expand parent fieldset var section = $(this).attr('id'); currentState[section] = !($(this).hasClass('collapsed')); if (!(section in newState)) { newState[section] = false; } $(this).find('div.component-select label').each(function() { if (filter == '') { if (currentState[section]) { Drupal.toggleFieldset($('#'+section)); currentState[section] = false; } $(this).parent().show(); } else if ($(this).text().match(regex)) { $(this).parent().show(); newState[section] = true; } else { $(this).parent().hide(); } }); }); for (section in newState) { if (currentState[section] != newState[section]) { Drupal.toggleFieldset($('#'+section)); } } } $('#features-filter input', context).bind("input", function() { _resetFilterTimeout(); }); $('#features-filter .features-filter-clear', context).click(function() { $('#features-filter input').val(''); _updateFilter(); }); // show the filter bar $('#features-filter', context).removeClass('element-invisible'); } } Drupal.features = { 'checkStatus': function() { $('table.features tbody tr').not('.processed').filter(':first').each(function() { var elem = $(this); $(elem).addClass('processed'); var uri = $(this).find('a.admin-check').attr('href'); if (uri) { $.get(uri, [], function(data) { $(elem).find('.admin-loading').hide(); switch (data.storage) { case 3: $(elem).find('.admin-rebuilding').show(); break; case 2: $(elem).find('.admin-needs-review').show(); break; case 1: $(elem).find('.admin-overridden').show(); break; default: $(elem).find('.admin-default').show(); break; } Drupal.features.checkStatus(); }, 'json'); } else { Drupal.features.checkStatus(); } }); } }; })(jQuery);