From bad47cbf856eb6197efff9b5d23c124d147a0441 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1bor=20Hojtsy?= <gabor@hojtsy.hu>
Date: Fri, 30 Nov 2007 09:02:51 +0000
Subject: [PATCH] #193998 by Rob Loach and quicksketch: profile fields drag and
 drop

---
 modules/profile/profile.admin.inc | 165 ++++++++++++++++++++++++++----
 modules/profile/profile.css       |  11 ++
 modules/profile/profile.js        |  54 ++++++++++
 modules/profile/profile.module    |   7 +-
 themes/garland/style.css          |   4 +-
 5 files changed, 217 insertions(+), 24 deletions(-)
 create mode 100644 modules/profile/profile.css
 create mode 100644 modules/profile/profile.js

diff --git a/modules/profile/profile.admin.inc b/modules/profile/profile.admin.inc
index ab309c147423..4268c01a6a8c 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 000000000000..b64ef9c43d46
--- /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 000000000000..dadc60a01e29
--- /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 746b379b5dc2..6e6ce260a9c1 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 473d3d6c189f..cc882fca89b8 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;
 }
 
-- 
GitLab