Grid inline editing demo


Inline editing for grid achieved using custom mods.
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 (as done on the home page), example: $grid.data('api').inlineCreate({ Name: 'hi' })
On Save you also get the grid parameters, for example this grid has txtSearch as parent, so you get the value of txtSearch as 'search' parameter in the Edit/Create post actions
GridInlineEditDemo/Index.cshtml

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

@{
var gridId = "DinnersGrid";
var chefId = Db.Chefs.First().Id;
var meals = Db.Meals.Take(2).Select(o => o.Id).ToArray();
var initObj = new { Name = "hi there", Date = DateTime.Now.ToShortDateString(), ChefId = chefId, MealsIds = meals };
}

@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"), Url.Action("Edit")))
.Url(Url.Action("GridGetItems"))
.Parent("txtSearch", "search")
.Height(350)
.Resizable()
.Reorderable()
.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 = 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.InlineBool(cssClass: "btoggle")),

new Column { ClientFormat = GridUtils.InlineEditFormat(), Width = 60 },
new Column { ClientFormat = Html.InlineDeleteFormatForGrid(gridId), Width = 70 }))

<link href="https://gitcdn.github.io/bootstrap-toggle/2.2.0/css/bootstrap-toggle.min.css" rel="stylesheet">
<script src="https://gitcdn.github.io/bootstrap-toggle/2.2.0/js/bootstrap-toggle.min.js"></script>

<script>
var meals = @Html.Raw(DemoUtils.Encode(Db.Meals.Select(o => new KeyContent(o.Id, o.Name))));

$(function () {
$(document).on('aweinlineedit', function () {
$(this).find(".btoggle").bootstrapToggle({
on: 'Yes',
off: 'No'
});
});
});
</script>
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" : "",

// 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,
Message = string.Format("Are you sure you want to delete dinner <b>{0}</b> ?", 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("txtSearch").Placeholder("search...").CssClass("searchtxt")
</div>
<button type="button" onclick="$('#@grid2Id').data('api').inlineCreate()" class="awe-btn mbtn">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("txtSearch", "search")
.Height(350)
.Resizable()
.Reorderable()
.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 = 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.InlineBool(cssClass: "btoggle")),

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




See also:
Grid inline editing conditional demo
Grid multiple editors demo

Data sync

Grids on this page are being synced using SignalR and grid api, so when you edit or delete an item you'll notice the change in the other grid, and it will also be noticed by other users seeing this page.
GridInlineEditDemo/Index.cshtml
<script src="@Url.Content("~/Scripts/jquery.signalR-2.2.2.min.js")"></script>
<script src="@Url.Content("~/signalr/hubs")"></script>

<script type="text/javascript">
function initSync() {
var hub = $.connection.syncHub;
var grid1 = '@gridId';
var grid2 = '@grid2Id';
var grid3 = 'inlineEditDinnersGrid'; // popup
var connId;

hub.client.broadcastMessage = function (srccid, srcgid, key, act) {
updateRow(grid1, srccid, srcgid, key, act);
updateRow(grid2, srccid, srcgid, key, act);
updateRow(grid3, srccid, srcgid, key, act);
};

$.connection.hub.start().done(function () {
connId = $.connection.hub.id;
$(document)
.on('itemdelete', function(e) {
send(e, 'del');
})
.on('aweinlinesave', '.awe-row', function(e) {
send(e, '');
});
});

function send(e, act) {
var key = $(e.target).data('k');
var srcgid = $(e.target).closest('.awe-grid').attr('id');
trysend(function(){ hub.server.send(connId, srcgid, key, act); });
}

function trysend(action, attempts) {
attempts = attempts || 0;
try {
action();
} catch (err) {
if (attempts < 1) {
$.connection.hub.start();
setTimeout(function() {
trysend(action, attempts + 1);
}, 300);
}
}
}

function updateRow(gid, srccid, srcgid, key, act) {
var grid = $('#' + gid);
if (!grid.length || srccid == connId && srcgid == gid) return;
var row = grid.find('.awe-row[data-k="' + key + '"]');
if(row.length && !row.hasClass('ginlrow')) {
if (act == 'del')
utils.delRow(row);
else
utils.itemEdited(gid)({ id: key });
}
}
}

$(function () {
try {
initSync();
} catch (err) {
}
});
</script>