commiting uncommited changes on live site
[weblabels.fsf.org.git] / crm.fsf.org / 20131203 / files / sites / all / modules-new / options_element / options_element.js
1
2 /**
3 * @file
4 * Add JavaScript behaviors for the "options" form element type.
5 */
6
7 (function($) {
8
9 Drupal.optionElements = Drupal.optionElements || {};
10 Drupal.behaviors.optionsElement = Drupal.behaviors.optionsElement || {};
11
12 // We need to check/set HTML properties frequently, such as the disabled and
13 // checked state of elements. In jQuery 1.6+, the "prop" method was added for
14 // this purpose, but in earlier versions you had to use "attr".
15 $.fn.oeProp = $.fn.prop ? $.fn.prop : $.fn.attr;
16
17 Drupal.behaviors.optionsElement.attach = function(context) {
18 $('div.form-options:not(.options-element-processed)', context).each(function() {
19 $(this).addClass('options-element-processed');
20 var optionsElement = new Drupal.optionsElement(this);
21 Drupal.optionElements[optionsElement.identifier] = optionsElement;
22 });
23 };
24
25 /**
26 * Constructor for an options element.
27 */
28 Drupal.optionsElement = function(element) {
29 var self = this;
30
31 // Find the original "manual" fields.
32 this.element = element;
33 this.manualElement = $(element).find('div.options').get(0);
34 this.manualOptionsElement = $(element).find('textarea').get(0);
35 this.manualDefaultValueElement = $(element).find('input.form-text').get(0);
36 this.keyTypeToggle = $(element).find('input.key-type-toggle').get(0);
37 this.multipleToggle = $(element).find('input.multiple-toggle').get(0);
38
39 // Setup variables containing the current status of the widget.
40 this.optgroups = $(element).is('.options-optgroups');
41 this.multiple = $(element).is('.options-multiple');
42 this.keyType = element.className.replace(/^.*?options-key-type-([a-z]+).*?$/, '$1');
43 this.customKeys = Boolean(element.className.match(/options-key-custom/));
44 this.identifier = this.manualOptionsElement.id + '-widget';
45 this.enabled = !$(this.manualOptionsElement).oeProp('readonly');
46 this.defaultValuePattern = $(element).find('input.default-value-pattern').val();
47
48 if (this.defaultValuePattern) {
49 this.defaultValuePattern = new RegExp(this.defaultValuePattern);
50 }
51
52 // Warning messages.
53 this.keyChangeWarning = Drupal.t('Custom keys have been specified in this list. Removing these custom keys may change way data is stored. Are you sure you wish to remove these custom keys?');
54
55 // Setup new DOM elements containing the actual options widget.
56 this.optionsElement = $('<div></div>').get(0); // Temporary DOM object.
57 this.optionsToggleElement = $(Drupal.theme('optionsElementToggle')).get(0);
58 this.optionAddElement = $(Drupal.theme('optionsElementAdd')).get(0);
59 this.removeDefaultElement = $(Drupal.theme('optionsElementRemoveDefault')).get(0);
60
61 // Add the options widget and toggle elements to the page.
62 $(this.manualElement).css('display', 'none').before(this.optionsElement).after(this.optionsToggleElement).after(this.optionAddElement);
63 if (this.manualDefaultValueElement) {
64 $(this.manualElement).after(this.removeDefaultElement);
65 }
66
67 // Enable add item link.
68 $(this.optionAddElement).find('a').click(function() {
69 var newOption = self.addOption($('table tr:last', self.optionsElement).get(0));
70 $(newOption).find('input[type=text]:visible:first').focus();
71 return false;
72 });
73
74 // Enable the toggle action for manual entry of options.
75 $(this.optionsToggleElement).find('a').click(function() {
76 self.toggleMode();
77 return false;
78 });
79
80 // Enable the remove default link.
81 $(this.removeDefaultElement).find('a').click(function() {
82 $(self.element).find('input.option-default').oeProp('checked', false).trigger('change');
83 return false;
84 });
85
86 // Add a handler for key type changes.
87 if (this.keyTypeToggle) {
88 $(this.keyTypeToggle).click(function() {
89 var checked = $(this).oeProp('checked');
90 // Before switching the key type, ensure we're not destroying user keys.
91 if (!checked) {
92 var options = self.optionsFromText();
93 var confirm = false;
94 if (self.keyType == 'associative') {
95 for (var n = 0; n < options.length; n++) {
96 if (options[n].key != options[n].value) {
97 confirm = true;
98 break;
99 }
100 }
101 }
102 if (confirm) {
103 if (window.confirm(self.keyChangeWarning)) {
104 self.setCustomKeys(false);
105 }
106 }
107 else {
108 self.setCustomKeys(false);
109 }
110 }
111 else {
112 self.setCustomKeys(true);
113 }
114 });
115 }
116
117 // Add a handler for multiple value changes.
118 if (this.multipleToggle) {
119 $(this.multipleToggle).click(function(){
120 self.setMultiple($(this).oeProp('checked'));
121 });
122 }
123
124 // Be sure to show the custom keys if we have any errors.
125 if (Drupal.settings.optionsElement && Drupal.settings.optionsElement.errors) {
126 this.customKeys = true;
127 }
128
129 // Update the options widget with the current state of the textarea.
130 this.updateWidgetElements();
131
132 // Highlight errors that may have occurred during Drupal validation.
133 if (Drupal.settings.optionsElement && Drupal.settings.optionsElement.errors) {
134 this.checkKeys(Drupal.settings.optionsElement.errors, 'error');
135 }
136 }
137
138 /**
139 * Update the widget element based on the current values of the manual elements.
140 */
141 Drupal.optionsElement.prototype.updateWidgetElements = function() {
142 var self = this;
143
144 // Create a new options element and replace the existing one.
145 var newElement = $(Drupal.theme('optionsElement', this)).get(0);
146 if ($(this.optionsElement).css('display') == 'none') {
147 $(newElement).css('display', 'none');
148 }
149 $(this.optionsElement).replaceWith(newElement);
150 this.optionsElement = newElement;
151
152 // Manually set up table drag for the created table.
153 Drupal.settings.tableDrag = Drupal.settings.tableDrag || {};
154 Drupal.settings.tableDrag[this.identifier] = {
155 'option-depth': {
156 0: {
157 action: 'depth',
158 hidden: false,
159 limit: 0,
160 relationship: 'self',
161 source: 'option-depth',
162 target: 'option-depth'
163 }
164 }
165 };
166
167 // Allow indentation of elements if optgroups are supported.
168 if (this.optgroups) {
169 Drupal.settings.tableDrag[this.identifier]['option-parent'] = {
170 0: {
171 action: 'match',
172 hidden: false,
173 limit: 1,
174 relationship: 'parent',
175 source: 'option-value',
176 target: 'option-parent'
177 }
178 };
179 }
180
181 // Enable button for adding options.
182 $('a.add', this.optionsElement).click(function() {
183 var newOption = self.addOption($(this).parents('tr:first').get(0));
184 $(newOption).find('input[type=text]:visible:first').focus();
185 return false;
186 });
187
188 // Enable button for removing options.
189 $('a.remove', this.optionsElement).click(function() {
190 self.removeOption($(this).parents('tr:first').get(0));
191 return false;
192 });
193
194 // Add the same update action to all textfields and radios.
195 $('input', this.optionsElement).change(function() {
196 self.updateOptionElements();
197 self.updateManualElements();
198 });
199
200 // Add a delayed update to textfields.
201 $('input.option-value', this.optionsElement).keyup(function(e) {
202 self.pendingUpdate(e);
203 });
204
205 // Attach behaviors as normal to the new widget.
206 Drupal.attachBehaviors(this.optionsElement);
207
208 // Remove the "Show row weights" link
209 $(".tabledrag-toggle-weight-wrapper").remove();
210
211 // Add an onDrop action to the table drag instance.
212 Drupal.tableDrag[this.identifier].onDrop = function() {
213 // Update the checkbox/radio buttons for selecting default values.
214 if (self.optgroups) {
215 self.updateOptionElements();
216 }
217 // Update the options within the hidden text area.
218 self.updateManualElements();
219 };
220
221 // Add an onIndent action to the table drag row instances.
222 Drupal.tableDrag[this.identifier].row.prototype.onIndent = function() {
223 if (this.indents) {
224 $(this.element).addClass('indented');
225 }
226 else {
227 $(this.element).removeClass('indented');
228 }
229 };
230
231 // Update the default value and optgroups.
232 this.updateOptionElements();
233 }
234
235 /**
236 * Update the original form element based on the current state of the widget.
237 */
238 Drupal.optionsElement.prototype.updateManualElements = function() {
239 var options = {};
240
241 // Build a list of current options.
242 var previousOption = false;
243 $(this.optionsElement).find('input.option-value').each(function() {
244 var $row = $(this).is('tr') ? $(this) : $(this).parents('tr:first');
245 var depth = $row.find('input.option-depth').val();
246 if (depth == 1 && previousOption) {
247 if (typeof(options[previousOption]) != 'object') {
248 options[previousOption] = {};
249 }
250 options[previousOption][this.value] = this.value;
251 }
252 else {
253 options[this.value] = this.value;
254 previousOption = this.value;
255 }
256 });
257 this.options = options;
258
259 // Update the default value.
260 var defaultValue = this.multiple ? [] : '';
261 var multiple = this.multiple;
262 $(this.optionsElement).find('input.option-default').each(function() {
263 if (this.checked && this.value) {
264 if (multiple) {
265 defaultValue.push(this.value);
266 }
267 else {
268 defaultValue = this.value;
269 }
270 }
271 });
272 this.defaultValue = defaultValue;
273
274 // Update with the new text and trigger the change action on the field.
275 this.optionsToText();
276
277 if (this.manualDefaultValueElement) {
278 // Don't wipe out custom pattern-matched default values.
279 defaultValue = multiple ? defaultValue.join(', ') : defaultValue;
280 if (defaultValue || !(this.defaultValuePattern && this.defaultValuePattern.test(this.manualDefaultValueElement.value))) {
281 this.manualDefaultValueElement.value = defaultValue;
282 $('.default-value-pattern-match', this.element).remove();
283 }
284 }
285
286 $(this.manualOptionsElement).change();
287 }
288
289 /**
290 * Several maintenance routines to update all rows of the options element.
291 *
292 * - Disable options for optgroups if indented.
293 * - Disable add and delete links if indented.
294 * - Match the default value radio button value to the key of the text element.
295 */
296 Drupal.optionsElement.prototype.updateOptionElements = function() {
297 var self = this;
298 var previousRow = false;
299 var previousElement = false;
300 var $rows = $(this.optionsElement).find('tbody tr');
301
302 $rows.each(function(index) {
303 var optionValue = $(this).find('input.option-value').val();
304 var optionKey = $(this).find('input.option-key').val();
305
306 // Update the elements key if matching the key and value.
307 if (self.keyType == 'associative') {
308 $(this).find('input.option-key').val(optionValue);
309 }
310
311 // Match the default value checkbox/radio button to the option's key.
312 $(this).find('input.option-default').val(optionKey ? optionKey : optionValue);
313
314 // Hide the add/remove links the row if indented.
315 var depth = $(this).find('input.option-depth').val();
316 var defaultInput = $(this).find('input.option-default').get(0);
317
318 if (depth == 1) {
319 // Affect the parent row, adjusting properties for optgroup items.
320 $(previousElement).oeProp('disabled', true).oeProp('checked', false);
321 $(previousRow).addClass('optgroup').find('a.add, a.remove').css('display', 'none');
322 $(this).find('a.add, a.remove').css('display', '');
323 $(defaultInput).oeProp('disabled', false);
324
325 // Hide the key column for the optgroup.
326 if (self.customKeys) {
327 $(previousRow).find('td.option-key-cell').css('display', 'none');
328 $(previousRow).find('td.option-value-cell').attr('colspan', 2);
329 }
330 }
331 else {
332 // Set properties for normal options that are not optgroups.
333 $(defaultInput).oeProp('disabled', false);
334 $(this).removeClass('optgroup').find('a.add, a.remove').css('display', '');
335
336 // Hide the key column.
337 if (self.customKeys) {
338 $(this).find('td.option-key-cell').css('display', '');
339 $(this).find('td.option-value-cell').attr('colspan', '');
340 }
341 previousElement = defaultInput;
342 previousRow = this;
343 }
344 });
345
346 // Do not allow the last item to be removed.
347 if ($rows.size() == 1) {
348 $rows.find('a.remove').css('display', 'none')
349 }
350
351 // Disable items if needed.
352 if (this.enabled == false) {
353 this.disable();
354 }
355 }
356
357 /**
358 * Add a new option below the current row.
359 */
360 Drupal.optionsElement.prototype.addOption = function(currentOption) {
361 var self = this;
362 var windowHieght = $(document).height();
363 var newOption = $(currentOption).clone()
364 .find('input.option-key').val(self.keyType == 'numeric' ? self.nextNumericKey() : '').end()
365 .find('input.option-value').val('').end()
366 .find('input.option-default').oeProp('checked', false).end()
367 .find('a.tabledrag-handle').remove().end()
368 .removeClass('drag-previous')
369 .insertAfter(currentOption)
370 .get(0);
371
372 // Scroll down to accomidate the new option.
373 $(window).scrollTop($(window).scrollTop() + $(document).height() - windowHieght);
374
375 // Make the new option draggable.
376 Drupal.tableDrag[this.identifier].makeDraggable(newOption);
377
378 // Enable button for adding options.
379 $('a.add', newOption).click(function() {
380 var newOption = self.addOption($(this).parents('tr:first').get(0));
381 $(newOption).find('input[type=text]:visible:first').focus();
382 return false;
383 });
384
385 // Enable buttons for removing options.
386 $('a.remove', newOption).click(function() {
387 self.removeOption(newOption);
388 return false;
389 });
390
391 // Add the update action to all textfields and radios.
392 $('input', newOption).change(function() {
393 self.updateOptionElements();
394 self.updateManualElements();
395 });
396
397 // Add a delayed update to textfields.
398 $('input.option-value', newOption).keyup(function(e) {
399 self.pendingUpdate(e);
400 });
401
402 this.updateOptionElements();
403 this.updateManualElements();
404
405 return newOption;
406 }
407
408 /**
409 * Remove the current row.
410 */
411 Drupal.optionsElement.prototype.removeOption = function(currentOption) {
412 $(currentOption).remove();
413
414 this.updateOptionElements();
415 this.updateManualElements();
416 }
417
418 /**
419 * Toggle link for switching between the JavaScript and manual entry.
420 */
421 Drupal.optionsElement.prototype.toggleMode = function() {
422 if ($(this.optionsElement).is(':visible')) {
423 var height = $(this.optionsElement).height();
424 $(this.optionsElement).css('display', 'none');
425 $(this.optionAddElement).css('display', 'none');
426 $(this.manualElement).css('display', '').find('textarea').height(height);
427 $(this.optionsToggleElement).find('a').text(Drupal.t('Normal entry'));
428 }
429 else {
430 this.updateWidgetElements();
431 $(this.optionsElement).css('display', '');
432 $(this.optionAddElement).css('display', '');
433 $(this.manualElement).css('display', 'none');
434 $(this.optionsToggleElement).find('a').text(Drupal.t('Manual entry'));
435 }
436 }
437
438 /**
439 * Enable the changing of options.
440 */
441 Drupal.optionsElement.prototype.enable = function() {
442 this.enabled = true;
443 $(this.manualOptionsElement).oeProp('readonly', false);
444 $(this.element).removeClass('options-disabled');
445
446 $('a.add, a.remove, a.tabledrag-handle, div.form-option-add a', this.element).css('display', '');
447 $('input.form-text', this.optionsElement).oeProp('disabled', false);
448 };
449
450 /**
451 * Disable the changing of options.
452 */
453 Drupal.optionsElement.prototype.disable = function() {
454 this.enabled = false;
455 $(this.manualOptionsElement).oeProp('readonly', true);
456 $(this.element).addClass('options-disabled');
457
458 $('a.add, a.remove, a.tabledrag-handle, div.form-option-add a', this.element).css('display', 'none');
459 $('input.form-text', this.optionsElement).oeProp('disabled', true);
460 };
461
462 /**
463 * Enable entering of custom key values.
464 */
465 Drupal.optionsElement.prototype.setCustomKeys = function(enabled) {
466 if (enabled) {
467 $(this.element).addClass('options-key-custom');
468 }
469 else {
470 $(this.element).removeClass('options-key-custom');
471 }
472
473 this.customKeys = enabled;
474 // Rebuild the options widget.
475 this.updateManualElements();
476 this.updateWidgetElements();
477 }
478
479 /**
480 * Change the current key type (associative, custom, numeric, none).
481 */
482 Drupal.optionsElement.prototype.setKeyType = function(type) {
483 $(this.element)
484 .removeClass('options-key-type-' + this.keyType)
485 .addClass('options-key-type-' + type);
486 this.keyType = type;
487 // Rebuild the options widget.
488 this.updateManualElements();
489 this.updateWidgetElements();
490 }
491
492 /**
493 * Set the element's #multiple property. Boolean TRUE or FALSE.
494 */
495 Drupal.optionsElement.prototype.setMultiple = function(multiple) {
496 if (multiple) {
497 $(this.element).addClass('options-multiple');
498 }
499 else {
500 // Unselect all default options except the first.
501 $(this.optionsElement).find('input.option-default:checked:not(:first)').oeProp('checked', false);
502 this.updateManualElements();
503 $(this.element).removeClass('options-multiple');
504 }
505 this.multiple = multiple;
506 // Rebuild the options widget.
507 this.updateWidgetElements();
508 };
509
510 /**
511 * Highlight duplicate keys.
512 */
513 Drupal.optionsElement.prototype.checkKeys = function(duplicateKeys, cssClass){
514 $(this.optionsElement).find('input.option-key').each(function() {
515 if (duplicateKeys[this.value]) {
516 $(this).addClass(cssClass);
517 }
518 });
519 };
520
521 /**
522 * Update a field after a delay.
523 *
524 * Similar to immediately changing a field, this field as pending changes that
525 * will be updated after a delay. This includes textareas and textfields in
526 * which updating continuously would be a strain the server and actually slow
527 * down responsiveness.
528 */
529 Drupal.optionsElement.prototype.pendingUpdate = function(e) {
530 var self = this;
531
532 // Only operate on "normal" keys, excluding special function keys.
533 // http://protocolsofmatrix.blogspot.com/2007/09/javascript-keycode-reference-table-for.html
534 if (!(
535 e.keyCode >= 48 && e.keyCode <= 90 || // 0-9, A-Z.
536 e.keyCode >= 93 && e.keyCode <= 111 || // Number pad.
537 e.keyCode >= 186 && e.keyCode <= 222 || // Symbols.
538 e.keyCode == 8) // Backspace.
539 ) {
540 return;
541 }
542
543 if (this.updateDelay) {
544 clearTimeout(this.updateDelay);
545 }
546
547 this.updateDelay = setTimeout(function(){
548 self.updateOptionElements();
549 self.updateManualElements();
550 }, 500);
551 };
552
553 /**
554 * Given an object of options, convert it to a text string.
555 */
556 Drupal.optionsElement.prototype.optionsToText = function() {
557 var $rows = $('tbody tr', this.optionsElement);
558 var output = '';
559 var inGroup = false;
560 var rowCount = $rows.size();
561 var defaultValues = [];
562
563 for (var rowIndex = 0; rowIndex < rowCount; rowIndex++) {
564 var isOptgroup = $rows.eq(rowIndex).is('.optgroup');
565 var isChild = $rows.eq(rowIndex).is('.indented');
566 var key = $rows.eq(rowIndex).find('input.option-key').val();
567 var value = $rows.eq(rowIndex).find('input.option-value').val();
568
569 // Handle groups.
570 if (this.optgroups && value !== '' && isOptgroup) {
571 output += '<' + ((key !== '') ? (key + '|') : '') + value + '>' + "\n";
572 inGroup = true;
573 }
574 // Typical key|value pairs.
575 else {
576 // Exit out of any groups.
577 if (this.optgroups && inGroup && !isChild) {
578 output += "<>\n";
579 inGroup = false;
580 }
581
582 // Add the row for the option.
583 if (this.keyType == 'none' || this.keyType == 'associative') {
584 output += value + "\n";
585 }
586 else if (value == '') {
587 output += "\n";
588 }
589 else {
590 output += ((key !== '') ? (key + '|') : '') + value + "\n";
591 }
592 }
593 }
594
595 this.manualOptionsElement.value = output;
596 };
597
598 /**
599 * Given a text string, convert it to an object.
600 */
601 Drupal.optionsElement.prototype.optionsFromText = function() {
602 // Use jQuery val() instead of value because it fixes Windows line breaks.
603 var rows = $(this.manualOptionsElement).val().match(/^.*$/mg);
604 var parent = '';
605 var options = [];
606 var defaultValues = {};
607
608 // Drop the last row if empty.
609 if (rows.length && rows[rows.length - 1] == '') {
610 rows.pop();
611 }
612
613 if (this.manualDefaultValueElement) {
614 if (this.multiple) {
615 var defaults = this.manualDefaultValueElement.value.split(',');
616 for (var n = 0; n < defaults.length; n++) {
617 var defaultValue = defaults[n].replace(/^[ ]*(.*?)[ ]*$/, '$1'); // trim().
618 defaultValues[defaultValue] = defaultValue;
619 }
620 }
621 else {
622 var defaultValue = this.manualDefaultValueElement.value.replace(/^[ ]*(.*?)[ ]*$/, '$1'); // trim().
623 defaultValues[defaultValue] = defaultValue;
624 }
625 }
626
627 for (var n = 0; n < rows.length; n++) {
628 var row = rows[n].replace(/^[ \r\n]*(.*?)[ \r\n]*$/, '$1'); // trim().
629 var key = '';
630 var value = '';
631 var checked = false;
632 var hasChildren = false;
633 var groupClear = false;
634
635 var matches = {};
636 // Row is a group.
637 if (this.optgroups && (matches = row.match(/^\<((([^>|]*)\|)?([^>]*))\>$/))) {
638 if (matches[0] == '<>') {
639 parent = '';
640 groupClear = true;
641 }
642 else {
643 key = matches[3] ? matches[3] : '';
644 parent = value = matches[4];
645 hasChildren = true;
646 }
647 }
648 // Check if this row is a key|value pair.
649 else if ((this.keyType == 'mixed' || this.keyType == 'numeric' || this.keyType == 'custom') && (matches = row.match(/^([^|]+)\|(.*)$/))) {
650 key = matches[1];
651 value = matches[2];
652 checked = defaultValues[key];
653 }
654 // Row is a straight value.
655 else {
656 key = (this.keyType == 'mixed' || this.keyType == 'numeric') ? '' : row;
657 value = row;
658 if (!key && this.keyType == 'mixed') {
659 checked = defaultValues[value];
660 }
661 else {
662 checked = defaultValues[key];
663 }
664 }
665
666 if (!groupClear) {
667 options.push({
668 key: key,
669 value: value,
670 parent: (value !== parent ? parent : ''),
671 hasChildren: hasChildren,
672 checked: (checked ? 'checked' : false)
673 });
674 }
675 }
676
677 // Convert options to numeric if no key is specified.
678 if (this.keyType == 'numeric') {
679 var nextKey = this.nextNumericKey();
680 for (var n = 0; n < options.length; n++) {
681 if (options[n].key == '') {
682 options[n].key = nextKey;
683 nextKey++;
684 }
685 }
686 }
687
688 return options;
689 };
690
691 /**
692 * Utility method to get the next numeric option in a list of options.
693 */
694 Drupal.optionsElement.prototype.nextNumericKey = function(options) {
695 this.keyType = 'custom';
696 options = this.optionsFromText();
697 this.keyType = 'numeric';
698
699 var maxKey = -1;
700 for (var n = 0; n < options.length; n++) {
701 if (options[n].key.match(/^[0-9]+$/)) {
702 maxKey = Math.max(maxKey, options[n].key);
703 }
704 }
705 return maxKey + 1;
706 };
707
708 /**
709 * Theme function for creating a new options element.
710 *
711 * @param optionsElement
712 * An options element object.
713 */
714 Drupal.theme.prototype.optionsElement = function(optionsElement) {
715 var output = '';
716 var options = optionsElement.optionsFromText();
717 var hasDefault = optionsElement.manualDefaultValueElement;
718 var defaultType = optionsElement.multiple ? 'checkbox' : 'radio';
719 var keyType = optionsElement.customKeys ? 'textfield' : 'hidden';
720
721 // Helper function to print out a single draggable option row.
722 function tableDragRow(key, value, parent, indent, status) {
723 var output = '';
724 output += '<tr class="draggable' + (indent > 0 ? ' indented' : '') + '">'
725 output += '<td class="' + (hasDefault ? 'option-default-cell' : 'option-order-cell') + '">';
726 for (var n = 0; n < indent; n++) {
727 output += Drupal.theme('tableDragIndentation');
728 }
729 output += '<input type="hidden" class="option-parent" value="' + parent.replace(/"/g, '&quot;') + '" />';
730 output += '<input type="hidden" class="option-depth" value="' + indent + '" />';
731 if (hasDefault) {
732 output += '<input type="' + defaultType + '" name="' + optionsElement.identifier + '-default" class="form-radio option-default" value="' + key.replace(/"/g, '&quot;') + '"' + (status == 'checked' ? ' checked="checked"' : '') + (status == 'disabled' ? ' disabled="disabled"' : '') + ' />';
733 }
734 output += '</td><td class="' + (keyType == 'textfield' ? 'option-key-cell' : 'option-value-cell') +'">';
735 output += '<input type="' + keyType + '" class="' + (keyType == 'textfield' ? 'form-text ' : '') + 'option-key" value="' + key.replace(/"/g, '&quot;') + '" />';
736 output += keyType == 'textfield' ? '</td><td class="option-value-cell">' : '';
737 output += '<input class="form-text option-value" type="text" value="' + value.replace(/"/g, '&quot;') + '" />';
738 output += '</td><td class="option-actions-cell">'
739 output += '<a class="add" title="' + Drupal.t('Add new option') + '" href="#"' + (status == 'disabled' ? ' style="display: none"' : '') + '><span class="add">' + Drupal.t('Add') + '</span></a>';
740 output += '<a class="remove" title="' + Drupal.t('Remove option') + '" href="#"' + (status == 'disabled' ? ' style="display: none"' : '') + '><span class="remove">' + Drupal.t('Remove') + '</span></a>';
741 output += '</td>';
742 output += '</tr>';
743 return output;
744 }
745
746 output += '<div class="options-widget">';
747 output += '<table id="' + optionsElement.identifier + '">';
748
749 output += '<thead><tr>';
750 output += '<th>' + (hasDefault ? Drupal.t('Default') : '&nbsp;') + '</th>';
751 output += keyType == 'textfield' ? '<th>' + Drupal.t('Key') + '</th>' : '';
752 output += '<th>' + Drupal.t('Value') + '</th>';
753 output += '<th>&nbsp;</th>';
754 output += '</tr></thead>';
755
756 output += '<tbody>';
757
758 // Make sure that at least a few options exist if empty.
759 if (!options.length) {
760 var newOption = {
761 key: '',
762 value: '',
763 parent: '',
764 hasChildren: false,
765 checked: false
766 }
767 options.push(newOption);
768 options.push(newOption);
769 options.push(newOption);
770 }
771
772 for (var n = 0; n < options.length; n++) {
773 var option = options[n];
774 var depth = option.parent === '' ? 0 : 1;
775 var checked = !option.hasChildren && option.checked;
776 output += tableDragRow(option.key, option.value, option.parent, depth, checked);
777 }
778
779 output += '</tbody>';
780 output += '</table>';
781
782 if (optionsElement.defaultValuePattern && optionsElement.manualDefaultValueElement && optionsElement.defaultValuePattern.test(optionsElement.manualDefaultValueElement.value)) {
783 output += Drupal.theme('optionsElementPatternMatch', optionsElement.manualDefaultValueElement.value);
784 }
785
786 output += '</div>';
787
788 return output;
789 };
790
791 Drupal.theme.prototype.optionsElementPatternMatch = function(matchedValue) {
792 return '<div class="default-value-pattern-match"><span>' + Drupal.t('Manual default value') + '</span>: ' + matchedValue + '</div>';
793 };
794
795 Drupal.theme.prototype.optionsElementAdd = function() {
796 return '<div class="form-option-add"><a href="#">' + Drupal.t('Add item') + '</a></div>';
797 };
798
799 Drupal.theme.prototype.optionsElementRemoveDefault = function() {
800 return '<div class="remove-default"><a href="#">' + Drupal.t('No default') + '</a></div>';
801 };
802
803 Drupal.theme.prototype.optionsElementToggle = function() {
804 return '<div class="form-options-manual"><a href="#">' + Drupal.t('Manual entry') + '</a></div>';
805 };
806
807 Drupal.theme.tableDragChangedMarker = function () {
808 return ' ';
809 };
810
811 Drupal.theme.tableDragChangedWarning = function() {
812 return '<span></span>';
813 };
814
815 /**
816 * Field module support for Options widgets.
817 */
818 Drupal.behaviors.optionsElementFieldUI = {};
819 Drupal.behaviors.optionsElementFieldUI.attach = function(context) {
820 var $cardinalityField = $(context).find('#edit-field-cardinality');
821 if ($cardinalityField.length) {
822 $cardinalityField.change(function() {
823 var optionsElementId = $(this).parents('fieldset:first').find('.form-type-options table').attr('id');
824 if (Drupal.optionElements[optionsElementId]) {
825 Drupal.optionElements[optionsElementId].setMultiple(this.value != 1);
826 }
827 }).trigger('change');
828 }
829 }
830
831 })(jQuery);