diff --git a/sites/all/modules/options_element/LICENSE.txt b/sites/all/modules/options_element/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..2c095c8d3f42488e8168f9710a4ffbfc4125a159 --- /dev/null +++ b/sites/all/modules/options_element/LICENSE.txt @@ -0,0 +1,274 @@ +GNU GENERAL PUBLIC LICENSE + + Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, +Cambridge, MA 02139, USA. Everyone is permitted to copy and distribute +verbatim copies of this license document, but changing it is not allowed. + + Preamble + +The licenses for most software are designed to take away your freedom to +share and change it. By contrast, the GNU General Public License is +intended to guarantee your freedom to share and change free software--to +make sure the software is free for all its users. This General Public License +applies to most of the Free Software Foundation's software and to any other +program whose authors commit to using it. (Some other Free Software +Foundation software is covered by the GNU Library General Public License +instead.) You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the +freedom to distribute copies of free software (and charge for this service if +you wish), that you receive source code or can get it if you want it, that you +can change the software or use pieces of it in new free programs; and that +you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to +deny you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of the +software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for +a fee, you must give the recipients all the rights that you have. You must make +sure that they, too, receive or can get the source code. And you must show +them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) +offer you this license which gives you legal permission to copy, distribute +and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If the +software is modified by someone else and passed on, we want its recipients +to know that what they have is not the original, so that any problems +introduced by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that redistributors of a free program will individually +obtain patent licenses, in effect making the program proprietary. To prevent +this, we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND + MODIFICATION + +0. This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms +of this General Public License. The "Program", below, refers to any such +program or work, and a "work based on the Program" means either the +Program or any derivative work under copyright law: that is to say, a work +containing the Program or a portion of it, either verbatim or with +modifications and/or translated into another language. (Hereinafter, translation +is included without limitation in the term "modification".) Each licensee is +addressed as "you". + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running the Program is +not restricted, and the output from the Program is covered only if its contents +constitute a work based on the Program (independent of having been made +by running the Program). Whether that is true depends on what the Program +does. + +1. You may copy and distribute verbatim copies of the Program's source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this License +and to the absence of any warranty; and give any other recipients of the +Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, +thus forming a work based on the Program, and copy and distribute such +modifications or work under the terms of Section 1 above, provided that you +also meet all of these conditions: + +a) You must cause the modified files to carry prominent notices stating that +you changed the files and the date of any change. + +b) You must cause any work that you distribute or publish, that in whole or in +part contains or is derived from the Program or any part thereof, to be +licensed as a whole at no charge to all third parties under the terms of this +License. + +c) If the modified program normally reads commands interactively when run, +you must cause it, when started running for such interactive use in the most +ordinary way, to print or display an announcement including an appropriate +copyright notice and a notice that there is no warranty (or else, saying that +you provide a warranty) and that users may redistribute the program under +these conditions, and telling the user how to view a copy of this License. +(Exception: if the Program itself is interactive but does not normally print such +an announcement, your work based on the Program is not required to print +an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be +reasonably considered independent and separate works in themselves, then +this License, and its terms, do not apply to those sections when you distribute +them as separate works. But when you distribute the same sections as part +of a whole which is a work based on the Program, the distribution of the +whole must be on the terms of this License, whose permissions for other +licensees extend to the entire whole, and thus to each and every part +regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to +work written entirely by you; rather, the intent is to exercise the right to +control the distribution of derivative or collective works based on the +Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of a +storage or distribution medium does not bring the other work under the scope +of this License. + +3. You may copy and distribute the Program (or a work based on it, under +Section 2) in object code or executable form under the terms of Sections 1 +and 2 above provided that you also do one of the following: + +a) Accompany it with the complete corresponding machine-readable source +code, which must be distributed under the terms of Sections 1 and 2 above +on a medium customarily used for software interchange; or, + +b) Accompany it with a written offer, valid for at least three years, to give +any third party, for a charge no more than your cost of physically performing +source distribution, a complete machine-readable copy of the corresponding +source code, to be distributed under the terms of Sections 1 and 2 above on +a medium customarily used for software interchange; or, + +c) Accompany it with the information you received as to the offer to distribute +corresponding source code. (This alternative is allowed only for +noncommercial distribution and only if you received the program in object +code or executable form with such an offer, in accord with Subsection b +above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source code +means all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation and +installation of the executable. However, as a special exception, the source +code distributed need not include anything that is normally distributed (in +either source or binary form) with the major components (compiler, kernel, +and so on) of the operating system on which the executable runs, unless that +component itself accompanies the executable. + +If distribution of executable or object code is made by offering access to +copy from a designated place, then offering equivalent access to copy the +source code from the same place counts as distribution of the source code, +even though third parties are not compelled to copy the source along with the +object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as +expressly provided under this License. Any attempt otherwise to copy, +modify, sublicense or distribute the Program is void, and will automatically +terminate your rights under this License. However, parties who have received +copies, or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. +However, nothing else grants you permission to modify or distribute the +Program or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the +Program (or any work based on the Program), you indicate your acceptance +of this License to do so, and all its terms and conditions for copying, +distributing or modifying the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the original +licensor to copy, distribute or modify the Program subject to these terms and +conditions. You may not impose any further restrictions on the recipients' +exercise of the rights granted herein. You are not responsible for enforcing +compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), conditions +are imposed on you (whether by court order, agreement or otherwise) that +contradict the conditions of this License, they do not excuse you from the +conditions of this License. If you cannot distribute so as to satisfy +simultaneously your obligations under this License and any other pertinent +obligations, then as a consequence you may not distribute the Program at all. +For example, if a patent license would not permit royalty-free redistribution +of the Program by all those who receive copies directly or indirectly through +you, then the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply and +the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or +other property right claims or to contest validity of any such claims; this +section has the sole purpose of protecting the integrity of the free software +distribution system, which is implemented by public license practices. Many +people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Program under this License may add an explicit +geographical distribution limitation excluding those countries, so that +distribution is permitted only in or among countries not thus excluded. In such +case, this License incorporates the limitation as if written in the body of this +License. + +9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will be +similar in spirit to the present version, but may differ in detail to address new +problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that +version or of any later version published by the Free Software Foundation. If +the Program does not specify a version number of this License, you may +choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software +Foundation, write to the Free Software Foundation; we sometimes make +exceptions for this. Our decision will be guided by the two goals of +preserving the free status of all derivatives of our free software and of +promoting the sharing and reuse of software generally. + + NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT +PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL +NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR +AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR +ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE +LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, +SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE +PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA +OR DATA BEING RENDERED INACCURATE OR LOSSES +SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE +PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN +IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF +THE POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/sites/all/modules/options_element/add.png b/sites/all/modules/options_element/add.png new file mode 100644 index 0000000000000000000000000000000000000000..cb88b1fe9be44aa048e1cc2bdd6e155e86d72dea Binary files /dev/null and b/sites/all/modules/options_element/add.png differ diff --git a/sites/all/modules/options_element/delete.png b/sites/all/modules/options_element/delete.png new file mode 100644 index 0000000000000000000000000000000000000000..0de805b6cb3bc1ba72b99ef642e04e1e1c9d06c6 Binary files /dev/null and b/sites/all/modules/options_element/delete.png differ diff --git a/sites/all/modules/options_element/options_element.css b/sites/all/modules/options_element/options_element.css new file mode 100644 index 0000000000000000000000000000000000000000..d3654f97c5edce1223fe299e6439ab76e2b97b73 --- /dev/null +++ b/sites/all/modules/options_element/options_element.css @@ -0,0 +1,96 @@ +/* $Id: options_element.css,v 1.6 2010/03/24 23:53:14 quicksketch Exp $ */ + +div.options-widget .draggable a.tabledrag-handle { + padding-right: .3em /* RTL */ +} + +div.form-options fieldset.options { + margin-bottom: 0; +} + +div.options-widget a.add span, +div.options-widget a.remove span { + display: none; +} + +div.options-widget a.add, +div.options-widget a.remove, +div.form-option-add a { + display: block; + width: 16px; + height: 16px; + margin: .2em .1em; + background-repeat: no-repeat; + background-position: 0 0; + float: left; /* RTL */ +} + +div.options-widget a.add:hover, +div.options-widget a.remove:hover, +div.form-option-add a:hover { + background-position: 0 -16px; +} + +div.options-widget a.add, +div.form-option-add a { + background-image: url(add.png); +} + +div.options-widget a.remove { + background-image: url(delete.png); +} + +div.form-options-manual, +div.form-option-add { + text-align: right; /* RTL */ +} +div.form-options-manual a, +div.form-option-add a { + float: none; + display: inline; + padding-left: 20px; /* RTL */ +} + +div.options-widget table { + width: 100%; + margin: 0; +} + +div.options-widget table, +div.options-widget tbody { + border: none; +} + +div.options-widget table tr { + border-style: none; + background: none; +} + +div.options-widget table td { + padding: 4px 6px; +} + +div.options-widget table td.option-default-cell { + width: 6em; + padding: 4px 0; +} + +div.options-widget table td.option-order-cell { + width: 2em; +} + +div.options-widget table tr.indented td.option-order-cell { + width: 4em; +} + +div.options-widget table td.option-key-cell { + width: 20%; +} + +div.options-widget table td.option-actions-cell { + width: 40px; +} + +div.options-widget input.form-text { + width: 100%; +} diff --git a/sites/all/modules/options_element/options_element.inc b/sites/all/modules/options_element/options_element.inc new file mode 100644 index 0000000000000000000000000000000000000000..eda288ad29c989a26b077f8f095d63012a4d3f38 --- /dev/null +++ b/sites/all/modules/options_element/options_element.inc @@ -0,0 +1,423 @@ +<?php +// $Id: options_element.inc,v 1.12 2011/01/12 07:25:13 quicksketch Exp $ + +/** + * @file + * All logic for options_element form elements. + */ + + +/** + * Theme an options element. + */ +function theme_options($variables) { + $element = $variables['element']; + + $classes = array(); + if (isset($element['#attributes']['class'])) { + $classes = $element['#attributes']['class']; + } + + $classes[] = 'form-options'; + $classes[] = 'options-key-type-'. $element['#key_type']; + + if ($element['#key_type_toggled']) { + $classes[] = 'options-key-custom'; + } + + if (isset($element['#optgroups']) && $element['#optgroups']) { + $classes[] = 'options-optgroups'; + } + + if (isset($element['#multiple']) && $element['#multiple']) { + $classes[] = 'options-multiple'; + } + + $options = ''; + $options .= drupal_render($element['options_field']); + if (isset($element['default_value_field'])) { + $options .= drupal_render($element['default_value_field']); + } + + $settings = ''; + if (isset($element['custom_keys'])) { + $settings .= drupal_render($element['custom_keys']); + } + if (isset($element['multiple'])) { + $settings .= drupal_render($element['multiple']); + } + if (isset($element['option_settings'])) { + $settings .= drupal_render($element['option_settings']); + } + + $output = ''; + $output .= '<div class="' . implode(' ', $classes) .'">'; + $output .= theme('fieldset', array('element' => array( + '#title' => t('Options'), + '#collapsible' => FALSE, + '#children' => $options, + '#attributes' => array('class' => array('options')), + ))); + + if (!empty($settings)) { + $output .= theme('fieldset', array('element' => array( + '#title' => t('Option settings'), + '#collapsible' => FALSE, + '#children' => $settings, + '#attributes' => array('class' => array('option-settings')), + ))); + } + $output .= '</div>'; + + return $output; +} + +/** + * Logic function for form_options_expand(). Do not call directly. + * + * @see form_options_expand() + */ +function _form_options_expand($element) { + $element['#options'] = isset($element['#options']) ? $element['#options'] : array(); + $element['#multiple'] = isset($element['#multiple']) ? $element['#multiple'] : FALSE; + + $element['#tree'] = TRUE; + $element['#theme'] = 'options'; + + $path = drupal_get_path('module', 'options_element'); + $element['#attached']['js'] = array( + 'misc/tabledrag.js' => array('group' => JS_LIBRARY, 'weight' => 5), + 'misc/jquery.cookie.js' => array('group' => JS_LIBRARY), + $path . '/options_element.js', + ); + $element['#attached']['css'] = array( + $path . '/options_element.css', + ); + + // Add the key type toggle checkbox. + if (!isset($element['custom_keys']) && $element['#key_type'] != 'custom' && !empty($element['#key_type_toggle'])) { + $element['custom_keys'] = array( + '#title' => is_string($element['#key_type_toggle']) ? $element['#key_type_toggle'] : t('Customize keys'), + '#type' => 'checkbox', + '#default_value' => $element['#key_type_toggled'], + '#attributes' => array('class' => array('key-type-toggle')), + '#description' => t('Customizing the keys will allow you to save one value internally while showing a different option to the user.'), + ); + } + + // Add the multiple value toggle checkbox. + if (!isset($element['multiple']) && !empty($element['#multiple_toggle'])) { + $element['multiple'] = array( + '#title' => is_string($element['#multiple_toggle']) ? $element['#multiple_toggle'] : t('Allow multiple values'), + '#type' => 'checkbox', + '#default_value' => !empty($element['#multiple']), + '#attributes' => array('class' => array('multiple-toggle')), + '#description' => t('Multiple values will let users select multiple items in this list.'), + ); + } + // If the element had a custom interface for toggling whether or not multiple + // values are accepted, make sure that form_type_options_value() knows to use + // it. + if (isset($element['multiple']) && empty($element['#multiple_toggle'])) { + $element['#multiple_toggle'] = TRUE; + } + + // Add the main textarea for adding options. + if (!isset($element['options'])) { + $element['options_field'] = array( + '#type' => 'textarea', + '#resizable' => TRUE, + '#cols' => 60, + '#rows' => 5, + '#required' => isset($element['#required']) ? $element['#required'] : FALSE, + '#description' => t('List options one option per line.'), + '#attributes' => $element['#disabled'] ? array('readonly' => 'readonly') : array(), + '#wysiwyg' => FALSE, // Prevent CKeditor from trying to hijack. + ); + + // If validation fails, reload the user's text even if it's not valid. + if (isset($element['#value']['text'])) { + $element['options_field']['#value'] = $element['#value']['text']; + } + // Most of the time, we'll be converting the options array into the text. + else { + $element['options_field']['#value'] = isset($element['#options']) ? form_options_to_text($element['#options'], $element['#key_type']) : ''; + } + + + if ($element['#key_type'] == 'mixed' || $element['#key_type'] == 'numeric' || $element['#key_type'] == 'custom') { + $element['options_field']['#description'] .= ' ' . t('Key-value pairs may be specified by separating each option with pipes, such as <em>key|value</em>.'); + } + elseif ($element['#key_type_toggle']) { + $element['options_field']['#description'] .= ' ' . t('If the %toggle field is checked, key-value pairs may be specified by separating each option with pipes, such as <em>key|value</em>.', array('%toggle' => $element['custom_keys']['#title'])); + } + if ($element['#key_type'] == 'numeric') { + $element['options_field']['#description'] .= ' ' . t('This field requires all specified keys to be integers.'); + } + } + + // Add the field for storing default values. + if ($element['#default_value'] !== FALSE && !isset($element['default_value_field'])) { + $element['default_value_field'] = array( + '#title' => t('Default value'), + '#type' => 'textfield', + '#size' => 60, + '#value' => isset($element['#default_value']) ? ($element['#multiple'] ? implode(', ', (array) $element['#default_value']) : $element['#default_value']) : '', + '#description' => t('Specify the keys that should be selected by default.'), + ); + if ($element['#multiple']) { + $element['default_value_field']['#description'] .= ' ' . t('Multiple default values may be specified by separating keys with commas.'); + } + } + + // Remove properties that will confuse the FAPI. + unset($element['#options']); + $element['#required'] = FALSE; + + return $element; +} + +/** + * Logic function for form_options_validate(). Do not call directly. + * + * @see form_options_validate() + */ +function _form_options_validate($element, &$form_state) { + // Convert text to an array of options. + $duplicates = array(); + $options = form_options_from_text($element['#value']['options_text'], $element['#key_type'], empty($element['#optgroups']), $duplicates); + + // Check if a key is used multiple times. + if (count($duplicates) == 1) { + form_error($element, t('The key %key has been used multiple times. Each key must be unique to display properly.', array('%key' => reset($duplicates)))); + } + elseif (!empty($duplicates)) { + array_walk($duplicates, 'check_plain'); + $duplicate_list = theme('item_list', array('items' => $duplicates)); + form_error($element, t('The following keys have been used multiple times. Each key must be unique to display properly.') . $duplicate_list); + } + + // Add the list of duplicates to the page so that we can highlight the fields. + if (!empty($duplicates)) { + drupal_add_js(array('optionsElement' => array('errors' => drupal_map_assoc($duplicates))), 'setting'); + } + + // Check if no options are specified. + if (empty($options) && $element['#required']) { + form_error($element, t('At least one option must be specified.')); + } + + // Check for numeric keys if needed. + if ($element['#key_type'] == 'numeric') { + foreach ($options as $key => $value) { + if (!is_int($key)) { + form_error($element, t('The keys for the %title field must be integers.', array('%title' => $element['#title']))); + break; + } + } + } + + // Check that the limit of options has not been exceeded. + if (!empty($element['#limit'])) { + $count = 0; + foreach ($options as $value) { + if (is_array($value)) { + $count += count($value); + } + else { + $count++; + } + } + if ($count > $element['#limit']) { + form_error($element, t('The %title field supports a maximum of @count options. Please reduce the number of options.', array('%title' => $element['#title'], '@count' => $element['#limit']))); + } + } +} + +/** + * Logic function for form_type_options_value(). Do not call directly. + * + * @see form_type_options_value() + */ +function _form_type_options_value(&$element, $edit = FALSE) { + if ($edit === FALSE) { + return array( + 'options' => isset($element['#options']) ? $element['#options'] : array(), + 'default_value' => isset($element['#default_value']) ? $element['#default_value'] : '', + ); + } + else { + // Convert text to an array of options. + $duplicates = array(); + $options = form_options_from_text($edit['options_field'], $element['#key_type'], empty($element['#optgroups']), $duplicates); + + // Convert default value. + if (isset($edit['default_value_field'])) { + // If the element supports toggling whether or not it will accept + // multiple values, use the value that was passed in via $edit (keeping + // in mind that this value may not be set, if a checkbox was used to + // configure it). Otherwise, use the current setting stored with the + // element itself. + $multiple = !empty($element['#multiple_toggle']) ? !empty($edit['multiple']) : !empty($element['#multiple']); + if ($multiple) { + $default_value = array(); + $default_items = explode(',', $edit['default_value_field']); + foreach ($default_items as $key) { + $key = trim($key); + if ($value = _form_options_search($key, $options)) { + $default_value[] = $value; + } + } + } + else { + $default_value = _form_options_search(trim($edit['default_value_field']), $options); + } + } + + return array( + 'options' => $options, + 'default_value' => $default_value, + 'options_text' => $edit['options_field'], + 'default_value_text' => $edit['default_value_field'], + ); + } +} + +/** + * Logic function for form_options_to_text(). Do not call directly. + * + * @see form_options_to_text() + */ +function _form_options_to_text($options, $key_type) { + $output = ''; + $previous_key = false; + + foreach ($options as $key => $value) { + // Convert groups. + if (is_array($value)) { + $output .= '<' . $key . '>' . "\n"; + foreach ($value as $subkey => $subvalue) { + $output .= (($key_type == 'mixed' || $key_type == 'numeric' || $key_type == 'custom') ? $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] !== '') { + if ($key_type == 'mixed' || $key_type == 'numeric' || $key_type == 'custom') { + $output .= $key . '|' . $value . "\n"; + } + else { + $output .= $value . "\n"; + } + } + $previous_key = $key; + } + } + + return $output; +} + +/** + * Logic function for form_options_from_text(). Do not call directly. + * + * @see form_options_from_text() + */ +function _form_options_from_text($text, $key_type, $flat = FALSE, &$duplicates = array()) { + $keys = array(); + $items = array(); + $rows = array_filter(explode("\n", trim($text))); + $group = FALSE; + foreach ($rows as $row) { + $row = trim($row); + $matches = array(); + + // Check for a simple empty row. + if (empty($row)) { + continue; + } + // Check if this row is a group. + elseif (!$flat && preg_match('/^\<((([^>|]*)\|)?([^>]*))\>$/', $row, $matches)) { + if ($matches[1] === '') { + $group = FALSE; + } + else { + $group = $matches[4] ? $matches[4] : ''; + $keys[] = $group; + } + } + // Check if this row is a key|value pair. + elseif (($key_type == 'mixed' || $key_type == 'custom' || $key_type == 'numeric') && preg_match('/^([^|]+)\|(.*)$/', $row, $matches)) { + $key = $matches[1]; + $value = $matches[2]; + $keys[] = $key; + $items[] = array( + 'key' => $key, + 'value' => $value, + 'group' => $group, + ); + } + // Set this this row as a straight value. + else { + $items[] = array( + 'key' => NULL, + 'value' => $row, + 'group' => $group, + ); + } + } + + // Expand the list into a nested array, assign keys and check duplicates. + $options = array(); + $new_key = 0; + foreach ($items as $item) { + // Assign a key if needed. + if ($key_type == 'none') { + $item['key'] = $new_key++; + } + elseif (!isset($item['key'])) { + while (in_array($new_key, $keys)) { + $new_key++; + } + $keys[] = $new_key; + $item['key'] = $new_key; + } + + if ($item['group']) { + if (isset($options[$item['group']][$item['key']])) { + $duplicates[] = $item['key']; + } + $options[$item['group']][$item['key']] = $item['value']; + } + else { + if (isset($options[$item['key']])) { + $duplicates[] = $item['key']; + } + $options[$item['key']] = $item['value']; + } + } + + return $options; +} + +/** + * Recursive function for finding default value keys. Matches on keys or values. + */ +function _form_options_search($needle, $haystack) { + if (isset($haystack[$needle])) { + return $needle; + } + foreach ($haystack as $key => $value) { + if (is_array($value) && ($return = _form_options_search($needle, $value))) { + return $return; + } + if ($value == $needle) { + return $key; + } + } +} diff --git a/sites/all/modules/options_element/options_element.info b/sites/all/modules/options_element/options_element.info new file mode 100644 index 0000000000000000000000000000000000000000..1b03698a5be3f3a0bfbccf391e476e28dc98f742 --- /dev/null +++ b/sites/all/modules/options_element/options_element.info @@ -0,0 +1,11 @@ +; $Id: options_element.info,v 1.3 2011/01/05 02:25:47 quicksketch Exp $ +name = Options element +description = A custom form element for entering the options in select lists, radios, or checkboxes. +core = 7.x + +; Information added by drupal.org packaging script on 2011-01-12 +version = "7.x-1.4" +core = "7.x" +project = "options_element" +datestamp = "1294818406" + diff --git a/sites/all/modules/options_element/options_element.js b/sites/all/modules/options_element/options_element.js new file mode 100644 index 0000000000000000000000000000000000000000..db771d387ae0b8b4abc0210d318df126332d7864 --- /dev/null +++ b/sites/all/modules/options_element/options_element.js @@ -0,0 +1,778 @@ +// $Id: options_element.js,v 1.13 2011/01/12 07:22:01 quicksketch Exp $ + +/** + * @file + * Add JavaScript behaviors for the "options" form element type. + */ + +(function($) { + +Drupal.optionElements = Drupal.optionElements || {}; +Drupal.behaviors.optionsElement = Drupal.behaviors.optionsElement || {}; + +Drupal.behaviors.optionsElement.attach = function(context) { + $('div.form-options:not(.options-element-processed)', context).each(function() { + $(this).addClass('options-element-processed'); + var optionsElement = new Drupal.optionsElement(this); + Drupal.optionElements[optionsElement.identifier] = optionsElement; + }); +}; + +/** + * Constructor for an options element. + */ +Drupal.optionsElement = function(element) { + var self = this; + + // Find the original "manual" fields. + this.element = element; + this.manualElement = $(element).find('fieldset.options, div.fieldset.options').get(0); + this.manualOptionsElement = $(element).find('textarea').get(0); + this.manualDefaultValueElement = $(element).find('input.form-text').get(0); + this.keyTypeToggle = $(element).find('input.key-type-toggle').get(0); + this.multipleToggle = $(element).find('input.multiple-toggle').get(0); + + // Setup variables containing the current status of the widget. + this.optgroups = $(element).is('.options-optgroups'); + this.multiple = $(element).is('.options-multiple'); + this.keyType = element.className.replace(/^.*?options-key-type-([a-z]+).*?$/, '$1'); + this.customKeys = Boolean(element.className.match(/options-key-custom/)); + this.identifier = this.manualOptionsElement.id + '-widget'; + this.enabled = $(this.manualOptionsElement).attr('readonly') == ''; + + // Warning messages. + 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?'); + + // Setup new DOM elements containing the actual options widget. + this.optionsElement = $('<div></div>').get(0); // Temporary DOM object. + this.optionsToggleElement = $(Drupal.theme('optionsElementToggle')).get(0); + this.optionAddElement = $(Drupal.theme('optionsElementAdd')).get(0); + + // Add the options widget and toggle elements to the page. + $(this.manualElement).css('display', 'none').before(this.optionsElement).after(this.optionsToggleElement).after(this.optionAddElement); + + // Enable add item link. + $(this.optionAddElement).find('a').click(function() { + self.addOption($('table tr:last', self.optionsElement).get(0)); + return false; + }); + + // Enable the toggle action for manual entry of options. + $(this.optionsToggleElement).find('a').click(function() { + self.toggleMode(); + return false; + }); + + // Add a handler for key type changes. + if (this.keyTypeToggle) { + $(this.keyTypeToggle).click(function() { + var checked = $(this).attr('checked'); + // Before switching the key type, ensure we're not destroying user keys. + if (!checked) { + var options = self.optionsFromText(); + var confirm = false; + if (self.keyType == 'associative') { + for (var n = 0; n < options.length; n++) { + if (options[n].key != options[n].value) { + confirm = true; + break; + } + } + } + if (confirm) { + if (window.confirm(self.keyChangeWarning)) { + self.setCustomKeys(false); + } + } + else { + self.setCustomKeys(false); + } + } + else { + self.setCustomKeys(true); + } + }); + } + + // Add a handler for multiple value changes. + if (this.multipleToggle) { + $(this.multipleToggle).click(function(){ + self.setMultiple($(this).attr('checked')); + }); + } + + // Be sure to show the custom keys if we have any errors. + if (Drupal.settings.optionsElement && Drupal.settings.optionsElement.errors) { + this.customKeys = true; + } + + // Update the options widget with the current state of the textarea. + this.updateWidgetElements(); + + // Highlight errors that may have occurred during Drupal validation. + if (Drupal.settings.optionsElement && Drupal.settings.optionsElement.errors) { + this.checkKeys(Drupal.settings.optionsElement.errors, 'error'); + } +} + +/** + * Update the widget element based on the current values of the manual elements. + */ +Drupal.optionsElement.prototype.updateWidgetElements = function() { + var self = this; + + // Create a new options element and replace the existing one. + var newElement = $(Drupal.theme('optionsElement', this)).get(0); + if ($(this.optionsElement).css('display') == 'none') { + $(newElement).css('display', 'none'); + } + $(this.optionsElement).replaceWith(newElement); + this.optionsElement = newElement; + + // Manually set up table drag for the created table. + Drupal.settings.tableDrag = Drupal.settings.tableDrag || {}; + Drupal.settings.tableDrag[this.identifier] = { + 'option-depth': { + 0: { + action: 'depth', + hidden: false, + limit: 0, + relationship: 'self', + source: 'option-depth', + target: 'option-depth' + } + } + }; + + // Allow indentation of elements if optgroups are supported. + if (this.optgroups) { + Drupal.settings.tableDrag[this.identifier]['option-parent'] = { + 0: { + action: 'match', + hidden: false, + limit: 1, + relationship: 'parent', + source: 'option-value', + target: 'option-parent' + } + }; + } + + // Enable button for adding options. + $('a.add', this.optionsElement).click(function() { + var newOption = self.addOption($(this).parents('tr:first').get(0)); + $(newOption).find('a.add').focus(); + return false; + }); + + // Enable button for removing options. + $('a.remove', this.optionsElement).click(function() { + self.removeOption($(this).parents('tr:first').get(0)); + return false; + }); + + // Add the same update action to all textfields and radios. + $('input', this.optionsElement).change(function() { + self.updateOptionElements(); + self.updateManualElements(); + }); + + // Add a delayed update to textfields. + $('input.option-value', this.optionsElement).keyup(function(e) { + self.pendingUpdate(e); + }); + + // Attach behaviors as normal to the new widget. + Drupal.attachBehaviors(this.optionsElement); + + // Remove the "Show row weights" link + $(".tabledrag-toggle-weight-wrapper").remove(); + + // Add an onDrop action to the table drag instance. + Drupal.tableDrag[this.identifier].onDrop = function() { + // Update the checkbox/radio buttons for selecting default values. + if (self.optgroups) { + self.updateOptionElements(); + } + // Update the options within the hidden text area. + self.updateManualElements(); + }; + + // Add an onIndent action to the table drag row instances. + Drupal.tableDrag[this.identifier].row.prototype.onIndent = function() { + if (this.indents) { + $(this.element).addClass('indented'); + } + else { + $(this.element).removeClass('indented'); + } + }; + + // Update the default value and optgroups. + this.updateOptionElements(); +} + +/** + * Update the original form element based on the current state of the widget. + */ +Drupal.optionsElement.prototype.updateManualElements = function() { + var options = {}; + + // Build a list of current options. + var previousOption = false; + $(this.optionsElement).find('input.option-value').each(function() { + var $row = $(this).is('tr') ? $(this) : $(this).parents('tr:first'); + var depth = $row.find('input.option-depth').val(); + if (depth == 1 && previousOption) { + if (typeof(options[previousOption]) != 'object') { + options[previousOption] = {}; + } + options[previousOption][this.value] = this.value; + } + else { + options[this.value] = this.value; + previousOption = this.value; + } + }); + this.options = options; + + // Update the default value. + var defaultValue = this.multiple ? [] : ''; + var multiple = this.multiple; + $(this.optionsElement).find('input.option-default').each(function() { + if (this.checked && this.value) { + if (multiple) { + defaultValue.push(this.value); + } + else { + defaultValue = this.value; + } + } + }); + this.defaultValue = defaultValue; + + // Update with the new text and trigger the change action on the field. + this.optionsToText(); + if (this.manualDefaultValueElement) { + this.manualDefaultValueElement.value = multiple ? defaultValue.join(', ') : defaultValue; + } + + $(this.manualOptionsElement).change(); +} + +/** + * Several maintenance routines to update all rows of the options element. + * + * - Disable options for optgroups if indented. + * - Disable add and delete links if indented. + * - Match the default value radio button value to the key of the text element. + */ +Drupal.optionsElement.prototype.updateOptionElements = function() { + var self = this; + var previousRow = false; + var previousElement = false; + var $rows = $(this.optionsElement).find('tbody tr'); + + $rows.each(function(index) { + var optionValue = $(this).find('input.option-value').val(); + var optionKey = $(this).find('input.option-key').val(); + + // Update the elements key if matching the key and value. + if (self.keyType == 'associative') { + $(this).find('input.option-key').val(optionValue); + } + + // Match the default value checkbox/radio button to the option's key. + $(this).find('input.option-default').val(optionKey ? optionKey : optionValue); + + // Hide the add/remove links the row if indented. + var depth = $(this).find('input.option-depth').val(); + var defaultInput = $(this).find('input.option-default').get(0); + + if (depth == 1) { + // Affect the parent row, adjusting properties for optgroup items. + $(previousElement).attr('disabled', true).attr('checked', false); + $(previousRow).addClass('optgroup').find('a.add, a.remove').css('display', 'none'); + $(this).find('a.add, a.remove').css('display', ''); + $(defaultInput).attr('disabled', false); + + // Hide the key column for the optgroup. It would be nice if hiding + // columns worked in IE7, but for now this only works in IE8 and other + // standards-compliant browsers. + if (self.customKeys && (!$.browser.msie || $.browser.version >= 8)) { + $(previousRow).find('td.option-key-cell').css('display', 'none'); + $(previousRow).find('td.option-value-cell').attr('colspan', 2); + } + } + else { + // Set properties for normal options that are not optgroups. + $(defaultInput).attr('disabled', false); + $(this).removeClass('optgroup').find('a.add, a.remove').css('display', ''); + + // Hide the key column. See note above for compatibility concerns. + if (self.customKeys && (!$.browser.msie || $.browser.version >= 8)) { + $(this).find('td.option-key-cell').css('display', ''); + $(this).find('td.option-value-cell').attr('colspan', ''); + } + previousElement = defaultInput; + previousRow = this; + } + }); + + // Do not allow the last item to be removed. + if ($rows.size() == 1) { + $rows.find('a.remove').css('display', 'none') + } + + // Disable items if needed. + if (this.enabled == false) { + this.disable(); + } +} + +/** + * Add a new option below the current row. + */ +Drupal.optionsElement.prototype.addOption = function(currentOption) { + var self = this; + var windowHieght = $(document).height(); + var newOption = $(currentOption).clone() + .find('input.option-key').val(self.keyType == 'numeric' ? self.nextNumericKey() : '').end() + .find('input.option-value').val('').end() + .find('input.option-default').attr('checked', false).end() + .find('a.tabledrag-handle').remove().end() + .removeClass('drag-previous') + .insertAfter(currentOption) + .get(0); + + // Scroll down to accomidate the new option. + $(window).scrollTop($(window).scrollTop() + $(document).height() - windowHieght); + + // Make the new option draggable. + Drupal.tableDrag[this.identifier].makeDraggable(newOption); + + // Enable button for adding options. + $('a.add', newOption).click(function() { + var newOption = self.addOption($(this).parents('tr:first').get(0)); + $(newOption).find('a.add').focus(); + return false; + }); + + // Enable buttons for removing options. + $('a.remove', newOption).click(function() { + self.removeOption(newOption); + return false; + }); + + // Add the update action to all textfields and radios. + $('input', newOption).change(function() { + self.updateOptionElements(); + self.updateManualElements(); + }); + + // Add a delayed update to textfields. + $('input.option-value', newOption).keyup(function(e) { + self.pendingUpdate(e); + }); + + this.updateOptionElements(); + this.updateManualElements(); + + return newOption; +} + +/** + * Remove the current row. + */ +Drupal.optionsElement.prototype.removeOption = function(currentOption) { + $(currentOption).remove(); + + this.updateOptionElements(); + this.updateManualElements(); +} + +/** + * Toggle link for switching between the JavaScript and manual entry. + */ +Drupal.optionsElement.prototype.toggleMode = function() { + if ($(this.optionsElement).is(':visible')) { + var height = $(this.optionsElement).height(); + $(this.optionsElement).css('display', 'none'); + $(this.optionAddElement).css('display', 'none'); + $(this.manualElement).css('display', '').find('textarea').height(height); + $(this.optionsToggleElement).find('a').text(Drupal.t('Normal entry')); + } + else { + this.updateWidgetElements(); + $(this.optionsElement).css('display', ''); + $(this.optionAddElement).css('display', ''); + $(this.manualElement).css('display', 'none'); + $(this.optionsToggleElement).find('a').text(Drupal.t('Manual entry')); + } +} + +/** + * Enable the changing of options. + */ +Drupal.optionsElement.prototype.enable = function() { + this.enabled = true; + $(this.manualOptionsElement).attr('readonly', ''); + $(this.element).removeClass('options-disabled'); + + $('a.add, a.remove, a.tabledrag-handle, div.form-option-add a', this.element).css('display', ''); + $('input.form-text', this.optionsElement).attr('disabled', ''); +}; + +/** + * Disable the changing of options. + */ +Drupal.optionsElement.prototype.disable = function() { + this.enabled = false; + $(this.manualOptionsElement).attr('readonly', true); + $(this.element).addClass('options-disabled'); + + $('a.add, a.remove, a.tabledrag-handle, div.form-option-add a', this.element).css('display', 'none'); + $('input.form-text', this.optionsElement).attr('disabled', 'disabled'); +}; + +/** + * Enable entering of custom key values. + */ +Drupal.optionsElement.prototype.setCustomKeys = function(enabled) { + if (enabled) { + $(this.element).addClass('options-key-custom'); + } + else { + $(this.element).removeClass('options-key-custom'); + } + + this.customKeys = enabled; + // Rebuild the options widget. + this.updateManualElements(); + this.updateWidgetElements(); +} + +/** + * Change the current key type (associative, custom, numeric, none). + */ +Drupal.optionsElement.prototype.setKeyType = function(type) { + $(this.element) + .removeClass('options-key-type-' + this.keyType) + .addClass('options-key-type-' + type); + this.keyType = type; + // Rebuild the options widget. + this.updateManualElements(); + this.updateWidgetElements(); +} + +/** + * Set the element's #multiple property. Boolean TRUE or FALSE. + */ +Drupal.optionsElement.prototype.setMultiple = function(multiple) { + if (multiple) { + $(this.element).addClass('options-multiple'); + } + else { + // Unselect all default options except the first. + $(this.optionsElement).find('input.option-default:checked:not(:first)').attr('checked', false); + this.updateManualElements(); + $(this.element).removeClass('options-multiple'); + } + this.multiple = multiple; + // Rebuild the options widget. + this.updateWidgetElements(); +}; + +/** + * Highlight duplicate keys. + */ +Drupal.optionsElement.prototype.checkKeys = function(duplicateKeys, cssClass){ + $(this.optionsElement).find('input.option-key').each(function() { + if (duplicateKeys[this.value]) { + $(this).addClass(cssClass); + } + }); +}; + +/** + * Update a field after a delay. + * + * Similar to immediately changing a field, this field as pending changes that + * will be updated after a delay. This includes textareas and textfields in + * which updating continuously would be a strain the server and actually slow + * down responsiveness. + */ +Drupal.optionsElement.prototype.pendingUpdate = function(e) { + var self = this; + + // Only operate on "normal" keys, excluding special function keys. + // http://protocolsofmatrix.blogspot.com/2007/09/javascript-keycode-reference-table-for.html + if (!( + e.keyCode >= 48 && e.keyCode <= 90 || // 0-9, A-Z. + e.keyCode >= 93 && e.keyCode <= 111 || // Number pad. + e.keyCode >= 186 && e.keyCode <= 222 || // Symbols. + e.keyCode == 8) // Backspace. + ) { + return; + } + + if (this.updateDelay) { + clearTimeout(this.updateDelay); + } + + this.updateDelay = setTimeout(function(){ + self.updateOptionElements(); + self.updateManualElements(); + }, 500); +}; + +/** + * Given an object of options, convert it to a text string. + */ +Drupal.optionsElement.prototype.optionsToText = function() { + var $rows = $('tbody tr', this.optionsElement); + var output = ''; + var inGroup = false; + var rowCount = $rows.size(); + var defaultValues = []; + + for (var rowIndex = 0; rowIndex < rowCount; rowIndex++) { + var isOptgroup = $rows.eq(rowIndex).is('.optgroup'); + var isChild = $rows.eq(rowIndex).is('.indented'); + var key = $rows.eq(rowIndex).find('input.option-key').val(); + var value = $rows.eq(rowIndex).find('input.option-value').val(); + + // Handle groups. + if (this.optgroups && value !== '' && isOptgroup) { + output += '<' + ((key !== '') ? (key + '|') : '') + value + '>' + "\n"; + inGroup = true; + } + // Typical key|value pairs. + else { + // Exit out of any groups. + if (this.optgroups && inGroup && !isChild) { + output += "<>\n"; + inGroup = false; + } + + // Add the row for the option. + if (this.keyType == 'none' || this.keyType == 'associative') { + output += value + "\n"; + } + else if (value == '') { + output += "\n"; + } + else { + output += ((key !== '') ? (key + '|') : '') + value + "\n"; + } + } + } + + this.manualOptionsElement.value = output; +}; + +/** + * Given a text string, convert it to an object. + */ +Drupal.optionsElement.prototype.optionsFromText = function() { + // Use jQuery val() instead of value because it fixes Windows line breaks. + var rows = $(this.manualOptionsElement).val().match(/^.*$/mg); + var parent = ''; + var options = []; + var defaultValues = {}; + + // Drop the last row if empty. + if (rows.length && rows[rows.length - 1] == '') { + rows.pop(); + } + + if (this.manualDefaultValueElement) { + if (this.multiple) { + var defaults = this.manualDefaultValueElement.value.split(','); + for (var n = 0; n < defaults.length; n++) { + var defaultValue = defaults[n].replace(/^[ ]*(.*?)[ ]*$/, '$1'); // trim(). + defaultValues[defaultValue] = defaultValue; + } + } + else { + var defaultValue = this.manualDefaultValueElement.value.replace(/^[ ]*(.*?)[ ]*$/, '$1'); // trim(). + defaultValues[defaultValue] = defaultValue; + } + } + + for (var n = 0; n < rows.length; n++) { + var row = rows[n].replace(/^[ \r\n]*(.*?)[ \r\n]*$/, '$1'); // trim(). + var key = ''; + var value = ''; + var checked = false; + var hasChildren = false; + var groupClear = false; + + var matches = {}; + // Row is a group. + if (this.optgroups && (matches = row.match(/^\<((([^>|]*)\|)?([^>]*))\>$/))) { + if (matches[0] == '<>') { + parent = ''; + groupClear = true; + } + else { + key = matches[3] ? matches[3] : ''; + parent = value = matches[4]; + hasChildren = true; + } + } + // Check if this row is a key|value pair. + else if ((this.keyType == 'mixed' || this.keyType == 'numeric' || this.keyType == 'custom') && (matches = row.match(/^([^|]+)\|(.*)$/))) { + key = matches[1]; + value = matches[2]; + checked = defaultValues[key]; + } + // Row is a straight value. + else { + key = (this.keyType == 'mixed' || this.keyType == 'numeric') ? '' : row; + value = row; + if (!key && this.keyType == 'mixed') { + checked = defaultValues[value]; + } + else { + checked = defaultValues[key]; + } + } + + if (!groupClear) { + options.push({ + key: key, + value: value, + parent: (value !== parent ? parent : ''), + hasChildren: hasChildren, + checked: (checked ? 'checked' : false) + }); + } + } + + // Convert options to numeric if no key is specified. + if (this.keyType == 'numeric') { + var nextKey = this.nextNumericKey(); + for (var n = 0; n < options.length; n++) { + if (options[n].key == '') { + options[n].key = nextKey; + nextKey++; + } + } + } + + return options; +}; + +/** + * Utility method to get the next numeric option in a list of options. + */ +Drupal.optionsElement.prototype.nextNumericKey = function(options) { + this.keyType = 'custom'; + options = this.optionsFromText(); + this.keyType = 'numeric'; + + var maxKey = -1; + for (var n = 0; n < options.length; n++) { + if (options[n].key.match(/^[0-9]+$/)) { + maxKey = Math.max(maxKey, options[n].key); + } + } + return maxKey + 1; +}; + +/** + * Theme function for creating a new options element. + * + * @param optionsElement + * An options element object. + */ +Drupal.theme.prototype.optionsElement = function(optionsElement) { + var output = ''; + var options = optionsElement.optionsFromText(); + var hasDefault = optionsElement.manualDefaultValueElement; + var defaultType = optionsElement.multiple ? 'checkbox' : 'radio'; + var keyType = optionsElement.customKeys ? 'textfield' : 'hidden'; + + // Helper function to print out a single draggable option row. + function tableDragRow(key, value, parent, indent, status) { + var output = ''; + output += '<tr class="draggable' + (indent > 0 ? ' indented' : '') + '">' + output += '<td class="' + (hasDefault ? 'option-default-cell' : 'option-order-cell') + '">'; + for (var n = 0; n < indent; n++) { + output += Drupal.theme('tableDragIndentation'); + } + output += '<input type="hidden" class="option-parent" value="' + parent + '" />'; + output += '<input type="hidden" class="option-depth" value="' + indent + '" />'; + if (hasDefault) { + output += '<input type="' + defaultType + '" name="' + optionsElement.identifier + '-default" class="form-radio option-default" value="' + key + '"' + (status == 'checked' ? ' checked="checked"' : '') + (status == 'disabled' ? ' disabled="disabled"' : '') + ' />'; + } + output += '</td><td class="' + (keyType == 'textfield' ? 'option-key-cell' : 'option-value-cell') +'">'; + output += '<input type="' + keyType + '" class="' + (keyType == 'textfield' ? 'form-text ' : '') + 'option-key" value="' + key + '" />'; + output += keyType == 'textfield' ? '</td><td class="option-value-cell">' : ''; + output += '<input class="form-text option-value" type="text" value="' + value + '" />'; + output += '</td><td class="option-actions-cell">' + output += '<a class="add" title="' + Drupal.t('Add new option') + '" href="#"' + (status == 'disabled' ? ' style="display: none"' : '') + '><span class="add">' + Drupal.t('Add') + '</span></a>'; + output += '<a class="remove" title="' + Drupal.t('Remove option') + '" href="#"' + (status == 'disabled' ? ' style="display: none"' : '') + '><span class="remove">' + Drupal.t('Remove') + '</span></a>'; + output += '</td>'; + output += '</tr>'; + return output; + } + + output += '<div class="options-widget">'; + output += '<table id="' + optionsElement.identifier + '">'; + + output += '<thead><tr>'; + output += '<th>' + (hasDefault ? Drupal.t('Default') : ' ') + '</th>'; + output += keyType == 'textfield' ? '<th>' + Drupal.t('Key') + '</th>' : ''; + output += '<th>' + Drupal.t('Value') + '</th>'; + output += '<th> </th>'; + output += '</tr></thead>'; + + output += '<tbody>'; + + // Make sure that at least a few options exist if empty. + if (!options.length) { + var newOption = { + key: '', + value: '', + parent: '', + hasChildren: false, + checked: false + } + options.push(newOption); + options.push(newOption); + options.push(newOption); + } + + for (var n = 0; n < options.length; n++) { + var option = options[n]; + var depth = option.parent === '' ? 0 : 1; + var checked = !option.hasChildren && option.checked; + output += tableDragRow(option.key, option.value, option.parent, depth, checked); + } + + output += '</tbody>'; + output += '</table>'; + output += '<div>'; + + return output; +}; + +Drupal.theme.prototype.optionsElementAdd = function() { + return '<div class="form-option-add"><a href="#">' + Drupal.t('Add item') + '</a></div>'; +}; + +Drupal.theme.prototype.optionsElementToggle = function() { + return '<div class="form-options-manual"><a href="#">' + Drupal.t('Manual entry') + '</a></div>'; +}; + +Drupal.theme.tableDragChangedMarker = function () { + return ' '; +}; + +Drupal.theme.tableDragChangedWarning = function() { + return '<span></span>'; +}; + +})(jQuery); diff --git a/sites/all/modules/options_element/options_element.module b/sites/all/modules/options_element/options_element.module new file mode 100644 index 0000000000000000000000000000000000000000..cee8a84f1aa911128d39c0933634bcc8aac793c3 --- /dev/null +++ b/sites/all/modules/options_element/options_element.module @@ -0,0 +1,168 @@ +<?php +// $Id: options_element.module,v 1.7 2010/09/05 02:17:22 quicksketch Exp $ + +/** + * @file + * Defines an "options" form element type for entering select list options. + */ + +/** + * Implements hook_element_info(). + * + * Defines the #type = 'options' form element type. + * + * The 'options' form element type is useful when collecting a series of + * values in a list. The values within the list may optionally have unique + * keys, such as that in a array structure. In addition, a default choice + * (or several default choices) may be selected by the user. + * + * @code + * $element['options'] = array( + * '#type' => 'options', + * '#limit' => 20, + * '#optgroups' => FALSE, + * '#multiple' => FALSE, + * '#options' => array( + * 'foo' => 'foo', + * 'bar' => 'bar', + * 'baz' => 'baz', + * ), + * '#default_value' => 'foo' + * '#key_type' => 'associative', + * ); + * @endcode + * + * Properties for the 'options' element include: + * - limit: The maximum number of options that can be added to a list. Defaults + * to 100. + * - optgroups: If nesting of options is supported, up to one level. This is + * used when building a select HTML element that uses optgroups. Defaults to + * FALSE. + * - multiple: Affects the number of default values that may be selected. + * - default_value: The key(s) for the options that are currently selected. If + * #multiple is TRUE then, the default value is an array, otherwise it is a + * string. + * - options: An array of options currently within the list. + * - key_type: The method by which keys are determined for each value in the + * option list. Available options include: + * - mixed: Each value is not given any ID automatically, but any manually + * specified keys will be retained. This most emulates the existing + * conventions within Drupal, where keys are optional but allowed. + * - numeric: Each value is automatically given a unique numeric ID. This can + * be useful when wanting duplicate values in a list and not have to bother + * the end-user for keys. + * - associative: Keys are automatically mapped from the user-entered values. + * This is equivalent to making key|value pairs, but both the key and value + * are the same. Each key must be unique. + * - custom: Keys are manually entered by the end user. A second set of + * textfields are presented to let the user provide keys as well as values. + * - none: No keys are specified at all. This effectively creates numeric keys + * but unlike numeric keys, the keys are renumbered if the options in the + * list are rearranged. + * - key_type_toggle: If specified, a checkbox will be added that allows the + * user to toggle between the current key type and the "custom" key type, + * letting them customize the keys as desired. This option has no effect with + * the "none" key type. + * - key_type_toggled: Determine if the toggle checkbox is set or not by + * default. + * @code + * $element['options'] = array( + * '#type' => 'options', + * '#key_type' => 'associative', + * '#key_type_toggle' => t('Custom keys'), + * '#key_type_toggled' => TRUE, + * ); + * @endcode + */ +function options_element_element_info() { + $type = array(); + + $type['options'] = array( + '#input' => TRUE, + '#process' => array('form_options_expand'), + '#limit' => 100, + '#optgroups' => TRUE, + '#multiple' => FALSE, + '#options' => array(), + '#key_type' => 'mixed', + '#key_type_toggle' => NULL, + '#key_type_toggled' => FALSE, + '#element_validate' => array('form_options_validate'), + '#disabled' => FALSE, + ); + + return $type; +} + +/** + * Implementation of hook_theme(). + */ +function options_element_theme() { + return array( + 'options' => array( + 'render element' => 'element', + 'file' => 'options_element.inc', + ), + ); +} + +/** + * Expand the "options" form element type. + * + * The "options" type is simply an enhanced textarea that makes it easier to + * create key|value pairs and put items into optgroups. + */ +function form_options_expand($element) { + module_load_include('inc', 'options_element'); + return _form_options_expand($element); +} + +/** + * Validate the "options" form element type. + */ +function form_options_validate($element, &$form_state) { + module_load_include('inc', 'options_element'); + _form_options_validate($element, $form_state); +} + +/** + * This function adjusts the value of the element from a text value to an array. + */ +function form_type_options_value(&$element, $edit = FALSE) { + module_load_include('inc', 'options_element'); + return _form_type_options_value($element, $edit); +} + +/** + * Create a textual representation of options from an array. + * + * @param $options + * An array of options used in a select list. + * @param $key_type + * How key/value pairs should be interpreted. Available options: + * - mixed + * - numeric + * - associative + * - custom + * - none + */ +function form_options_to_text($options, $key_type) { + module_load_include('inc', 'options_element'); + return _form_options_to_text($options, $key_type); +} + +/** + * Create an array representation of text option values. + * + * If the Key of the option is within < >, treat as an optgroup + * + * <Group 1> + * creates an optgroup with the label "Group 1" + * + * <> + * Exits the current group, allowing items to be inserted at the root element. + */ +function form_options_from_text($text, $key_type, $flat = FALSE, &$duplicates = array()) { + module_load_include('inc', 'options_element'); + return _form_options_from_text($text, $key_type, $flat, $duplicates); +} diff --git a/sites/all/modules/options_element/translations/ru.po b/sites/all/modules/options_element/translations/ru.po new file mode 100644 index 0000000000000000000000000000000000000000..c3116ea6cb8b7c9cc880c7d956fe985d1deebeb0 --- /dev/null +++ b/sites/all/modules/options_element/translations/ru.po @@ -0,0 +1,146 @@ +# $Id$ +# +# LANGUAGE translation of Drupal (general) +# Copyright YEAR NAME <EMAIL@ADDRESS> +# Generated from files: +# options_element.inc,v 1.6 2010/03/29 03:22:11 quicksketch +# options_element.info,v 1.2 2010/03/24 23:53:14 quicksketch +# options_element.js,v 1.8 2010/03/29 03:22:11 quicksketch +# +msgid "" +msgstr "" +"Project-Id-Version: rus-drupal.ru\n" +"POT-Creation-Date: 2010-03-31 03:04+0400\n" +"PO-Revision-Date: 2010-03-31 03:32+0300\n" +"Last-Translator: Andrey Mikheychik <andrey@armaturich.ru>\n" +"Language-Team: Rus-Drupal <mail@rus-drupal.ru>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Russian\n" +"X-Poedit-Country: Russia\n" + +#: options_element.inc:56 +msgid "Options" +msgstr "Варианты выбора" + +#: options_element.inc:64 +msgid "Option settings" +msgstr "Настройки варианта выбора" + +#: options_element.inc:99 +msgid "Customize keys" +msgstr "Модифицировать ключи" + +#: options_element.inc:103 +msgid "Customizing the keys will allow you to save one value internally while showing a different option to the user." +msgstr "Модификация ключей позволит сохранить одно значение внутренним, показывая другое значение пользователю." + +#: options_element.inc:110 +msgid "Allow multiple values" +msgstr "Позволить различные значения" + +#: options_element.inc:114 +msgid "Multiple values will let users select multiple items in this list." +msgstr "Различные значения позволят пользователям выбирать несколько пунктов из списка." + +#: options_element.inc:126 +msgid "List options one option per line." +msgstr "Перечислите варианты выбора по одному варианту в строке." + +#: options_element.inc:141 +msgid "Key-value pairs may be specified by separating each option with pipes, such as <em>key|value</em>." +msgstr "Пары ключ-значение могут быть определены разделением каждого пункта вертикальной чертой, например, <em>key|value</em>." + +#: options_element.inc:144 +msgid "If the %toggle field is checked, key-value pairs may be specified by separating each option with pipes, such as <em>key|value</em>." +msgstr "Если поле %toggle отмечено, то пары ключ-значение могут быть определены разделением каждого пункта с помощью вертикальной черты, например <em>key|value</em>." + +#: options_element.inc:147 +msgid "This field requires all specified keys to be integers." +msgstr "Это поле требует, чтобы все ключи были целочисленными." + +#: options_element.inc:154 +msgid "Default value" +msgstr "Значение по умолчанию" + +#: options_element.inc:158 +msgid "Specify the keys that should be selected by default." +msgstr "Определяет ключ, который должен быть выбран по умолчанию." + +#: options_element.inc:161 +msgid "Multiple default values may be specified by separating keys with commas." +msgstr "Различные значения могут быть определены, указав ключи через запятую." + +#: options_element.inc:184 +msgid "The key %key has been used multiple times. Each key must be unique to display properly." +msgstr "Ключ %key использован несколько раз. Каждый ключ должен быть уникальным, чтобы отображаться правильно." + +#: options_element.inc:189 +msgid "The following keys have been used multiple times. Each key must be unique to display properly." +msgstr "Эти ключи использованы несколько раз. Каждый ключ должен быть уникальным для корректного отображения." + +#: options_element.inc:199 +msgid "At least one option must be specified." +msgstr "Как минимум один вариант выбора должен быть определён." + +#: options_element.inc:206 +msgid "The keys for the %title field must be integers." +msgstr "Ключи для поля %title должны быть целочисленными." + +#: options_element.inc:224 +msgid "The %title field supports a maximum of @count options. Please reduce the number of options." +msgstr "Поле %title поддерживает максимум @count вариантов выбора. Пожалуйста, уменьшите количество вариантов." + +#: options_element.info:0 +msgid "Options element" +msgstr "Элемент вариантов выбора" + +#: options_element.info:0 +msgid "A custom form element for entering the options in select lists, radios, or checkboxes." +msgstr "Специальный элемент формы для ввода вариантов выбор в выпадающих списках, кнопках-переключателях или флаговой кнопки." + +#: options_element.js:0 +msgid "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?" +msgstr "Отдельные ключи определяются в этом списке. Удаление этих отдельных ключей может изменить то, как хранятся данные. Вы уверены, что хотите удалить эти ключи?" + +#: options_element.js:0 +msgid "Normal entry" +msgstr "Обычный ввод" + +#: options_element.js:0;0 +msgid "Manual entry" +msgstr "Ввод вручную" + +#: options_element.js:0 +msgid "Add new option" +msgstr "Добавить новый вариант выбора" + +#: options_element.js:0 +msgid "Add" +msgstr "Добавить" + +#: options_element.js:0 +msgid "Remove option" +msgstr "Удалить вариант выбора" + +#: options_element.js:0 +msgid "Remove" +msgstr "Удалить" + +#: options_element.js:0 +msgid "Default" +msgstr "По умолчанию" + +#: options_element.js:0 +msgid "Key" +msgstr "Ключ" + +#: options_element.js:0 +msgid "Value" +msgstr "Значение" + +#: options_element.js:0 +msgid "Add item" +msgstr "Добавить пункт" +