Grid inline batch editing
The Awesome Grid supports inline editing with batch save. Using batch save you can perform the saving of all the edited rows in one single request. In case of validation errors on the server side you can decide to save only the valid rows or to cancel the save for all rows and show the validation messages.
Edit on row click and batch save
Grid inline editing with batch save.
Edit on row click, and save by clicking the save all button on top.
Save can be performed either for each row individually by clicking the row save button, for all at once using the "Save all" button.
To disable editing on row click, set `RowClickEdit = false`.
Save can be performed either for each row individually by clicking the row save button, for all at once using the "Save all" button.
To disable editing on row click, set `RowClickEdit = false`.
Shared/Demos/GridInlineBatchEdit.cshtml
@{
var gridId = "BatchSaveGrid";
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">
<button type="button" onclick="$('#@gridId').data('api').inlineCreate()" class="awe-btn o-btnf">@MyAweIcons.PlusIco() Create </button>
@Html.InlineCreateButtonForGrid(gridId, initObj, MyAweIcons.PlusIco() + "Create with predefined values")
<button type="button" onclick="$('#@gridId').data('api').batchSave()" class="awe-btn o-btnf">@MyAweIcons.SaveIco() Save All </button>
<button type="button" onclick="$('#@gridId').data('api').inlineCancelAll()" class="awe-btn o-btnf">@MyAweIcons.CancelIco() Cancel All </button>
<div style="margin-left: auto;">
@Html.Awe().TextBox("txtSearch2").Placeholder("search...").CssClass("searchtxt")
</div>
</div>
@Html.InitDeletePopupForGrid(gridId, "GridInlineEditDemo")
@(Html.Awe().Grid(gridId)
.Mod(o => o.Main(false))
.Url(Url.Action("GridGetData", "GridInlineBatchEditing"))
.InlEdit(new InlEditOpt
{
SaveUrl = Url.Action("BatchSave", "GridInlineBatchEditing"),
Batch = true,
StopLoad = true // prevent reloading grid when there are unsaved changes
})
.Validation(type: typeof(DinnerInput))
.Parent("txtSearch2", "search")
.Height(350)
.Resizable()
.Reorderable()
.Attr("data-syncg", "dinner")
.Columns(
new Column { Bind = "Id", Width = 75 }
.InlId()
.InlVldSummary(),
new Column { Bind = "Name" }
.InlString(),
//.Inl(Html.Awe().TextBox("Name")),
new Column { Bind = "Date", Width = 160 }
.InlDate(),
new Column { Bind = "Chef.FirstName,Chef.LastName", Prop = "ChefName", Header = "Chef", MinWidth = 170 }
.InlDropdownList(new DropdownListOpt { Name = "ChefId", Url = Url.Action("GetChefs", "Data") }),
new Column { Prop = "Meals", Header = "Meals", MinWidth = 250 }
.Inl(Html.Awe().Multiselect(new MultiselectOpt { Name = "MealsIds", Url = Url.Action("GetMealsImg", "Data") })),
new Column { Bind = "BonusMeal.Name", Prop = "BonusMeal", Header = "Bonus Meal" }
.InlDropdownList(new DropdownListOpt
{
Name = "BonusMealId",
Url = Url.Action("GetMealsImg", "Data")
}
.ImgItem()),
new Column { Bind = "Organic", Width = 90, Prop = "DispOrganic" }
.Inl(Html.Awe().Toggle(new ToggleOpt { Name = "Organic" })),
Html.InlEditDelColumn(gridId)))
@*name of each inline helper/editor is matching the names of DinnerInput properties, and the names of row model properties set in MapToGridModel*@
Demos/Grid/GridInlineBatchEditingController.cs
public class GridInlineBatchEditingController : Controller
{
public IActionResult Index()
{
return View();
}
private object MapToGridModel(Dinner o)
{
return new
{
o.Id,
o.Name,
Date = o.Date.ToShortDateString(),
ChefName = o.Chef.FullName,
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.ToString()).ToArray(), // value for meals multiselect
ChefId = o.Chef.Id, // value for chef dropdown
BonusMealId = o.BonusMeal.Id // value for bonus meal dropdown
};
}
public IActionResult GridGetData(GridParams g, string search = "")
{
var query = Db.Dinners.Where(o => o.Name.Contains(search)).AsQueryable();
var model = new GridModelBuilder<Dinner>(query, g)
{
KeyProp = o => o.Id, // needed for api select, update, tree, nesting, EF
Map = MapToGridModel,
GetItem = () => Db.Get<Dinner>(Convert.ToInt32(g.Key)), // used when calling api.update (signalrSync.js)
};
return Json(model.Build());
}
[HttpPost]
public IActionResult BatchSave(DinnerInput[] inputs)
{
var res = new List<object>();
foreach (var input in inputs)
{
var vldState = ModelUtil.Validate(input);
// custom validation example
if (input.Name != null && input.Name.Contains("aaa"))
{
vldState.Add("Name", "Name can't contain aaa");
}
if (vldState.IsValid())
{
try
{
var isCreate = !input.Id.HasValue;
var ent = isCreate ? new Dinner() :
Db.Dinners.First(o => o.Id == input.Id);
ent.Name = input.Name;
ent.Date = input.Date.Value;
ent.Chef = Db.Find<Chef>(input.ChefId);
// ToList req for EF
ent.Meals = Db.Meals
.Where(o => input.MealsIds.Contains(o.Id)).ToList();
ent.BonusMeal = Db.Find<Meal>(input.BonusMealId);
ent.Organic = input.Organic ?? false;
if (isCreate)
{
Db.Add(ent);
}
res.Add(new { });
}
catch (Exception ex)
{
vldState.Add("Name", ex.Message);
}
}
if (!vldState.IsValid())
{
res.Add(vldState.ToInlineErrors());
}
}
return Json(res);
}
public IActionResult Delete(int id)
{
var dinner = Db.Find<Dinner>(id);
return PartialView(new DeleteConfirmInput
{
Id = id,
Type = "dinner",
Name = dinner.Name
});
}
[HttpPost]
public IActionResult Delete(DeleteConfirmInput input)
{
Db.Remove(Db.Find<Dinner>(input.Id));
// the PopupForm's success function aweUtils.itemDeleted expects an obj with "Id" property
return Json(new { input.Id });
}
}
Comments