CRM-16353 - Migrating the Manage Groups list to DataTables API v2
authorJoseph Lacey <joseph@palantetech.coop>
Tue, 15 Dec 2015 17:58:02 +0000 (12:58 -0500)
committerJoseph Lacey <joseph@palantetech.coop>
Tue, 15 Dec 2015 17:58:02 +0000 (12:58 -0500)
----------------------------------------
* CRM-16353: Upgrade datatables integration to use v2 api
  https://issues.civicrm.org/jira/browse/CRM-16353

CRM/Contact/BAO/Group.php
CRM/Group/Page/AJAX.php
templates/CRM/Group/Form/Search.tpl

index dd44875a455de7b40be787f64019da1176f79645..790a5759b306a9f5bce33bd462a51fbf6b169339 100644 (file)
@@ -732,41 +732,61 @@ class CRM_Contact_BAO_Group extends CRM_Contact_DAO_Group {
 
     // format params and add links
     $groupList = array();
-    if (!empty($groups)) {
-      foreach ($groups as $id => $value) {
-        $groupList[$id]['group_id'] = $value['id'];
-        $groupList[$id]['count'] = $value['count'];
-        $groupList[$id]['group_name'] = $value['title'];
-
-        // append parent names if in search mode
-        if (empty($params['parent_id']) && !empty($value['parents'])) {
-          $groupIds = explode(',', $value['parents']);
-          $title = array();
-          foreach ($groupIds as $gId) {
-            $title[] = $allGroups[$gId];
+    foreach ($groups as $id => $value) {
+      $group = array();
+      $group['group_id'] = $value['id'];
+      $group['count'] = $value['count'];
+      $group['title'] = $value['title'];
+
+      // append parent names if in search mode
+      if (empty($params['parent_id']) && !empty($value['parents'])) {
+        $group['parent_id'] = $value['parents'];
+        $groupIds = explode(',', $value['parents']);
+        $title = array();
+        foreach ($groupIds as $gId) {
+          $title[] = $allGroups[$gId];
+        }
+        $group['title'] .= '<div class="crm-row-parent-name"><em>' . ts('Child of') . '</em>: ' . implode(', ', $title) . '</div>';
+        $value['class'] = array_diff($value['class'], array('crm-row-parent'));
+      }
+      $group['DT_RowId'] = 'row_' . $value['id'];
+      if (!$params['parentsOnly']) {
+        foreach($value['class'] as $id => $class) {
+          if ($class = 'crm-group-parent') {
+            unset($value['class'][$id]);
           }
-          $groupList[$id]['group_name'] .= '<div class="crm-row-parent-name"><em>' . ts('Child of') . '</em>: ' . implode(', ', $title) . '</div>';
-          $value['class'] = array_diff($value['class'], array('crm-row-parent'));
         }
-        $value['class'][] = 'crm-entity';
-        $groupList[$id]['class'] = $value['id'] . ',' . implode(' ', $value['class']);
+      }
+      $group['DT_RowClass'] = 'crm-entity ' . implode(' ', $value['class']);
+      $group['DT_RowAttr'] = array();
+      $group['DT_RowAttr']['data-id'] = $value['id'];
+      $group['DT_RowAttr']['data-entity'] = 'group';
 
-        $groupList[$id]['group_description'] = CRM_Utils_Array::value('description', $value);
-        if (!empty($value['group_type'])) {
-          $groupList[$id]['group_type'] = $value['group_type'];
-        }
-        else {
-          $groupList[$id]['group_type'] = '';
-        }
-        $groupList[$id]['visibility'] = $value['visibility'];
-        $groupList[$id]['links'] = $value['action'];
-        $groupList[$id]['org_info'] = CRM_Utils_Array::value('org_info', $value);
-        $groupList[$id]['created_by'] = CRM_Utils_Array::value('created_by', $value);
+      $group['description'] = CRM_Utils_Array::value('description', $value);
 
-        $groupList[$id]['is_parent'] = $value['is_parent'];
+      if (!empty($value['group_type'])) {
+        $group['group_type'] = $value['group_type'];
+      }
+      else {
+        $group['group_type'] = '';
       }
-      return $groupList;
+
+      $group['visibility'] = $value['visibility'];
+      $group['links'] = $value['action'];
+      $group['org_info'] = CRM_Utils_Array::value('org_info', $value);
+      $group['created_by'] = CRM_Utils_Array::value('created_by', $value);
+
+      $group['is_parent'] = $value['is_parent'];
+
+      array_push($groupList, $group);
     }
+
+    $groupsDT = array();
+    $groupsDT['data'] = $groupList;
+    $groupsDT['recordsTotal'] = $params['total'];
+    $groupsDT['recordsFiltered'] = $params['total'];
+
+    return $groupsDT;
   }
 
   /**
@@ -864,10 +884,6 @@ class CRM_Contact_BAO_Group extends CRM_Contact_DAO_Group {
           'count' => '0',
         );
         CRM_Core_DAO::storeValues($object, $values[$object->id]);
-        // Wrap with crm-editable. Not an ideal solution.
-        if (in_array(CRM_Core_Permission::EDIT, $groupPermissions)) {
-          $values[$object->id]['title'] = '<span class="crm-editable crmf-title">' . $values[$object->id]['title'] . '</span>';
-        }
 
         if ($object->saved_search_id) {
           $values[$object->id]['title'] .= ' (' . ts('Smart Group') . ')';
index e6c3e7c1629a0b22345d5a362952667a9f7f9d18..47ca4f49ad25c806be4d473f4299d7a74dec88c3 100644 (file)
@@ -42,7 +42,7 @@ class CRM_Group_Page_AJAX {
    * @return array
    */
   public static function getGroupList() {
-    $params = $_REQUEST;
+    $params = $_GET;
 
     if (isset($params['parent_id'])) {
       // requesting child groups for a given parent
@@ -53,20 +53,16 @@ class CRM_Group_Page_AJAX {
       CRM_Utils_JSON::output($groups);
     }
     else {
-      $sortMapper = array(
-        0 => 'groups.title',
-        1 => 'count',
-        2 => 'createdBy.sort_name',
-        3 => '',
-        4 => 'groups.group_type',
-        5 => 'groups.visibility',
-      );
 
-      $sEcho = CRM_Utils_Type::escape($_REQUEST['sEcho'], 'Integer');
-      $offset = isset($_REQUEST['iDisplayStart']) ? CRM_Utils_Type::escape($_REQUEST['iDisplayStart'], 'Integer') : 0;
-      $rowCount = isset($_REQUEST['iDisplayLength']) ? CRM_Utils_Type::escape($_REQUEST['iDisplayLength'], 'Integer') : 25;
-      $sort = isset($_REQUEST['iSortCol_0']) ? CRM_Utils_Array::value(CRM_Utils_Type::escape($_REQUEST['iSortCol_0'], 'Integer'), $sortMapper) : NULL;
-      $sortOrder = isset($_REQUEST['sSortDir_0']) ? CRM_Utils_Type::escape($_REQUEST['sSortDir_0'], 'String') : 'asc';
+      $sortMapper = array();
+      foreach ($_GET['columns'] as $key => $value) {
+        $sortMapper[$key] = $value['data'];
+      };
+
+      $offset = isset($_GET['start']) ? CRM_Utils_Type::escape($_GET['start'], 'Integer') : 0;
+      $rowCount = isset($_GET['length']) ? CRM_Utils_Type::escape($_GET['length'], 'Integer') : 25;
+      $sort = isset($_GET['order'][0]['column']) ? CRM_Utils_Array::value(CRM_Utils_Type::escape($_GET['order'][0]['column'], 'Integer'), $sortMapper) : NULL;
+      $sortOrder = isset($_GET['order'][0]['dir']) ? CRM_Utils_Type::escape($_GET['order'][0]['dir'], 'String') : 'asc';
 
       if ($sort && $sortOrder) {
         $params['sortBy'] = $sort . ' ' . $sortOrder;
@@ -90,31 +86,18 @@ class CRM_Group_Page_AJAX {
         }
       }
 
-      $iFilteredTotal = $iTotal = $params['total'];
-      $selectorElements = array(
-        'group_name',
-        'count',
-        'created_by',
-        'group_description',
-        'group_type',
-        'visibility',
-        'org_info',
-        'links',
-        'class',
-      );
-
+      /*
       if (empty($params['showOrgInfo'])) {
         unset($selectorElements[6]);
       }
+      */
       //add setting so this can be tested by unit test
       //@todo - ideally the portion of this that retrieves the groups should be extracted into a function separate
       // from the one which deals with web inputs & outputs so we have a properly testable & re-usable function
       if (!empty($params['is_unit_test'])) {
         return array($groups, $iFilteredTotal);
       }
-      CRM_Utils_System::setHttpHeader('Content-Type', 'application/json');
-      echo CRM_Utils_JSON::encodeDataTableSelector($groups, $sEcho, $iTotal, $iFilteredTotal, $selectorElements);
-      CRM_Utils_System::civiExit();
+      CRM_Utils_JSON::output($groups);
     }
   }
 
index 1eeaebdd51fe1fb6c5738887445b4856db6f6b5a..9b67bb54961bf29500e6e897bf0716d5477be0f2 100644 (file)
       {$form.group_status.html}
     </td>
   </tr>
-  <tr>
-     <td>{$form.buttons.html}</td><td colspan="2">
-  </tr>
 </table>
 </div>
 <div class="css_right">
   <a class="crm-hover-button action-item" href="{crmURL q="reset=1&update_smart_groups=1"}">{ts}Update Smart Group Counts{/ts}</a> {help id="update_smart_groups"}
 </div>
-<table class="crm-group-selector">
+<table class="crm-group-selector crm-ajax-table" data-order='[[0,"asc"]]'>
   <thead>
     <tr>
-      <th class='crm-group-name'>{ts}Name{/ts}</th>
-      <th class='crm-group-count'>{ts}Count{/ts}</th>
-      <th class='crm-group-created_by'>{ts}Created By{/ts}</th>
-      <th class='crm-group-description'>{ts}Description{/ts}</th>
-      <th class='crm-group-group_type'>{ts}Group Type{/ts}</th>
-      <th class='crm-group-visibility'>{ts}Visibility{/ts}</th>
+      <th data-data="title" cell-class="crm-group-name crm-editable crmf-title" class='crm-group-name'>{ts}Name{/ts}</th>
+      <th data-data="count" cell-class="crm-group-count right" class='crm-group-count'>{ts}Count{/ts}</th>
+      <th data-data="created_by" cell-class="crm-group-created_by" class='crm-group-created_by'>{ts}Created By{/ts}</th>
+      <th data-data="description" data-orderable="false" cell-class="crm-group-description crmf-description crm-editable" class='crm-group-description'>{ts}Description{/ts}</th>
+      <th data-data="group_type" cell-class="crm-group-group_type" class='crm-group-group_type'>{ts}Group Type{/ts}</th>
+      <th data-data="visibility" cell-class="crm-group-visibility crmf-visibility crm-editable" cell-data-type="select" class='crm-group-visibility'>{ts}Visibility{/ts}</th>
       {if $showOrgInfo}
-      <th class='crm-group-org_info'>{ts}Organization{/ts}</th>
+        <th data-data="org_info" data-orderable="false" cell-class="crm-group-org_info" class='crm-group-org_info'>{ts}Organization{/ts}</th>
       {/if}
-      <th class='crm-group-group_links nosort'>&nbsp;</th>
-      <th class='hiddenElement'>&nbsp;</th>
+      <th data-data="links" data-orderable="false" cell-class="crm-group-group_links" class='crm-group-group_links'>&nbsp;</th>
     </tr>
   </thead>
 </table>
 
 {literal}
 <script type="text/javascript">
-CRM.$(function($) {
-  // for CRM-11310 and CRM-10635 : processing just parent groups on initial display
-  // passing '1' for parentsOnlyArg to show parent child hierarchy structure display
-  // on initial load of manage group page and
-  // also to handle search filtering for initial load of same page.
-  buildGroupSelector(true, 1);
-  $('#_qf_Search_refresh').click( function() {
-    buildGroupSelector( true );
-  });
-  // Add livePage functionality
-  $('#crm-container')
-    .on('click', 'a.button, a.action-item[href*="action=update"], a.action-item[href*="action=delete"]', CRM.popup)
-    .on('crmPopupFormSuccess', 'a.button, a.action-item[href*="action=update"], a.action-item[href*="action=delete"]', function() {
-        // Refresh datatable when form completes
-        var $context = $('#crm-main-content-wrapper');
-        $('table.crm-group-selector', $context).dataTable().fnDraw();
-    });
-
-  function buildGroupSelector( filterSearch, parentsOnlyArg ) {
-    if ( filterSearch ) {
-      if (typeof crmGroupSelector !== 'undefined') {
-        crmGroupSelector.fnDestroy();
-      }
-      var parentsOnly = 0;
-      var ZeroRecordText = '<div class="status messages">{/literal}{ts escape="js"}No matching Groups found for your search criteria. Suggestions:{/ts}{literal}<div class="spacer"></div><ul><li>{/literal}{ts escape="js"}Check your spelling.{/ts}{literal}</li><li>{/literal}{ts escape="js"}Try a different spelling or use fewer letters.{/ts}{literal}</li><li>{/literal}{ts escape="js"}Make sure you have enough privileges in the access control system.{/ts}{literal}</li></ul></div>';
-    } else {
-        var parentsOnly = 1;
-        var ZeroRecordText = {/literal}'{ts escape="js"}<div class="status messages">No Groups have been created for this site.{/ts}</div>'{literal};
-    }
-
-    // this argument should only be used on initial display i.e onPageLoad
-    if (typeof parentsOnlyArg !== 'undefined') {
-      parentsOnly = parentsOnlyArg;
-    }
-
-    var columns = '';
-    var sourceUrl = {/literal}'{crmURL p="civicrm/ajax/grouplist" h=0 q="snippet=4"}'{literal};
-    var showOrgInfo = {/literal}"{$showOrgInfo}"{literal};
-    var $context = $('#crm-main-content-wrapper');
+  (function($) {
+    // for CRM-11310 and CRM-10635 : processing just parent groups on initial display
+    // passing '1' for parentsOnlyArg to show parent child hierarchy structure display
+    // on initial load of manage group page and
+    // also to handle search filtering for initial load of same page.
+    var parentsOnly = 1
+    var ZeroRecordText = {/literal}'{ts escape="js"}<div class="status messages">No Groups have been created for this site.{/ts}</div>'{literal};
+    CRM.$('table.crm-group-selector').data({
+      "ajax": {
+        "url": {/literal}'{crmURL p="civicrm/ajax/grouplist" h=0 q="snippet=4"}'{literal},
+        "data": function (d) {
 
-    crmGroupSelector = $('table.crm-group-selector', $context).dataTable({
-        "bFilter"    : false,
-        "bAutoWidth" : false,
-        "aaSorting"  : [],
-        "aoColumns"  : [
-                        {sClass:'crm-group-name'},
-                        {sClass:'crm-group-count'},
-                        {sClass:'crm-group-created_by'},
-                        {sClass:'crm-group-description', bSortable:false},
-                        {sClass:'crm-group-group_type'},
-                        {sClass:'crm-group-visibility'},
-                        {sClass:'crm-group-group_links', bSortable:false},
-                        {/literal}{if $showOrgInfo}{literal}
-                        {sClass:'crm-group-org_info', bSortable:false},
-                        {/literal}{/if}{literal}
-                        {sClass:'hiddenElement', bSortable:false}
-                       ],
-        "bProcessing": true,
-        "asStripClasses" : [ "odd-row", "even-row" ],
-        "sPaginationType": "full_numbers",
-        "sDom"       : '<"crm-datatable-pager-top"lfp>rt<"crm-datatable-pager-bottom"ip>',
-        "bServerSide": true,
-        "bJQueryUI": true,
-        "sAjaxSource": sourceUrl,
-        "iDisplayLength": 25,
-        "oLanguage": { "sZeroRecords":  ZeroRecordText,
-                       "sProcessing":    {/literal}"{ts escape='js'}Processing...{/ts}"{literal},
-                       "sLengthMenu":    {/literal}"{ts escape='js'}Show _MENU_ entries{/ts}"{literal},
-                       "sInfo":          {/literal}"{ts escape='js'}Showing _START_ to _END_ of _TOTAL_ entries{/ts}"{literal},
-                       "sInfoEmpty":     {/literal}"{ts escape='js'}Showing 0 to 0 of 0 entries{/ts}"{literal},
-                       "sInfoFiltered":  {/literal}"{ts escape='js'}(filtered from _MAX_ total entries){/ts}"{literal},
-                       "sSearch":        {/literal}"{ts escape='js'}Search:{/ts}"{literal},
-                       "oPaginate": {
-                            "sFirst":    {/literal}"{ts escape='js'}First{/ts}"{literal},
-                            "sPrevious": {/literal}"{ts escape='js'}Previous{/ts}"{literal},
-                            "sNext":     {/literal}"{ts escape='js'}Next{/ts}"{literal},
-                            "sLast":     {/literal}"{ts escape='js'}Last{/ts}"{literal}
-                        }
-                    },
-        "fnRowCallback": function(nRow, aData, iDisplayIndex, iDisplayIndexFull) {
-          var id = $('td:last', nRow).text().split(',')[0];
-          var cl = $('td:last', nRow).text().split(',')[1];
-          $(nRow).addClass(cl).attr({id: 'row_' + id, 'data-id': id, 'data-entity': 'group'});
-          //$('td:eq(0)', nRow).wrapInner('<span class="crm-editable crmf-title" />');
-          $('td:eq(1)', nRow).addClass('right');
-          if (CRM.checkPerm('edit groups')) {
-            $('td:eq(3)', nRow).wrapInner('<div class="crm-editable crmf-description" data-type="textarea" />');
-            $('td:eq(5)', nRow).wrapInner('<div class="crm-editable crmf-visibility" data-type="select" />');
+          var groupTypes = ($('.crm-group-search-form-block #group_type_1').prop('checked')) ? '1' : '';
+          if (groupTypes) {
+            groupTypes = ($('.crm-group-search-form-block #group_type_2').prop('checked')) ? groupTypes + ',2' : groupTypes;
+          } else {
+            groupTypes = ($('.crm-group-search-form-block #group_type_2').prop('checked')) ? '2' : '';
           }
-          if (parentsOnly) {
-            if ($(nRow).hasClass('crm-group-parent')) {
-              $(nRow).find('td:first').prepend('{/literal}<span class="collapsed show-children" title="{ts}show child groups{/ts}"/></span>{literal}');
-            }
-          }
-          return nRow;
-        },
-        "fnDrawCallback": function() {
-          // FIXME: trigger crmLoad and crmEditable would happen automatically
-          $('.crm-editable').crmEditable();
-        },
-        "fnServerData": function ( sSource, aoData, fnCallback ) {
-            aoData.push( {name:'showOrgInfo', value: showOrgInfo },
-                         {name:'parentsOnly', value: parentsOnly }
-                       );
-            if ( filterSearch ) {
-                var groupTypes = '';
-                $('#group_type-block input').each(function(index) {
-                if ($(this).prop('checked')) {
-                  if (groupTypes) {
-                    groupTypes = groupTypes + ',' + $(this).attr('id').substr(11);
-                  }
-                  else {
-                    groupTypes = $(this).attr('id').substr(11);
-                  }
-                }
-                });
 
-                var groupStatus = '';
-                if ( $('.crm-group-search-form-block #group_status_1').prop('checked') ) {
-                    groupStatus = '1';
-                }
-
-                if ( $('.crm-group-search-form-block #group_status_2').prop('checked') ) {
-                    if ( groupStatus ) {
-                        groupStatus = '3';
-                    } else {
-                        groupStatus = '2';
-                    }
-                }
+          var groupStatus = ($('.crm-group-search-form-block #group_status_1').prop('checked')) ? 1 : '';
+          if (groupStatus) {
+            groupStatus = ($('.crm-group-search-form-block #group_status_2').prop('checked')) ? 3 : groupStatus;
+          } else {
+            groupStatus = ($('.crm-group-search-form-block #group_status_2').prop('checked')) ? 2 : '';
+          }
 
-                aoData.push(
-                    {name:'title', value: $('.crm-group-search-form-block #title').val()},
-                    {name:'created_by', value: $('.crm-group-search-form-block #created_by').val()},
-                    {name:'group_type', value: groupTypes },
-                    {name:'visibility', value: $('.crm-group-search-form-block #visibility').val()},
-                    {name:'status', value: groupStatus }
-                );
+          d.title = $(".crm-group-search-form-block input#title").val(),
+          d.created_by = $(".crm-group-search-form-block input#created_by").val(),
+          d.group_type = groupTypes,
+          d.visibility = $(".crm-group-search-form-block select#visibility").val(),
+          d.status = groupStatus,
+          d.showOrgInfo = {/literal}"{$showOrgInfo}"{literal},
+          d.parentsOnly = parentsOnly
+        }
+      },
+      "language": {
+        "zeroRecords": ZeroRecordText 
+      },
+      "drawCallback": function(settings) {
+        //Add data attributes to cells
+        $('thead th', settings.nTable).each( function( index ) {
+          $.each(this.attributes, function() {
+            if(this.name.match("^cell-")) {
+              var cellAttr = this.name.substring(5);
+              var cellValue = this.value;
+              $('tbody tr', settings.nTable).each( function() {
+                $('td:eq('+ index +')', this).attr( cellAttr, cellValue );
+              });
             }
-            $.ajax( {
-                "dataType": 'json',
-                "type": "POST",
-                "url": sSource,
-                "data": aoData,
-                "success": fnCallback
-            } );
+          });
+        });
+        //Reload table after draw
+        $(settings.nTable).trigger('crmLoad');
+        if (parentsOnly) {
+          $('tbody tr.crm-group-parent', settings.nTable).each( function() {
+            $(this).find('td:first').prepend('{/literal}<span class="collapsed show-children" title="{ts}show child groups{/ts}"/></span>{literal}');
+          });
         }
+      }
+    });
+    $(function($) {
+      $('.crm-group-search-form-block :input').change(function(){
+        parentsOnly = 0;
+        ZeroRecordText = '<div class="status messages">{/literal}{ts escape="js"}No matching Groups found for your search criteria. Suggestions:{/ts}{literal}<div class="spacer"></div><ul><li>{/literal}{ts escape="js"}Check your spelling.{/ts}{literal}</li><li>{/literal}{ts escape="js"}Try a different spelling or use fewer letters.{/ts}{literal}</li><li>{/literal}{ts escape="js"}Make sure you have enough privileges in the access control system.{/ts}{literal}</li></ul></div>';
+        $('table.crm-group-selector').DataTable().draw();
+      });
+    });
+  })(CRM.$);
+  CRM.$('#crm-container')
+    .on('click', 'a.button, a.action-item[href*="action=update"], a.action-item[href*="action=delete"]', CRM.popup)
+    .on('crmPopupFormSuccess', 'a.button, a.action-item[href*="action=update"], a.action-item[href*="action=delete"]', function() {
+        // Refresh datatable when form completes
+        CRM.$('table.crm-group-selector').DataTable().draw();
     });
-  }
-
   // show hide children
-  var $context = $('#crm-main-content-wrapper');
-  $('table.crm-group-selector', $context).on( 'click', 'span.show-children', function(){
+  var context = CRM.$('#crm-main-content-wrapper');
+  CRM.$('table.crm-group-selector', context).on( 'click', 'span.show-children', function(){
     var showOrgInfo = {/literal}"{$showOrgInfo}"{literal};
-    var rowID = $(this).parents('tr').prop('id');
+    var rowID = CRM.$(this).parents('tr').prop('id');
     var parentRow = rowID.split('_');
     var parent_id = parentRow[1];
     var group_id = '';
@@ -254,21 +175,21 @@ CRM.$(function($) {
     }
     var levelClass = 'level_2';
     // check enclosing td if already at level 2
-    if ( $(this).parent().hasClass('level_2') ) {
+    if ( CRM.$(this).parent().hasClass('level_2') ) {
       levelClass = 'level_3';
     }
-    if ( $(this).hasClass('collapsed') ) {
-      $(this).removeClass("collapsed").addClass("expanded").attr("title",{/literal}"{ts escape='js'}hide child groups{/ts}"{literal});
+    if ( CRM.$(this).hasClass('collapsed') ) {
+      CRM.$(this).removeClass("collapsed").addClass("expanded").attr("title",{/literal}"{ts escape='js'}hide child groups{/ts}"{literal});
       showChildren( parent_id, showOrgInfo, group_id, levelClass );
     }
     else {
-      $(this).removeClass("expanded").addClass("collapsed").attr("title",{/literal}"{ts escape='js'}show child groups{/ts}"{literal});
-      $('.parent_is_' + parent_id).find('.show-children').removeClass("expanded").addClass("collapsed").attr("title",{/literal}"{ts escape='js'}show child groups{/ts}"{literal});
-      $('.parent_is_' + parent_id).hide();
-      $('.parent_is_' + parent_id).each(function(i, obj) {
+      CRM.$(this).removeClass("expanded").addClass("collapsed").attr("title",{/literal}"{ts escape='js'}show child groups{/ts}"{literal});
+      CRM.$('.parent_is_' + parent_id).find('.show-children').removeClass("expanded").addClass("collapsed").attr("title",{/literal}"{ts escape='js'}show child groups{/ts}"{literal});
+      CRM.$('.parent_is_' + parent_id).hide();
+      CRM.$('.parent_is_' + parent_id).each(function(i, obj) {
         // also hide children of children
         var gID = $(this).find('td:nth-child(2)').text();
-        $('.parent_is_' + gID).hide();
+        CRM.$('.parent_is_' + gID).hide();
       });
     }
   });
@@ -277,28 +198,28 @@ CRM.$(function($) {
     if ( group_id ) {
       rowID = '#row_' + parent_id + '_' + group_id;
     }
-    if ( $(rowID).next().hasClass('parent_is_' + parent_id ) ) {
+    if ( CRM.$(rowID).next().hasClass('parent_is_' + parent_id ) ) {
       // child rows for this parent have already been retrieved so just show them
-      $('.parent_is_' + parent_id ).show();
+      CRM.$('.parent_is_' + parent_id ).show();
     } else {
-      var sourceUrl = {/literal}'{crmURL p="civicrm/ajax/grouplist" h=0 q="snippet=4"}'{literal};
-      $.ajax( {
+      //FIXME Is it possible to replace all this with a datatables call? 
+      CRM.$.ajax( {
           "dataType": 'json',
-          "url": sourceUrl,
+          "url": {/literal}'{crmURL p="civicrm/ajax/grouplist" h=0 q="snippet=4"}'{literal},
           "data": {'parent_id': parent_id, 'showOrgInfo': showOrgInfo},
           "success": function(response){
             var appendHTML = '';
-            $.each( response, function( i, val ) {
-              appendHTML += '<tr id="row_'+ val.group_id +'_'+parent_id+'" data-entity="group" data-id="'+ val.group_id +'" class="parent_is_' + parent_id + ' crm-row-child ' + val.class.split(',')[1] + '">';
+            CRM.$.each( response.data, function( i, val ) {
+              appendHTML += '<tr id="row_'+val.group_id+'_'+parent_id+'" data-entity="group" data-id="'+val.group_id+'" class="parent_is_'+parent_id+' crm-row-child">';
               if ( val.is_parent ) {
-                appendHTML += '<td class="crm-group-name ' + levelClass + '">' + '{/literal}<span class="collapsed show-children" title="{ts}show child groups{/ts}"/></span>{literal}' + val.group_name + '</td>';
+                appendHTML += '<td class="crm-group-name crmf-title crm-editable ' + levelClass + '">' + '{/literal}<span class="collapsed show-children" title="{ts}show child groups{/ts}"/></span>{literal}' + val.group_name + '</td>';
               }
               else {
-                appendHTML += '<td class="crm-group-name ' + levelClass + '"><span class="crm-no-children"></span>' + val.group_name + '</td>';
+                appendHTML += '<td class="crm-group-name  crmf-title crm-editable ' + levelClass + '"><span class="crm-no-children"></span>' + val.title + '</td>';
               }
               appendHTML += '<td class="right">' + val.count + "</td>";
               appendHTML += "<td>" + val.created_by + "</td>";
-              appendHTML += '<td class="crm-editable crmf-description" data-type="textarea">' + (val.group_description || '') + "</td>";
+              appendHTML += '<td class="crm-editable crmf-description" data-type="textarea">' + (val.description || '') + "</td>";
               appendHTML += "<td>" + val.group_type + "</td>";
               appendHTML += '<td class="crm-editable crmf-visibility" data-type="select">' + val.visibility + "</td>";
               if (showOrgInfo) {
@@ -307,13 +228,11 @@ CRM.$(function($) {
               appendHTML += "<td>" + val.links + "</td>";
               appendHTML += "</tr>";
             });
-            $( rowID ).after( appendHTML );
-            $( rowID ).next().trigger('crmLoad');
+            CRM.$( rowID ).after( appendHTML );
+            CRM.$( rowID ).next().trigger('crmLoad');
           }
       });
     }
   }
-});
-
 </script>
 {/literal}