features.js
14.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
/**
* 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(/^([^\[]+)(\[.+\])?\[(.+)\]\[(.+)\]$/);
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]', 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
$('#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);