Master Detail CRUD Demo using Grid and PopupForm

This is a demo for master detail CRUD using the Grid.
For master-detail grid see Master Detail Grid, or Hierarchy (Nested Grids)
MasterDetailCrudDemo/Index.cshtml
@Html.InitCrudPopupsForGrid("RestaurantGrid", "MasterDetailCrudDemo", 470, 1000)

<div class="bar">
<button type="button" class="awe-btn mbtn" onclick="awe.open('createRestaurantGrid')">Create</button>
</div>

@(Html.Awe().Grid("RestaurantGrid")
.Height(350)
.Attr("data-syncg", "rest")
.Url(Url.Action("RestaurantGridGetItems"))
.Groupable(false)
.Columns(
new Column { ClientFormat = ".Id", Header = "Id", Width = 70 },
new Column { Bind = "Name" },
new Column { ClientFormat = GridUtils.EditFormatForGrid("RestaurantGrid"), Width = 50 },
new Column { ClientFormat = GridUtils.DeleteFormatForGrid("RestaurantGrid"), Width = 50 }))
MasterDetailCrudDemo/Create.cshtml
@model AwesomeMvcDemo.ViewModels.Input.RestaurantInput
@using (Html.Awe().BeginContext())
{
var gridId = "AddressesGrid";
using (Html.BeginForm())
{
@Html.EditorFor(o => o.Id)
@Html.EditorFor(o => o.Name)
}

@Html.InitCrudPopupsForGrid(gridId, "AddressesGridCrud", 230)

<div class="bar">
@Html.CreateButtonForGrid(gridId, new { restaurantId = Model.Id}, "Add address")
</div>
<div>
@(Html.Awe().Grid(gridId).Url(Url.Action("GridGetItems", "AddressesGridCrud"))
.Parameter("restaurantId", Model.Id)
.Attr("data-syncg","addr")
.Height(230)
.Groupable(false)
.Columns(
new Column { Bind = "Line1,Line2", ClientFormat = ".Line1 .Line2", Header = "Address" },
new Column { ClientFormat = Html.EditFormatForGrid(gridId), Width = 52 },
new Column { ClientFormat = Html.DeleteFormatForGrid(gridId), Width = 52 }))
</div>
}
AddressesGridCrud/Create.cshtml
@model AwesomeMvcDemo.ViewModels.Input.RestaurantAddressInput

@using (Html.BeginForm())
{
@Html.EditorFor(o => o.RestaurantId)
@Html.EditorFor(o => o.Line1)
@Html.EditorFor(o => o.Line2)
}
Demos/Grid/MasterDetailCrud/MasterDetailCrudDemoController.cs
using System;
using System.Linq;
using System.Web.Mvc;
using AwesomeMvcDemo.Models;
using AwesomeMvcDemo.ViewModels.Input;
using Omu.AwesomeMvc;

namespace AwesomeMvcDemo.Controllers.Demos.Grid.MasterDetailCrud
{
public class MasterDetailCrudDemoController : Controller
{
public ActionResult Index()
{
return View();
}

public ActionResult RestaurantGridGetItems(GridParams g)
{
var model = new GridModelBuilder<Restaurant>(Db.Restaurants.Where(o => o.IsCreated).AsQueryable(), g)
{
Key = "Id",
GetItem = () => Db.Get<Restaurant>(Convert.ToInt32(g.Key))
}.Build();
return Json(model);
}

public ActionResult Create()
{
// needed so we could add addresses even before the restaurant is created/saved
var rest = Db.Insert(new Restaurant());

return PartialView(new RestaurantInput { Id = rest.Id });
}

[HttpPost]
public ActionResult Create(RestaurantInput input)
{
if (!ModelState.IsValid)
{
return PartialView(input);
}

var restaurant = Db.Get<Restaurant>(input.Id);
restaurant.Name = input.Name;
restaurant.IsCreated = true;

return Json(restaurant); // use MapToGridModel like in Grid Crud Demo when grid uses Map
}

public ActionResult Edit(int id)
{
var rest = Db.Get<Restaurant>(id);
return PartialView("Create", new RestaurantInput { Id = id, Name = rest.Name });
}

[HttpPost]
public ActionResult Edit(RestaurantInput input)
{
if (!ModelState.IsValid)
{
return PartialView("Create", input);
}

var rest = Db.Get<Restaurant>(Convert.ToInt32(input.Id));

rest.Name = input.Name;

return Json(new { rest.Id });
}

public ActionResult Delete(int id, string gridId)
{
var restaurant = Db.Get<Restaurant>(id);

return PartialView(new DeleteConfirmInput
{
Id = id,
Message = string.Format("Are you sure you want to delete restaurant <b>{0}</b> ?", restaurant.Name)
});
}

[HttpPost]
public ActionResult Delete(DeleteConfirmInput input)
{
Db.Delete<Restaurant>(input.Id);
return Json(new { input.Id });
}
}
}
Demos/Grid/MasterDetailCrud/AddressesGridCrudController.cs
using System;
using System.Linq;
using System.Web.Mvc;
using AwesomeMvcDemo.Models;
using AwesomeMvcDemo.ViewModels.Input;
using Omu.AwesomeMvc;

namespace AwesomeMvcDemo.Controllers.Demos.Grid.MasterDetailCrud
{
public class AddressesGridCrudController : Controller
{
public ActionResult GridGetItems(GridParams g, int restaurantId)
{
var items = Db.RestaurantAddresses.Where(o => o.RestaurantId == restaurantId).AsQueryable();
var model = new GridModelBuilder<RestaurantAddress>(items, g)
{
Key = "Id",
GetItem = () => Db.Get<RestaurantAddress>(Convert.ToInt32(g.Key))
}.Build();
return Json(model);
}

public ActionResult Create(int restaurantId)
{
return PartialView(new RestaurantAddressInput { RestaurantId = restaurantId });
}

[HttpPost]
public ActionResult Create(RestaurantAddressInput input)
{
if (!ModelState.IsValid)
{
return PartialView(input);
}

var address = Db.Insert(new RestaurantAddress { Line1 = input.Line1, Line2 = input.Line2, RestaurantId = input.RestaurantId });

return Json(address); // use MapToGridModel like in Grid Crud Demo when grid uses Map
}

public ActionResult Edit(int id)
{
var address = Db.Get<RestaurantAddress>(id);

return PartialView(
"Create",
new RestaurantAddressInput
{
Line1 = address.Line1,
Line2 = address.Line2,
RestaurantId = address.RestaurantId
});
}

[HttpPost]
public ActionResult Edit(RestaurantAddressInput input)
{
if (!ModelState.IsValid)
{
return PartialView("Create", input);
}

var address = Db.Get<RestaurantAddress>(input.Id);
address.Line1 = input.Line1;
address.Line2 = input.Line2;
return Json(new { input.Id });
}

public ActionResult Delete(int id)
{
var address = Db.Get<RestaurantAddress>(id);

return PartialView(new DeleteConfirmInput
{
Id = id,
Message = string.Format("Are you sure you want to delete restaurant address <b>{0} {1}</b> ?", address.Line1, address.Line2)
});
}

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

Master Detail CRUD using in nest editing

MasterDetailCrudDemo/Index.cshtml
@Html.InitCrudForGridNest("RestaurantGrid2", "MasterDetailCrudDemo")

<div class="bar">
<button type="button" onclick="utils.nestCreate('RestaurantGrid2', 'createRestaurantGrid2')" class="awe-btn mbtn">Create</button>
</div>

@(Html.Awe().Grid("RestaurantGrid2")
.Url(Url.Action("RestaurantGridGetItems"))
.PageSize(10)
.Attr("data-syncg", "rest")
.Nests(
new Nest { Name = "editnst", GetFunc = "utils.loadNestPopup('editRestaurantGrid2')" },
new Nest { Name = "delnst", GetFunc = "utils.loadNestPopup('deleteRestaurantGrid2')" })
.Columns(
new Column { ClientFormat = ".Id", Header = "Id", Width = 70 },
new Column { Bind = "Name" },
new Column { ClientFormat = GridUtils.EditGridNestFormat(), Width = 50 },
new Column { ClientFormat = GridUtils.DeleteGridNestFormat(), Width = 50 }))

Master Detail CRUD using Inline Editing and nesting

MasterDetailCrudDemo/Index.cshtml
@{
var grid3 = "RestaurantGridInline";
}

@Html.InitDeletePopupForGrid(grid3)

<div class="bar">
@Html.Awe().Button().Text("Create").OnClick("$('#" + grid3 + "').data('api').inlineCreate()").CssClass("mbtn")
</div>

@(Html.Awe().Grid(grid3)
.Url(Url.Action("RestaurantGridGetItems"))
.Mod(o => o.InlineEdit(Url.Action("Create", "RestInl"), Url.Action("Edit", "RestInl"), rowClickEdit: true))
.Groupable(false)
.Attr("data-syncg", "rest")
.Nests(new Nest { Name = "detailnst", Url = Url.Action("Addresses", "RestInl"), LoadOnce = true})
.Columns(
new Column { ClientFormat = ".Id", Header = "Id", Width = 70 }.Mod(o => o.InlineId()),
new Column { Bind = "Name" }.Mod(o => o.Inline(Html.Awe().TextBox("Name"))),
new Column { ClientFormat = "<button type='button' class='awe-btn o-pad detailnst'>details <i class='caretc'><i class='o-caret'></i></i></button>", Width = 105 },
new Column { ClientFormat = GridUtils.InlineEditFormat(), Width = 70 },
new Column { ClientFormat = Html.InlineDeleteFormatForGrid(grid3), Width = 80 }))
<style>
/* hide addresses button for new rows */
.o-glnew .detailnst {
display: none;
}

.caretc
{
position: relative;
padding: .4em .5em;
display: inline-block;
}

.caretc .o-caret
{
transform: rotate(-90deg);
zoom: 1.1;
}

.detailnst-on .caretc .o-caret
{
transform: rotate(0);
}
</style>
RestInl/Addresses.cshtml
@{
var restId = ViewData["Id"];
var gridId = "AddrGrid-" + restId;
}

@Html.InitDeletePopupForGrid(gridId, "AddressesGridCrud")

<div style="padding: .5em;">
<div class="bar">
<button type="button" class="awe-btn mbtn" onclick="$('#@gridId').data('api').inlineCreate()">Create</button>
</div>
@(Html.Awe().Grid(gridId)
.Url(Url.Action("GridGetItems", "AddressesGridCrud"))
.Mod(o => o.InlineEdit(Url.Action("Create", "AddrInlGrid"), Url.Action("Edit", "AddrInlGrid"), rowClickEdit:true))
.Parameter("restaurantId", restId)
.Attr("data-syncg", "addr")
.Height(200)
.Groupable(false)
.Columns(
new Column { Bind = "Id", Hidden = true }.Mod(o => o.InlineReadonly()),
new Column { Bind = "Line1", ClientFormat = ".Line1", Header = "Line 1" }
.Mod(o => o.Inline(Html.Awe().TextBox("Line1"))),
new Column { Bind = "Line2", ClientFormat = ".Line2", Header = "Line 2" }
.Mod(o => o.Inline(Html.Awe().TextBox("Line2"))),
new Column { ClientFormat = GridUtils.InlineEditFormat(), Width = 70 },
new Column { ClientFormat = Html.InlineDeleteFormatForGrid(gridId), Width = 80 }))
</div>
Demos/Grid/MasterDetailCrud/RestInlController.cs
using System.Web.Mvc;
using AwesomeMvcDemo.Models;
using AwesomeMvcDemo.ViewModels.Input;
using Omu.Awem.Utils;

namespace AwesomeMvcDemo.Controllers.Demos.Grid.MasterDetailCrud
{
public class RestInlController : Controller
{
public ActionResult Addresses(int key)
{
ViewData["Id"] = key;
return PartialView();
}

private object MapToGridModel(Restaurant o)
{
return new { o.Id, o.Name };
}

[HttpPost]
public ActionResult Create(RestaurantInput input)
{
if (!ModelState.IsValid)
{
return Json(ModelState.GetErrorsInline());
}

var ent = Db.Insert(new Restaurant
{
Name = input.Name,
IsCreated = true
});

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

}

[HttpPost]
public ActionResult Edit(RestaurantInput input)
{
if (!ModelState.IsValid)
{
return Json(ModelState.GetErrorsInline());
}

var ent = Db.Get<Restaurant>(input.Id);
ent.Name = input.Name;
Db.Update(ent);

return Json(new { });
}
}
}


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.
MasterDetailCrudDemo/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 connId;

hub.client.broadcastMessage = function (srccid, srcgid, key, act, group) {
$('.awe-grid[data-syncg="' + group + '"]').each(function (_, el) {
updateRow($(el), srccid, srcgid, key, act);
});
};

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

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

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(g, srccid, srcgid, key, act) {
var gid = g.attr('id');
if (srccid == connId && srcgid == gid) return;
var row = g.find('.awe-row[data-k="' + key + '"]');
if (row.length && !row.hasClass('o-glrow')) {
if (act == 'del')
utils.delRow(row);
else
utils.itemEdited(gid, 1)({ id: key });
}
}
}

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