Grid inline editing demo


Inline editing for grid achieved using InlineEdit grid mod
Delete action is the same as in the grid crud demo, using a popup.
To set initial values on create set the initial model as a parameter in the inlineCreate method, example: $grid.data('api').inlineCreate({ Name: 'hi' })
On Save you also get the grid parameters, for example this grid has txtSearchInl as parent, so you get the value of txtSearchInl as 'search' parameter in the Edit/Create post actions
Shared/Demos/GridInlineCrud.cshtml
@{
var gridId = "DinnersInlineCrudGrid";
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("txtSearchInl").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)
.Mod(o => o.Main(false)
.Freeze(0, 2)
.InlineEdit(Url.Action("Create", "GridInlineEditDemo"), Url.Action("Edit", "GridInlineEditDemo"))) //, reloadOnSave:true, oneRow:true
.Url(Url.Action("GridGetItems", "GridInlineEditDemo"))
.Parent("txtSearchInl", "search")
.Height(350)
.Resizable()
.Reorderable()
.Attr("data-syncg", "dinner") // crud sync using signalr in site.js
.Columns(
new Column { Bind = "Id", Width = 75 }
.Mod(o => o.InlineId()),

new Column { Bind = "Name" }
.Mod(o => o.Inline(Html.Awe().TextBox("Name"))),

new Column { Bind = "Date", Width = 180 }
.Mod(o => o.Inline(Html.Awe().DatePicker("Date").ReadonlyInput().ChangeYear().ChangeMonth())),

new Column { Bind = "Chef.FirstName,Chef.LastName", ClientFormat = ".(ChefName)", Header = "Chef", MinWidth = 170 }
.Mod(o => o.Inline(Html.Awe().Lookup("Chef").Controller("ChefLookup"), "ChefId")),

new Column { ClientFormat = ".(Meals)", Header = "Meals", MinWidth = 200, Grow = 2.2 }
.Mod(o => o.Inline(
Html.Awe().AjaxCheckboxList("Meals")
.DataFunc(Url.Action("GetMealsImg", "Data").CacheGetFunc())
.Multiselect(d => d.ItemFunc("awem.imgItem").CaptionFunc("utils.imgCaption")), "MealsIds")),

new Column { Bind = "BonusMeal.Name", ClientFormat = ".(BonusMeal)", Header = "Bonus Meal" }
.Mod(o => o.Inline(
Html.Awe().AjaxRadioList("BonusMealId")
.DataFunc(Url.Action("GetMealsImg", "Data").CacheGetFunc())
.Odropdown(d => d.ItemFunc("awem.imgItem").CaptionFunc("utils.imgCaption")), "BonusMealId")),

new Column { Bind = "Organic", Width = 90, ClientFormat = ".(DispOrganic)" }
.Mod(o => o.Inline(Html.Awe().CheckBox("Organic").Otoggl())),

new Column { ClientFormat = GridUtils.InlineEditFormat(), Width = 80 },
new Column { ClientFormat = Html.InlineDeleteFormatForGrid(gridId), Width = 85 }))
Demos/Grid/GridInlineEditDemoController.cs
public class GridInlineEditDemoController : Controller
{
public IActionResult Index()
{
return View();
}

public IActionResult ConditionalDemo()
{
return View();
}

public IActionResult MultiEditorsDemo()
{
return View();
}

private object MapToGridModel(Dinner o)
{
return new
{
o.Id,
o.Name,
Date = o.Date.ToShortDateString(),
ChefName = o.Chef.FirstName + " " + o.Chef.LastName,
Meals = string.Join(", ", o.Meals.Select(m => m.Name)),
BonusMeal = o.BonusMeal.Name,
o.Organic,
DispOrganic = o.Organic ? "Yes" : "No",

// below properties used for inline editing only
MealsIds = o.Meals.Select(m => m.Id).ToArray(),
ChefId = o.Chef.Id,
BonusMealId = o.BonusMeal.Id,

// for conditional demo
Editable = o.Meals.Count() > 1,
DateReadOnly = o.Date.Year < 2015
};
}

public IActionResult GridGetItems(GridParams g, string search)
{
search = (search ?? "").ToLower();
var items = Db.Dinners.Where(o => o.Name.ToLower().Contains(search)).AsQueryable();

var model = new GridModelBuilder<Dinner>(items, g)
{
KeyProp = o => o.Id, // needed for api select, update, tree, nesting, EF
GetItem = () => Db.Get<Dinner>(Convert.ToInt32(g.Key)), // called by the grid.api.update
Map = MapToGridModel,
}.Build();

return Json(model);
}

[HttpPost]
public IActionResult Create(DinnerInput input)
{
if (ModelState.IsValid)
{
var dinner = new Dinner
{
Name = input.Name,
Date = input.Date.Value,
Chef = Db.Get<Chef>(input.Chef),
Meals = input.Meals.Select(mid => Db.Get<Meal>(mid)),
BonusMeal = Db.Get<Meal>(input.BonusMealId),
Organic = input.Organic ?? false
};

Db.Insert(dinner);

return Json(new { Item = MapToGridModel(dinner) });
}

return Json(ModelState.GetErrorsInline());
}

[HttpPost]
public IActionResult Edit(DinnerInput input)
{
if (ModelState.IsValid)
{
var dinner = Db.Get<Dinner>(input.Id);
dinner.Name = input.Name;
dinner.Date = input.Date.Value;
dinner.Chef = Db.Get<Chef>(input.Chef);

dinner.Meals = input.Meals.Select(mid => Db.Get<Meal>(mid));

dinner.BonusMeal = Db.Get<Meal>(input.BonusMealId);
dinner.Organic = input.Organic ?? false;
Db.Update(dinner);

return Json(new { });
}

return Json(ModelState.GetErrorsInline());
}

public IActionResult Delete(int id)
{
var dinner = Db.Get<Dinner>(id);

return PartialView(new DeleteConfirmInput
{
Id = id,
Type = "dinner",
Name = dinner.Name
});
}

[HttpPost]
public IActionResult Delete(DeleteConfirmInput input)
{
Db.Delete<Dinner>(input.Id);

// the PopupForm's success function utils.itemDeleted expects an obj with "Id" property
return Json(new { input.Id });
}

public IActionResult Popup()
{
return PartialView();
}
}

Edit on row click


Edit on row click, and save when clicking out if there is are any changes.
In case of validation errors grid loading (go to another page, sort, group) will be prevented.
GridInlineEditDemo/Index.cshtml
@{ var grid2Id = "DinnersGrid2"; }
<div class="bar">
<div style="float: right;">
@Html.Awe().TextBox("txtSearch2").Placeholder("search...").CssClass("searchtxt")
</div>
<button type="button" onclick="$('#@grid2Id').data('api').inlineCreate()" class="awe-btn">Create</button>
</div>

@Html.InitDeletePopupForGrid(grid2Id, "GridInlineEditDemo")

@(Html.Awe().Grid(grid2Id)
.Mod(o => o.Main(false).InlineEdit(Url.Action("Create"), Url.Action("Edit"), rowClickEdit: true))
.Url(Url.Action("GridGetItems"))
.Parent("txtSearch2", "search")
.Height(350)
.Resizable()
.Reorderable()
.Attr("data-syncg", "dinner")
.Columns(
new Column { Bind = "Id", Width = 75 }
.Mod(o => o.InlineId()),

new Column { Bind = "Name" }
//.Mod(o => o.Inline(Html.Awe().TextBox("Name"))), // simple textbox
.Mod(o => o.Inline(
Html.Awe().Autocomplete("Name")
.DataFunc(Url.Action("GetMealsImg", "Data").CacheGetFunc()))),

new Column { Bind = "Date", Width = 160 }
.Mod(o => o.Inline(Html.Awe().DatePicker("Date").ReadonlyInput().ChangeYear().ChangeMonth())),

new Column { Bind = "Chef.FirstName,Chef.LastName", ClientFormat = ".(ChefName)", Header = "Chef", MinWidth = 170 }
.Mod(o => o.Inline(Html.Awe().Lookup("Chef").Controller("ChefLookup"), "ChefId")),

new Column { ClientFormat = ".(Meals)", Header = "Meals", MinWidth = 250 }
.Mod(o => o.Inline(
Html.Awe().AjaxCheckboxList("Meals")
.Multiselect()
.DataFunc(Url.Action("GetMealsImg", "Data").CacheGetFunc()), "MealsIds")),

new Column { Bind = "BonusMeal.Name", ClientFormat = ".(BonusMeal)", Header = "Bonus Meal" }
.Mod(o => o.Inline(
Html.Awe().AjaxRadioList("BonusMealId")
.Odropdown()
.DataFunc(Url.Action("GetMealsImg", "Data").CacheGetFunc()), "BonusMealId")),

new Column { Bind = "Organic", Width = 90, ClientFormat = ".(DispOrganic)" }
.Mod(o => o.Inline(Html.Awe().CheckBox("Organic").Otoggl())),

new Column { ClientFormat = GridUtils.InlineEditFormat(), Width = 80 },
new Column { ClientFormat = Html.InlineDeleteFormatForGrid(grid2Id), Width = 85 }))

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"))
.Parent("txtSearchInlCr", "search")
.Height(620)
.Mod(o => o.PageInfo()
.InlineEdit(Url.Action("Create", "GridInlineEditDemo"), Url.Action("Edit", "GridInlineEditDemo"))
.CustomRender("inlDinner"))
.Columns(
new Column { Bind = "Id", Width = 75, Hidden = true }
.Mod(o => o.InlineId()),

new Column { Bind = "Name" }
.Mod(o => o.Inline(Html.Awe().TextBox("Name"))),

new Column { Bind = "Date" }
.Mod(o => o.Inline(Html.Awe().DatePicker("Date").ReadonlyInput().ChangeYear().ChangeMonth())),

new Column { Id = "chef", Bind = "Chef.FirstName,Chef.LastName", ClientFormat = ".(ChefName)", Header = "Chef" }
.Mod(o => o.Inline(Html.Awe().Lookup("Chef").Controller("ChefLookup"), "ChefId")),

new Column { Id = "meals", ClientFormat = ".(Meals)", Header = "Meals" }
.Mod(o => o.Inline(
Html.Awe().AjaxCheckboxList("Meals")
.DataFunc(Url.Action("GetMealsImg", "Data").CacheGetFunc())
.Multiselect(d => d.ItemFunc("awem.imgItem").CaptionFunc("utils.imgCaption")), "MealsIds")),

new Column { Bind = "BonusMeal.Name", ClientFormat = ".(BonusMeal)", Header = "Bonus Meal", Width = 140 }
.Mod(o => o.Inline(
Html.Awe().AjaxRadioList("BonusMealId")
.Odropdown(d => d.ItemFunc("awem.imgItem")
.CaptionFunc("utils.imgCaption"))
.DataFunc(Url.Action("GetMealsImg", "Data").CacheGetFunc()), "BonusMealId")),

new Column { Bind = "Organic", ClientFormat = ".(DispOrganic)" }
.Mod(o => o.Inline(Html.Awe().CheckBox("Organic").Otoggl())),

new Column { Id = "edit", ClientFormat = GridUtils.InlineEditFormat(), Hidden = true },
new Column { Id = "del", ClientFormat = Html.InlineDeleteFormatForGrid(gridId), Hidden = true }))
<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 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.itmf = function(opt) {
var content = '';

// content already set (group header)
if (opt.con) {
content = opt.con;
} else {
var colFuncs = utils.colf(o.columns);
var colByBind = colFuncs.fcol; // find column by Bind
var colById = colFuncs.fcoli; // find column by Id

// get column value
function val(col, cls) {
return utils.gcvw(api, col, opt, cls);
}

function field(col, nolabel) {
var label = '';
if (col.H && !nolabel) {
label = '<div class="elabel">' + col.H + ':</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 (opt.ceb) {
opt.clss += ' cardhead';
opt.style += 'margin-left:' + opt.ind + 'em;';
} else {
opt.clss += ' edcard';
}

var attr = opt.attr;
attr += ' class="' + opt.clss + '"';
opt.style && (attr += ' style="' + opt.style + '"');

return '<div ' + attr + '>' + content + '</div>';
};

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

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



Comments