Awesome ASP.net Core and MVC Controls

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 mbtn">Create</button>

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

@Html.InitDeletePopupForGrid(gridId, "GridInlineEditDemo")

@(Html.Awe().Grid(gridId)
.Mod(o => o.PageInfo().InlineEdit(Url.Action("Create", "GridInlineEditDemo"), Url.Action("Edit", "GridInlineEditDemo")))
.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"))),
.Mod(o => o.Inline(
Html.Awe().Autocomplete("Name")
.CacheKey("atcmeals") // set to share cache with all rows
//.Url(Url.Action("GetItems", "MealAutocomplete"))
.DataFunc("utils.getItems(meals)"))),

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

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

new Column { ClientFormat = ".Meals", Header = "Meals", Width = 250 }
.Mod(o => o.Inline(Html.Awe().AjaxCheckboxList("Meals").Multiselect().DataFunc("utils.getItems(meals)"), "MealsIds")),

new Column { Bind = "BonusMeal.Name", ClientFormat = ".BonusMeal", Header = "Bonus Meal" }
.Mod(o => o.Inline(Html.Awe().AjaxRadioList("BonusMealId").Odropdown().DataFunc("utils.getItems(meals)"), "BonusMealId")),

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

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

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

public ActionResult 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 < 2012
};
}

public ActionResult 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)
{
Key = "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 ActionResult 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 ActionResult 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 ActionResult Delete(int id)
{
var dinner = Db.Get<Dinner>(id);

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

[HttpPost]
public ActionResult Delete(DeleteConfirmInput input)
{
Db.Delete<Dinner>(input.Id);
return Json(new { input.Id });
}

public ActionResult 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.PageInfo().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"))),
.Mod(o => o.Inline(
Html.Awe().Autocomplete("Name")
.CacheKey("atcmeals")
//.Url(Url.Action("GetItems", "MealAutocomplete"))
.DataFunc("utils.getItems(meals)"))),

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

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

new Column { ClientFormat = ".(Meals)", Header = "Meals", Width = 250 }
.Mod(o => o.Inline(Html.Awe().AjaxCheckboxList("Meals").Multiselect().DataFunc("utils.getItems(meals)"), "MealsIds")),

new Column { Bind = "BonusMeal.Name", ClientFormat = ".(BonusMeal)", Header = "Bonus Meal" }
.Mod(o => o.Inline(Html.Awe().AjaxRadioList("BonusMealId").Odropdown().DataFunc("utils.getItems(meals)"), "BonusMealId")),

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

new Column { ClientFormat = GridUtils.InlineEditFormat(), Width = 70 },
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")
.Mod(o => o.PageInfo()
.InlineEdit(Url.Action("Create", "GridInlineEditDemo"), Url.Action("Edit", "GridInlineEditDemo"))
.CustomRender("inlDinner"))
.Url(Url.Action("GridGetItems", "GridInlineEditDemo"))
.Parent("txtSearchInlCr", "search")
.Height(600)
.ColumnWidth(100)
.Attr("data-syncg", "dinner") // crud sync using signalr in site.js
.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").CssClass("cedMulti").Multiselect().DataFunc("utils.getItems(meals)"), "MealsIds")),

new Column { Bind = "BonusMeal.Name", ClientFormat = ".(BonusMeal)", Header = "Bonus Meal", Width = 140 }
.Mod(o => o.Inline(Html.Awe().AjaxRadioList("BonusMealId").Odropdown().DataFunc("utils.getItems(meals)"), "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 colf = utils.colf(o.columns);
var fcol = colf.fcol; // find column by Bind
var fcoli = colf.fcoli; // find column by Id

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

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(fcol('Id')) +
'<div class="earea">' +
field(fcol('Name')) +
field(fcol('Date')) +
field(fcoli('chef')) +
'</div><div class="earea">' +
field(fcol('BonusMeal.Name')) +
field(fcol('Organic')) +
'</div>' +
field(fcoli('meals')) +
'<div class="inlbtns">' +
val(fcoli('edit')) +
val(fcoli('del')) +
'</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