[ Index ]

PHP Cross Reference of Drupal 6 (gatewave)

title

Body

[close]

/sites/all/modules/form_builder/options_element/ -> options_element.js (source)

   1  // $Id: options_element.js,v 1.8 2009/01/13 03:39:56 quicksketch Exp $
   2  
   3  /**
   4   * @file
   5   * Add JavaScript behaviors for the "options" form element type.
   6   */
   7  Drupal.behaviors.optionsElement = function(context) {
   8    $('div.form-options:not(.options-element-processed)', context).each(function() {
   9      $(this).addClass('options-element-processed');
  10      new Drupal.optionsElement(this);
  11    });
  12  };
  13  
  14  /**
  15   * Constructor for an options element.
  16   */
  17  Drupal.optionsElement = function(element) {
  18    var self = this;
  19  
  20    // Find the original "manual" fields.
  21    this.element = element;
  22    this.manualElement = $(element).find('fieldset.options').get(0);
  23    this.manualOptionsElement = $(element).find('textarea').get(0);
  24    this.manualDefaultValueElement = $(element).find('input.form-text').get(0);
  25    this.keyTypeToggle = $(element).find('input.key-type-toggle').get(0);
  26    this.multipleToggle = $(element).find('input.multiple-toggle').get(0);
  27  
  28    // Setup variables containing the current status of the widget.
  29    this.optgroups = $(element).is('.options-optgroups');
  30    this.multiple = $(element).is('.options-multiple');
  31    this.keyType = element.className.replace(/^.*?options-key-type-([a-z]+).*?$/, '$1');
  32    this.identifier = this.manualOptionsElement.id + '-widget';
  33  
  34    // Warning messages.
  35    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?');
  36  
  37    // Setup new DOM elements containing the actual options widget.
  38    this.optionsElement = $('<div></div>').get(0); // Temporary DOM object. 
  39    this.optionsToggleElement = $(Drupal.theme('optionsElementToggle')).get(0);
  40  
  41    // Add the options widget and toggle elements to the page.
  42    $(this.manualElement).hide().before(this.optionsElement).after(this.optionsToggleElement);
  43  
  44    // Add a toggle action for manual entry of options.
  45    $(this.optionsToggleElement).find('a').click(function() {
  46      self.toggleMode();
  47      return false;
  48    });
  49  
  50    // Add a handler for key type changes.
  51    if (this.keyTypeToggle) {
  52      $(this.keyTypeToggle).change(function(){
  53        var checked = $(this).attr('checked');
  54        // Before switching to associative keys, ensure we're not destorying
  55        // any custom specified keys.
  56        if (!checked) {
  57          var options = self.optionsFromText();
  58          var confirm = false;
  59          for (var n in options) {
  60            if (options[n].key != options[n].value) {
  61              confirm = true;
  62              break;
  63            }
  64          }
  65          if (confirm) {
  66            if (window.confirm(self.keyChangeWarning)) {
  67              self.setKeyType('associative');
  68            }
  69          }
  70          else {
  71            self.setKeyType('associative');
  72          }
  73        }
  74        else {
  75          self.setKeyType('custom');
  76        }
  77      });
  78    }
  79  
  80    // Add a handler for multiple value changes.
  81    if (this.multipleToggle) {
  82      $(this.multipleToggle).change(function(){
  83        self.setMultiple($(this).attr('checked'));
  84      });
  85    }
  86  
  87    // Update the options widget with the current state of the textarea.
  88    this.updateWidgetElements();
  89  }
  90  
  91  /**
  92   * Update the widget element based on the current values of the manual elements.
  93   */
  94  Drupal.optionsElement.prototype.updateWidgetElements = function () {
  95    var self = this;
  96  
  97    // Create a new options element and replace the existing one.
  98    var newElement = $(Drupal.theme('optionsElement', this)).get(0);
  99    if ($(this.optionsElement).is(':not(:visible)')) {
 100      $(newElement).css('display', 'none');
 101    }
 102    $(this.optionsElement).replaceWith(newElement);
 103    this.optionsElement = newElement;
 104  
 105    // Manually setup table drag for the created table.
 106    Drupal.settings.tableDrag = Drupal.settings.tableDrag || {};
 107    Drupal.settings.tableDrag[this.identifier] = {
 108      'option-depth': {
 109        0: {
 110          action: 'depth',
 111          hidden: false,
 112          limit: 0,
 113          relationship: 'self',
 114          source: 'option-depth',
 115          target: 'option-depth',
 116        }
 117      }
 118    };
 119  
 120    // Allow indentation of elements if optgroups are supported.
 121    if (this.optgroups) {
 122      Drupal.settings.tableDrag[this.identifier]['option-parent'] = {
 123        0: {
 124          action: 'match',
 125          hidden: false,
 126          limit: 1,
 127          relationship: 'parent',
 128          source: 'option-key',
 129          target: 'option-parent',
 130        }
 131      };
 132    }
 133  
 134    // Enable button for adding options.
 135    $('a.add', this.optionElement).click(function() {
 136      self.addOption($(this).parents('tr:first').get(0));
 137      return false;
 138    });
 139  
 140    // Enable button for removing options.
 141    $('a.remove', this.optionElement).click(function() {
 142      self.removeOption($(this).parents('tr:first').get(0));
 143      return false;
 144    });
 145  
 146    // Add the same update action to all textfields and radios.
 147    $('input', this.optionsElement).change(function() {
 148      self.updateOptionElements();
 149      self.updateManualElements();
 150    });
 151  
 152    // Add a delayed update to textfields.
 153    $('input.option-value', this.optionsElement).keyup(function(e) {
 154      self.pendingUpdate(e);
 155    });
 156  
 157    // Attach behaviors as normal to the new widget.
 158    Drupal.attachBehaviors(this.optionsElement);
 159  
 160    // Add an onDrop action to the table drag instance.
 161    Drupal.tableDrag[this.identifier].onDrop = function() {
 162      // Update the checkbox/radio buttons for selecting default values.
 163      if (self.optgroups) {
 164        self.updateOptionElements();
 165      }
 166      // Update the options within the hidden text area.
 167      self.updateManualElements();
 168    };
 169  
 170    // Add an onIndent action to the table drag row instances.
 171    Drupal.tableDrag[this.identifier].row.prototype.onIndent = function() {
 172      if (this.indents) {
 173        $(this.element).addClass('indented');
 174      }
 175      else {
 176        $(this.element).removeClass('indented');
 177      }
 178    };
 179  
 180    // Set the tab indexes.
 181    this.updateOptionElements();
 182  }
 183  
 184  /**
 185   * Update the original form elment based on the current state of the widget.
 186   */
 187  Drupal.optionsElement.prototype.updateManualElements = function() {
 188    var options = {};
 189  
 190    // Build a list of current options.
 191    var previousOption = false;
 192    $(this.optionsElement).find('input.option-value').each(function() {
 193      var $row = $(this).is('tr') ? $(this) : $(this).parents('tr:first');
 194      var depth = $row.find('input.option-depth').val();
 195      if (depth == 1 && previousOption) {
 196        if (typeof(options[previousOption]) != 'object') {
 197          options[previousOption] = {};
 198        }
 199        options[previousOption][this.value] = this.value;
 200      }
 201      else {
 202        options[this.value] = this.value;
 203        previousOption = this.value;
 204      }
 205    });
 206    this.options = options;
 207  
 208    // Update the default value.
 209    var defaultValue = this.multiple ? [] : '';
 210    var multiple = this.multiple;
 211    $(this.optionsElement).find('input.option-default').each(function() {
 212      if (this.checked) {
 213        if (multiple) {
 214          defaultValue.push(this.value);
 215        }
 216        else {
 217          defaultValue = this.value;
 218        }
 219      }
 220    });
 221    this.defaultValue = defaultValue;
 222  
 223    // Update with the new text and trigger the change action on the field.
 224    this.optionsToText();
 225    $(this.manualOptionsElement).change();
 226  }
 227  
 228  /**
 229   * Several maintenance routines to update all rows of the options element.
 230   *
 231   * - Disable options for optgroups if indented.
 232   * - Disable add and delete links if indented.
 233   * - Match the default value radio button value to the key of the text element.
 234   * - Reset the taborder.
 235   */
 236  Drupal.optionsElement.prototype.updateOptionElements = function() {
 237    var self = this;
 238    var previousElement = false;
 239    var $rows = $(this.optionsElement).find('tr');
 240  
 241    $rows.each(function(index) {
 242      // Update the elements key if matching the key and value.
 243      if (self.keyType == 'associative') {
 244        var optionValue = $(this).find('input.option-value').val();
 245        $(this).find('input.option-key').val(optionValue);
 246      }
 247  
 248      // Match the default value checkbox/radio button to the option's key.
 249      var optionKey = $(this).find('input.option-key').val();
 250      $(this).find('input.option-default').val(optionKey);
 251  
 252      // Set the tab order.
 253      $(this).find('input.form-text').attr('tabindex', index + 1);
 254  
 255      // Hide the add/remove links the row if indented.
 256      var depth = $(this).find('input.option-depth').val();
 257      var parent = $(this).find('input.option-parent').val();
 258      var defaultInput = $(this).find('input.option-default').get(0);
 259  
 260      if (depth == 1) {
 261        $(previousElement).attr('disabled', true).attr('checked', false);
 262        $(previousElement).parents('tr:first').find('a.add, a.remove').hide();
 263        $(defaultInput).parents('tr:first').find('a.add, a.remove').show();
 264        $(defaultInput).attr('disabled', false);
 265      }
 266      else {
 267        $(defaultInput).attr('disabled', false);
 268        $(defaultInput).parents('tr:first').find('a.add, a.remove').show();
 269        previousElement = defaultInput;
 270      }
 271    });
 272  
 273    // Do not allow the last item to be removed.
 274    if ($rows.size() == 1) {
 275      $rows.find('a.remove').hide();
 276    }
 277  }
 278  
 279  /**
 280   * Add a new option below the current row.
 281   */
 282  Drupal.optionsElement.prototype.addOption = function(currentOption) {
 283    var self = this;
 284    var newOption = $(currentOption).clone()
 285      .find('input.option-key').val(self.keyType == 'numeric' ? self.nextNumericKey() : '').end()
 286      .find('input.option-value').val('').end()
 287      .find('input.option-default').attr('checked', false).end()
 288      .find('a.tabledrag-handle').remove().end()
 289      .removeClass('drag-previous')
 290      .insertAfter(currentOption)
 291      .get(0);
 292  
 293    // Make the new option draggable.
 294    Drupal.tableDrag[this.identifier].makeDraggable(newOption);
 295  
 296    // Enable button for adding options.
 297    $('a.add', newOption).click(function() {
 298      self.addOption(newOption);
 299      return false;
 300    });
 301  
 302    // Enable buttons for removing options.
 303    $('a.remove', newOption).click(function() {
 304      self.removeOption(newOption);
 305      return false;
 306    });
 307  
 308    // Add the update action to all textfields and radios.
 309    $('input', newOption).change(function() {
 310      self.updateOptionElements();
 311      self.updateManualElements();
 312    });
 313  
 314    // Add a delayed update to textfields.
 315    $('input.option-value', newOption).keyup(function(e) {
 316      self.pendingUpdate(e);
 317    });
 318  
 319    this.updateOptionElements();
 320    this.updateManualElements();
 321  }
 322  
 323  /**
 324   * Remove the current row.
 325   */
 326  Drupal.optionsElement.prototype.removeOption = function(currentOption) {
 327    $(currentOption).remove();
 328  
 329    this.updateOptionElements();
 330    this.updateManualElements();
 331  }
 332  
 333  /**
 334   * Toggle link for switching between the JavaScript and manual entry.
 335   */
 336  Drupal.optionsElement.prototype.toggleMode = function() {
 337    if ($(this.optionsElement).is(':visible')) {
 338      var height = $(this.optionsElement).height();
 339      $(this.optionsElement).hide();
 340      $(this.manualElement).show().find('textarea').height(height);
 341      $(this.optionsToggleElement).find('a').html(Drupal.t('Normal entry'));
 342    }
 343    else {
 344      this.updateWidgetElements();
 345      $(this.optionsElement).show();
 346      $(this.manualElement).hide();
 347      $(this.optionsToggleElement).find('a').html(Drupal.t('Manual entry'));
 348    }
 349  }
 350  
 351  /**
 352   * Change the current key type (associative, custom, numeric, none).
 353   */
 354  Drupal.optionsElement.prototype.setKeyType = function(type) {
 355    $(this.element)
 356      .removeClass('options-key-type-' + this.keyType)
 357      .addClass('options-key-type-' + type);
 358    this.keyType = type;
 359    // Rebuild the options widget.
 360    this.updateManualElements();
 361    this.updateWidgetElements();
 362  }
 363  
 364  /**
 365   * Set the element's #multiple property. Boolean TRUE or FALSE.
 366   */
 367  Drupal.optionsElement.prototype.setMultiple = function(multiple) {
 368    if (multiple) {
 369      $(this.element).addClass('options-multiple');
 370    }
 371    else {
 372      // Unselect all default options except the first.
 373      $(this.optionsElement).find('input.option-default:checked:not(:first)').attr('checked', false);
 374      this.updateManualElements();
 375      $(this.element).removeClass('options-multiple');
 376    }
 377    this.multiple = multiple;
 378    // Rebuild the options widget.
 379    this.updateWidgetElements();
 380  }
 381  
 382  /**
 383   * Update a field after a delay.
 384   *
 385   * Similar to immediately changing a field, this field as pending changes that
 386   * will be updated after a delay. This includes textareas and textfields in
 387   * which updating continuously would be a strain the server and actually slow
 388   * down responsiveness.
 389   */
 390  Drupal.optionsElement.prototype.pendingUpdate = function(e) {
 391    var self = this;
 392  
 393    // Only operate on "normal" keys, excluding special function keys.
 394    // http://protocolsofmatrix.blogspot.com/2007/09/javascript-keycode-reference-table-for.html
 395    if (!(
 396      e.keyCode >= 48 && e.keyCode <= 90 || // 0-9, A-Z.
 397      e.keyCode >= 93 && e.keyCode <= 111 || // Number pad.
 398      e.keyCode >= 186 && e.keyCode <= 222 || // Symbols.
 399      e.keyCode == 8) // Backspace.
 400      ) {
 401      return;
 402    }
 403  
 404    if (this.updateDelay) {
 405      clearTimeout(this.updateDelay);
 406    }
 407  
 408    this.updateDelay = setTimeout(function(){
 409      self.updateOptionElements();
 410      self.updateManualElements();
 411    }, 500);
 412  };
 413  
 414  /**
 415   * Given an object of options, convert it to a text string.
 416   */
 417  Drupal.optionsElement.prototype.optionsToText = function() {
 418    var $rows = $('tbody tr', this.optionsElement);
 419    var output = '';
 420    var previousParent = $rows.filter(':last').find('input.option-parent').val();
 421    var rowCount = $rows.size();
 422    var defaultValues = [];
 423  
 424    // Loop through rows in reverse to find parents easier.
 425    for (var rowIndex = rowCount - 1; rowIndex >= 0; rowIndex--) {
 426      var indent = $rows.eq(rowIndex).find('input.option-depth').val();
 427      var key = $rows.eq(rowIndex).find('input.option-key').val();
 428      var value = $rows.eq(rowIndex).find('input.option-value').val();
 429      var parent = $rows.eq(rowIndex).find('input.option-parent').val();
 430      var checked = $rows.eq(rowIndex).find('input.option-default').attr('checked');
 431  
 432      // Add to default values.
 433      if (checked) {
 434        defaultValues.push(key);
 435      }
 436  
 437      // Handle groups.
 438      if (this.optgroups && key == previousParent) {
 439        output = '<' + key + '>' + "\n" + output;
 440        previousParent = '';
 441      }
 442      // Typical key|value pairs.
 443      else {
 444        // Exit out of any groups.
 445        if (this.optgroups && previousParent != parent && parent !== '') {
 446          output = "<>\n" + output;
 447        }
 448        if (this.keyType == 'none' || this.keyType == 'associative') {
 449          output = value + "\n" + output;
 450          previousParent = parent;
 451        }
 452        else if (key == '' && value == '') {
 453          output = "\n" + output;
 454        }
 455        else {
 456          output = key + '|' + value + "\n" + output;
 457          previousParent = parent;
 458        }
 459      }
 460    }
 461  
 462    this.manualOptionsElement.value = output;
 463    this.manualDefaultValueElement.value = defaultValues.join(', ');
 464  };
 465  
 466  /**
 467   * Given a text string, convert it to an object.
 468   */
 469  Drupal.optionsElement.prototype.optionsFromText = function() {
 470    var rows = this.manualOptionsElement.value.match(/^.*$/mg);
 471    var parentKey = '';
 472    var options = [];
 473    var defaultValues = {};
 474  
 475    // Drop the last row if empty.
 476    if (rows.length && rows[rows.length - 1] == '') {
 477      rows.pop();
 478    }
 479  
 480    if (this.multiple) {
 481      var defaults = this.manualDefaultValueElement.value.split(',');
 482      for (var n in defaults) {
 483        var defaultValue = defaults[n].replace(/^[ ]*(.*?)[ ]*$/, '$1'); // trim().
 484        defaultValues[defaultValue] = defaultValue;
 485      }
 486    }
 487    else {
 488      var defaultValue = this.manualDefaultValueElement.value.replace(/^[ ]*(.*?)[ ]*$/, '$1'); // trim().
 489      defaultValues[defaultValue] = defaultValue;
 490    }
 491  
 492    for (var n in rows) {
 493      var row = rows[n].replace(/^[ ]*(.*?)[ ]*$/, '$1'); // trim().
 494      var key = '';
 495      var value = '';
 496      var checked = false;
 497      var hasChildren = false;
 498      var groupClear = false;
 499  
 500      var matches = {};
 501      // Row is a group.
 502      if (this.optgroups && (matches = row.match(/^\<([^>]*)\>$/))) {
 503        if (matches[0] == '<>') {
 504          parentKey = '';
 505          groupClear = true;
 506        }
 507        else {
 508          parentKey = key = value = matches[1];
 509          hasChildren = true;
 510        }
 511      }
 512      // Row is a key|value pair.
 513      else if ((this.keyType == 'numeric' || this.keyType == 'custom') && (matches = row.match(/^([^|]+)\|(.*)$/))) {
 514        key = matches[1];
 515        value = matches[2];
 516      }
 517      // Row is a straight value.
 518      else {
 519        key = this.keyType == 'numeric' ? '' : row;
 520        value = row;
 521      }
 522  
 523      if (!groupClear) {
 524        options.push({
 525          key: key,
 526          value: value,
 527          parent: (key !== parentKey ? parentKey : ''),
 528          hasChildren: hasChildren,
 529          checked: (defaultValues[key] ? 'checked' : false)
 530        });
 531      }
 532    }
 533  
 534    // Convert options to numeric if no key is specified.
 535    if (this.keyType == 'numeric') {
 536      var nextKey = this.nextNumericKey();
 537      for (var n in options) {
 538        if (options[n].key == '') {
 539          options[n].key = nextKey;
 540          nextKey++;
 541        }
 542      }
 543    }
 544  
 545    return options;
 546  }
 547  
 548  /**
 549   * Utility method to get the next numeric option in a list of options.
 550   */
 551  Drupal.optionsElement.prototype.nextNumericKey = function(options) {
 552    this.keyType = 'custom';
 553    options = this.optionsFromText();
 554    this.keyType = 'numeric';
 555  
 556    var maxKey = -1;
 557    for (var n in options) {
 558      if (options[n].key.match(/^[0-9]+$/)) {
 559        maxKey = Math.max(maxKey, options[n].key);
 560      }
 561    }
 562    return maxKey + 1;
 563  }
 564  
 565  /**
 566   * Theme function for creating a new options element.
 567   *
 568   * @param optionsElement
 569   *   An options element object.
 570   */
 571  Drupal.theme.prototype.optionsElement = function(optionsElement) {
 572    var output = '';
 573    var options = optionsElement.optionsFromText();
 574    var defaultType = optionsElement.multiple ? 'checkbox' : 'radio';
 575    var keyType = optionsElement.keyType == 'custom' ? 'textfield' : 'hidden';
 576  
 577    // Helper function to print out a single draggable option row.
 578    function tableDragRow(key, value, parentKey, indent, status) {
 579      var output = '';
 580      output += '<tr class="draggable' + (indent > 0 ? ' indented' : '') + '">'
 581      output += '<td class="option-default-cell">';
 582      for (var n = 0; n < indent; n++) {
 583        output += Drupal.theme('tableDragIndentation');
 584      }
 585      output += '<input type="hidden" class="option-parent" value="' + parentKey + '" />';
 586      output += '<input type="hidden" class="option-depth" value="' + indent + '" />';
 587      output += '<input type="' + defaultType + '" name="' + optionsElement.identifier + '-default" class="form-radio option-default" value="' + key + '"' + (status == 'checked' ? ' checked="checked"' : '') + (status == 'disabled' ? ' disabled="disabled"' : '') + ' />';
 588      output += '</td><td class="' + (keyType == 'textfield' ? 'option-key-cell' : 'option-value-cell') +'">';
 589      output += '<input type="' + keyType + '" class="' + (keyType == 'textfield' ? 'form-text ' : '') + 'option-key" value="' + key + '" />';
 590      output += keyType == 'textfield' ? '</td><td class="option-value-call">' : '';
 591      output += '<input class="form-text option-value" type="text" value="' + value + '" />';
 592      output += '</td><td class="option-actions-cell">'
 593      output += '<a class="add" title="' + Drupal.t('Add new option') + '" href="#"' + (status == 'disabled' ? ' style="display: none"' : '') + '><span class="add">' + Drupal.t('Add') + '</span></a>';
 594      output += '<a class="remove" title="' + Drupal.t('Remove option') + '" href="#"' + (status == 'disabled' ? ' style="display: none"' : '') + '><span class="remove">' + Drupal.t('Remove') + '</span></a>';
 595      output += '</td>';
 596      output += '</tr>';
 597      return output;
 598    }
 599  
 600    output += '<div class="options-widget">';
 601    output += '<table id="' + optionsElement.identifier + '">';
 602  
 603    output += '<thead><tr>';
 604    output += '<th>' + Drupal.t('Default') + '</th>';
 605    output += keyType == 'textfield' ? '<th>' + Drupal.t('Key') + '</th>' : '';
 606    output += '<th>' + Drupal.t('Value') + '</th>';
 607    output += '<th>&nbsp;</th>';
 608    output += '</tr></thead>';
 609  
 610    output += '<tbody>';
 611  
 612    for (var n in options) {
 613      var option = options[n];
 614      var depth = option.parent === '' ? 0 : 1;
 615      if (option.hasChildren) {
 616        output += tableDragRow(option.key, option.key, option.parent, depth, 'disabled');
 617      }
 618      else {
 619        output += tableDragRow(option.key, option.value, option.parent, depth, option.checked);
 620      }
 621    }
 622  
 623    output += '</tbody>';
 624    output += '</table>';
 625    output += '<div>';
 626  
 627    return output;
 628  }
 629  
 630  Drupal.theme.prototype.optionsElementToggle = function() {
 631    return '<div class="form-options-manual"><a href="#">' + Drupal.t('Manual entry') + '</a></div>';
 632  }
 633  
 634  Drupal.theme.tableDragChangedMarker = function () {
 635    return ' ';
 636  };
 637  
 638  Drupal.theme.tableDragChangedWarning = function() {
 639    return ' ';
 640  }


Generated: Thu Mar 24 11:18:33 2011 Cross-referenced by PHPXref 0.7