# angular-ui-tree套件 **下載:** https://github.com/angular-ui-tree/angular-ui-tree ## 前端處理 ### 引用css/js ```htmlmixed= <link rel="stylesheet" href="你css放的位置"> 【_PublicHeader.cshtml】 <script type="text/javascript" src="你js放的位置"></script> 【BundleConfig.cs】 ``` ### 引用模塊ui-tree ```javascript= var myApp = angular.module('myApp', ['ngGrid', 'ksijs', 'ui.tree']); ``` ### ui樹結構 ![](https://i.imgur.com/ZP4CVFv.png) ### 建立樹的外層 ```htmlmixed= @*自立樹的第一層*@ <div data-ui-tree="treeOptions" id="tree-root" data-drag-enabled="enabled"> @* data-drag-enabled:false=不允許拖拉 *@ <ol ui-tree-nodes="" ng-model="arr" data-nodrop-enabled> <li ng-repeat="item in arr" ui-tree-node ng-include="'nodes_renderer.html'" data-expand-on-hover="true"></li> </ol> </div> ``` ```javascript= $scope.treeOptions = { beforeDrop: function (e) { $scope.sourceValue = e.source.nodeScope.$modelValue.ID; //移動的部門節點ID $scope.destValue = e.dest.nodesScope.$parent.$modelValue.ID; //移動到新位置的上一層部門 $http({ //取得更新菜單樹SQL指令 method: 'POST', url: '/api/apiESIGN0110F/GetUpdateSQL?', data: { ID: $scope.sourceValue, UID: $scope.destValue, } }).then(function (response) { $scope.MenuSQL = $scope.MenuSQL + response.data.UpdateSQL; }) } }; ``` 移動節點後必寫方法(避免陣列增生許多重複項次) ```javascript= $scope.moveLastToTheBeginning = function () { var a = $scope.data.pop(); $scope.data.splice(0, 0, a); }; ``` * **data-ui-tree**設定之名稱可對應於同名function中,以便獲取模組function之參數(**beforeDrop**為angular-ui-tree之function) * 開啟拖曳節點必寫**moveLastToTheBeginning**方法(避免垃圾節點增生,造成菜單樹結構錯誤) * **ui-tree-nodes**的ng-model要為該層節點所有集合(ui-tree-nodes>對應很多的ui-tree-node) * **ng-include="'nodes_renderer.html'"** 可以將樹節點循環js放入元素內(它會放在該元素的內容中) * li使用ng-repeat,使得樹節點循環js之**ui-tree-handle**可以正確塞值 * **data-drag-enabled**為true時展開各節點,可利用變數去調節節點展開與收縮 * 設立 **data-expand-on-hover="true"** 防止因拖動時節點未展開造成節點錯放 * **data-nodrop-enabled**屬性可防止元素被放入該節點(通常放在第一層根節點上) ### 建立樹節點循環 ```htmlmixed= <script type="text/ng-template" id="nodes_renderer.html"> @*為了樹節點無限層級顯示 使用重複item變數去撈*@ <div ui-tree-handle class="tree-node tree-node-content" ng-click="btnDEPT(item)"> <a style="margin-right: 0px;" class=" btn btn-primary btn-xs" data-nodrag ng-click="newSubItem(this)" data-toggle="modal" data-target="#DEPTModal" ng-show="statusEdit == 1"> <span class="glyphicon glyphicon-plus"></span> </a> <a class=" btn btn-danger btn-xs" data-nodrag ng-click="remove1(this)" ng-show="statusEdit == 1"> <span class="glyphicon glyphicon-remove"></span> </a> <a class="btn btn-success btn-xs" ng-if="item.CHILD && item.CHILD.length > 0" data-nodrag ng-click="toggle(this)"> <span class="glyphicon" ng-class="{ 'glyphicon-chevron-right': collapsed, 'glyphicon-chevron-down': !collapsed }"> </span> </a> {{item.DEPTRANK_NAME}} </div> @*與第一層相同結構之html*@ <ol ui-tree-nodes="" ng-model="item.CHILD" data-nodrop-enabled ng-class="{hidden: collapsed}"> <li ng-repeat="item in item.CHILD" ui-tree-node ng-include="'nodes_renderer.html'" data-collapsed="true"> </li> </ol> </script> ``` * **ui-tree-handle**為各節點,利用 **{{item.DEPTRANK_NAME}}** 給值呈現,內部<a></a>分別為新增、刪除、展開按鈕 * 再塞入與自立第一層樹節點同樣架構的html,此時ng-model改為子節點,繼續循環下層節點 * **data-nodrag**屬性為關閉該節點拖動   ### 設定CSS ```htmlmixed= <style> .angular-ui-tree-handle { cursor: pointer; /**這我自寫的 為了蓋掉模塊之move**/ background: #f8faff; border: 1px solid #dae2ea; color: #7c9eb2; padding: 10px 10px; white-space: nowrap; width: auto; float: initial; } .angular-ui-tree-handle:hover { color: #438eb9; background: #f4f6f7; border-color: #dce2e8; } //以上必設 //以下依照需求複寫 a.btn.btn-primary.btn-xs { box-shadow: none; } </style> ``` ## 資料處理(Js&Controller) ### Controller #### 取得菜單資料庫之json資料 ```csharp= [System.Web.Http.HttpPost] public IHttpActionResult GetsubArray() { DBController dbc = new DBController("FlowDB"); Hashtable ht = new Hashtable(); //得到一個DataTable物件 DataTable data = new DataTable(); data = dbc.FillDataTable("select M.orgchart_dept as id, M.orgchart_udept as parent, N.deptrank_name, M.* from FlowDB.dbo.esignorgchart M left join FlowDB.dbo.esigndeptrank N on M.orgchart_dept = N.deptrank_code", ht); //將DataTable轉成JSON字串 string data_json = JsonConvert.SerializeObject(data, Formatting.Indented); Dictionary<string, object> result = new Dictionary<string, object>(); result.Add("data_json", data_json); return new OkNegotiatedContentResult<Dictionary<string, object>>(result, this); } ``` ### Js #### 資料處理:**單層json** > **單層陣列** > **父子陣列** ```javascript= $http({ //取得菜單樹 method: 'POST', url: '/api/apiESIGN0110F/GetsubArray?', data: { } }).then(function (response) { $scope.data_json = response.data.data_json; //取得部門菜單之json資料 $scope.arr = JSON.parse($scope.data_json)//複製一份,並轉為Array陣列 $scope.arr.forEach(obj => { //跑雙重迴圈將同級Array處理成有父子階層的Array !Array.isArray(obj.CHILD) && (obj.CHILD = []) //if判斷是否為陣列,為否就增加CHILD欄位 $scope.arr.forEach(o => { if (o.PARENT === obj.ID) { obj.CHILD.push(o) //將對應的資料放入子層 } }) }) $scope.arr = $scope.arr.filter(obj => obj.ID === '000') //將原來的單層Array(arr)轉為父子Array(obj)【此時的ID要為根節點】 }) ``` #### 新增節點(一定要寫必寫處) ```javascript= $scope.newSubItem = function (scope) { //部門菜單新增子節點事件 var nodeData = scope.$modelValue; //取得該節點資訊 if (nodeData == undefined) { //代表觸發modal $http({ //新增資料庫的子節點 method: 'POST', url: '/api/apiESIGN0110F/InsertArray?', data: { ID: $scope.editRow.DPD_NO, DEPTRANK_NAME: $scope.editRow.DPD_NM, ORGCHART_DEPT: $scope.editRow.DPD_NO, ORGCHART_LEVEL: padLeft((parseInt($scope.newSubItem.buffer.ORGCHART_LEVEL) + 1), 2), ORGCHART_RANK: $scope.newSubItem.buffer.ORGCHART_RANK, ORGCHART_TYPE: $scope.newSubItem.buffer.ORGCHART_TYPE, ORGCHART_UDEPT: $scope.newSubItem.buffer.ID, ORGCHART_VERSION: $scope.newSubItem.buffer.ORGCHART_VERSION, ORGCHART_YEAR: $scope.newSubItem.buffer.ORGCHART_YEAR, } }).then(function (response) { if (response.data.msg == undefined) { //資料庫新增成功 //-------從這裡開始必寫----------------------------// $scope.newSubItem.buffer.CHILD.push({ //新增菜單陣列中的子節點 ID: $scope.editRow.DPD_NO, DEPTRANK_NAME: $scope.editRow.DPD_NM, CHILD: [], ORGCHART_DEPT: $scope.editRow.DPD_NO, ORGCHART_LEVEL: padLeft((parseInt($scope.newSubItem.buffer.ORGCHART_LEVEL) + 1), 2), ORGCHART_RANK: $scope.newSubItem.buffer.ORGCHART_RANK, ORGCHART_TYPE: $scope.newSubItem.buffer.ORGCHART_TYPE, ORGCHART_UDEPT: $scope.newSubItem.buffer.ID, ORGCHART_VERSION: $scope.newSubItem.buffer.ORGCHART_VERSION, ORGCHART_YEAR: $scope.newSubItem.buffer.ORGCHART_YEAR, }); //-------必寫結束---------------------------------// } else { //資料庫新增失敗 swal(response.data.msg, "", "error"); } $scope.editRow.DPD_NO = undefined; $scope.editRow.DPD_NM = undefined; $('#DEPTModal').modal('hide'); }) } else{ //代表為"+"號按鈕觸發,先行暫存資料 $scope.newSubItem.buffer = nodeData; if (scope.collapsed) { //true為關閉狀態 scope.toggle(); } } }; ``` #### 刪除節點 可不自寫 內建方法是remove() 此筆記我用remove1()自寫,為了配合資料庫增刪 ```javascript= $scope.remove1 = function (scope) { //部門菜單刪除子節點事件 swal({ title: "確定刪除?", type: "warning", showCancelButton: true, confirmButtonColor: "#DD6B55", confirmButtonText: "確定", cancelButtonText: "取消", }) .then(function () { $http({ //刪除資料庫的子節點 method: 'POST', url: '/api/apiESIGN0110F/DeleteArray?', data: { ID: scope.$modelValue.ID, } }).then(function (response) { if (response.data.msg == undefined) { //資料庫刪除成功 return scope.$parentNodesScope.removeNode(scope); //刪除菜單陣列中的子節點 } else { //資料庫刪除失敗 swal(response.data.msg, "", "error"); } }) }).catch(swal.noop) //防止swal在console跳【Uncaught (in promise) cancel】錯誤(若sweetalert2有useRejections參數則替代) }; ``` ### 其他 #### 關閉子節點 ```javascript= $scope.$broadcast('angular-ui-tree:collapse-all'); //預設關閉所有子節點(前提節點要有屬性【data-collapsed="true"】) ```