432 lines
15 KiB
JavaScript
432 lines
15 KiB
JavaScript
/**
|
|
* 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(){
|
|
|
|
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);
|
|
|
|
|