Tree Grid
Tree grid is achieved by setting the GridModelBuilder.GetChildren
,
this function should return an IQueryable<T>
when the item is a node, or an Awe.Lazy
when it is a lazy node.
For lazy loading GridModelBuilder.GetItem
also needs to be set, it will be executed in the lazy request, GetItem is also used by the api.updateItem.
In the constructor of the GridModelBuilder
collection of only the root items are passed.
Tree grid, basic features
simple tree grid, without lazy loadingTreeGrid/Index.cshtml
@(Html.Awe().Grid("TreeGrid1")
.Url(Url.Action("SimpleTree", "TreeGrid"))
.Columns(
new Column { Bind = "Name" },
new Column { Bind = "Id", Width = 100 })
.Groupable(false)
.PageSize(3)
.Height(400))
Demos/Grid/TreeGridController.cs
public IActionResult SimpleTree(GridParams g)
{
var rootNodes = Db.TreeNodes.Where(o => o.Parent == null).AsQueryable();
var builder = new GridModelBuilder<TreeNode>(rootNodes, g)
{
KeyProp = o => o.Id,
GetChildrenAsync = async (node, nodeLevel) =>
Db.TreeNodes.Where(n => n.Parent == node).ToArray(),
Map = o => new { o.Name, o.Id }
};
return Json(builder.Build());
}
Lazy loading Nodes, Tree Grid
TreeGrid/Index.cshtml
@(Html.Awe().Grid("LazyTreeGrid")
.Url(Url.Action("LazyTree", "TreeGrid"))
.Columns(
new Column { Bind = "Name" },
new Column { Bind = "Id", Width = 100 })
.Persistence(Persistence.Session)
.PageSize(3)
.Height(400))
Demos/Grid/TreeGridController.cs
public IActionResult LazyTree(GridParams g)
{
var rootNodes = Db.TreeNodes.Where(o => o.Parent == null).AsQueryable();
var builder = new GridModelBuilder<TreeNode>(rootNodes, g)
{
KeyProp = o => o.Id,
GetChildrenAsync = async (node, nodeLevel) =>
{
var children = Db.TreeNodes.Where(o => o.Parent == node).ToArray();
// set this node as lazy when it's above level 1 (relative), it has children, and this is not a lazy request already
if (nodeLevel > 1 && children.Any() && g.Key == null) return Awe.LazyNode;
return children;
},
GetItem = () => // used for lazy loading
{
var id = Convert.ToInt32(g.Key);
return Db.TreeNodes.First(o => o.Id == id);
},
Map = MapNode
};
return Json(builder.Build());
}
Tree Grid with CRUD operations
TreeGrid/Index.cshtml
@{
var gridId = "CrudTreeGrid";
}
@(Html.InitCrudPopupsForGrid(gridId, "TreeGrid", 200))
<div class="bar">
<button type="button" class="awe-btn" style="float: left;" onclick="awe.open('create@(gridId)')">add root</button>
<div style="text-align: right;">
@Html.Awe().TextBox("txtTreeName").Placeholder("search...").CssClass("searchtxt")
</div>
</div>
@(Html.Awe().Grid(gridId)
.Url(Url.Action("CrudTree", "TreeGrid"))
.Columns(
new Column { Bind = "Name" },
new Column { Bind = "Id", Width = 100 },
new Column { ClientFormat = GridUtils.AddChildFormat(gridId), Width = 120 },
new Column { ClientFormat = GridUtils.EditFormatForGrid(gridId), Width = GridUtils.EditBtnWidth },
new Column { ClientFormat = GridUtils.DeleteFormatForGrid(gridId), Width = GridUtils.DeleteBtnWidth })
.Resizable()
.PageSize(3)
.Parent("txtTreeName", "name")
.Height(400))
Demos/Grid/TreeGridController.cs
public IActionResult CrudTree(GridParams g, string name = "")
{
var all = Db.TreeNodes.ToList();
var sresult = all.Where(o => o.Name.Contains(name)).ToList();
var result = sresult.ToList();
foreach (var treeNode in sresult)
{
AddParentsTo(result, treeNode);
}
var roots = result.Where(o => o.Parent == null).AsQueryable();
var gmb = new GridModelBuilder<TreeNode>(roots, g)
{
KeyProp = o => o.Id,
Map = MapNode
};
gmb.GetItemAsync = async () =>
{
var key = Convert.ToInt32(gmb.GridParams.Key);
return Db.Find<TreeNode>(key);
};
gmb.GetChildren = (node, nodeLevel) =>
{
var children = result.Where(o => o.Parent == node);
return gmb.OrderBy(children.AsQueryable());
};
return Json(gmb.Build());
}
private object MapNode(TreeNode node)
{
return new { node.Name, node.Id };
}
private void AddParentsTo(ICollection<TreeNode> result, TreeNode node)
{
if (node.Parent != null)
{
if (!result.Contains(node.Parent))
{
result.Add(node.Parent);
AddParentsTo(result, node.Parent);
}
}
}
public IActionResult Create(int? parentId)
{
return PartialView(new TreeNodeInput { ParentId = parentId });
}
[HttpPost]
public IActionResult Create(TreeNodeInput input)
{
if (!ModelState.IsValid) return PartialView(input);
var parent = input.ParentId.HasValue ? Db.Find<TreeNode>(input.ParentId) : null;
var node = new TreeNode { Name = input.Name, Parent = parent };
Db.Add(node);
var result = new
{
node.Id // used to flash the new row if visibile
};
return Json(result);
}
public IActionResult Edit(int id)
{
var node = Db.Find<TreeNode>(id);
return PartialView("Create", new TreeNodeInput { Id = node.Id, Name = node.Name });
}
[HttpPost]
public IActionResult Edit(TreeNodeInput input)
{
if (!ModelState.IsValid) return PartialView("Create", input);
var node = Db.Find<TreeNode>(input.Id);
node.Name = input.Name;
return Json(new { node.Id });
}
public IActionResult Delete(int id)
{
var node = Db.Find<TreeNode>(id);
return PartialView(new DeleteConfirmInput
{
Id = id,
Type = "node",
Name = node.Name
});
}
[HttpPost]
public IActionResult Delete(DeleteConfirmInput input)
{
var node = Db.Find<TreeNode>(input.Id);
DeleteNodeTree(node);
var result = new
{
node.Id
};
return Json(result);
}
private void DeleteNodeTree(TreeNode node)
{
var children = Db.TreeNodes.Where(o => o.Parent == node).ToList();
foreach (var child in children)
{
DeleteNodeTree(child);
}
Db.Remove(Db.Find<TreeNode>(node.Id));
}
Use as TreeView
The menu on the left is a TreeGrid with hidden footer and header.Shared/Menu.cshtml
<div id="sideCont">
<aside>
<div id="menuPlaceh"></div>
<div id="demoMenu">
@(Html.Awe().TextBox(new TextBoxOpt{
Name = "msearch",
Placeholder = "search...",
CssClass = "o-src msearch",
EditorAttr = new { data_brf = "true" },
ClearBtn = true
}))
<nav role="navigation">
@(Html.Awe().Grid("Menu")
.Columns(new Column { Id = "col1" })
.DataFunc("site.menuDataFunc('" + @Url.Action("GetMenuNodes", "Data", new { area = string.Empty }) + "')")
.Mod(o => o.CustomRender("sideMenu.menutree").Loading(false))
.CssClass("scrlh")
.RowClassClientFormat(".(Selected)")
.Parent("msearch", "search", false)
.ShowFooter(false)
.ShowHeader(false)
.ShowGroupBar(false)
.Load(false)
.ColumnWidth(100))
</nav>
</div>
</aside>
</div>
Tree grid with custom rendering
Using custom render mod to override api methods and render the grid using divs with padding instead of the default table.
Shared/Demos/TreeGridCustomRender.cshtml
@(Html.Awe().Grid("TreeGridCr")
.Url(Url.Action("SimpleTree", "TreeGrid"))
.Mod(o => o.CustomRender("crtree"))
.Columns(
new Column { Bind = "Name" },
new Column { Bind = "Id", Width = 100 }
)
.Groupable(false)
.PageSize(3)
.Height(400))
<script>
function crtree(o) {
var api = o.api;
// node content wrap
api.ncon = function (p) {
// don't wrap at level 0 and nodetype = items
if (!p.lvl || p.gv.nt == 2) return p.ren();
return '<div style="padding-left:' + p.lvl + 'em;" >' + p.ren() + '</div>';
};
// node
api.nodef = function (p) {
var attr = p.h ? 'style="display:none;"' : '';
if (p.lvl == 1) {
var res = '<div class="tgroot" ' + attr + '>' + p.ren() + '</div>';
return res;
}
return p.ren();
};
// group header content
api.ghead = function (g, lvl) {
return api.ceb() + g.c;
};
// render row
api.itmf = function (opt) {
function val(col) {
return aweUtils.gcv(api, col, opt);
}
var content = '';
if (opt.con) {
content = opt.con;
} else {
if (opt.ceb) content += api.ceb();
content += val(aweUtils.columnByBind(o, 'Name'));
}
if (opt.ceb) {
opt.clss += ' tgitem awe-ceb';
} else {
opt.clss += ' tgitem';
}
var attr = opt.attr;
attr += ' class="' + opt.clss + '"';
opt.style && (attr += ' style="' + opt.style + '"');
return '<div ' + attr + '>' + content + '</div>';
};
}
</script>
See also: TreeGrid inline editing
Comments