增加功能:菜单维护界面增加带层次功能 closed #IS7X4

#Issue
https://gitee.com/LongbowEnterprise/dashboard/issues?id=IS7X4
This commit is contained in:
Argo Zhang 2019-03-19 21:51:02 +08:00
parent b43752d240
commit 586901e2cb
8 changed files with 1025 additions and 225 deletions

View File

@ -1,218 +1,223 @@
@model NavigatorBarModel
@{
ViewBag.Title = "菜单管理";
Layout = "_Default";
}
@section css {
<environment include="Development">
<link href="~/lib/nestable2/jquery.nestable.css" rel="stylesheet" />
</environment>
<environment exclude="Development">
<link href="~/lib/nestable2/jquery.nestable.min.css" rel="stylesheet" />
</environment>
<link href="~/css/fa.css" rel="stylesheet" asp-append-version="true" />
}
@section javascript {
<environment include="Development">
<script src="~/lib/nestable2/jquery.nestable.js"></script>
<script src="~/lib/bootstrap-3-typeahead/bootstrap3-typeahead.js"></script>
</environment>
<environment exclude="Development">
<script src="~/lib/nestable2/jquery.nestable.min.js"></script>
<script src="~/lib/bootstrap-3-typeahead/bootstrap3-typeahead.min.js"></script>
</environment>
<script src="~/js/menus.js" asp-append-version="true"></script>
}
@section query {
<form class="form-inline">
<div class="row">
<div class="form-group col-sm-6 col-lg-auto">
<label class="control-label" for="txt_menus_name">菜单名称</label>
<input type="text" class="form-control" id="txt_menus_name" />
</div>
<div class="form-group col-sm-6 col-lg-auto">
<label class="control-label" for="txt_parent_menus_name">父级菜单</label>
<input type="text" class="form-control" id="txt_parent_menus_name" />
</div>
<div class="form-group form-group-dropdown col-sm-6 col-lg-auto">
<label class="control-label" for="sel_menus_category">菜单类别</label>
<div class="dropdown">
<button id="sel_menus_category" class="btn btn-success dropdown-select dropdown-toggle" data-toggle="dropdown">全部</button>
<div class="dropdown-menu">
<a href="#" data-val="">全部</a>
<div class="dropdown-divider"></div>
<a href="#" data-val="0">系统菜单</a>
<a href="#" data-val="1">外部菜单</a>
</div>
</div>
</div>
<div class="form-group form-group-dropdown col-sm-6 col-lg-auto">
<label class="control-label" for="sel_menus_res">菜单类型</label>
<div class="dropdown">
<button id="sel_menus_res" class="btn btn-success dropdown-select dropdown-toggle" data-toggle="dropdown">全部</button>
<div class="dropdown-menu">
<a href="#" data-val="">全部</a>
<div class="dropdown-divider"></div>
<a href="#" data-val="0">菜单</a>
<a href="#" data-val="1">资源</a>
</div>
</div>
</div>
<div class="form-group form-group-dropdown col-sm-6 col-lg-auto">
<label class="control-label" for="sel_app">所属应用</label>
<div class="dropdown">
<button id="sel_app" class="btn btn-info dropdown-select dropdown-toggle" data-toggle="dropdown" data-default-val="0">未设置</button>
<div class="dropdown-menu">
@foreach (var kv in Model.Applications)
{
<a href="#" data-val="@kv.Key">@kv.Value</a>
}
</div>
</div>
</div>
<div class="form-group col-lg-auto flex-lg-fill justify-content-lg-end">
<button type="button" id="btn_query" class="btn btn-primary btn-fill"><i class="fa fa-search" aria-hidden="true"></i><span>查询</span></button>
</div>
</div>
</form>
}
@section toolbar {
<button id="btn_assignRole" type="button" class="btn btn-info">
<span class="fa fa-sitemap" aria-hidden="true"></span><span>分配角色</span>
</button>
<div class="toolbar btn-group">
<button class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" type="button"><i class="fa fa-gear"></i></button>
<div class="dropdown-menu">
@await Html.PartialAsync("_ButtonBarGroup")
<a id="tb_assignRole" href="#" title="分配角色"><i class="fa fa-sitemap"></i></a>
</div>
</div>
}
@section modal {
<div class="modal-header">
<h5 class="modal-title" id="myModalLabel">菜单编辑窗口</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form class="form-inline" id="dataForm">
<div class="form-row">
<div class="form-group col-sm-6">
<input type="hidden" id="menuID" />
<label class="control-label" for="name">菜单名称</label>
<div class="input-group">
<input type="text" class="form-control" id="name" name="name" placeholder="不可为空50字以内" maxlength="50" data-valid="true" />
<div class="input-group-append">
<button data-method="clear" class="btn" type="button"><i class="fa fa-remove"></i></button>
<button data-method="sel" class="btn" type="button"><i class="fa fa-edit"></i>全选</button>
</div>
</div>
</div>
<div class="form-group col-sm-6">
<input type="text" class="form-control d-none" id="parentId" data-default-val="0" />
<label class="control-label" for="parentName">父级菜单</label>
<div class="input-group">
<input type="text" class="form-control" readonly id="parentName" name="parentName" placeholder="请选择...(可为空)50字以内" maxlength="50" />
<div class="input-group-append">
<button data-method="clear" class="btn" type="button"><i class="fa fa-remove"></i></button>
<button id="btnMenuParent" class="btn" type="button"><i class="fa fa-hand-o-up"></i>选择</button>
</div>
</div>
</div>
<div class="form-group col-sm-6">
<label class="control-label" for="order">菜单序号</label>
<div class="input-group">
<input type="text" class="form-control digits" id="order" name="order" data-default-val="10" placeholder="默认为10" maxlength="8" data-valid="true" />
<div class="input-group-append">
<button data-method="clear" class="btn" type="button"><i class="fa fa-remove"></i></button>
<button id="btnMenuOrder" class="btn" type="button"><i class="fa fa-list-ol"></i>调整</button>
</div>
</div>
</div>
<div class="form-group col-sm-6">
<label class="control-label" for="icon">菜单图标</label>
<div class="input-group">
<input type="text" class="form-control" readonly id="icon" data-default-val="fa fa-fa" placeholder="请选择...(可为空)50字以内" maxlength="50" />
<div class="input-group-append">
<button data-method="clear" class="btn" type="button"><i class="fa fa-remove"></i></button>
<button id="btnIcon" class="btn" type="button"><i class="fa fa-dashboard"></i>选择</button>
</div>
</div>
</div>
<div class="form-group col-12">
<label class="control-label" for="url">路径</label>
<input type="text" class="form-control flex-sm-fill" id="url" placeholder="不可为空4000字以内" maxlength="4000" data-valid="true" />
</div>
<div class="form-group form-group-dropdown col-sm-6">
<label class="control-label" for="category">菜单类别</label>
<div class="dropup">
<button id="category" class="btn btn-success dropdown-select dropdown-toggle" data-toggle="dropdown" data-default-val="1">外部菜单</button>
<div class="dropdown-menu">
<a href="#" data-val="0">系统菜单</a>
<a href="#" data-val="1">外部菜单</a>
</div>
</div>
</div>
<div class="form-group form-group-dropdown col-sm-6">
<label class="control-label" for="target">目标</label>
<div class="dropup">
<button id="target" class="btn btn-info dropdown-select dropdown-toggle" data-toggle="dropdown" data-default-val="_self">本窗口</button>
<div class="dropdown-menu">
<a href="#" data-val="_self">本窗口</a>
<a href="#" data-val="_blank">新窗口</a>
<a href="#" data-val="_parent">父级窗口</a>
<a href="#" data-val="_top">顶级窗口</a>
</div>
</div>
</div>
<div class="form-group form-group-dropdown col-sm-6">
<label class="control-label" for="isRes">菜单类型</label>
<div class="dropup">
<button id="isRes" class="btn btn-success dropdown-select dropdown-toggle" data-toggle="dropdown" data-default-val="0">菜单</button>
<div class="dropdown-menu">
<a href="#" data-val="0">菜单</a>
<a href="#" data-val="1">资源</a>
</div>
</div>
</div>
<div class="form-group form-group-dropdown col-sm-6">
<label class="control-label" for="app">所属应用</label>
<div class="dropup">
<button id="app" class="btn btn-info dropdown-select dropdown-toggle" data-toggle="dropdown" data-default-val="0">未设置</button>
<div class="dropdown-menu">
@foreach (var kv in Model.Applications)
{
<a href="#" data-val="@kv.Key">@kv.Value</a>
}
</div>
</div>
</div>
</div>
</form>
</div>
}
@section customModal {
@await Html.PartialAsync("RoleConfig")
<div id="dialogIcon" class="modal-content icon-content">
<div class="modal-header">
<h5 class="modal-title" id="myIconModalLabel">请选择图标</h5>
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
</div>
<div class="modal-body">
<div class="loadIconView"><i class="fa fa-spinner fa-pulse"></i><span>正在加载图标...请稍等</span></div>
</div>
<div class="modal-footer">
<span>被选择的图标</span><i class="fa fa-fa" id="pickIcon"></i>
<button type="button" class="btn btn-secondary" data-dismiss="modal">
<i class="fa fa-times"></i>
<span>关闭</span>
</button>
<button type="button" class="btn btn-primary" id="btnSubmitIcon">
<i class="fa fa-save"></i>
<span>确定</span>
</button>
</div>
</div>
@await Html.PartialAsync("NavigatorConfig")
@model NavigatorBarModel
@{
ViewBag.Title = "菜单管理";
Layout = "_Default";
}
@section css {
<environment include="Development">
<link href="~/lib/nestable2/jquery.nestable.css" rel="stylesheet" />
</environment>
<environment exclude="Development">
<link href="~/lib/nestable2/jquery.nestable.min.css" rel="stylesheet" />
</environment>
<link href="~/lib/treegrid/css/jquery.treegrid.css" rel="stylesheet" />
<link href="~/css/fa.css" rel="stylesheet" asp-append-version="true" />
}
@section javascript {
<environment include="Development">
<script src="~/lib/nestable2/jquery.nestable.js"></script>
<script src="~/lib/bootstrap-3-typeahead/bootstrap3-typeahead.js"></script>
<script src="~/lib/treegrid/js/jquery.treegrid.js"></script>
<script src="~/lib/bootstrap-table/extensions/treegrid/bootstrap-table-treegrid.js"></script>
</environment>
<environment exclude="Development">
<script src="~/lib/nestable2/jquery.nestable.min.js"></script>
<script src="~/lib/bootstrap-3-typeahead/bootstrap3-typeahead.min.js"></script>
<script src="~/lib/treegrid/js/jquery.treegrid.min.js"></script>
<script src="~/lib/bootstrap-table/extensions/treegrid/bootstrap-table-treegrid.min.js"></script>
</environment>
<script src="~/js/menus.js" asp-append-version="true"></script>
}
@section query {
<form class="form-inline">
<div class="row">
<div class="form-group col-sm-6 col-lg-auto">
<label class="control-label" for="txt_menus_name">菜单名称</label>
<input type="text" class="form-control" id="txt_menus_name" />
</div>
<div class="form-group col-sm-6 col-lg-auto">
<label class="control-label" for="txt_parent_menus_name">父级菜单</label>
<input type="text" class="form-control" id="txt_parent_menus_name" />
</div>
<div class="form-group form-group-dropdown col-sm-6 col-lg-auto">
<label class="control-label" for="sel_menus_category">菜单类别</label>
<div class="dropdown">
<button id="sel_menus_category" class="btn btn-success dropdown-select dropdown-toggle" data-toggle="dropdown">全部</button>
<div class="dropdown-menu">
<a href="#" data-val="">全部</a>
<div class="dropdown-divider"></div>
<a href="#" data-val="0">系统菜单</a>
<a href="#" data-val="1">外部菜单</a>
</div>
</div>
</div>
<div class="form-group form-group-dropdown col-sm-6 col-lg-auto">
<label class="control-label" for="sel_menus_res">菜单类型</label>
<div class="dropdown">
<button id="sel_menus_res" class="btn btn-success dropdown-select dropdown-toggle" data-toggle="dropdown">全部</button>
<div class="dropdown-menu">
<a href="#" data-val="">全部</a>
<div class="dropdown-divider"></div>
<a href="#" data-val="0">菜单</a>
<a href="#" data-val="1">资源</a>
</div>
</div>
</div>
<div class="form-group form-group-dropdown col-sm-6 col-lg-auto">
<label class="control-label" for="sel_app">所属应用</label>
<div class="dropdown">
<button id="sel_app" class="btn btn-info dropdown-select dropdown-toggle" data-toggle="dropdown" data-default-val="0">未设置</button>
<div class="dropdown-menu">
@foreach (var kv in Model.Applications)
{
<a href="#" data-val="@kv.Key">@kv.Value</a>
}
</div>
</div>
</div>
<div class="form-group col-lg-auto flex-lg-fill justify-content-lg-end">
<button type="button" id="btn_query" class="btn btn-primary btn-fill"><i class="fa fa-search" aria-hidden="true"></i><span>查询</span></button>
</div>
</div>
</form>
}
@section toolbar {
<button id="btn_assignRole" type="button" class="btn btn-info">
<span class="fa fa-sitemap" aria-hidden="true"></span><span>分配角色</span>
</button>
<div class="toolbar btn-group">
<button class="btn btn-secondary dropdown-toggle" data-toggle="dropdown" type="button"><i class="fa fa-gear"></i></button>
<div class="dropdown-menu">
@await Html.PartialAsync("_ButtonBarGroup")
<a id="tb_assignRole" href="#" title="分配角色"><i class="fa fa-sitemap"></i></a>
</div>
</div>
}
@section modal {
<div class="modal-header">
<h5 class="modal-title" id="myModalLabel">菜单编辑窗口</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form class="form-inline" id="dataForm">
<div class="form-row">
<div class="form-group col-sm-6">
<input type="hidden" id="menuID" />
<label class="control-label" for="name">菜单名称</label>
<div class="input-group">
<input type="text" class="form-control" id="name" name="name" placeholder="不可为空50字以内" maxlength="50" data-valid="true" />
<div class="input-group-append">
<button data-method="clear" class="btn" type="button"><i class="fa fa-remove"></i></button>
<button data-method="sel" class="btn" type="button"><i class="fa fa-edit"></i>全选</button>
</div>
</div>
</div>
<div class="form-group col-sm-6">
<input type="text" class="form-control d-none" id="parentId" data-default-val="0" />
<label class="control-label" for="parentName">父级菜单</label>
<div class="input-group">
<input type="text" class="form-control" readonly id="parentName" name="parentName" placeholder="请选择...(可为空)50字以内" maxlength="50" />
<div class="input-group-append">
<button data-method="clear" class="btn" type="button"><i class="fa fa-remove"></i></button>
<button id="btnMenuParent" class="btn" type="button"><i class="fa fa-hand-o-up"></i>选择</button>
</div>
</div>
</div>
<div class="form-group col-sm-6">
<label class="control-label" for="order">菜单序号</label>
<div class="input-group">
<input type="text" class="form-control digits" id="order" name="order" data-default-val="10" placeholder="默认为10" maxlength="8" data-valid="true" />
<div class="input-group-append">
<button data-method="clear" class="btn" type="button"><i class="fa fa-remove"></i></button>
<button id="btnMenuOrder" class="btn" type="button"><i class="fa fa-list-ol"></i>调整</button>
</div>
</div>
</div>
<div class="form-group col-sm-6">
<label class="control-label" for="icon">菜单图标</label>
<div class="input-group">
<input type="text" class="form-control" readonly id="icon" data-default-val="fa fa-fa" placeholder="请选择...(可为空)50字以内" maxlength="50" />
<div class="input-group-append">
<button data-method="clear" class="btn" type="button"><i class="fa fa-remove"></i></button>
<button id="btnIcon" class="btn" type="button"><i class="fa fa-dashboard"></i>选择</button>
</div>
</div>
</div>
<div class="form-group col-12">
<label class="control-label" for="url">路径</label>
<input type="text" class="form-control flex-sm-fill" id="url" placeholder="不可为空4000字以内" maxlength="4000" data-valid="true" />
</div>
<div class="form-group form-group-dropdown col-sm-6">
<label class="control-label" for="category">菜单类别</label>
<div class="dropup">
<button id="category" class="btn btn-success dropdown-select dropdown-toggle" data-toggle="dropdown" data-default-val="1">外部菜单</button>
<div class="dropdown-menu">
<a href="#" data-val="0">系统菜单</a>
<a href="#" data-val="1">外部菜单</a>
</div>
</div>
</div>
<div class="form-group form-group-dropdown col-sm-6">
<label class="control-label" for="target">目标</label>
<div class="dropup">
<button id="target" class="btn btn-info dropdown-select dropdown-toggle" data-toggle="dropdown" data-default-val="_self">本窗口</button>
<div class="dropdown-menu">
<a href="#" data-val="_self">本窗口</a>
<a href="#" data-val="_blank">新窗口</a>
<a href="#" data-val="_parent">父级窗口</a>
<a href="#" data-val="_top">顶级窗口</a>
</div>
</div>
</div>
<div class="form-group form-group-dropdown col-sm-6">
<label class="control-label" for="isRes">菜单类型</label>
<div class="dropup">
<button id="isRes" class="btn btn-success dropdown-select dropdown-toggle" data-toggle="dropdown" data-default-val="0">菜单</button>
<div class="dropdown-menu">
<a href="#" data-val="0">菜单</a>
<a href="#" data-val="1">资源</a>
</div>
</div>
</div>
<div class="form-group form-group-dropdown col-sm-6">
<label class="control-label" for="app">所属应用</label>
<div class="dropup">
<button id="app" class="btn btn-info dropdown-select dropdown-toggle" data-toggle="dropdown" data-default-val="0">未设置</button>
<div class="dropdown-menu">
@foreach (var kv in Model.Applications)
{
<a href="#" data-val="@kv.Key">@kv.Value</a>
}
</div>
</div>
</div>
</div>
</form>
</div>
}
@section customModal {
@await Html.PartialAsync("RoleConfig")
<div id="dialogIcon" class="modal-content icon-content">
<div class="modal-header">
<h5 class="modal-title" id="myIconModalLabel">请选择图标</h5>
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
</div>
<div class="modal-body">
<div class="loadIconView"><i class="fa fa-spinner fa-pulse"></i><span>正在加载图标...请稍等</span></div>
</div>
<div class="modal-footer">
<span>被选择的图标</span><i class="fa fa-fa" id="pickIcon"></i>
<button type="button" class="btn btn-secondary" data-dismiss="modal">
<i class="fa fa-times"></i>
<span>关闭</span>
</button>
<button type="button" class="btn btn-primary" id="btnSubmitIcon">
<i class="fa fa-save"></i>
<span>确定</span>
</button>
</div>
</div>
@await Html.PartialAsync("NavigatorConfig")
}

View File

@ -57,7 +57,19 @@
"bootstrap-table.js",
"bootstrap-table.min.css",
"extensions/export/bootstrap-table-export.js",
"extensions/export/bootstrap-table-export.min.js"
"extensions/export/bootstrap-table-export.min.js",
"extensions/treegrid/bootstrap-table-treegrid.js",
"extensions/treegrid/bootstrap-table-treegrid.min.js"
]
},
{
"provider": "cdnjs",
"library": "jquery-treegrid@0.2.0",
"destination": "wwwroot/lib/treegrid/",
"files": [
"css/jquery.treegrid.css",
"js/jquery.treegrid.js",
"js/jquery.treegrid.min.js"
]
},
{

View File

@ -1,4 +1,4 @@
$(function () {
$(function () {
var $dialog = $('#dialogNew');
var $pickIcon = $('#pickIcon');
var $dialogNew = $dialog;
@ -22,8 +22,8 @@
};
var state = [];
$('table').lgbTable({
var $table = $('table');
$table.lgbTable({
url: Menu.url,
dataBinder: {
map: {
@ -80,11 +80,19 @@
},
columns: [
{
title: "父级菜单", field: "ParentName", sortable: true, formatter: function (value, row, index) {
return (value === "0" || value === null) ? "" : value;
title: "菜单名称", field: "Name", sortable: true, formatter: function (value, row, index) {
return $.format('<span class="menu">{0}</span>', value);
},
events: {
'click .menu': function (e, value, row, index) {
var $plus = $(this).prev();
if ($plus.hasClass('fa')) {
$plus.trigger('click');
}
return false;
}
}
},
{ title: "菜单名称", field: "Name", sortable: true },
{ title: "菜单序号", field: "Order", sortable: true },
{
title: "菜单图标", field: "Icon", sortable: false, align: 'center', formatter: function (value, row, index) {
@ -128,7 +136,29 @@
return $('#app').next().find('[data-val="' + value + '"]:first').text();
}
}
]
],
idField: "Id",
//在哪一列展开树形
treeShowField: 'Name',
//指定父id列
parentIdField: 'ParentId',
onResetView: function (data) {
//console.log('load');
$table.treegrid({
treeColumn: 2,
expanderExpandedClass: 'fa fa-chevron-circle-down',
expanderCollapsedClass: 'fa fa-chevron-circle-down',
onChange: function () {
$table.bootstrapTable('resetWidth');
}
});
//只展开树形的第一级节点
$table.treegrid('getRootNodes').treegrid('expand');
},
onCheckRoot: function (row, data) {
return row[this.options.parentIdField] === '' || row[this.options.parentIdField] === '0';
}
}
});

View File

@ -0,0 +1,95 @@
/**
* @author: YL
* @version: v1.0.0
*/
!function ($) {
'use strict';
$.extend($.fn.bootstrapTable.defaults, {
treeShowField: null,
idField: 'id',
parentIdField: 'pid',
onGetNodes: function (row, data) {
var that = this;
var nodes = [];
$.each(data, function (i, item) {
if (row[that.options.idField] === item[that.options.parentIdField]) {
nodes.push(item);
}
});
return nodes;
},
onCheckRoot: function (row, data) {
var that = this;
return !row[that.options.parentIdField];
}
});
var BootstrapTable = $.fn.bootstrapTable.Constructor,
_initRow = BootstrapTable.prototype.initRow,
_initHeader = BootstrapTable.prototype.initHeader;
// td
BootstrapTable.prototype.initHeader = function () {
var that = this;
_initHeader.apply(that, Array.prototype.slice.apply(arguments));
var treeShowField = that.options.treeShowField;
if (treeShowField) {
$.each(this.header.fields, function (i, field) {
if (treeShowField === field) {
that.treeEnable = true;
return false;
}
});
}
};
var initTr = function (item, idx, data, parentDom) {
var that = this;
var nodes = that.options.onGetNodes.apply(that, [item, data]);
item._nodes = nodes;
parentDom.append(_initRow.apply(that, [item, idx, data, parentDom]));
// init sub node
var len = nodes.length - 1;
for (var i = 0; i <= len; i++) {
var node = nodes[i];
node._level = item._level + 1;
node._parent = item;
if (i === len)
node._last = 1;
// jquery.treegrid.js
that.options.rowStyle = function (item, idx) {
var id = item[that.options.idField] ? item[that.options.idField] : 0;
var pid = item[that.options.parentIdField] ? item[that.options.parentIdField] : 0;
return {
classes: 'treegrid-' + id + ' treegrid-parent-' + pid
};
};
initTr.apply(that, [node, $.inArray(node, data), data, parentDom]);
}
};
// tr
BootstrapTable.prototype.initRow = function (item, idx, data, parentDom) {
var that = this;
if (that.treeEnable) {
// init root node
if (that.options.onCheckRoot.apply(that, [item, data])) {
if (item._level === undefined) {
item._level = 0;
}
// jquery.treegrid.js
that.options.rowStyle = function (item, idx) {
var x = item[that.options.idField] ? item[that.options.idField] : 0;
return {
classes: 'treegrid-' + x
};
};
initTr.apply(that, [item, idx, data, parentDom]);
return true;
}
return false;
}
return _initRow.apply(that, Array.prototype.slice.apply(arguments));
};
}(jQuery);

View File

@ -0,0 +1,7 @@
/*
* bootstrap-table - v1.12.1 - 2018-03-12
* https://github.com/wenzhixin/bootstrap-table
* Copyright (c) 2018 zhixin wen
* Licensed MIT License
*/
!function(a){"use strict";a.extend(a.fn.bootstrapTable.defaults,{treeShowField:null,idField:"id",parentIdField:"pid",onGetNodes:function(b,c){var d=this,e=[];return a.each(c,function(a,c){b[d.options.idField]===c[d.options.parentIdField]&&e.push(c)}),e},onCheckRoot:function(a){var b=this;return!a[b.options.parentIdField]}});var b=a.fn.bootstrapTable.Constructor,c=b.prototype.initRow,d=b.prototype.initHeader;b.prototype.initHeader=function(){var b=this;d.apply(b,Array.prototype.slice.apply(arguments));var c=b.options.treeShowField;c&&a.each(this.header.fields,function(a,d){return c===d?(b.treeEnable=!0,!1):void 0})};var e=function(b,d,f,g){var h=this,i=h.options.onGetNodes.apply(h,[b,f]);b._nodes=i,g.append(c.apply(h,[b,d,f,g]));for(var j=i.length-1,k=0;j>=k;k++){var l=i[k];l._level=b._level+1,l._parent=b,k===j&&(l._last=1),h.options.rowStyle=function(a){var b=a[h.options.idField]?a[h.options.idField]:0,c=a[h.options.parentIdField]?a[h.options.parentIdField]:0;return{classes:"treegrid-"+b+" treegrid-parent-"+c}},e.apply(h,[l,a.inArray(l,f),f,g])}};b.prototype.initRow=function(a,b,d,f){var g=this;return g.treeEnable?g.options.onCheckRoot.apply(g,[a,d])?(void 0===a._level&&(a._level=0),g.options.rowStyle=function(a){var b=a[g.options.idField]?a[g.options.idField]:0;return{classes:"treegrid-"+b}},e.apply(g,[a,b,d,f]),!0):!1:c.apply(g,Array.prototype.slice.apply(arguments))}}(jQuery);

View File

@ -0,0 +1,31 @@
.treegrid-indent {
width:16px;
height: 16px;
display: inline-block;
position: relative;
}
.treegrid-expander {
width:16px;
height: 16px;
display: inline-block;
transition: all .3s ease;
transform: rotate(0);
margin-right: 2px;
}
.treegrid-expander.fa {
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
color: #17a2b8;
}
.treegrid-expander.fa + .menu {
cursor: pointer;
}
.treegrid-collapsed .treegrid-expander {
transform: rotate(-90deg);
}

View File

@ -0,0 +1,619 @@
/*
* jQuery treegrid Plugin 0.2.0
* https://github.com/maxazan/jquery-treegrid
*
* Copyright 2013, Pomazan Max
* Licensed under the MIT licenses.
*/
(function($) {
var methods = {
/**
* Initialize tree
*
* @param {Object} options
* @returns {Object[]}
*/
initTree: function(options) {
var settings = $.extend({}, this.treegrid.defaults, options);
return this.each(function() {
var $this = $(this);
$this.treegrid('setTreeContainer', $(this));
$this.treegrid('setSettings', settings);
settings.getRootNodes.apply(this, [$(this)]).treegrid('initNode', settings);
});
},
/**
* Initialize node
*
* @param {Object} settings
* @returns {Object[]}
*/
initNode: function(settings) {
return this.each(function() {
var $this = $(this);
$this.treegrid('setTreeContainer', settings.getTreeGridContainer.apply(this));
$this.treegrid('getChildNodes').treegrid('initNode', settings);
$this.treegrid('initExpander').treegrid('initIndent').treegrid('initEvents').treegrid('initState').treegrid("initSettingsEvents");
});
},
/**
* Initialize node events
*
* @returns {Node}
*/
initEvents: function() {
var $this = $(this);
//Save state on change
$this.on("change", function() {
var $this = $(this);
$this.treegrid('render');
if ($this.treegrid('getSetting', 'saveState')) {
$this.treegrid('saveState');
}
});
//Default behavior on collapse
$this.on("collapse", function() {
var $this = $(this);
$this.removeClass('treegrid-expanded');
$this.addClass('treegrid-collapsed');
});
//Default behavior on expand
$this.on("expand", function() {
var $this = $(this);
$this.removeClass('treegrid-collapsed');
$this.addClass('treegrid-expanded');
});
return $this;
},
/**
* Initialize events from settings
*
* @returns {Node}
*/
initSettingsEvents: function() {
var $this = $(this);
//Save state on change
$this.on("change", function() {
var $this = $(this);
if (typeof ($this.treegrid('getSetting', 'onChange')) === "function") {
$this.treegrid('getSetting', 'onChange').apply($this);
}
});
//Default behavior on collapse
$this.on("collapse", function() {
var $this = $(this);
if (typeof ($this.treegrid('getSetting', 'onCollapse')) === "function") {
$this.treegrid('getSetting', 'onCollapse').apply($this);
}
});
//Default behavior on expand
$this.on("expand", function() {
var $this = $(this);
if (typeof ($this.treegrid('getSetting', 'onExpand')) === "function") {
$this.treegrid('getSetting', 'onExpand').apply($this);
}
});
return $this;
},
/**
* Initialize expander for node
*
* @returns {Node}
*/
initExpander: function() {
var $this = $(this);
var cell = $this.find('td').get($this.treegrid('getSetting', 'treeColumn'));
var tpl = $this.treegrid('getSetting', 'expanderTemplate');
var expander = $this.treegrid('getSetting', 'getExpander').apply(this);
if (expander) {
expander.remove();
}
$(tpl).prependTo(cell).click(function() {
$($(this).closest('tr')).treegrid('toggle');
});
return $this;
},
/**
* Initialize indent for node
*
* @returns {Node}
*/
initIndent: function() {
var $this = $(this);
$this.find('.treegrid-indent').remove();
for (var i = 0; i < $(this).treegrid('getDepth'); i++) {
$($this.treegrid('getSetting', 'indentTemplate')).insertBefore($this.find('.treegrid-expander'));
}
return $this;
},
/**
* Initialise state of node
*
* @returns {Node}
*/
initState: function() {
var $this = $(this);
if ($this.treegrid('getSetting', 'saveState') && !$this.treegrid('isFirstInit')) {
$this.treegrid('restoreState');
} else {
if ($this.treegrid('getSetting', 'initialState') === "expanded") {
$this.treegrid('expand');
} else {
$this.treegrid('collapse');
}
}
return $this;
},
/**
* Return true if this tree was never been initialised
*
* @returns {Boolean}
*/
isFirstInit: function() {
var tree = $(this).treegrid('getTreeContainer');
if (tree.data('first_init') === undefined) {
tree.data('first_init', $.cookie(tree.treegrid('getSetting', 'saveStateName')) === undefined);
}
return tree.data('first_init');
},
/**
* Save state of current node
*
* @returns {Node}
*/
saveState: function() {
var $this = $(this);
if ($this.treegrid('getSetting', 'saveStateMethod') === 'cookie') {
var stateArrayString = $.cookie($this.treegrid('getSetting', 'saveStateName')) || '';
var stateArray = (stateArrayString === '' ? [] : stateArrayString.split(','));
var nodeId = $this.treegrid('getNodeId');
if ($this.treegrid('isExpanded')) {
if ($.inArray(nodeId, stateArray) === -1) {
stateArray.push(nodeId);
}
} else if ($this.treegrid('isCollapsed')) {
if ($.inArray(nodeId, stateArray) !== -1) {
stateArray.splice($.inArray(nodeId, stateArray), 1);
}
}
$.cookie($this.treegrid('getSetting', 'saveStateName'), stateArray.join(','));
}
return $this;
},
/**
* Restore state of current node.
*
* @returns {Node}
*/
restoreState: function() {
var $this = $(this);
if ($this.treegrid('getSetting', 'saveStateMethod') === 'cookie') {
var stateArray = $.cookie($this.treegrid('getSetting', 'saveStateName')).split(',');
if ($.inArray($this.treegrid('getNodeId'), stateArray) !== -1) {
$this.treegrid('expand');
} else {
$this.treegrid('collapse');
}
}
return $this;
},
/**
* Method return setting by name
*
* @param {type} name
* @returns {unresolved}
*/
getSetting: function(name) {
if (!$(this).treegrid('getTreeContainer')) {
return null;
}
return $(this).treegrid('getTreeContainer').data('settings')[name];
},
/**
* Add new settings
*
* @param {Object} settings
*/
setSettings: function(settings) {
$(this).treegrid('getTreeContainer').data('settings', settings);
},
/**
* Return tree container
*
* @returns {HtmlElement}
*/
getTreeContainer: function() {
return $(this).data('treegrid');
},
/**
* Set tree container
*
* @param {HtmlE;ement} container
*/
setTreeContainer: function(container) {
return $(this).data('treegrid', container);
},
/**
* Method return all root nodes of tree.
*
* Start init all child nodes from it.
*
* @returns {Array}
*/
getRootNodes: function() {
return $(this).treegrid('getSetting', 'getRootNodes').apply(this, [$(this).treegrid('getTreeContainer')]);
},
/**
* Method return all nodes of tree.
*
* @returns {Array}
*/
getAllNodes: function() {
return $(this).treegrid('getSetting', 'getAllNodes').apply(this, [$(this).treegrid('getTreeContainer')]);
},
/**
* Mthod return true if element is Node
*
* @returns {String}
*/
isNode: function() {
return $(this).treegrid('getNodeId') !== null;
},
/**
* Mthod return id of node
*
* @returns {String}
*/
getNodeId: function() {
if ($(this).treegrid('getSetting', 'getNodeId') === null) {
return null;
} else {
return $(this).treegrid('getSetting', 'getNodeId').apply(this);
}
},
/**
* Method return parent id of node or null if root node
*
* @returns {String}
*/
getParentNodeId: function() {
return $(this).treegrid('getSetting', 'getParentNodeId').apply(this);
},
/**
* Method return parent node or null if root node
*
* @returns {Object[]}
*/
getParentNode: function() {
if ($(this).treegrid('getParentNodeId') === null) {
return null;
} else {
return $(this).treegrid('getSetting', 'getNodeById').apply(this, [$(this).treegrid('getParentNodeId'), $(this).treegrid('getTreeContainer')]);
}
},
/**
* Method return array of child nodes or null if node is leaf
*
* @returns {Object[]}
*/
getChildNodes: function() {
return $(this).treegrid('getSetting', 'getChildNodes').apply(this, [$(this).treegrid('getNodeId'), $(this).treegrid('getTreeContainer')]);
},
/**
* Method return depth of tree.
*
* This method is needs for calculate indent
*
* @returns {Number}
*/
getDepth: function() {
if ($(this).treegrid('getParentNode') === null) {
return 0;
}
return $(this).treegrid('getParentNode').treegrid('getDepth') + 1;
},
/**
* Method return true if node is root
*
* @returns {Boolean}
*/
isRoot: function() {
return $(this).treegrid('getDepth') === 0;
},
/**
* Method return true if node has no child nodes
*
* @returns {Boolean}
*/
isLeaf: function() {
return $(this).treegrid('getChildNodes').length === 0;
},
/**
* Method return true if node last in branch
*
* @returns {Boolean}
*/
isLast: function() {
if ($(this).treegrid('isNode')) {
var parentNode = $(this).treegrid('getParentNode');
if (parentNode === null) {
if ($(this).treegrid('getNodeId') === $(this).treegrid('getRootNodes').last().treegrid('getNodeId')) {
return true;
}
} else {
if ($(this).treegrid('getNodeId') === parentNode.treegrid('getChildNodes').last().treegrid('getNodeId')) {
return true;
}
}
}
return false;
},
/**
* Method return true if node first in branch
*
* @returns {Boolean}
*/
isFirst: function() {
if ($(this).treegrid('isNode')) {
var parentNode = $(this).treegrid('getParentNode');
if (parentNode === null) {
if ($(this).treegrid('getNodeId') === $(this).treegrid('getRootNodes').first().treegrid('getNodeId')) {
return true;
}
} else {
if ($(this).treegrid('getNodeId') === parentNode.treegrid('getChildNodes').first().treegrid('getNodeId')) {
return true;
}
}
}
return false;
},
/**
* Return true if node expanded
*
* @returns {Boolean}
*/
isExpanded: function() {
return $(this).hasClass('treegrid-expanded');
},
/**
* Return true if node collapsed
*
* @returns {Boolean}
*/
isCollapsed: function() {
return $(this).hasClass('treegrid-collapsed');
},
/**
* Return true if at least one of parent node is collapsed
*
* @returns {Boolean}
*/
isOneOfParentsCollapsed: function() {
var $this = $(this);
if ($this.treegrid('isRoot')) {
return false;
} else {
if ($this.treegrid('getParentNode').treegrid('isCollapsed')) {
return true;
} else {
return $this.treegrid('getParentNode').treegrid('isOneOfParentsCollapsed');
}
}
},
/**
* Expand node
*
* @returns {Node}
*/
expand: function() {
return $(this).each(function() {
var $this = $(this);
if (!$this.treegrid('isLeaf') && !$this.treegrid("isExpanded")) {
$this.trigger("expand");
$this.trigger("change");
}
});
},
/**
* Expand all nodes
*
* @returns {Node}
*/
expandAll: function() {
var $this = $(this);
$this.treegrid('getRootNodes').treegrid('expandRecursive');
return $this;
},
/**
* Expand current node and all child nodes begin from current
*
* @returns {Node}
*/
expandRecursive: function() {
return $(this).each(function() {
var $this = $(this);
$this.treegrid('expand');
if (!$this.treegrid('isLeaf')) {
$this.treegrid('getChildNodes').treegrid('expandRecursive');
}
});
},
/**
* Collapse node
*
* @returns {Node}
*/
collapse: function() {
return $(this).each(function() {
var $this = $(this);
if (!$this.treegrid('isLeaf') && !$this.treegrid("isCollapsed")) {
$this.trigger("collapse");
$this.trigger("change");
}
});
},
/**
* Collapse all nodes
*
* @returns {Node}
*/
collapseAll: function() {
var $this = $(this);
$this.treegrid('getRootNodes').treegrid('collapseRecursive');
return $this;
},
/**
* Collapse current node and all child nodes begin from current
*
* @returns {Node}
*/
collapseRecursive: function() {
return $(this).each(function() {
var $this = $(this);
$this.treegrid('collapse');
if (!$this.treegrid('isLeaf')) {
$this.treegrid('getChildNodes').treegrid('collapseRecursive');
}
});
},
/**
* Expand if collapsed, Collapse if expanded
*
* @returns {Node}
*/
toggle: function() {
var $this = $(this);
if ($this.treegrid('isExpanded')) {
$this.treegrid('collapse');
} else {
$this.treegrid('expand');
}
return $this;
},
/**
* Rendering node
*
* @returns {Node}
*/
render: function() {
return $(this).each(function() {
var $this = $(this);
if ($this.treegrid('isOneOfParentsCollapsed')) {
$this.hide();
} else {
$this.show();
}
if (!$this.treegrid('isLeaf')) {
$this.treegrid('renderExpander');
$this.treegrid('getChildNodes').treegrid('render');
}
});
},
/**
* Rendering expander depends on node state
*
* @returns {Node}
*/
renderExpander: function() {
return $(this).each(function() {
var $this = $(this);
var expander = $this.treegrid('getSetting', 'getExpander').apply(this);
if (expander) {
if (!$this.treegrid('isCollapsed')) {
expander.removeClass($this.treegrid('getSetting', 'expanderCollapsedClass'));
expander.addClass($this.treegrid('getSetting', 'expanderExpandedClass'));
} else {
expander.removeClass($this.treegrid('getSetting', 'expanderExpandedClass'));
expander.addClass($this.treegrid('getSetting', 'expanderCollapsedClass'));
}
} else {
$this.treegrid('initExpander');
$this.treegrid('renderExpander');
}
});
}
};
$.fn.treegrid = function(method) {
if (methods[method]) {
return methods[ method ].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || !method) {
return methods.initTree.apply(this, arguments);
} else {
$.error('Method with name ' + method + ' does not exists for jQuery.treegrid');
}
};
/**
* Plugin's default options
*/
$.fn.treegrid.defaults = {
initialState: 'expanded',
saveState: false,
saveStateMethod: 'cookie',
saveStateName: 'tree-grid-state',
expanderTemplate: '<span class="treegrid-expander"></span>',
indentTemplate: '<span class="treegrid-indent"></span>',
expanderExpandedClass: 'treegrid-expander-expanded',
expanderCollapsedClass: 'treegrid-expander-collapsed',
treeColumn: 0,
getExpander: function() {
return $(this).find('.treegrid-expander');
},
getNodeId: function() {
var template = /treegrid-([A-Za-z0-9_-]+)/;
if (template.test($(this).attr('class'))) {
return template.exec($(this).attr('class'))[1];
}
return null;
},
getParentNodeId: function() {
var template = /treegrid-parent-([A-Za-z0-9_-]+)/;
if (template.test($(this).attr('class'))) {
return template.exec($(this).attr('class'))[1];
}
return null;
},
getNodeById: function(id, treegridContainer) {
var templateClass = "treegrid-" + id;
return treegridContainer.find('tr.' + templateClass);
},
getChildNodes: function(id, treegridContainer) {
var templateClass = "treegrid-parent-" + id;
return treegridContainer.find('tr.' + templateClass);
},
getTreeGridContainer: function() {
return $(this).closest('table');
},
getRootNodes: function(treegridContainer) {
var result = $.grep(treegridContainer.find('tr'), function(element) {
var classNames = $(element).attr('class');
var templateClass = /treegrid-([A-Za-z0-9_-]+)/;
var templateParentClass = /treegrid-parent-([A-Za-z0-9_-]+)/;
return templateClass.test(classNames) && !templateParentClass.test(classNames);
});
return $(result);
},
getAllNodes: function(treegridContainer) {
var result = $.grep(treegridContainer.find('tr'), function(element) {
var classNames = $(element).attr('class');
var templateClass = /treegrid-([A-Za-z0-9_-]+)/;
return templateClass.test(classNames);
});
return $(result);
},
//Events
onCollapse: null,
onExpand: null,
onChange: null
};
})(jQuery);

File diff suppressed because one or more lines are too long