CRM-20179 - Upgrade jstree on navigation admin screen
authorColeman Watts <coleman@civicrm.org>
Fri, 24 Feb 2017 18:17:35 +0000 (11:17 -0700)
committerColeman Watts <coleman@civicrm.org>
Fri, 24 Feb 2017 18:17:35 +0000 (11:17 -0700)
CRM/Admin/Form/Navigation.php
CRM/Admin/Page/AJAX.php
CRM/Admin/Page/Navigation.php
CRM/Core/BAO/Navigation.php
CRM/Core/xml/Menu/Admin.xml
css/civicrmNavigation.css
templates/CRM/Admin/Page/Navigation.tpl
templates/CRM/Tag/Page/Tag.tpl

index 37c08225363439d29d2ae95c5f17e11319e0855a..1441b7d7a030335b9898c77af5f00587c74d08c9 100644 (file)
@@ -106,7 +106,7 @@ class CRM_Admin_Form_Navigation extends CRM_Admin_Form {
    * @return array
    */
   public function setDefaultValues() {
-    $defaults = $this->_defaults;
+    $defaults = parent::setDefaultValues();
     if (isset($this->_id)) {
       //Take parent id in object variable to calculate the menu
       //weight if menu parent id changed
index ebf77802a8f6553f2b634ae3e922b344f24f0516..35f5f2aabd567ed603b412846db8899c148f68b5 100644 (file)
@@ -54,14 +54,6 @@ class CRM_Admin_Page_AJAX {
     CRM_Utils_System::civiExit();
   }
 
-  /**
-   * Return menu tree as json data for editing.
-   */
-  public static function getNavigationList() {
-    echo CRM_Core_BAO_Navigation::buildNavigation(TRUE, FALSE);
-    CRM_Utils_System::civiExit();
-  }
-
   /**
    * Process drag/move action for menu tree.
    */
index 22561bee50db5848b3b9f1b85f57057a3b275b4e..d22d8be7cd22f40c20af7149176fc6d9953a1f39 100644 (file)
@@ -105,8 +105,8 @@ class CRM_Admin_Page_Navigation extends CRM_Core_Page_Basic {
 
     // Add jstree support
     CRM_Core_Resources::singleton()
-      ->addScriptFile('civicrm', 'packages/jquery/plugins/jstree/jquery.jstree.js', 0, 'html-header', FALSE)
-      ->addStyleFile('civicrm', 'packages/jquery/plugins/jstree/themes/default/style.css', 0, 'html-header');
+      ->addScriptFile('civicrm', 'bower_components/jstree/dist/jstree.min.js', 0, 'html-header')
+      ->addStyleFile('civicrm', 'bower_components/jstree/dist/themes/default/style.min.css');
   }
 
 }
index 82cd56bbe2adb250c383d5fe7621ca012380c794..a4ae608bbdb9729397dd4f81ac26ae552943eaf3 100644 (file)
@@ -264,45 +264,25 @@ FROM civicrm_navigation WHERE domain_id = $domainID {$whereClause} ORDER BY pare
   /**
    * Build navigation tree.
    *
-   * @param array $navigationTree
-   *   Nested array of menus.
-   * @param int $parentID
-   *   Parent id.
-   * @param bool $navigationMenu
-   *   True when called for building top navigation menu.
-   *
    * @return array
    *   nested array of menus
    */
-  public static function buildNavigationTree(&$navigationTree, $parentID, $navigationMenu = TRUE) {
-    $whereClause = " parent_id IS NULL";
-
-    if ($parentID) {
-      $whereClause = " parent_id = {$parentID}";
-    }
-
+  public static function buildNavigationTree() {
     $domainID = CRM_Core_Config::domainID();
+    $navigationTree = array();
 
     // get the list of menus
     $query = "
 SELECT id, label, url, permission, permission_operator, has_separator, parent_id, is_active, name
 FROM civicrm_navigation
-WHERE {$whereClause}
-AND domain_id = $domainID
+WHERE domain_id = $domainID
 ORDER BY parent_id, weight";
 
     $navigation = CRM_Core_DAO::executeQuery($query);
-    $config = CRM_Core_Config::singleton();
     while ($navigation->fetch()) {
-      $label = $navigation->label;
-      if (!$navigationMenu) {
-        $label = addcslashes($label, '"');
-      }
-
-      // for each menu get their children
       $navigationTree[$navigation->id] = array(
         'attributes' => array(
-          'label' => $label,
+          'label' => $navigation->label,
           'name' => $navigation->name,
           'url' => $navigation->url,
           'permission' => $navigation->permission,
@@ -313,75 +293,69 @@ ORDER BY parent_id, weight";
           'active' => $navigation->is_active,
         ),
       );
-      self::buildNavigationTree($navigationTree[$navigation->id]['child'], $navigation->id, $navigationMenu);
     }
 
-    return $navigationTree;
+    return self::buildTree($navigationTree);
   }
 
   /**
-   * Build menu.
+   * Convert flat array to nested.
+   *
+   * @param array $elements
+   * @param int|null $parentId
    *
-   * @param bool $json
-   *   By default output is html.
-   * @param bool $navigationMenu
-   *   True when called for building top navigation menu.
+   * @return array
+   */
+  private static function buildTree($elements, $parentId = NULL) {
+    $branch = array();
+
+    foreach ($elements as $id => $element) {
+      if ($element['attributes']['parentID'] == $parentId) {
+        $children = self::buildTree($elements, $id);
+        if ($children) {
+          $element['child'] = $children;
+        }
+        $branch[$id] = $element;
+      }
+    }
+
+    return $branch;
+  }
+
+  /**
+   * Build menu.
    *
    * @return string
-   *   html or json string
    */
-  public static function buildNavigation($json = FALSE, $navigationMenu = TRUE) {
-    $navigations = array();
-    self::buildNavigationTree($navigations, $parent = NULL, $navigationMenu);
-    $navigationString = NULL;
+  public static function buildNavigation() {
+    $navigations = self::buildNavigationTree();
+    $navigationString = '';
 
     // run the Navigation  through a hook so users can modify it
     CRM_Utils_Hook::navigationMenu($navigations);
     self::fixNavigationMenu($navigations);
 
-    $i18n = CRM_Core_I18n::singleton();
-
     //skip children menu item if user don't have access to parent menu item
     $skipMenuItems = array();
     foreach ($navigations as $key => $value) {
-      if ($json) {
-        if ($navigationString) {
-          $navigationString .= '},';
-        }
-        $data = $value['attributes']['label'];
-        $class = '';
-        if (!$value['attributes']['active']) {
-          $class = ', "attr": { "class" : "disabled"} ';
-        }
-        $l10nName = $i18n->crm_translate($data, array('context' => 'menu'));
-        $navigationString .= ' { "attr": { "id" : "node_' . $key . '"}, "data": { "title":"' . $l10nName . '"' . $class . '}';
-      }
-      else {
-        // Home is a special case
-        if ($value['attributes']['name'] != 'Home') {
-          $name = self::getMenuName($value, $skipMenuItems);
-          if ($name) {
-            //separator before
-            if (isset($value['attributes']['separator']) && $value['attributes']['separator'] == 2) {
-              $navigationString .= '<li class="menu-separator"></li>';
-            }
-            $removeCharacters = array('/', '!', '&', '*', ' ', '(', ')', '.');
-            $navigationString .= '<li class="menumain crm-' . str_replace($removeCharacters, '_', $value['attributes']['label']) . '">' . $name;
+      // Home is a special case
+      if ($value['attributes']['name'] != 'Home') {
+        $name = self::getMenuName($value, $skipMenuItems);
+        if ($name) {
+          //separator before
+          if (isset($value['attributes']['separator']) && $value['attributes']['separator'] == 2) {
+            $navigationString .= '<li class="menu-separator"></li>';
           }
+          $removeCharacters = array('/', '!', '&', '*', ' ', '(', ')', '.');
+          $navigationString .= '<li class="menumain crm-' . str_replace($removeCharacters, '_', $value['attributes']['label']) . '">' . $name;
         }
       }
-
-      self::recurseNavigation($value, $navigationString, $json, $skipMenuItems);
+      self::recurseNavigation($value, $navigationString, $skipMenuItems);
     }
 
-    if ($json) {
-      $navigationString = '[' . $navigationString . '}]';
-    }
-    else {
-      // clean up - Need to remove empty <ul>'s, this happens when user don't have
-      // permission to access parent
-      $navigationString = str_replace('<ul></ul></li>', '', $navigationString);
-    }
+    // clean up - Need to remove empty <ul>'s, this happens when user don't have
+    // permission to access parent
+    $navigationString = str_replace('<ul></ul></li>', '', $navigationString);
 
     return $navigationString;
   }
@@ -391,74 +365,40 @@ ORDER BY parent_id, weight";
    *
    * @param array $value
    * @param string $navigationString
-   * @param bool $json
-   * @param bool $skipMenuItems
+   * @param array $skipMenuItems
    *
    * @return string
    */
-  public static function recurseNavigation(&$value, &$navigationString, $json, $skipMenuItems) {
-    if ($json) {
-      if (!empty($value['child'])) {
-        $navigationString .= ', "children": [ ';
-      }
-      else {
-        return $navigationString;
-      }
-
-      if (!empty($value['child'])) {
-        $appendComma = TRUE;
-        $count = 1;
-        foreach ($value['child'] as $k => $val) {
-          if ($count == count($value['child'])) {
-            $appendComma = FALSE;
-          }
-          $data = $val['attributes']['label'];
-          $class = '';
-          if (!$val['attributes']['active']) {
-            $class = ', "attr": { "class" : "disabled"} ';
-          }
-          $navigationString .= ' { "attr": { "id" : "node_' . $k . '"}, "data": { "title":"' . $data . '"' . $class . '}';
-          self::recurseNavigation($val, $navigationString, $json, $skipMenuItems);
-          $navigationString .= $appendComma ? ' },' : ' }';
-          $count++;
-        }
-      }
-
-      if (!empty($value['child'])) {
-        $navigationString .= ' ]';
-      }
+  public static function recurseNavigation(&$value, &$navigationString, $skipMenuItems) {
+    if (!empty($value['child'])) {
+      $navigationString .= '<ul>';
     }
     else {
-      if (!empty($value['child'])) {
-        $navigationString .= '<ul>';
-      }
-      else {
-        $navigationString .= '</li>';
-        //locate separator after
-        if (isset($value['attributes']['separator']) && $value['attributes']['separator'] == 1) {
-          $navigationString .= '<li class="menu-separator"></li>';
-        }
+      $navigationString .= '</li>';
+      //locate separator after
+      if (isset($value['attributes']['separator']) && $value['attributes']['separator'] == 1) {
+        $navigationString .= '<li class="menu-separator"></li>';
       }
+    }
 
-      if (!empty($value['child'])) {
-        foreach ($value['child'] as $val) {
-          $name = self::getMenuName($val, $skipMenuItems);
-          if ($name) {
-            //locate separator before
-            if (isset($val['attributes']['separator']) && $val['attributes']['separator'] == 2) {
-              $navigationString .= '<li class="menu-separator"></li>';
-            }
-            $removeCharacters = array('/', '!', '&', '*', ' ', '(', ')', '.');
-            $navigationString .= '<li class="crm-' . str_replace($removeCharacters, '_', $val['attributes']['label']) . '">' . $name;
-            self::recurseNavigation($val, $navigationString, $json, $skipMenuItems);
+    if (!empty($value['child'])) {
+      foreach ($value['child'] as $val) {
+        $name = self::getMenuName($val, $skipMenuItems);
+        if ($name) {
+          //locate separator before
+          if (isset($val['attributes']['separator']) && $val['attributes']['separator'] == 2) {
+            $navigationString .= '<li class="menu-separator"></li>';
           }
+          $removeCharacters = array('/', '!', '&', '*', ' ', '(', ')', '.');
+          $navigationString .= '<li class="crm-' . str_replace($removeCharacters, '_', $val['attributes']['label']) . '">' . $name;
+          self::recurseNavigation($val, $navigationString, $skipMenuItems);
         }
       }
-      if (!empty($value['child'])) {
-        $navigationString .= '</ul></li>';
-        if (isset($value['attributes']['separator']) && $value['attributes']['separator'] == 1) {
-          $navigationString .= '<li class="menu-separator"></li>';
-        }
+    }
+    if (!empty($value['child'])) {
+      $navigationString .= '</ul></li>';
+      if (isset($value['attributes']['separator']) && $value['attributes']['separator'] == 1) {
+        $navigationString .= '<li class="menu-separator"></li>';
       }
     }
     return $navigationString;
@@ -836,26 +776,6 @@ ORDER BY parent_id, weight";
     CRM_Core_DAO::executeQuery($query);
   }
 
-  /**
-   * Get the info on navigation item.
-   *
-   * @param int $navigationID
-   *   Navigation id.
-   *
-   * @return array
-   *   associated array
-   */
-  public static function getNavigationInfo($navigationID) {
-    $query = "SELECT parent_id, weight FROM civicrm_navigation WHERE id = %1";
-    $params = array($navigationID, 'Integer');
-    $dao = CRM_Core_DAO::executeQuery($query, array(1 => $params));
-    $dao->fetch();
-    return array(
-      'parent_id' => $dao->parent_id,
-      'weight' => $dao->weight,
-    );
-  }
-
   /**
    * Update menu.
    *
index 1e913a2af253aced5660b009b59bc278867ff22e..4293f50622e5bc30a4d19c52eb99ba02ff2a3d18 100644 (file)
      <page_callback>CRM_Admin_Page_AJAX::getNavigationMenu</page_callback>
      <access_arguments>access CiviCRM</access_arguments>
   </item>
-  <item>
-     <path>civicrm/ajax/menu</path>
-     <page_callback>CRM_Admin_Page_AJAX::getNavigationList</page_callback>
-     <access_arguments>access CiviCRM,administer CiviCRM</access_arguments>
-     <page_type>3</page_type>
-  </item>
   <item>
      <path>civicrm/ajax/menutree</path>
      <page_callback>CRM_Admin_Page_AJAX::menuTree</page_callback>
index 3458bc3081bdfaff5624d9b827fd058b8e4fc03f..18bc29f25c2eb0df4365c0d83e4f7f255104aee1 100644 (file)
@@ -127,7 +127,8 @@ li.menu-separator{
   line-height: 0; /* for ie */
   margin: 2px 0;
 }
-#civicrm-menu .crm-logo-sm {
+#civicrm-menu .crm-logo-sm,
+.crm-container .crm-logo-sm {
   background: url('../i/item_sprites.png') no-repeat scroll -80px -16px;
   display: inline-block;
   width: 16px;
index 094507f80b143d78eebeddd01e1ca3f34cb599ff..e37969e8834e64ecfa9491cf86661f753359425c 100644 (file)
@@ -39,6 +39,7 @@
         </span><br/><br/>
     </div>
     <div class="spacer"></div>
+    <div style="padding-left: 25px;"><div class="crm-logo-sm"></div></div>
     <div id="navigation-tree" class="navigation-tree" style="height:auto; border-collapse:separate; background-color:#FFFFFF;"></div>
     <div class="spacer"></div>
     <div>
     <div class="spacer"></div>
   </div>
   {literal}
-  <style type="text/css">
-    #navigation-tree li {
-      font-weight: normal;
-    }
-    #navigation-tree > ul > li {
-      font-weight: bold;
-    }
-  </style>
   <script type="text/javascript">
     CRM.$(function($) {
       $("#navigation-tree").jstree({
-        plugins: [ "themes", "json_data", "dnd","ui", "crrm","contextmenu" ],
-        json_data: {
-          ajax:{
-            dataType: "json",
-            url: {/literal}"{crmURL p='civicrm/ajax/menu' h=0 q='key='}{crmKey name='civicrm/ajax/menu'}"{literal}
+        plugins: ["dnd", "contextmenu"],
+        core: {
+          data: function(tree, callBack) {
+            CRM.api3('Navigation', 'get', {
+              domain_id: {/literal}{$config->domainID()}{literal},
+              options: {limit: 0, sort: 'weight'},
+              return: ['label', 'parent_id'],
+              name: {'!=': 'Home'},
+              sequential: 1
+            }).done(function(data) {
+              var items = [];
+              $.each(data.values, function(key, value) {
+                items.push({
+                  id: value.id,
+                  text: value.label,
+                  icon: false,
+                  parent: value.parent_id || '#'
+                });
+              });
+              callBack(items);
+            });
           },
-          progressive_render: true
-        },
-        themes: {
-          "theme": 'classic',
-          "dots": true,
-          "icons": false,
-          "url": CRM.config.resourceBase + 'packages/jquery/plugins/jstree/themes/classic/style.css'
+          progressive_render: true,
+          check_callback: true
         },
-        rules: {
-          droppable: [ "tree-drop" ],
-          multiple: true,
-          deletable: "all",
-          draggable: "all"
-        },
-        crrm: {
-          move: {
-            check_move: function(m) {
-              var homeMenuId = {/literal}"{$homeMenuId}"{literal};
-              if ( $( m.r[0] ).attr('id').replace("node_","") == homeMenuId ||
-                $( m.o[0] ).attr('id').replace("node_","") == homeMenuId ) {
-                return false;
-              } else {
-                return true;
-              }
-            }
-          }
+        dnd: {
+          copy: false
         },
         contextmenu: {
-          items: {
-            create: false,
-            ccp: {
-              label : "{/literal}{ts escape='js'}Edit{/ts}{literal}",
-              visible: function (node, obj) { if(node.length != 1) return false;
-                return obj.check("renameable", node); },
-              action: function (node, obj) {
-                var nid = $(node).prop('id');
-                var nodeID = nid.substr( 5 );
-                var editURL = {/literal}"{crmURL p='civicrm/admin/menu' h=0 q='action=update&reset=1&id='}"{literal} + nodeID;
-                CRM.loadForm(editURL).on('crmFormSuccess', function() {
-                  $("#navigation-tree").jstree('refresh');
-                  $("#reset-menu").show( );
-                });
+          items: function (node, callBack) {
+            var items = {
+              add: {
+                label: "{/literal}{ts escape='js'}Add{/ts}{literal}",
+                icon: 'crm-i fa-plus',
+                action: editForm
+              },
+              edit: {
+                label: "{/literal}{ts escape='js'}Edit{/ts}{literal}",
+                icon: 'crm-i fa-pencil',
+                action: editForm
               },
-              submenu: false
-            }
+              delete: {
+                label: "{/literal}{ts escape='js'}Delete{/ts}{literal}",
+                icon: 'crm-i fa-trash',
+                action: function (menu) {
+                  var nodeID = menu.reference.attr('id').replace('_anchor', ''),
+                    node = $("#navigation-tree").jstree(true).get_node(nodeID),
+                    menuName = node.text;
+                  var deleteMsg = {/literal}"{ts escape='js'}Are you sure you want to delete this menu item:{/ts} " + '"'{literal} + menuName + {/literal}'"? {ts escape='js'}This action cannot be undone.{/ts}'{literal};
+                  if (node.children.length) {
+                    deleteMsg += {/literal}"<br /><br />" + ts('{ts escape='js' 1='<strong>%1</strong>'}%1 sub-menu items will also be deleted.{/ts}'{literal}, {1: node.children.length});
+                  }
+                  CRM.confirm({message: deleteMsg})
+                    .on('crmConfirm:yes', function() {
+                      CRM.api3('Navigation', 'delete', {id: nodeID}, true);
+                      $("#navigation-tree").jstree(true).delete_node(menu.reference.closest('li'));
+                      $("#reset-menu").show();
+                    });
+                }
+              }
+            };
+            callBack(items);
           }
         }
-
-      }).bind("rename.jstree", function ( e,node ) {
-        var nodeID  = node.rslt.obj.attr('id').replace("node_","");
-        var newName = node.rslt.new_name;
+      }).on("move_node.jstree", function (e, data) {
+        var nodeID = data.node.id;
+        var refID = data.parent === '#' ? '' : data.parent;
+        var ps = data.position;
         var postURL = {/literal}"{crmURL p='civicrm/ajax/menutree' h=0 q='key='}{crmKey name='civicrm/ajax/menutree'}"{literal};
-        $.get( postURL + '&type=rename&id=' + nodeID + '&data=' + newName,
-          function (data) {
-            $("#reset-menu").show( );
-          }
-        );
-
-      }).bind("remove.jstree", function( e,node ) {
-        var menuName  = node.rslt.obj.find('a').first( ).text( );
-        var nodeID  = node.rslt.obj.attr('id').replace("node_","");
+        CRM.status({}, $.get( postURL + '&type=move&id=' +  nodeID + '&ref_id=' + refID + '&ps='+ps));
+        $("#reset-menu").show();
+      });
 
-        // don't allow deleting of home
-        var homeMenuId = {/literal}"{$homeMenuId}"{literal};
-        if ( nodeID == homeMenuId ) {
-          var cannotDeleteMsg = {/literal}"{ts escape='js'}You cannot delete this menu item:{/ts}" + " "{literal} + menuName;
-          CRM.alert( cannotDeleteMsg, {/literal}"{ts escape='js'}Cannot Delete{/ts}"{literal} );
-          $("#navigation-tree").jstree('refresh');
-          return false;
-        }
-        var deleteMsg = {/literal}"{ts escape='js'}Are you sure you want to delete this menu item:{/ts}" + " "{literal} + menuName + {/literal}" ? {ts}This action cannot be undone.{/ts}"{literal};
-        var isDelete  = confirm( deleteMsg );
-        if ( isDelete ) {
-          var postURL = {/literal}"{crmURL p='civicrm/ajax/menutree' h=0 q='key='}{crmKey name='civicrm/ajax/menutree'}"{literal};
-          $.get( postURL + '&type=delete&id=' + nodeID,
-            function (data) {
-              $("#reset-menu").show( );
-            }
-          );
+      function editForm(menu) {
+        var nodeID = menu.reference.attr('id').replace('_anchor', ''),
+          action = menu.item.icon === 'crm-i fa-pencil' ? 'update' : 'add',
+          args = {reset: 1, action: action};
+        if (action === 'add') {
+          args.parent_id = nodeID;
         } else {
-          $("#navigation-tree").jstree('refresh');
+          args.id = nodeID;
         }
-
-      }).bind("move_node.jstree", function ( e,node ) {
-        node.rslt.o.each(function (i) {
-          var nodeID = node.rslt.o.attr('id').replace("node_","");
-          var refID  = node.rslt.np.attr('id').replace("node_","");
-          if (isNaN( refID ) ){ refID =''; }
-          var ps = node.rslt.cp+i;
-          var postURL = {/literal}"{crmURL p='civicrm/ajax/menutree' h=0 q='key='}{crmKey name='civicrm/ajax/menutree'}"{literal};
-          $.get( postURL + '&type=move&id=' +  nodeID + '&ref_id=' + refID + '&ps='+ps,
-            function (data) {
-              $("#reset-menu").show( );
-            });
+        CRM.loadForm(CRM.url('civicrm/admin/menu', args)).on('crmFormSuccess', function() {
+          $("#navigation-tree").jstree(true).refresh();
+          $("#reset-menu").show();
         });
-      });
+      }
+
       $('#new-menu-item a.button')
         .on('click', CRM.popup)
         .on('crmPopupFormSuccess', function() {
-          $("#navigation-tree").jstree('refresh');
+          $("#navigation-tree").jstree(true).refresh();
           $("#reset-menu").show();
         });
 
             CRM.api3('Navigation', 'reset', {'for': 'report'}, true)
               .done(function() {
                 $('#crm-container').unblock();
-                $("#navigation-tree").jstree('refresh');
+                $("#navigation-tree").jstree(true).refresh();
                 $("#reset-menu").show();
               })
           });
index 2a522df4c54402ec503cb08d90bc308dde6e3dac..7364745d338a3e2449a74e6dc66ad255854cab79 100644 (file)
             plugins: plugins,
             dnd: {
               copy: false
-            },
-            themes: {
-              stripes: true,
-              dots: false
             }
           });
       }