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 loading
TreeGrid/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