Master Details Demo using the 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
@{ var grid1 = "RestaurantGrid"; }
@Html.InitCrudPopupsForGrid(grid1, "MasterDetailCrudDemo", 470, 1000)

<div class="bar">
@Html.CreateButtonForGrid(grid1)
</div>

@(Html.Awe().Grid(grid1)
.Height(350)
.Attr("data-syncg", "rest") // crud sync using signalr in site.js
.Url(Url.Action("RestaurantGridGetItems"))
.Groupable(false)
.Columns(
new Column { Prop = "Id", Header = "Id", Width = 70 },
new Column { Bind = "Name" },
Html.EditColumnForGrid(grid1),
Html.DeleteColumnForGrid(grid1)))
MasterDetailCrudDemo/Create.cshtml
@model AweCoreDemo.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 { Bind = "Chef.FirstName,Chef.LastName", Prop = "ChefName", Header = "Chef" },
Html.EditColumnForGrid(gridId),
Html.DeleteColumnForGrid(gridId)))
</div>
}
AddressesGridCrud/Create.cshtml
@model AweCoreDemo.ViewModels.Input.RestaurantAddressInput

@using (Html.Awe().BeginContext())
{
using (Html.BeginForm())
{
@Html.EditorFor(o => o.RestaurantId)
@Html.EditorFor(o => o.Line1)
@Html.EditorFor(o => o.Line2)
@Html.EditorFor(o => o.ChefId)
}
}
Demos/Grid/MasterDetailCrud/MasterDetailCrudDemoController.cs
public class MasterDetailCrudDemoController : Controller
{
private readonly MyContext mcx = new MyContext();// mock EF Db context

public MasterDetailCrudDemoController()
{

}

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

public async Task<IActionResult> RestaurantGridGetItems(GridParams g)
{
var query = mcx.Restaurants.Where(o => o.IsCreated);

var gmb = new GridModelBuilder<Restaurant>(query, g)
{
KeyProp = o => o.Id,
};

return Json(await gmb.EFBuildAsync());
}

public async Task<IActionResult> Create()
{
// needed so we could add addresses even before the restaurant is created/saved
var rest = new Restaurant();

mcx.Add(rest);
await mcx.SaveChangesAsync();

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

[HttpPost]
public async Task<IActionResult> Create(RestaurantInput input)
{
if (!ModelState.IsValid)
{
return PartialView(input);
}

var restaurant = await mcx.FindAsync<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 async Task<IActionResult> Edit(int id)
{
var rest = await mcx.FindAsync<Restaurant>(id);
return PartialView("Create", new RestaurantInput { Id = id, Name = rest.Name });
}

[HttpPost]
public async Task<IActionResult> Edit(RestaurantInput input)
{
if (!ModelState.IsValid)
{
return PartialView("Create", input);
}

var rest = await mcx.FindAsync<Restaurant>(input.Id);

rest.Name = input.Name;

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

public async Task<IActionResult> Delete(int id)
{
var restaurant = await mcx.FindAsync<Restaurant>(id);

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

[HttpPost]
public async Task<IActionResult> Delete(DeleteConfirmInput input)
{
mcx.Remove(await mcx.FindAsync<Restaurant>(input.Id));

await mcx.SaveChangesAsync();
return Json(new { input.Id });
}
}
Demos/Grid/MasterDetailCrud/AddressesGridCrudController.cs
public class AddressesGridCrudController : Controller
{
private readonly MyContext mcx = new MyContext();// mock EF Db context

public AddressesGridCrudController()
{

}

private object mapToGridModel(RestaurantAddress o)
{
return new
{
o.Id,
o.Line1,
o.Line2,
ChefName = o.Chef.FullName,

ChefId = o.Chef.Id // for inline editing, value to the Chef inline dropdown
};
}

public async Task<IActionResult> GridGetItems(GridParams g, int restaurantId)
{
var query = mcx.RestaurantAddresses
.Include(o => o.Chef)
.Where(o => o.RestaurantId == restaurantId);

var gmb = new GridModelBuilder<RestaurantAddress>(query, g)
{
KeyProp = o => o.Id,
Map = mapToGridModel,
};

return Json(await gmb.EFBuildAsync());
}

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

[HttpPost]
public async Task<IActionResult> Create(RestaurantAddressInput input)
{
if (!ModelState.IsValid)
{
return PartialView(input);
}

var address = new RestaurantAddress
{
Line1 = input.Line1,
Line2 = input.Line2,
RestaurantId = input.RestaurantId,
Chef = await mcx.FindAsync<Chef>(input.ChefId)
};

mcx.Add(address);

await mcx.SaveChangesAsync();

return Json(mapToGridModel(address));
}

public async Task<IActionResult> Edit(int id)
{
var address = await mcx.RestaurantAddresses
.Include(o => o.Chef)
.FirstAsync<RestaurantAddress>(o => o.Id == id);

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

[HttpPost]
public async Task<IActionResult> Edit(RestaurantAddressInput input)
{
if (!ModelState.IsValid)
{
return PartialView("Create", input);
}

var address = await mcx.FindAsync<RestaurantAddress>(input.Id);
address.Line1 = input.Line1;
address.Line2 = input.Line2;
address.Chef = await mcx.FindAsync<Chef>(input.ChefId);

await mcx.SaveChangesAsync();
return Json(new { input.Id });
}

public async Task<IActionResult> Delete(int id)
{
var address = await mcx.FindAsync<RestaurantAddress>(id);

return PartialView(new DeleteConfirmInput
{
Id = id,
Type = "restaurant address",
Name = address.Line1 + " " + address.Line2
});
}

[HttpPost]
public async Task<IActionResult> Delete(DeleteConfirmInput input)
{
mcx.Remove(await mcx.FindAsync<RestaurantAddress>(input.Id));

await mcx.SaveChangesAsync();

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

#region for inline editing
[HttpPost]
public async Task<IActionResult> CreateInline(RestaurantAddressInput input)
{
if (!ModelState.IsValid)
{
return Json(ModelState.GetErrorsInline());
}

var ent = new RestaurantAddress
{
RestaurantId = input.RestaurantId,
Line1 = input.Line1,
Line2 = input.Line2,
Chef = await mcx.FindAsync<Chef>(input.ChefId)
};

mcx.Add(ent);
await mcx.SaveChangesAsync();

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

[HttpPost]
public async Task<IActionResult> EditInline(RestaurantAddressInput input)
{
if (!ModelState.IsValid)
{
return Json(ModelState.GetErrorsInline());
}

var ent = await mcx.FindAsync<RestaurantAddress>(input.Id);
ent.Line1 = input.Line1;
ent.Line2 = input.Line2;
ent.Chef = await mcx.FindAsync<Chef>(input.ChefId);

await mcx.SaveChangesAsync();

return Json(new { });
}
#endregion
}

Inline editing inside details popup

MasterDetailCrudDemo/Index.cshtml
@{ var gridIE = "RestaurantGridIE1"; }
@Html.InitCrudPopupsForGrid(gridIE, "MasterDetailInline", 470, 1000)

<div class="bar">
@Html.CreateButtonForGrid(gridIE)
</div>

@(Html.Awe().Grid(gridIE)
.Height(350)
.Attr("data-syncg", "rest") // crud sync using signalr in site.js
.Url(Url.Action("RestaurantGridGetItems"))
.Groupable(false)
.Columns(
new Column { Prop = "Id", Header = "Id", Width = 70 },
new Column { Bind = "Name" },
Html.EditColumnForGrid(gridIE),
Html.DeleteColumnForGrid(gridIE)))
MasterDetailInline/Create.cshtml
@model AweCoreDemo.ViewModels.Input.RestaurantInput
@using (Html.Awe().BeginContext())
{
var gridId = "AddressesGrid";

// for delete we use the same controller actions as in the PopupForm Crud demo
@Html.InitDeletePopupForGrid(gridId, "AddressesGridCrud")

using (Html.BeginForm())
{
@Html.EditorFor(o => o.Id)
@Html.EditorFor(o => o.Name)
}

<div class="bar">
@Html.InlineCreateButtonForGrid(gridId, new { restaurantId = Model.Id }, "Add address")
</div>
<div>
@(Html.Awe().Grid(gridId)
.Url(Url.Action("AddressGrid", "MasterDetailInline"))
.InlEdit(new InlEditOpt {
SaveUrl = Url.Action("CreateAddr"),
EditUrl = Url.Action("EditAddr"),
RowClickEdit = true
})
.Parameter("restaurantId", Model.Id)
.Attr("data-syncg", "addr")
.Height(230)
.Groupable(false)
.Columns(
new Column { Bind = "Id", Width = 75 }
.InlId(),

new Column { Bind = "Line1,Line2", ClientFormat = ".(Line1) .(Line2)", Header = "Address" }
.Inl(Html.Awe().TextBox("Line1"))
.Inl(Html.Awe().TextBox("Line2")),

new Column { Bind = "Chef.FirstName,Chef.LastName", Prop = "ChefName", Header = "Chef" }
.InlDropdownList(new DropdownListOpt { Name = "ChefId", Url = Url.Action("GetChefs", "Data") }),

Html.InlEditColumn(),
Html.InlDeleteColumn(gridId)))
</div>
}

Master Detail CRUD using in nest editing

This demo is similar to the first one which uses PopupForms, except here the Restaurant PopupForms (create/edit/delete) are opened inside grid nests
MasterDetailCrudDemo/Index.cshtml
@{ var grid2 = "RestaurantGrid2"; }
@Html.InitCrudForGridNest(grid2, "MasterDetailCrudDemo")

<div class="bar">
<button type="button" onclick="aweUtils.nestCreate('@grid2', 'create@(grid2)')" class="awe-btn mbtn">Create</button>
</div>

@(Html.Awe().Grid(grid2)
.Url(Url.Action("RestaurantGridGetItems"))
.PageSize(10)
.Attr("data-syncg", "rest")
.Nests(
new Nest { Name = "editnst", GetFunc = "aweUtils.loadNestPopup('edit" + grid2 + "')" },
new Nest { Name = "delnst", GetFunc = "aweUtils.loadNestPopup('delete" + grid2 + "')" })
.Columns(
new Column { Prop = "Id", Header = "Id", Width = 70 },
new Column { Bind = "Name" },
new Column { ClientFormat = GridUtils.EditGridNestFormat(), Width = GridUtils.EditBtnWidth },
new Column { ClientFormat = GridUtils.DeleteGridNestFormat(), Width = GridUtils.DeleteBtnWidth }))

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)
.Attr("data-syncg", "rest")
.Url(Url.Action("RestaurantGridGetItems"))
.InlEdit(new InlEditOpt {
SaveUrl = Url.Action("Create", "RestInl"),
EditUrl = Url.Action("Edit", "RestInl"),
RowClickEdit = true
})
.Groupable(false)
.Nests(new Nest { Name = "detailnst", Url = Url.Action("Addresses", "RestInl"), LoadOnce = true })
.Columns(
new Column { ClientFormat = ".(Id)", Header = "Id", Width = 70 }
.InlId(),

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

new Column
{
ClientFormat = "<button type='button' class='awe-btn detailnst'>details <i class='caretc'><i class='o-caret'></i></i></button>",
Width = 130
},
Html.InlEditColumn(),
Html.InlDeleteColumn(grid3)))
<style>
/* hide addresses button for new rows */
.o-glnew .detailnst {
display: none;
}

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

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

.detailnst-on .caretc .o-caret {
transform: rotate(0);
}
</style>



Comments