# 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樹結構

### 建立樹的外層
```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"】)
```