Index: webform_hooks.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/webform/webform_hooks.php,v
retrieving revision 1.9.2.2
diff -u -r1.9.2.2 webform_hooks.php
--- webform_hooks.php	11 Mar 2010 01:40:15 -0000	1.9.2.2
+++ webform_hooks.php	29 Mar 2010 02:53:12 -0000
@@ -15,6 +15,30 @@
  */
 
 /**
+ * Define callbacks that can be used as select list options.
+ *
+ * @return
+ *   An array of callbacks that can be used for select list options. This array
+ *   should be keyed by the "name" of the pre-defined list. The values should
+ *   be an array with the following additional keys:
+ *     - title: The translated title for this list.
+ *     - options callback: The name of the function that will return the list.
+ *     - file: Optional. The file containing the options callback, relative to
+ *       the module root.
+ */
+function hook_webform_options_info() {
+  $items = array();
+
+  $items['days'] = array(
+    'title' => t('Days of the week'),
+    'options callback' => 'webform_options_days',
+    'file' => 'includes/webform.options.inc',
+  );
+
+  return $items;
+}
+
+/**
  * Respond to the loading of Webform submissions.
  *
  * @param $submissions
Index: webform.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/webform/webform.module,v
retrieving revision 1.196.2.12
diff -u -r1.196.2.12 webform.module
--- webform.module	27 Mar 2010 23:51:15 -0000	1.196.2.12
+++ webform.module	29 Mar 2010 02:53:12 -0000
@@ -195,6 +195,16 @@
     'type' => MENU_LOCAL_TASK,
   );
 
+  // AJAX callback for loading select list options.
+  $items['webform/ajax/options/%webform_menu'] = array(
+    'load arguments' => array(3),
+    'page callback' => 'webform_select_options_ajax',
+    'access callback' => 'node_access',
+    'access arguments' => array('update', 3),
+    'file' => 'components/select.inc',
+    'type' => MENU_CALLBACK,
+  );
+
   // Node webform results.
   $items['node/%webform_menu/webform-results'] = array(
     'title' => 'Results',
@@ -356,7 +366,7 @@
   module_load_include('inc', 'webform', 'includes/webform.components');
   if ($cid == 'new') {
     $components = webform_components();
-    $component = in_array($type, array_keys($components)) ? array('type' => $type, 'name' => $_GET['name'], 'mandatory' => $_GET['mandatory'], 'pid' => $_GET['pid'], 'weight' => $_GET['weight']) : FALSE;
+    $component = in_array($type, array_keys($components)) ? array('type' => $type, 'nid' => $nid, 'name' => $_GET['name'], 'mandatory' => $_GET['mandatory'], 'pid' => $_GET['pid'], 'weight' => $_GET['weight']) : FALSE;
   }
   else {
     $node = node_load($nid);
@@ -747,6 +757,14 @@
 }
 
 /**
+ * Implementation of hook_webform_select_options_info().
+ */
+function webform_webform_select_options_info() {
+  module_load_include('inc', 'webform', 'includes/webform.options');
+  return _webform_options_info();
+}
+
+/**
  * Implementation of hook_file_download().
  *
  * Only allow users with view webform submissions to download files.
Index: components/select.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/webform/components/select.inc,v
retrieving revision 1.39.2.9
diff -u -r1.39.2.9 select.inc
--- components/select.inc	25 Mar 2010 01:00:03 -0000	1.39.2.9
+++ components/select.inc	29 Mar 2010 02:53:12 -0000
@@ -27,6 +27,7 @@
       'other_text' => t('Other...'),
       'description' => '',
       'custom_keys' => FALSE,
+      'options_source' => '',
     ),
   );
 }
@@ -48,7 +49,28 @@
 function _webform_edit_select($component) {
   $form = array();
 
+  drupal_add_js(drupal_get_path('module', 'webform') . '/components/select.js', 'module', 'header', FALSE, TRUE, FALSE);
+  drupal_add_js(array('webform' => array('selectOptionsUrl' => url('webform/ajax/options/' . $component['nid']))), 'setting');
+
   $other = array();
+  if ($info = _webform_select_options_info()) {
+    $options = array('' => t('None'));
+    foreach ($info as $name => $source) {
+      $options[$name] = $source['title'];
+    }
+
+    $other['options_source'] = array(
+      '#title' => t('Load a pre-built option list'),
+      '#type' => 'select',
+      '#options' => $options,
+      '#default_value' => $component['extra']['options_source'],
+      '#weight' => 1,
+      '#description' => t('Use a pre-built list of options rather than entering options manually. Options will not be editable if using pre-built list.'),
+      '#parents' => array('extra', 'options_source'),
+      '#weight' => 5,
+    );
+  }
+
   if (module_exists('select_or_other')) {
     $other['other_option'] = array(
       '#type' => 'checkbox',
@@ -56,6 +78,7 @@
       '#default_value' => $component['extra']['other_option'],
       '#description' => t('Check this option if you want to allow users to enter an option not on the list.'),
       '#parents' => array('extra', 'other_option'),
+      '#weight' => 2,
     );
     $other['other_text'] = array(
       '#type' => 'textfield',
@@ -63,11 +86,12 @@
       '#default_value' => $component['extra']['other_text'],
       '#description' => t('If allowing other options, enter text to be used for other-enabling option.'),
       '#parents' => array('extra', 'other_text'),
+      '#weight' => 3,
     );
   }
 
   if (module_exists('options_element')) {
-    $options = _webform_select_options($component['extra']['items']);
+    $options = _webform_select_options($component);
 
     $form['items'] = array(
       '#type' => 'fieldset',
@@ -75,6 +99,7 @@
       '#collapsible' => TRUE,
       '#attributes' => array('class' => 'webform-options-element'),
       '#element_validate' => array('_webform_edit_validate_options'),
+      '#weight' => 2,
     );
 
     $form['items']['options'] = array(
@@ -88,6 +113,7 @@
       '#key_type' => 'mixed',
       '#key_type_toggle' => t('Customize keys (Advanced)'),
       '#key_type_toggled' => $component['extra']['custom_keys'],
+      '#disabled' => !empty($component['extra']['options_source']),
       '#weight' => 1,
     );
 
@@ -103,6 +129,7 @@
       '#rows' => 5,
       '#weight' => -2,
       '#required' => TRUE,
+      '#attributes' => array('readonly' => !empty($component['extra']['options_source'])),
       '#element_validate' => array('_webform_edit_validate_select'),
     );
     $form['extra'] = array_merge($form['extra'], $other);
@@ -120,6 +147,7 @@
       '#title' => t('Multiple'),
       '#default_value' => $component['extra']['multiple'],
       '#description' => t('Check this option if the user should be allowed to choose multiple values.'),
+      '#weight' => 0,
     );
   }
 
@@ -233,7 +261,7 @@
 
   // Convert the user-entered options list into an array.
   $default_value = $filter ? _webform_filter_values($component['value'], NULL, NULL, NULL, FALSE) : $component['value'];
-  $options = _webform_select_options($component['extra']['items'], !$component['extra']['aslist'], $filter);
+  $options = _webform_select_options($component, $filter);
 
   if ($component['extra']['optrand']) {
     _webform_shuffle_options($options);
@@ -395,7 +423,7 @@
  * Convert FAPI 0/1 values into something saveable.
  */
 function _webform_submit_select($component, $value) {
-  $options = drupal_map_assoc(array_flip(_webform_select_options($component['extra']['items'], TRUE)));
+  $options = drupal_map_assoc(array_flip(_webform_select_options($component)));
 
   $return = NULL;
   if (is_array($value)) {
@@ -431,7 +459,7 @@
   $component = $element['#component'];
 
   // Convert submitted 'safe' values to un-edited, original form.
-  $options = _webform_select_options($component['extra']['items'], TRUE);
+  $options = _webform_select_options($component);
 
   $items = array();
   if ($component['extra']['multiple']) {
@@ -483,7 +511,7 @@
  * Implementation of _webform_analysis_component().
  */
 function _webform_analysis_select($component, $sids = array(), $single = FALSE) {
-  $options = _webform_select_options($component['extra']['items'], TRUE);
+  $options = _webform_select_options($component);
   $show_other_results = $single;
 
   $sid_placeholders = count($sids) ? array_fill(0, count($sids), "'%s'") : array();
@@ -568,7 +596,7 @@
   if ($component['extra']['multiple'] && $export_options['select_format'] == 'separate') {
     $headers[0][] = '';
     $headers[1][] = $component['name'];
-    $items = _webform_select_options($component['extra']['items'], TRUE);
+    $items = _webform_select_options($component);
     if ($component['extra']['other_option']) {
       $other_label = !empty($component['extra']['other_text']) ? check_plain($component['extra']['other_text']) : t('Other...');
       $items[$other_label] = $other_label;
@@ -596,7 +624,7 @@
  * Implementation of _webform_csv_data_component().
  */
 function _webform_csv_data_select($component, $export_options, $value) {
-  $options = _webform_select_options($component['extra']['items'], TRUE);
+  $options = _webform_select_options($component);
   $return = array();
 
   if ($component['extra']['multiple']) {
@@ -628,6 +656,92 @@
 }
 
 /**
+ * Menu callback; Return a predefined list of select options as JSON.
+ */
+function webform_select_options_ajax($source_name = '') {
+  $info = _webform_select_options_info();
+
+  $component['extra']['options_source'] = $source_name;
+  if ($source_name && isset($info[$source_name])) {
+    $options = _webform_select_options_to_text(_webform_select_options($component, FALSE));
+  }
+  else {
+    $options = '';
+  }
+
+  $return = array(
+    'elementId' => module_exists('options_element') ? 'edit-items-options-options-field-widget' : 'edit-extra-items',
+    'options' => $options,
+  );
+
+  drupal_json($return);
+}
+
+
+/**
+ * Generate a list of options for a select list.
+ */
+function _webform_select_options($component, $filter = TRUE) {
+  if ($component['extra']['options_source']) {
+    $options = _webform_select_options_callback($component['extra']['options_source'], 'options', $component);
+  }
+  else {
+    $options = _webform_select_options_from_text($component['extra']['items'], !$component['extra']['aslist'], $filter);
+  }
+
+  return isset($options) ? $options : array();
+}
+
+/**
+ * Load Webform select option info from 3rd party modules.
+ */
+function _webform_select_options_info() {
+  static $info;
+  if (!isset($info)) {
+    $info = array();
+
+    foreach (module_implements('webform_select_options_info') as $module) {
+      $additions = module_invoke($module, 'webform_select_options_info');
+      foreach ($additions as $key => $addition) {
+        $additions[$key]['module'] = $module;
+      }
+      $info = array_merge($info, $additions);
+    }
+    drupal_alter('webform_select_options_info', $info);
+  }
+  return $info;
+}
+
+/**
+ * Execute a select option callback.
+ *
+ * @param $name
+ *   The name of the options group.
+ * @param $type
+ *   The callback to be executed, with "form" or "options".
+ * @param $component
+ *   The full Webform component.
+ * @param $filter
+ *   Whether information returned should be sanitized. Defaults to TRUE.
+ */
+function _webform_select_options_callback($name, $type, $component, $filter = TRUE) {
+  $info = _webform_select_options_info();
+
+  // Include any necessary files.
+  if (isset($info[$name]['file'])) {
+    $pathinfo = pathinfo($info[$name]['file']);
+    $path = ($pathinfo['dirname'] ? $pathinfo['dirname'] . '/' : '') . basename($pathinfo['basename'], '.' . $pathinfo['extension']);
+    module_load_include($pathinfo['extension'], $info[$name]['module'], $path);
+  }
+
+  // Execute the callback function.
+  if (isset($info[$name][$type . ' callback']) && function_exists($info[$name][$type . ' callback'])) {
+    $function = $info[$name][$type . ' callback'];
+    return $function($component, $filter);
+  }
+}
+
+/**
  * Utility function to split user-entered values from new-line seperated
  * text into an array of options.
  *
@@ -638,7 +752,7 @@
  * @param $filter
  *   Optional. Whether or not to filter returned values.
  */
-function _webform_select_options($text, $flat = FALSE, $filter = TRUE) {
+function _webform_select_options_from_text($text, $flat = FALSE, $filter = TRUE) {
   static $option_cache = array();
 
   // Keep each processed option block in an array indexed by the MD5 hash of
@@ -687,6 +801,40 @@
   return $option_cache[$flat][$md5];
 }
 
+
+/**
+ * Convert an array of options into text.
+ */
+function _webform_select_options_to_text($options) {
+  $output = '';
+  $previous_key = false;
+
+  foreach ($options as $key => $value) {
+    // Convert groups.
+    if (is_array($value)) {
+      $output .= '<' . $key . '>' . "\n";
+      foreach ($value as $subkey => $subvalue) {
+        $output .= $subkey . '|' . $subvalue . "\n";
+      }
+      $previous_key = $key;
+    }
+    // Typical key|value pairs.
+    else {
+      // Exit out of any groups.
+      if (isset($options[$previous_key]) && is_array($options[$previous_key])) {
+        $output .= "<>\n";
+      }
+      // Skip empty rows.
+      if ($options[$key] !== '') {
+        $output .= $key . '|' . $value . "\n";
+      }
+      $previous_key = $key;
+    }
+  }
+
+  return $output;
+}
+
 /**
  * Utility function to shuffle an array while preserving key-value pairs.
  */
Index: components/grid.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/webform/components/grid.inc,v
retrieving revision 1.14.2.4
diff -u -r1.14.2.4 grid.inc
--- components/grid.inc	25 Mar 2010 01:00:03 -0000	1.14.2.4
+++ components/grid.inc	29 Mar 2010 02:53:12 -0000
@@ -64,7 +64,7 @@
     );
     $form['options']['options'] = array(
       '#type' => 'options',
-      '#options' => _webform_select_options($component['extra']['options'], TRUE),
+      '#options' => _webform_select_options_from_text($component['extra']['options'], TRUE),
       '#optgroups' => FALSE,
       '#default_value' => FALSE,
       '#optgroups' => FALSE,
@@ -83,7 +83,7 @@
     );
     $form['questions']['options'] = array(
       '#type' => 'options',
-      '#options' => _webform_select_options($component['extra']['questions'], TRUE),
+      '#options' => _webform_select_options_from_text($component['extra']['questions'], TRUE),
       '#optgroups' => FALSE,
       '#default_value' => FALSE,
       '#optgroups' => FALSE,
@@ -148,8 +148,8 @@
     '#suffix' => '</div>',
   );
 
-  $questions = _webform_select_options($component['extra']['questions'], TRUE);
-  $options = _webform_select_options($component['extra']['options'], TRUE);
+  $questions = _webform_select_options_from_text($component['extra']['questions'], TRUE);
+  $options = _webform_select_options_from_text($component['extra']['options'], TRUE);
 
   if ($component['extra']['optrand']) {
     _webform_shuffle_options($options);
@@ -185,8 +185,8 @@
  * Implementation of _webform_display_component().
  */
 function _webform_display_grid($component, $value, $format = 'html') {
-  $questions = _webform_select_options($component['extra']['questions'], TRUE);
-  $options = _webform_select_options($component['extra']['options'], TRUE);
+  $questions = _webform_select_options_from_text($component['extra']['questions'], TRUE);
+  $options = _webform_select_options_from_text($component['extra']['options'], TRUE);
 
   $element = array(
     '#title' => $component['name'],
@@ -261,8 +261,8 @@
  */
 function _webform_analysis_grid($component, $sids = array()) {
   // Generate the list of options and questions.
-  $options = _webform_select_options($component['extra']['options'], TRUE);
-  $questions = _webform_select_options($component['extra']['questions'], TRUE);
+  $options = _webform_select_options_from_text($component['extra']['options'], TRUE);
+  $questions = _webform_select_options_from_text($component['extra']['questions'], TRUE);
 
   // Generate a lookup table of results.
   $placeholders = count($sids) ? array_fill(0, count($sids), "'%s'") : array();
@@ -306,7 +306,7 @@
  * Implementation of _webform_table_component().
  */
 function _webform_table_grid($component, $value) {
-  $questions = _webform_select_options($component['extra']['questions'], TRUE);
+  $questions = _webform_select_options_from_text($component['extra']['questions'], TRUE);
   $output = '';
   // Set the value as a single string.
   if (is_array($value)) {
@@ -329,7 +329,7 @@
   $header = array();
   $header[0] = array('');
   $header[1] = array($component['name']);
-  $items = _webform_select_options($component['extra']['questions'], TRUE);
+  $items = _webform_select_options_from_text($component['extra']['questions'], TRUE);
   $count = 0;
   foreach ($items as $key => $item) {
     // Empty column per sub-field in main header.
@@ -349,7 +349,7 @@
  * Implementation of _webform_csv_data_component().
  */
 function _webform_csv_data_grid($component, $export_options, $value) {
-  $questions = _webform_select_options($component['extra']['questions'], TRUE);
+  $questions = _webform_select_options_from_text($component['extra']['questions'], TRUE);
   $return = array();
   foreach ($questions as $key => $question) {
     $return[] = isset($value[$key]) ? $question : '';
Index: includes/webform.options.inc
===================================================================
RCS file: includes/webform.options.inc
diff -N includes/webform.options.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ includes/webform.options.inc	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,134 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * A collection of built-in select list options for Webform.
+ */
+
+/**
+ * Private implementation of hook_webform_options_info().
+ *
+ * @see webform_webform_options_info().
+ */
+function _webform_options_info() {
+  $items = array();
+
+  $items['days'] = array(
+    'title' => t('Days of the week'),
+    'options callback' => 'webform_options_days',
+    'file' => 'includes/webform.options.inc',
+  );
+
+  if (function_exists('countries_api_get_array')) {
+    $items['countries'] = array(
+      'title' => t('Countries'),
+      'options callback' => 'webform_options_countries',
+      'file' => 'includes/webform.options.inc',
+    );
+  }
+
+  $items['united_states'] = array(
+    'title' => t('US states'),
+    'options callback' => 'webform_options_united_states',
+    'file' => 'includes/webform.options.inc',
+  );
+
+  return $items;
+}
+
+/**
+ * Option list containing the days of the week.
+ */
+function webform_options_days() {
+  $days = array(
+    'sunday' => t('Sunday'),
+    'monday' => t('Monday'),
+    'tuesday' => t('Tuesday'),
+    'wednesday' => t('Wednesday'),
+    'thursday' => t('Thursday'),
+    'friday' => t('Friday'),
+    'saturday' => t('Saturday'),
+  );
+
+  // Order according to site settings for first day.
+  if ($first_day = variable_get('date_first_day', 0)) {
+    $week = array_splice($days, $first_day);
+    $days = array_merge($week, $days);
+  }
+
+  return $days;
+}
+
+/**
+ * Options list containing country names.
+ */
+function webform_options_countries() {
+  return countries_api_get_array();
+}
+
+/**
+ * Options list containing United States states and territories.
+ */
+function webform_options_united_states() {
+  return array(
+    'AL' => t('Alabama'),
+    'AK' => t('Alaska'),
+    'AS' => t('American Samoa'),
+    'AZ' => t('Arizona'),
+    'AR' => t('Arkansas'),
+    'CA' => t('California'),
+    'CO' => t('Colorado'),
+    'CT' => t('Connecticut'),
+    'DE' => t('Delaware'),
+    'DC' => t('District of Columbia'),
+    'FL' => t('Florida'),
+    'GA' => t('Georgia'),
+    'GU' => t('Guam'),
+    'HI' => t('Hawaii'),
+    'ID' => t('Idaho'),
+    'IL' => t('Illinois'),
+    'IN' => t('Indiana'),
+    'IA' => t('Iowa'),
+    'KS' => t('Kansas'),
+    'KY' => t('Kentucky'),
+    'LA' => t('Louisiana'),
+    'ME' => t('Maine'),
+    'MH' => t('Marshall Islands'),
+    'MD' => t('Maryland'),
+    'MA' => t('Massachusetts'),
+    'MI' => t('Michigan'),
+    'MN' => t('Minnesota'),
+    'MS' => t('Mississippi'),
+    'MO' => t('Missouri'),
+    'MT' => t('Montana'),
+    'NE' => t('Nebraska'),
+    'NV' => t('Nevada'),
+    'NH' => t('New Hampshire'),
+    'NJ' => t('New Jersey'),
+    'NM' => t('New Mexico'),
+    'NY' => t('New York'),
+    'NC' => t('North Carolina'),
+    'ND' => t('North Dakota'),
+    'MP' => t('Northern Marianas Islands'),
+    'OH' => t('Ohio'),
+    'OK' => t('Oklahoma'),
+    'OR' => t('Oregon'),
+    'PW' => t('Palau'),
+    'PA' => t('Pennsylvania'),
+    'PR' => t('Puerto Rico'),
+    'RI' => t('Rhode Island'),
+    'SC' => t('South Carolina'),
+    'SD' => t('South Dakota'),
+    'TN' => t('Tennessee'),
+    'TX' => t('Texas'),
+    'UT' => t('Utah'),
+    'VT' => t('Vermont'),
+    'VI' => t('Virgin Islands'),
+    'VA' => t('Virginia'),
+    'WA' => t('Washington'),
+    'WV' => t('West Virginia'),
+    'WI' => t('Wisconsin'),
+    'WY' => t('Wyoming'),
+  );
+}
Index: components/select.js
===================================================================
RCS file: components/select.js
diff -N components/select.js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ components/select.js	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,56 @@
+// $Id$
+
+/**
+ * @file
+ * Enhancements for select list configuration options.
+ */
+
+(function ($) {
+
+Drupal.behaviors.webformSelectLoadOptions = function(context) {
+  settings = Drupal.settings;
+
+  $('#edit-extra-options-source', context).change(function() {
+    var url = settings.webform.selectOptionsUrl + '/' + this.value;
+    $.ajax({
+      url: url,
+      success: Drupal.webform.selectOptionsLoad,
+      dataType: 'json'
+    });
+  });
+}
+
+Drupal.webform = Drupal.webform || {};
+
+Drupal.webform.selectOptionsOriginal = false;
+Drupal.webform.selectOptionsLoad = function(result) {
+  if (Drupal.optionsElement) {
+    if (result.options) {
+      // Save the current select options the first time a new list is chosen.
+      if (Drupal.webform.selectOptionsOriginal === false) {
+        Drupal.webform.selectOptionsOriginal = $(Drupal.optionElements[result.elementId].manualOptionsElement).val();
+      }
+      $(Drupal.optionElements[result.elementId].manualOptionsElement).val(result.options);
+      Drupal.optionElements[result.elementId].disable();
+      Drupal.optionElements[result.elementId].updateWidgetElements();
+    }
+    else {
+      Drupal.optionElements[result.elementId].enable();
+      if (Drupal.webform.selectOptionsOriginal) {
+        $(Drupal.optionElements[result.elementId].manualOptionsElement).val(Drupal.webform.selectOptionsOriginal);
+        Drupal.optionElements[result.elementId].updateWidgetElements();
+        Drupal.webform.selectOptionsOriginal = false;
+      }
+    }
+  }
+  else {
+    if (result.options) {
+      $('#' + result.elementId).val(result.options).attr('readonly', 'readonly');
+    }
+    else {
+      $('#' + result.elementId).attr('readonly', '');
+    }
+  }
+}
+
+})(jQuery);
