diff --git a/modules/profile/profile.admin.inc b/modules/profile/profile.admin.inc index ab309c1474232aab50d6fcb78c954b0bfc2ec908..4268c01a6a8c8e9ec8ccee7546c823864e140007 100644 --- a/modules/profile/profile.admin.inc +++ b/modules/profile/profile.admin.inc @@ -7,29 +7,153 @@ */ /** - * Menu callback; display a listing of all editable profile fields. + * Form builder to display a listing of all editable profile fields. + * + * @ingroup forms + * @see profile_admin_overview_submit(). */ -function profile_admin_overview() { - - $result = db_query('SELECT title, name, type, category, fid FROM {profile_fields} ORDER BY category, weight'); - $rows = array(); +function profile_admin_overview(&$form_state = NULL) { + $result = db_query('SELECT title, name, type, category, fid, weight FROM {profile_fields} ORDER BY category, weight'); + + $form = array(); + $categories = array(); while ($field = db_fetch_object($result)) { - $rows[] = array(check_plain($field->title), check_plain($field->name), _profile_field_types($field->type), check_plain($field->category), l(t('edit'), "admin/user/profile/edit/$field->fid"), l(t('delete'), "admin/user/profile/delete/$field->fid")); + // Collect all category information + $categories[] = $field->category; + + // Save all field information + $form[$field->fid]['name'] = array('#value' => check_plain($field->name)); + $form[$field->fid]['title'] = array('#value' => check_plain($field->title)); + $form[$field->fid]['type'] = array('#value' => $field->type); + $form[$field->fid]['category'] = array('#type' => 'select', '#default_value' => $field->category, '#options' => array()); + $form[$field->fid]['weight'] = array('#type' => 'weight', '#default_value' => $field->weight); + $form[$field->fid]['edit'] = array('#value' => l(t('edit'), "admin/user/profile/edit/$field->fid")); + $form[$field->fid]['delete'] = array('#value' => l(t('delete'), "admin/user/profile/delete/$field->fid")); + } + + // Add the cateogory combo boxes + $categories = array_unique($categories); + foreach($form as $fid => $field) { + foreach($categories as $cat => $category) { + $form[$fid]['category']['#options'][$category] = $category; + } } - if (count($rows) == 0) { - $rows[] = array(array('data' => t('No fields defined.'), 'colspan' => '6')); + + // Display the submit button only when there's more than one field + if (count($form) > 1) { + $form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration')); } + else { + // Disable combo boxes when there isn't a submit button + foreach ($form as $fid => $field) { + unset($form[$fid]['weight']); + $form[$fid]['category']['#type'] = 'value'; + } + } + $form['#tree'] = TRUE; + + $addnewfields = '<h2>'. t('Add new field') .'</h2>'; + $addnewfields .= '<ul>'; + foreach (_profile_field_types() as $key => $value) { + $addnewfields .= '<li>'. l($value, "admin/user/profile/add/$key") .'</li>'; + } + $addnewfields .= '</ul>'; + $form['addnewfields'] = array('#value' => $addnewfields); - $header = array(t('Title'), t('Name'), t('Type'), t('Category'), array('data' => t('Operations'), 'colspan' => '2')); + return $form; +} - $output = theme('table', $header, $rows); - $output .= '<h2>'. t('Add new field') .'</h2>'; - $output .= '<ul>'; - foreach (_profile_field_types() as $key => $value) { - $output .= '<li>'. l($value, "admin/user/profile/add/$key") .'</li>'; +/** + * Submit hanlder to update changed profile field weights and categories. + * + * @see profile_admin_overview(). + */ +function profile_admin_overview_submit($form, &$form_state) { + foreach (element_children($form_state['values']) as $fid) { + if (is_numeric($fid)) { + $weight = $form_state['values'][$fid]['weight']; + $category = $form_state['values'][$fid]['category']; + if ($weight != $form[$fid]['weight']['#default_value'] || $category != $form[$fid]['category']['#default_value']) { + db_query("UPDATE {profile_fields} SET weight = %d, category = '%s' WHERE fid = %d", $weight, $category, $fid); + } + } } - $output .= '</ul>'; + drupal_set_message(t('Profile fields have been updated.')); + cache_clear_all(); + menu_rebuild(); +} + +/** + * Theme the profile field overview into a drag and drop enabled table. + * + * @ingroup themeable + * @see profile_admin_overview(). + */ +function theme_profile_admin_overview($form) { + drupal_add_css(drupal_get_path('module', 'profile') .'/profile.css'); + // Add javascript if there's more than one field. + if (isset($form['submit'])) { + drupal_add_js(drupal_get_path('module', 'profile') .'/profile.js'); + } + + $rows = array(); + $categories = array(); + $category_number = 0; + foreach (element_children($form) as $key) { + // Don't take form control structures. + if (array_key_exists('category', $form[$key])) { + $field = &$form[$key]; + $category = $field['category']['#default_value']; + + if (!isset($categories[$category])) { + // Category classes are given numeric IDs because there's no guarantee + // class names won't contain invalid characters. + $categories[$category] = $category_number; + $category_field['#attributes']['class'] = 'profile-category profile-category-'. $category_number; + $rows[] = array(array('data' => $category, 'colspan' => 7, 'class' => 'category')); + $rows[] = array('data' => array(array('data' => '<em>'. t('No fields in this category. If this category remains empty when saved, it will be removed.') .'</em>', 'colspan' => 7)), 'class' => 'category-'. $category_number .'-message category-message category-populated'); + + // Make it dragable only if there is more than one field + if (isset($form['submit'])) { + drupal_add_tabledrag('profile-fields', 'order', 'sibling', 'profile-weight', 'profile-weight-'. $category_number); + drupal_add_tabledrag('profile-fields', 'match', 'sibling', 'profile-category', 'profile-category-'. $category_number); + } + $category_number++; + } + + // Add special drag and drop classes that group fields together. + $field['weight']['#attributes']['class'] = 'profile-weight profile-weight-'. $categories[$category]; + $field['category']['#attributes']['class'] = 'profile-category profile-category-'. $categories[$category]; + + // Add the row + $row = array(); + $row[] = drupal_render($field['title']); + $row[] = drupal_render($field['name']); + $row[] = drupal_render($field['type']); + if (isset($form['submit'])) { + $row[] = drupal_render($field['category']); + $row[] = drupal_render($field['weight']); + } + $row[] = drupal_render($field['edit']); + $row[] = drupal_render($field['delete']); + $rows[] = array('data' => $row, 'class' => 'draggable'); + } + } + if (empty($rows)) { + $rows[] = array(array('data' => t('No fields available.'), 'colspan' => 7)); + } + + $header = array(t('Title'), t('Name'), t('Type')); + if (isset($form['submit'])) { + $header[] = t('Category'); + $header[] = t('Weight'); + } + $header[] = array('data' => t('Operations'), 'colspan' => 2); + + $output = theme('table', $header, $rows, array('id' => 'profile-fields')); + $output .= drupal_render($form); + return $output; } @@ -118,12 +242,6 @@ function profile_field_form(&$form_state, $arg = NULL) { '#description' => t('A list of all options. Put each option on a separate line. Example options are "red", "blue", "green", etc.'), ); } - $form['fields']['weight'] = array('#type' => 'weight', - '#title' => t('Weight'), - '#default_value' => $edit['weight'], - '#delta' => 5, - '#description' => t('The weights define the order in which the form fields are shown. Lighter fields "float up" towards the top of the category.'), - ); $form['fields']['visibility'] = array('#type' => 'radios', '#title' => t('Visibility'), '#default_value' => isset($edit['visibility']) ? $edit['visibility'] : PROFILE_PUBLIC, @@ -143,6 +261,11 @@ function profile_field_form(&$form_state, $arg = NULL) { '#description' => t('To enable browsing this field by value, enter a title for the resulting page. An example page title is "People who are employed". This is only applicable for a public field.'), ); } + $form['fields']['weight'] = array('#type' => 'weight', + '#title' => t('Weight'), + '#default_value' => $edit['weight'], + '#description' => t('The weights define the order in which the form fields are shown. Lighter fields "float up" towards the top of the category.'), + ); $form['fields']['autocomplete'] = array('#type' => 'checkbox', '#title' => t('Form will auto-complete while user is typing.'), '#default_value' => $edit['autocomplete'], diff --git a/modules/profile/profile.css b/modules/profile/profile.css new file mode 100644 index 0000000000000000000000000000000000000000..b64ef9c43d46babf2ce600636f9cb0fa545e0006 --- /dev/null +++ b/modules/profile/profile.css @@ -0,0 +1,11 @@ +/* $Id$ */ + +#profile-fields td.category { + font-weight: bold; +} +#profile-fields tr.category-message { + color: #999; +} +#profile-fields tr.category-populated { + display: none; +} diff --git a/modules/profile/profile.js b/modules/profile/profile.js new file mode 100644 index 0000000000000000000000000000000000000000..dadc60a01e29253a495af5d5fbe22ff6c170156b --- /dev/null +++ b/modules/profile/profile.js @@ -0,0 +1,54 @@ +// $Id$ + +/** + * Add functionality to the profile drag and drop table. + * + * This behavior is dependent on the tableDrag behavior, since it uses the + * objects initialized in that behavior to update the row. It shows and hides + * a warning message when removing the last field from a profile category. + */ +Drupal.behaviors.profileDrag = function(context) { + var table = $('#profile-fields'); + var tableDrag = Drupal.tableDrag['profile-fields']; // Get the profile tableDrag object. + + // Add a handler for when a row is swapped, update empty categories. + tableDrag.row.prototype.onSwap = function(swappedRow) { + var rowObject = this; + $('tr.category-message', table).each(function() { + // If the dragged row is in this category, but above the message row, swap it down one space. + if ($(this).prev('tr').get(0) == rowObject.element) { + // Prevent a recursion problem when using the keyboard to move rows up. + if ((rowObject.method != 'keyboard' || rowObject.direction == 'down')) { + rowObject.swap('after', this); + } + } + // This category has become empty + if ($(this).next('tr').is(':not(.draggable)') || $(this).next('tr').size() == 0) { + $(this).removeClass('category-populated').addClass('category-empty'); + } + // This category has become populated. + else if ($(this).is('.category-empty')) { + $(this).removeClass('category-empty').addClass('category-populated'); + } + }); + }; + + // Add a handler so when a row is dropped, update fields dropped into new categories. + tableDrag.onDrop = function() { + dragObject = this; + if ($(dragObject.rowObject.element).prev('tr').is('.category-message')) { + var categoryRow = $(dragObject.rowObject.element).prev('tr').get(0); + var categoryNum = categoryRow.className.replace(/([^ ]+[ ]+)*category-([^ ]+)-message([ ]+[^ ]+)*/, '$2'); + var categoryField = $('select.profile-category', dragObject.rowObject.element); + var weightField = $('select.profile-weight', dragObject.rowObject.element); + var oldcategoryNum = weightField[0].className.replace(/([^ ]+[ ]+)*profile-weight-([^ ]+)([ ]+[^ ]+)*/, '$2'); + + if (!categoryField.is('.profile-category-'+ categoryNum)) { + categoryField.removeClass('profile-category-' + oldcategoryNum).addClass('profile-category-' + categoryNum); + weightField.removeClass('profile-weight-' + oldcategoryNum).addClass('profile-weight-' + categoryNum); + + categoryField.val(categoryField[0].options[categoryNum].value); + } + } + }; +}; diff --git a/modules/profile/profile.module b/modules/profile/profile.module index 746b379b5dc2460b486c0056539be44a7d458360..6e6ce260a9c1923ccc1e57add22e45ff6a601db5 100644 --- a/modules/profile/profile.module +++ b/modules/profile/profile.module @@ -67,6 +67,10 @@ function profile_theme() { 'profile_wrapper' => array( 'arguments' => array('content' => NULL), 'template' => 'profile-wrapper', + ), + 'profile_admin_overview' => array( + 'arguments' => array('form' => NULL), + 'file' => 'profile.admin.inc', ) ); } @@ -85,7 +89,8 @@ function profile_menu() { $items['admin/user/profile'] = array( 'title' => 'Profiles', 'description' => 'Create customizable fields for your users.', - 'page callback' => 'profile_admin_overview', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('profile_admin_overview'), 'file' => 'profile.admin.inc', ); $items['admin/user/profile/add'] = array( diff --git a/themes/garland/style.css b/themes/garland/style.css index 473d3d6c189f014bb848d90f1f0e79479e343f28..cc882fca89b88bacbaf13f8ecc37e5721f309990 100644 --- a/themes/garland/style.css +++ b/themes/garland/style.css @@ -242,7 +242,7 @@ tr.even td.active { background-color: #e6f1f7; } -td.region, td.module, td.container { +td.region, td.module, td.container, td.category { border-top: 1.5em solid #fff; border-bottom: 1px solid #b4d7f0; background-color: #d4e7f3; @@ -250,7 +250,7 @@ td.region, td.module, td.container { font-weight: bold; } -tr:first-child td.region, tr:first-child td.module, tr:first-child td.container { +tr:first-child td.region, tr:first-child td.module, tr:first-child td.container, tr:first-child td.category { border-top-width: 0; }