multiselect.module in Multiselect 7
Same filename and directory in other branches
Allows users to select multiple items in an easier way than the normal node-reference widget.
File
multiselect.moduleView source
<?php
/**
* @file
* Allows users to select multiple items in an easier way than the normal node-reference widget.
*/
/**
* Implements hook_help().
*/
function multiselect_help($path, $arg) {
$output = '';
switch ($path) {
case 'admin/help#multiselect':
$output = '<p>' . t('Provides a CCK and Form API widget for editing fields that allows users to select from a list of options in a left box and have them visually moved into the right box when options are chosen.') . '</p>';
$output .= '<h2>' . t('Methods of Implementing a Multiselect Widget') . '</h2>';
$output .= '<h3>' . t('Method 1: Using CCK') . '</h3>';
$output .= '<p>' . t('When creating a new content field, select "Multiselect" as your widget type. You can use Multiselect on fields of type "list", "list_text", "list_number", "node_reference", "taxonomy_term_reference", and "user_reference".') . '</p>';
$output .= '<h3>' . t('Method 2: Coding Your Own Module') . '</h3>';
$output .= '<p>' . t('If you\'re developing a custom module and wish to use the Multiselect widget in place of a traditional "select" widget, you may use the Drupal 7 Form API. Included with the module is an example module called "multiselect_fapi_example" that creates a simple form on a page that stores the selected options in the Drupal "variables" table. The page is made available at the path: /multiselect_fapi_example.') . '</p>';
break;
}
return $output;
}
/**
* Implements hook_menu().
*/
function multiselect_menu() {
$items['admin/config/content/multiselect'] = array(
'title' => t('Multiselect'),
'description' => t('Configure how multiselect fields are displayed to content editors.'),
'page callback' => 'drupal_get_form',
'page arguments' => array(
'multiselect_settings',
),
'file' => 'multiselect.admin.inc',
'access arguments' => array(
'administer site configuration',
),
'type' => MENU_NORMAL_ITEM,
);
return $items;
}
/**
* Implements hook_init().
*/
function multiselect_init() {
$multiselect_widths = variable_get('multiselect_widths');
if (!empty($multiselect_widths)) {
drupal_add_js(array(
'multiselect' => array(
'widths' => $multiselect_widths,
),
), 'setting');
}
}
/**
* Implements hook_form_alter().
*/
function multiselect_form_alter(&$form, &$form_state, $form_id) {
// Provide additional help for the field settings form.
switch ($form_id) {
case 'field_ui_field_edit_form':
if (isset($form['instance']['widget'])) {
$widget_type = $form['instance']['widget']['type']['#value'];
$field_type = $form['#field']['type'];
$label = $form['instance']['label']['#default_value'];
if (in_array($widget_type, array(
'multiselect',
))) {
if (in_array($field_type, array(
'list',
'list_number',
'list_text',
'taxonomy_term_reference',
))) {
// CCK user_reference and node_reference fields don't need allowed values list.
// For other field types, if no 'allowed values' were set yet, add a reminder message.
if (empty($form['field']['settings']['allowed_values']['#default_value'])) {
drupal_set_message(t("You need to specify the 'allowed values' for this field."), 'warning');
}
}
}
}
break;
}
}
/**
* Implement hook_field_widget_info().
*/
function multiselect_field_widget_info() {
return array(
'multiselect' => array(
'label' => t('Multiselect'),
'field types' => array(
'list',
'list_text',
'list_number',
'node_reference',
'taxonomy_term_reference',
'user_reference',
'entityreference',
),
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_CUSTOM,
'default value' => FIELD_BEHAVIOR_CUSTOM,
),
),
);
}
/**
* Implements hook_field_widget_form().
* Build the form widget using Form API (as much as possible).
*/
function multiselect_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
/* START copy from options.module */
// Abstract over the actual field columns, to allow different field types to
// reuse those widgets.
$value_key = key($field['columns']);
$type = str_replace('options_', '', $instance['widget']['type']);
//$multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED;
$multiple = $field['cardinality'];
$required = $element['#required'];
$has_value = isset($items[0][$value_key]);
$properties = _options_properties($type, $multiple, $required, $has_value);
// Prepare the list of options.
$options = _options_get_options($field, $instance, $properties, NULL, NULL);
// Remove all tags from values. Very useful for views displays.
$options = array_map("strip_tags", $options);
// Put current field values in shape.
$default_value = _options_storage_to_form($items, $options, $value_key, $properties);
/* END copy from options.module */
$widget = _multiselect_build_widget_code($options, $items, $element, $required);
//for each selected item ($items are in delta order still), we unset the item from $options (which is in nid/tid order)
//then just re-append it to the bottom of the options array, in order of $items, this fools the FAPI and retains order on edit
foreach ($items as $item) {
if (isset($options[$item[$value_key]])) {
$val = $options[$item[$value_key]];
unset($options[$item[$value_key]]);
$options[$item[$value_key]] = $val;
}
}
// Build the basic select box using Form API.
$element += array(
'#type' => 'select',
'#title' => $element['#title'],
'#description' => $element['#description'],
'#required' => $required,
'#multiple' => $multiple,
'#options' => $options,
//'#options' => $selected_options,
'#size' => 10,
'#prefix' => $widget['prefix_pre'] . $widget['prefix_options'] . $widget['prefix_post'],
'#suffix' => "\n</div>\n",
'#attributes' => array(
'class' => array(
$widget['selfield'],
'multiselect_sel',
),
'id' => array(
$element['#field_name'],
),
),
'#default_value' => $default_value,
'#value_key' => $value_key,
'#element_validate' => array(
'options_field_widget_validate',
),
'#properties' => $properties,
'#after_build' => array(
'_multiselect_after_build',
),
);
// Attach CSS and Javascript for this widget.
$module_path = drupal_get_path('module', 'multiselect');
$element['#attached'] = array(
'css' => array(
$module_path . '/multiselect.css',
),
'js' => array(
$module_path . '/multiselect.js',
),
);
return $element;
}
/**
* After_build callback for this widget.
*/
function _multiselect_after_build($element, $form_state) {
$value_key = $element['#columns'][0];
$items = array();
if (isset($form_state['values'][$element['#field_name']][LANGUAGE_NONE])) {
foreach ($form_state['values'][$element['#field_name']][LANGUAGE_NONE] as $nid) {
$items[] = array(
$value_key => $nid,
);
}
}
else {
if (module_exists('field_collection')) {
$field_info = field_info_field($element['#field_name']);
$bundle = $field_info['bundles']['field_collection_item'][0];
foreach ($form_state['values'][$bundle][LANGUAGE_NONE] as $field) {
$field_name = $element['#field_name'];
if (is_array($field) && isset($field[$field_name])) {
foreach ($field[$field_name][LANGUAGE_NONE] as $nid) {
$items[] = array(
$value_key => $nid,
);
}
}
}
}
}
$widget = _multiselect_build_widget_code($element['#options'], $items, $element, $element['#required']);
$element['#prefix'] = $widget['prefix_pre'] . $widget['prefix_options'] . $widget['prefix_post'];
return $element;
}
/**
* Implements hook_field_error().
*/
function multiselect_field_widget_error($element, $error) {
switch ($error['error']) {
case 'field_cardinality':
form_error($element, $error['message']);
break;
}
}
/**
* Copied from D6 CCK content module. Not sure where this went in D7.
* Filter out HTML from allowed values array while leaving entities unencoded.
*/
function _multiselect_allowed_values_filter_html(&$options) {
foreach ($options as $key => $opt) {
if (is_array($opt)) {
_multiselect_allowed_values_filter_html($options[$key]);
}
else {
$options[$key] = html_entity_decode(strip_tags($opt), ENT_QUOTES);
}
}
}
function _multiselect_html_for_box_options($options) {
$boxhtml = '';
foreach ($options as $value => $name) {
$boxhtml .= "<option value=\"" . $value . "\">" . $name . "</option>\n";
}
return $boxhtml;
}
/**
* Build the widget HTML code.
* @param $options = array of options available for use in the widget
* @param $items = array of previously selected options (or default values in FAPI calls)
* @param $element = the form element being created
* return $widget = an array of html items and values for use in the final presentation of widget.
*/
function _multiselect_build_widget_code($options, $items, $element, $required = FALSE) {
// For this specific widget, HTML should be filtered out and entities left unencoded.
// See content_allowed_values / content_filter_xss / filter_xss.
_multiselect_allowed_values_filter_html($options);
// Create some arrays for use later in the function.
$selected_options = array();
$unselected_options = array();
// Add selected items to the array first
if (is_array($items)) {
foreach ($items as $key => $value) {
if (isset($value['value']['value'])) {
// Field collections have them nested.
$selected_options[$value['value']['value']] = $value['value']['value'];
}
elseif (isset($value['value']['tid'])) {
// Field collections have them nested. Taxonomy
$selected_options[$value['value']['tid']] = $value['value']['tid'];
}
elseif (isset($value['value']['nid'])) {
// Field collections have them nested. Node ref
$selected_options[$value['value']['nid']] = $value['value']['nid'];
}
elseif (isset($value['value']['uid'])) {
// Field collections have them nested. User ref
$selected_options[$value['value']['uid']] = $value['value']['uid'];
}
elseif (isset($value['value'])) {
// With CCK, it's an array.
$selected_options[$value['value']] = $value['value'];
}
elseif (isset($value['tid'])) {
// With CCK, it's an array. Taxonomy
$selected_options[$value['tid']] = $value['tid'];
}
elseif (isset($value['nid'])) {
// With CCK, it's an array. Node ref
$selected_options[$value['nid']] = $value['nid'];
}
elseif (isset($value['uid'])) {
// With CCK, it's an array. User ref
$selected_options[$value['uid']] = $value['uid'];
}
elseif (isset($value['target_id'])) {
// Handle Entity Reference.
$selected_options[$value['target_id']] = $value['target_id'];
}
elseif (isset($options[$value])) {
// With FAPI, it's not.
$selected_options[$value] = $options[$value];
}
}
}
else {
// There's only one selected option.
if (array_key_exists($items, $options)) {
$selected_options[$items] = $options[$items];
}
}
// Add the remaining options to the arrays
foreach ($options as $key => $value) {
if (!isset($selected_options[$key])) {
$unselected_options[$key] = $value;
//$selected_options[$key] = $value;
}
}
// Set up useful variables.
$selfield = $element['#field_name'] . "_sel";
$unselfield = $element['#field_name'] . "_unsel";
// Call methods to create prefix. (ie the non-selected table, etc)
$prefix_pre = '<div class="form-item multiselect"><label for="edit-title">' . t($element['#title']) . ':';
if ($required) {
$prefix_pre .= '<span class="form-required" title="' . t('This field is required.') . '"> * </span>';
}
$prefix_pre .= "</label>\n";
$prefix_pre .= "<div id=\"multiselect_labels" . "_" . $element['#field_name'] . "\" class=\"multiselect_labels\"><div id=\"label_unselected" . "_" . $element['#field_name'] . "\" class=\"label_unselected\">" . t('Available Options') . ":</div>\n";
$prefix_pre .= "<div id=\"label_selected" . "_" . $element['#field_name'] . "\" class=\"label_selected\">" . t('Selected Options') . ":</div>\n</div>\n";
$prefix_pre .= "<div id=\"multiselect_available" . "_" . $element['#field_name'] . "\" class=\"multiselect_available\">";
if (array_key_exists('#size', $element)) {
$size = $element['#size'];
// Modules can pass in the size of the select boxes.
}
else {
$size = '10';
// Default size.
}
$prefix_pre .= "<select name=\"" . $unselfield . "\" multiple=\"multiple\" class=\"form-multiselect " . $unselfield . " multiselect_unsel\" id=\"" . $element['#field_name'] . "\" size=\"" . $size . "\">\n";
$prefix_options = _multiselect_html_for_box_options($unselected_options);
$prefix_post = "</select>\n</div>\n";
$prefix_post .= "<ul id=\"multiselect_btns" . "_" . $element['#field_name'] . "\" class=\"multiselect_btns\">\n<li class=\"multiselect_add\" id=\"" . $element['#field_name'] . "\"><a href=\"javascript:;\">Add</a></li>\n<li class=\"multiselect_remove\" id=\"" . $element['#field_name'] . "\"><a href=\"javascript:;\">Remove</a></li>\n</ul>";
$widget['selected_options'] = $selected_options;
$widget['unselected_options'] = $unselected_options;
$widget['selfield'] = $selfield;
$widget['unselfield'] = $unselfield;
$widget['prefix_pre'] = $prefix_pre;
$widget['prefix_options'] = $prefix_options;
$widget['prefix_post'] = $prefix_post;
return $widget;
}
// ! FAPI Section
/**
* FAPI section. Add as a new FAPI element. TODO!!!Looking at form.inc and http://drupal.org/node/169815
*/
/**
* Implements hook_element_info().
*/
function multiselect_element_info() {
$type['multiselect'] = array(
'#input' => TRUE,
'#multiple' => TRUE,
'#process' => array(
'form_process_select',
'ajax_process_form',
),
'#theme' => 'multiselect',
'#theme_wrappers' => array(
'form_element',
),
);
return $type;
}
/**
* Implements hook_theme().
*/
function multiselect_theme() {
return array(
'multiselect' => array(
'arguments' => array(
'element' => NULL,
),
'render element' => 'element',
),
);
}
/**
* Returns HTML for a select form element.
*
* It is possible to group options together; to do this, change the format of
* $options to an associative array in which the keys are group labels, and the
* values are associative arrays in the normal $options format.
*
* @param $variables
* An associative array containing:
* - element: An associative array containing the properties of the element.
* Properties used: #title, #value, #options, #description, #extra,
* #multiple, #required, #name, #attributes, #size.
*
* @ingroup themeable
*/
function theme_multiselect($variables) {
$element = $variables['element'];
element_set_attributes($element, array(
'id',
'name',
'size',
'multiple',
'default_value',
'required',
));
_form_set_class($element, array(
'form-multiselect',
));
$options = $element['#options'];
// All available options as defined by the element
$items = $element['#default_value'];
// All selected options are referred to as "items".
$element['#field_name'] = $element['#name'];
// CCK calls the #name "#field_name", so let's duplicate that..
$required = $element['#required'];
$widget = _multiselect_build_widget_code($options, $items, $element, $required);
// Add a couple of things into the attributes.
$element['#attributes']['class'][] = $widget['selfield'];
$element['#attributes']['class'][] = "multiselect_sel";
$element['#attributes']['id'] = $element['#field_name'];
return $widget['prefix_pre'] . $widget['prefix_options'] . $widget['prefix_post'] . '<div class="form-item form-type-select"><select' . drupal_attributes($element['#attributes']) . '>' . _multiselect_html_for_box_options($widget['selected_options']) . '</select></div>' . "\n</div>\n";
}
/**
* Implementation of form_type_hook_value().
*/
function form_type_multiselect_value($element, $edit = FALSE) {
if (func_num_args() == 1) {
return $element['#default_value'];
}
}
Functions
Name | Description |
---|---|
form_type_multiselect_value | Implementation of form_type_hook_value(). |
multiselect_element_info | Implements hook_element_info(). |
multiselect_field_widget_error | Implements hook_field_error(). |
multiselect_field_widget_form | Implements hook_field_widget_form(). Build the form widget using Form API (as much as possible). |
multiselect_field_widget_info | Implement hook_field_widget_info(). |
multiselect_form_alter | Implements hook_form_alter(). |
multiselect_help | Implements hook_help(). |
multiselect_init | Implements hook_init(). |
multiselect_menu | Implements hook_menu(). |
multiselect_theme | Implements hook_theme(). |
theme_multiselect | Returns HTML for a select form element. |
_multiselect_after_build | After_build callback for this widget. |
_multiselect_allowed_values_filter_html | Copied from D6 CCK content module. Not sure where this went in D7. Filter out HTML from allowed values array while leaving entities unencoded. |
_multiselect_build_widget_code | Build the widget HTML code. |
_multiselect_html_for_box_options |