Grid custom render

Custom rendering achieved using mods that override grid api methods

Merge grid group row cells vertically

Merging vertically grid group cells. Done by setting a custom renderCell function which sets rowspan for the top cell and skips rendering for the rest of the cells for the same column in the same group.
GridCustomRender/Index.cshtml
@(Html.Awe().Grid("GridMergeRows")        
.CustomRender(new CustomRenderOpt { Name = "mergeGroupCells" })
.Columns(
new Column { Bind = "Id", Width = 75, Groupable = false, Resizable = false },
new Column { Bind = "Person", Group = true },
new Column { Bind = "Food.Name" },
new Column { Bind = "Location" })
.Url(Url.Action("GetItems", "LunchGrid"))
.Resizable()
.Height(400))
<script>
// merge grouped cells vertically grid mod
function mergeGroupCells(o) {
var api = o.api;

api.renderCell = ({ content, attr, group, item, column }) => {
const items = group?.items;
const itemslen = items?.length;

if (itemslen && column.Group){
const itemGroupIndex = group.items.indexOf(item);
if(itemGroupIndex === 0){
// set rowspan for the first item
attr.rowspan = itemslen;
}
else {
// skip rendering for cells afer index 0
return '';
}
}

return `<td ${awef.objToAttrStr(attr)}>${content}</td>`;
};
}
</script>

Grid Cards View



Grid cards view achieved by overriding api methods, columns can be reordered, hidden/shown, and the changes will be reflected in the rendered card. The column headers width is ignored for calculating grid content width, so when you shrink the browser window the grid content won't get a horizontal scrollbar. On touch you can scroll the grid header horizontally by dragging from the left an right sides of the grid header.
Shared/Demos/GridCardsView.cshtml
@(Html.Awe().Grid("GridCardsView")
.CssClass("scrlh cardsViewGrid") // scrollbar for grid header when needed
.Reorderable()
.Mod(o => o.Main())
.CustomRender(new CustomRenderOpt { Name = "cardsview", BlockLayout = true })
.Columns(
new Column { Bind = "Id", Width = 75, Groupable = false, Resizable = false },
new Column { Bind = "Person" }.Mod(o => o.Nohide()),
new Column { Bind = "Food.Name" },
new Column { Bind = "Location" },
new Column { Bind = "Date", Width = 120 }.Mod(o => o.Autohide()),
new Column { Bind = "Country.Name", Header = "Country" },
new Column { Bind = "Chef.FirstName,Chef.LastName", Prop = "ChefName", Header = "Chef" })
.Url(Url.Action("GetItems", "LunchGrid"))
.Resizable()
.Height(450))
<script>
function cardsview(o) {
var api = o.api;

// node content add wrap for padding
api.ncon = function(p) {
if (!p.lvl) return p.ren();
return '<div class="gridTempAutoCont" style="padding-left:' + p.lvl + 'em;" >' + p.ren() + '</div>';
};

// group header content
api.ghead = function(g) {
return api.ceb() + g.c;
};

api.renderItem = function (opt) {
let { attr, item, ceb, content, indent } = opt;
const columns = o.columns;

if (!content) {
content = '';
for (let col of columns) {
// is column hidden
if (api.isColHid(col)) continue;
const header = col.Header ? col.Header +': ' : '';
content += `<div class="elabel">${header}</div>${aweUtils.gcvw(api, col, opt)}</br>`;
}
}

if (ceb) {
attr.class += ' cardhead';
attr.style = `margin-left:${indent}em;`;
} else {
attr.class += ' itcard';
}

return `<div ${awef.objToAttrStr(attr)}>${content}</div>`;
};

// ignore columns width for grid content
o.syncon = 0;

// no alt rows
o.alt = 0;
}
</script>

Grid Custom Render with Inline Editing


Grid with custom rendering and inline editing mod applied. Edit on row click can also be used.
Shared/Demos/GridInlineCrudCustomRender.cshtml
@{
var gridId = "InlineCustomRender";
var initObj = new
{
Name = DemoUtils.RndName(),
Date = DateTime.Now.ToShortDateString(),
ChefId = Db.Chefs.First().Id,
MealsIds = Db.Meals.Take(2).Select(o => o.Id).ToArray()
};

}
<div class="bar">
<div style="float: right;">
@Html.Awe().TextBox("txtSearchInlCr").Placeholder("search...").CssClass("searchtxt")
</div>
<button type="button" onclick="$('#@gridId').data('api').inlineCreate()" class="awe-btn">Create</button>

@Html.InlineCreateButtonForGrid(gridId, initObj, "Create with predefined values")
</div>

@Html.InitDeletePopupForGrid(gridId, "GridInlineEditDemo")

@(Html.Awe().Grid(gridId)
.CssClass("scrlh inlEditCards")
.Attr("data-syncg", "dinner") // crud sync using signalr in site.js
.Url(Url.Action("GridGetItems", "GridInlineEditDemo"))
.InlEdit(new InlEditOpt { SaveUrl = Url.Action("Save", "GridInlineEditDemo") })
.Parent("txtSearchInlCr", "search")
.Height(620)
.Mod(o => o.PageInfo().CustomRender("inlDinner"))
.Columns(
new Column { Bind = "Id", Width = 75, Hidden = true }
.InlId(),

new Column { Bind = "Name" }
.InlString(),

new Column { Bind = "Date" }
.InlDate(),

new Column { Id = "chef", Bind = "Chef.FirstName,Chef.LastName", Prop = "ChefName", Header = "Chef" }
.Inl(Html.Awe().Lookup("ChefId").Controller("ChefLookup")),

new Column { Id = "meals", Prop = "Meals", Header = "Meals" }
.InlMultiselect(new MultiselectOpt { Name = "MealsIds", Url = Url.Action("GetMealsImg", "Data") }.ImgItem()),

new Column { Bind = "BonusMeal.Name", Prop = "BonusMeal", Header = "Bonus Meal", Width = 140 }
.InlDropdownList(new DropdownListOpt
{
Name = "BonusMealId",
Url = Url.Action("GetMealsImg", "Data")
}
.ImgItem()),

new Column { Bind = "Organic", Prop = "DispOrganic" }
.Inl(Html.Awe().Toggle(new ToggleOpt { Name = "Organic" })),

new Column { Id = "edit", ClientFormat = GridUtils.InlineEditFormat() },
new Column { Id = "del", ClientFormat = Html.InlineDeleteFormatForGrid(gridId) }))
@*column.Id edit/del used in inlDinner func*@
<script>
function inlDinner(o) {
var api = o.api;

// node (group) content add wrap for padding
api.ncon = function (p) {
if (!p.lvl) return p.ren();
return '<div class="gridTempAutoCont" style="padding-left:' + p.lvl + 'em;" >' + p.ren() + '</div>';
};

// group header content
api.ghead = function (g) {
return api.ceb() + g.c;
};

// render item (row)
api.renderItem = function (opt) {
let { attr, item, ceb, content, indent } = opt;

// content set for group header
if (!content) {
content = '';
const colByBind = (bind) => {
return aweUtils.columnByBind(o, bind);
};

const colById = (id) => {
return aweUtils.columnById(o, id);
};

// get column value
const val = (col, cls) => {
return aweUtils.gcvw(api, col, opt, cls);
}

const field = (col, nolabel) => {
var label = '';
if (col.Header && !nolabel) {
label = `<div class="elabel">${col.Header}:</div>`;
}
return `<div class="efield">${label}<div class="einput">${val(col)}</div></div>`;
}

function hid(col) {
return `<div style="display:none;">${val(col)}</div>`;
}

content += hid(colByBind('Id')) +
'<div class="earea">' +
field(colByBind('Name')) +
field(colByBind('Date')) +
field(colById('chef')) +
'</div><div class="earea">' +
field(colByBind('BonusMeal.Name')) +
field(colByBind('Organic')) +
'</div>' +
field(colById('meals')) +
'<div class="inlbtns">' +
val(colById('edit'), 'awe-il') +
val(colById('del'), 'awe-il') +
'</div>';
}

// has collapse button
if (ceb) {
attr.class += ' cardhead';
attr.style = `margin-left: ${indent}em;`;
} else {
attr.class += ' edcard';
}

return `<div ${awef.objToAttrStr(attr)}>${content}</div>`;

}

// ignore columns width for grid content width
o.syncon = 0;

// no alt rows
o.alt = 0;
}
</script>

Tree grid with custom rendering



This example demonstrates how to use the custom render mod to control the HTML output of the TreeGrid.
By overriding grid API methods, the grid is rendered using div elements with custom padding and structure instead of the default table layout.
You can customize node appearance, indentation, group headers, and row rendering logic to match your application's design requirements.
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) {
return api.ceb() + g.c;
};

// render row
api.renderItem = function (opt) {
let { attr, ceb, content} = opt;

function val(col) {
return aweUtils.gcv(api, col, opt);
}

if (!content) {
content = '';
if (ceb) content += api.ceb();
content += val(aweUtils.columnByBind(o, 'Name'));
}

if (ceb) {
attr.class += ' tgitem awe-ceb';
} else {
attr.class += ' tgitem';
}

return `<div ${awef.objToAttrStr(attr)}>${content}</div>`;
};
}
</script>

Tree grid nested cards



Rendering the grid nodes as cards and placing the child nodes inside the parent card. The grid is also using lazy loading.
GridCustomRender/Index.cshtml
<h2> Tree grid nested cards </h2>
@(Html.Awe().Grid("LazyTreeGridCr")
.Url(Url.Action("LazyTree", "TreeGrid"))
.CustomRender(new CustomRenderOpt { Name = "cardsTree", BlockLayout = true })
.Columns(
new Column { Bind = "Name" },
new Column { Bind = "Id", Width = 100 })
.PageSize(3)
.Height(500))
<script>
function cardsTree(o) {
var api = o.api;

// node
api.nodef = function(p) {
// don't wrap nodetype = items
if (p.gv.nt == 2) return p.ren();
var attr = p.h ? 'style="display:none;"' : ''; // collapsed
var res = '<div class="gwrap" ' + attr + '>' + p.ren() + '</div>';
return res;
};

// group header content
api.ghead = function(g) {
return api.ceb() + g.c;
};

api.renderItem = function ({ attr, item, ceb }) {
function val(column) {
return api.getCellValue({item, column});
}

let content = '';

if (ceb) {
content += api.ceb(); // collapse expand button
}

content += val(aweUtils.columnByBind(o, 'Name'));


if (ceb) {
attr.class += ' cardhead awe-ceb';
} else {
attr.class += ' itcard';
}

return `<div ${awef.objToAttrStr(attr)}>${content}</div>`;
};
}
</script>



Comments