BootStrap-bable-treegrid
Bootstrap是目前很流行的一款前端框架,也有很多第三方基于Bootstrap开发出了很多跟实用性的一些功能,比如BootStrap-bable-treegrid树形表格。树形表格在开发中应该是很常用到的。
引入样式文件
<link rel="stylesheet" href="static/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="static/bootstrap-table/dist/bootstrap-table.css" />
<link rel="stylesheet" href="static/bootstrap-table/dist/extensions/treegrid/treegrid.css" />
<script src="static/bootstrap/dist/js/jquery.js" ></script>
<script src="static/bootstrap/dist/js/bootstrap.js" ></script>
<script src="static/bootstrap-table/dist/bootstrap-table.js" ></script>
<script src="static/bootstrap-table/dist/locale/bootstrap-table-zh-CN.js" ></script>
<script src="static/bootstrap-table/dist/extensions/treegrid/bootstrap-table-treegrid.js" ></script>
<script src="static/bootstrap-table/dist/extensions/treegrid/jquery.treegrid.js" ></script>
<script src="static/combotree/bootstrap-treeview.js" ></script>
<script src="static/combotree/combotree.js" ></script>
其中combotree.js是自己写的一个js文件,用于下拉树显示。下面附有代码。
页面部分代码(页面只需要一个table标签用于存放数据就可以了,可以自定义一些操作表格的按钮,比如增删改)。
<!-- 增删改 -->
<div id="toolbar" class="btn-group">
<button type="button" class="btn btn-default" onclick="btn_add()">
<span class="glyphicon glyphicon-plus"></span>新增
</button>
<button type="button" class="btn btn-default" onclick="btn_edit()">
<span class="glyphicon glyphicon-pencil"></span>修改
</button>
<button type="button" class="btn btn-default" onclick="btn_delete()">
<span class="glyphicon glyphicon-remove"></span>删除
</button>
</div>
<table id="menu_table"></table>
JS部分代码,获取数据,渲染表格,其中的字段名需要和实体类中的字段名一样。
var $table = $("#menu_table");
$("#menu_table").bootstrapTable({
url: 'menu/list', //表格数据请求地址
toolbar: '#toolbar', //自定义组件
striped: true, //隔行换色
height: tableHeight(), //设置高度
pagination: false, //显示表格的底部工具栏
sidePagination: 'server', //client客户端分页,server服务器分页
search: false, //定义右上方的搜索框,输入即可以开始搜索
showColumns: true, //选列的下拉菜单
showRefresh: true, //刷新按钮
showToggle: true, //视图切换
toolbarAlign: 'left', //自定义按钮位置
clickToSelect: true, //点击行选中
singleSelect: true, //单选
// 设置树状表格参数
treeShowField: 'name',
sortName:'id',
parentIdField: 'parentId', //父节点id
onLoadSuccess: function(data) {
//console.log(data);
$table.treegrid({
initialState: 'expanded',//展开
treeColumn: 1,//指明第几列数据改为树形
expanderExpandedClass: 'glyphicon glyphicon-triangle-bottom',
expanderCollapsedClass: 'glyphicon glyphicon-triangle-right',
onChange: function() {
$table.bootstrapTable('resetWidth');
}
});
},
columns:[{
checkbox: true //多选框
},{
field: 'id', //每列的字段名
title: 'ID', //表头所显示的名字
halign: 'center', //表头的对齐方式
valign: 'middle', //表格对齐方式居中
order: 'asc', //默认排序方式
sortable: true, //设置可以排序
align: 'center' //表格数据对齐方式
},{
field: 'name',
title: '名称',
halign: 'center',
align: 'center'
},{
field: 'url',
title: '路径',
halign: 'center',
align: 'center'
},{
field: 'idx',
title: '排序',
halign: 'center',
align: 'center'
}]
});
function tableHeight() {
return $(window).height() - 100;
}
后台响应请求数据方法。
@RequestMapping(value="/list")
@ResponseBody
public Object list(TablePageable pageParam,TbMenuForm form) {
Sort sort = pageParam.bulidSort(); //排序
Specification<TbMenu> spec = buildSpec(form); //配置查询参数
return baseService.findAll(spec,sort);
}
方法中TablePageable类是用于接收Bootstrap表格分页和排序参数的一个工具类,类中的bulidSort方法是指定排序字段和排序规则。
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
/**
* 分页的一个工具类,接收分页信息
*/
public class TablePageable{
private Integer limit; //分页
private Integer offset;//首记录号(从0开始)
private String sort; //排序字段
private String order; //顺序,逆序
public Integer getLimit() {
return limit;
}
public void setLimit(Integer limit) {
this.limit = limit;
}
public Integer getOffset() {
return offset;
}
public void setOffset(Integer offset) {
this.offset = offset;
}
public String getSort() {
return sort;
}
public void setSort(String sort) {
this.sort = sort;
}
public String getOrder() {
return order;
}
public void setOrder(String order) {
this.order = order;
}
public PageRequest bulidPageRequest() {
int page=(offset!=null&&limit!=null)?offset/limit:0;
int size=limit!=null?limit:10;
if(sort==null) {
return PageRequest.of(page, size);
}else {
Order order2=new Order(Direction.fromString(order), sort);
Sort sort2= Sort.by(order2);
return PageRequest.of(page,size,sort2 );
}
}
public PageRequest bulidPageable(Sort sort) {
int page=(offset!=null&&limit!=null)?offset/limit:0;
int size=limit!=null?limit:10;
return PageRequest.of(page, size, sort);
}
public Sort bulidSort() {
Order order2=new Order(Direction.fromString(order), sort);
Sort sort2= Sort.by(order2);
return sort2;
}
}
这里持久层查询是用的Spring-Data-Jpa,如果是其他持久层框架,只要查询后返回的JSON数据格式一致就可以了。
JSON数据格式如下,parentId为父节点Id,和前面js中设置的一定要对应一样,不然树形结构显示不出来。
注意:如果父节点为空,parentId的值需要为null,不能为""空串,一个parentId类型为Integer类型,为空串的话,树形结构显示不出来。
加载完成后数据显示的效果如下。
实体类代码。
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.mcy.springbootsecurity.custom.BaseEntity;
import org.springframework.data.annotation.CreatedBy;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
/**
* 菜单表
* @author
*
*/
@Entity
public class TbMenu {
private Integer id;
private String name;
private String url;
private Integer idx;
@JsonIgnore
private TbMenu parent;
@JsonIgnore
private List<TbMenu> children=new ArrayList<>();
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Column(unique=true)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Integer getIdx() {
return idx;
}
public void setIdx(Integer idx) {
this.idx = idx;
}
@ManyToOne
@CreatedBy
public TbMenu getParent() {
return parent;
}
public void setParent(TbMenu parent) {
this.parent = parent;
}
@OneToMany(cascade=CascadeType.ALL,mappedBy="parent")
@OrderBy(value="idx")
public List<TbMenu> getChildren() {
return children;
}
public void setChildren(List<TbMenu> children) {
this.children = children;
}
public TbMenu(Integer id) {
super(id);
}
public TbMenu(){
super();
}
public TbMenu(String name, String url, Integer idx, TbMenu parent, List<TbMenu> children) {
this.name = name;
this.url = url;
this.idx = idx;
this.parent = parent;
this.children = children;
}
public TbMenu(Integer integer, String name, String url, Integer idx, TbMenu parent, List<TbMenu> children) {
super(integer);
this.name = name;
this.url = url;
this.idx = idx;
this.parent = parent;
this.children = children;
}
@Transient
public Integer getParentId() {
return parent==null?null:parent.getId();
}
}
新增和修改方法,前台JS代码(因为新增和修改是共用的一个页面和一个方法,所以这里放一起写了)。
//新增
function btn_add(){
var selectRows = $table.bootstrapTable('getSelections');
var dialog = $('<div class="modal fade" id="myModal" tabindex="-1" aria-labelledby="myModalLabel"></div>');
if(selectRows&&selectRows.length==1){
var parentId=selectRows[0].id;
dialog.load('menu/edit',{parentId:parentId});
}else{
dialog.load('menu/edit');
}
$("body").append(dialog);
dialog.modal().on('hidden.bs.modal', function () {
dialog.remove();
$table.bootstrapTable('refresh');
});
}
//修改
function btn_edit(){
var str = $("#menu_table").bootstrapTable('getSelections');
if(str.length != 1){
bootbox.alert("请选中一行进行编辑");
return ;
}
var dialog = $('<div class="modal fade" id="myModal" tabindex="-1" aria-labelledby="myModalLabel"></div>');
var id = str[0].id;
dialog.load("menu/edit?id="+id);
/*添加到body中*/
$("body").append(dialog);
/*弹出模态框,绑定关闭后的事件*/
dialog.modal().on('hidden.bs.modal', function () {
//删除模态框
dialog.remove();
$table.bootstrapTable('refresh');
});
}
新增和修改的后台请求方法,跳转到新增修改页面,通过id来判断是修改还是新增,parentId判断新增时是否勾选行,勾选后为默认父级。
@Override
public void edit(TbMenuForm form, ModelMap map) throws InstantiationException, IllegalAccessException {
TbMenu model = new TbMenu();
if(form.getId() != null) {
model = menuService.findById(form.getId());
}
if(form.getParentId() != null) {
model.setParent(new TbMenu(form.getParentId()));
}
map.put("model", model);
}
新增修改页面代码
<body>
<form id="myForm" class="form-horizontal" role="form" >
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">操作</h4>
</div>
<div class="modal-body">
<!-- 以下为要修改的的内容 -->
<input type="hidden" name="id" th:value="${model.id}" id="modelId">
<div class="form-group">
<label for="txt_parent" class="col-sm-2 control-label">上级菜单</label>
<div class="col-sm-9">
<input name="parentId" data-th-value="${model.parent==null?null:model.parent.id}" class="form-control" id="txt_parent">
</div>
</div>
<div class="form-group">
<label for="txt_name" class="col-sm-2 control-label">菜单名称</label>
<div class="col-sm-9">
<input type="text" name="name" data-th-value="${model.name}" class="form-control" id="txt_name">
</div>
</div>
<div class="form-group">
<label for="txt_url" class="col-sm-2 control-label">访问地址</label>
<div class="col-sm-9">
<input type="text" name="url" data-th-value=${model.url} class="form-control" id="txt_url" placeholder="">
</div>
</div>
<div class="form-group">
<label for="txt_sort" class="col-sm-2 control-label">排序</label>
<div class="col-sm-9">
<input type="text" name="idx" data-th-value="${model.idx}" class="form-control" id="txt_sort">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" onclick="btnSubmit()" class="btn btn-primary"><span class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span>保存</button>
<button type="button" class="btn btn-default" data-dismiss="modal"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span>关闭</button>
</div>
</div>
</div>
</form>
</body>
JS部分代码。
<script th:inline="javascript">
$(function(){
$('#txt_parent').bootstrapCombotree({
url:'menu/treedata?id='+$('#modelId').val()
});
$('.selectpicker').selectpicker({});
});
function btnSubmit() {
var form= new FormData($("#myForm")[0]);
$.ajax({
url: 'menu/save',
type: 'post',
data: form,
processData: false, //不处理数据
contentType: false, //不设置内容类型
success: function(result){
if(result.success){
$("#myModal").modal("hide");
bootbox.alert("数据保存成功");
}else{
bootbox.alert(result.msg);
}
},
error: function(result){
bootbox.alert("数据保存失败!");
}
})
}
</script>
页面初始化后,加载Combotree下拉树的数据,并初始化,后台Combotree数据请求方法,如果是修改,就把当前修改项的id传递到后台,代码如下。
/***
* combotree树形加载
* @return
*/
@RequestMapping(value="/treedata")
@ResponseBody
public Object treedata(Integer id) {
Sort sort = Sort.by("idx");
Specification<TbMenu> spec = buildSpec1();
List<TbMenu> list = menuService.findAll(spec,sort);
return buildTree(list,id);
}
private Object buildTree(List<TbMenu> list, Integer id) {
List<HashMap<String, Object>> result=new ArrayList<>();
for (TbMenu dept : list) {
//判断如果是修改,修改节点在下拉树中不显示
if(dept.getId()!=id) {
//因为实体类中的字段名和需要返回的JSON字段名不同,这里需要转换一下
HashMap<String, Object> node=new HashMap<>();
node.put("id", dept.getId());
node.put("text",dept.getName());
node.put("pid", dept.getParentId());
node.put("nodes",buildTree(dept.getChildren(), id));
result.add(node);
}
}
return result;
}
//条件查询,先只显示最上级的菜单,即parent父级节点为空的数据
public Specification<TbMenu> buildSpec1() {
Specification<TbMenu> specification = new Specification<TbMenu>() {
private static final long serialVersionUID = 1L;
@Override
public Predicate toPredicate(Root<TbMenu> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
HashSet<Predicate> rules=new HashSet<>();
Predicate parent = cb.isNull(root.get("parent"));
rules.add(parent);
return cb.and(rules.toArray(new Predicate[rules.size()]));
}
};
return specification;
}
这里持久层查询是用的Spring-Data-Jpa,如果是其他持久层框架,只要查询后返回的JSON数据格式一致就可以了。
响应请求数据返回的JSON格式数据,pid为父节点id。
Combotree下拉树,在前面还引入了一个自己写的combotree.js文件,代码如下。
(function ($) {
function getTree(url){
var result=$.ajax({
type : 'post',
url : url ,
dataType : 'json' ,
async : false
}).responseText;
return eval('('+result+')');
}
$.fn.extend({
bootstrapCombotree: function(options, param){
var self=this;
this.onBodyDown=function(event){
var $options=undefined;
try{
$options=$.data($(event.target).prev()[0],'bootstrapCombotree');
}catch (e) {
$options=undefined;
}
if (!($options!=undefined || event.target.id == 'comboDiv' || $(event.target).parents("#comboDiv").length>0)) {
//if (!(event.target.id == 'comboDiv' || $(event.target).parents("#comboDiv").length>0)) {
self.hideMenu();
}
}
this.hideMenu=function(){
$('#comboDiv').fadeOut('fast');
$("#comboDiv").remove();
$("body").unbind("mousedown", self.onBodyDown);
}
this.getNode=function(root,id){
for(var i=0;i<root.length;i++){
var node=root[i];
if(node.id==id){
return node;
}
var x=self.getNode(node.nodes?node.nodes:[],id);
if(x){
return x;
}
}
return null;
},
this.create=function(target){
var $self=$(target);
var $option=$.data(target,'bootstrapCombotree').options;
$self.attr("type","hidden");
var $this=$('<input class="form-control" />');
$this.attr('placeholder',$self.attr('placeholder'));
$this.attr('readonly',true);
$self.after($this[0]);
//取出options
var options=$.extend({},$option,{
onNodeSelected:function(event,node){
$self.val(node.id);
$this.val(node.text);
$('#comboDiv').fadeOut('fast');
$("#comboDiv").remove();
$("body").unbind("mousedown", self.onBodyDown);
}
});
//在显示的框中写入默认值对应的text的内容
var $value=$self.val();
if($value){
var $node=self.getNode(options.data,$value);
if($node){
$this.val($node.text);
}
}
$this.focus(function(){
if($('#comboDiv').length>0){
return;
}
var $div=$('<div class="panel panel-default combotree" id="comboDiv"><div class="panel-body"><div id="bootstrapCombotree"></div></div></div>');
$div.appendTo($this.parent());
$('#bootstrapCombotree').treeview(options);
var thisObj=$(this);
var thisOffset=$(this).position();
var $dialogOffset=thisObj.closest('.modal-dialog').offset();
var $left=thisOffset.left;
var $top=thisOffset.top+thisObj.outerHeight();
$div.css({
left : $left+"px",
top: $top+"px",
zIndex: 1100
}).slideDown('fast');
$('body').bind("mousedown",self.onBodyDown);
});
};
if (typeof options == 'string'){
var method = $.fn.bootstrapCombotree.methods[options];
if (method){
return method(this, param);
}
return;
}
options = options || {};
return this.each(function(){
var state = $.data(this, 'bootstrapCombotree');
if (state){
$.extend(state.options, options);
} else {
$options=$.extend({}, $.fn.bootstrapCombotree.defaults, options);
$options.thisObj=this;
if($options.url){
$options.data=getTree($options.url);
}
state = $.data(this, 'bootstrapCombotree', {
options: $options,
});
}
self.create(this);
});
}
});
$.fn.bootstrapCombotree.methods={
options:function(jq){
var $option=$.data(jq[0],'bootstrapCombotree').options;
return $option;
}
}
$.fn.bootstrapCombotree.defaults = {
expandIcon: "glyphicon glyphicon-plus-sign",
collapseIcon: "glyphicon glyphicon-minus-sign",
emptyIcon:"glyphicon glyphicon-file",
showBorder:false,
showTags: true,
url:'combotree',
data:[]
};
})(jQuery)
保存JS中的btnSubmit()方法用于保存数据方法,后台保存响应方法。
@Override
public Object save(TbMenuForm form) {
try {
TbMenu model = new TbMenu();
Integer id = form.getId();
if(id != null) {
model = menuService.findById(id);
}
//父级菜单id
Integer parentId = form.getParentId();
if(parentId == null) {
model.setParent(null);
}else {
model.setParent(new TbMenu(parentId));
}
BeanUtils.copyProperties(form, model,"id", "parent");
menuService.save(model);
return new AjaxResult("数据保存成功!");
} catch (Exception e) {
return new AjaxResult(false,"数据保存失败");
}
}
方法中TbMenuForm接收类,中字段和实体类差不多。代码如下。
import com.mcy.springbootsecurity.custom.BaseForm;
import com.mcy.springbootsecurity.entity.TbMenu;
public class TbMenuForm {
private Integer id;
private String name;
private String url;
private Integer idx;
private TbMenu parent;
private Integer parentId;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Integer getIdx() {
return idx;
}
public void setIdx(Integer idx) {
this.idx = idx;
}
public TbMenu getParent() {
return parent;
}
public void setParent(TbMenu parent) {
this.parent = parent;
}
public Integer getParentId() {
return parentId;
}
public void setParentId(Integer parentId) {
this.parentId = parentId;
}
}
AjaxResult为方法执行完,返回的一个类,这个可以自己随便返回一个字符串就可以,用于前台判断是否保存成功,代码如下。
import org.springframework.data.domain.Page;
import java.util.HashMap;
/**
* 方法执行成功后,返回的工具类
*/
public class AjaxResult {
private Boolean success;
private String msg; //提示信息
public Boolean getSuccess() {
return success;
}
public void setSuccess(Boolean success) {
this.success = success;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public AjaxResult(String msg) {
super();
this.success=true;
this.msg = msg;
}
public AjaxResult(Boolean success, String msg) {
super();
this.success = success;
this.msg = msg;
}
@SuppressWarnings("rawtypes")
public static HashMap<String, Object> bulidPageResult(Page page) {
HashMap<String, Object> result=new HashMap<>();
result.put("total", page.getTotalElements());
result.put("rows", page.getContent());
return result;
}
}
新增和修改效果展示。
最后就剩下删除了,删除也是最简单的。
删除JS方法代码。
function btn_delete(){
var str = $("#menu_table").bootstrapTable('getSelections');
if(str.length != 1){
bootbox.alert("请选中一行进行删除");
}else{
bootbox.confirm("确定删除选中这一行吗?", function(s){
if(s){
var id = str[0].id;
$.post('menu/delete',{id:id},function(){
/* refresh刷新 */
$("#menu_table").bootstrapTable('refresh');
bootbox.alert('<h4>'+"删除成功!"+'</h4>');
});
}
});
}
}
后台响应请求代码。
@RequestMapping(value="/delete")
@ResponseBody
public Object delete(Integer id) {
try {
menuService.deleteById(id);
return new AjaxResult("数据删除成功");
} catch (Exception e) {
return new AjaxResult(false,"数据删除失败");
}
}
就这些了,如果对你有帮助,点赞关注一下呗^_^,留下你的足迹。
来源:CSDN
作者:一枕江风
链接:https://blog.csdn.net/qq_40205116/article/details/103740104