Master Detail

Page Description

In this article, the step-by-step process of creating the following webpage will be described:

The webpage consists of 2 functional parts. On the left side, there is a List that displays all Work Orders. On the right side, Details of a specific Work Order are getting displayed.

List Section

The List section of the webpage only consists of a Data Table with the columns "WorkOrder", "Plant", "Delivery", "SalesOrder", "Product" and "Consumer". On the far left of every row, there is also an Icon that can be clicked to display the detailed information of the specific Work Order in the right side of the page.

Detail Section

The Detail section of the webpage consists of 2 parts: a Header at the top and another List below.

The Header is a Form with 6 Control Inputs that are labeled "Work Order", "Plant Location", "Sales Order", "Delivery Number", "Product" and "Customer Code". This Section enables the User to edit the data of a specific Work Order. When the "Update" Button in the far left of the Toolbar of the webpage is clicked, the edited data gets saved and the page gets refreshed. It is also possible to refresh the page by clicking the "Refresh" Icon next to the Page Title "Work Orders".

The List below the Header is a Data Table with 4 columns "Pos", "Name", "Quantity" and "Category". It shows further details of the specific Work Order that cannot be edited.

How to Create

Required Files and their General Structure

Since we are using the MVC Pattern (see "Architecture Pattern"), we require a Model file, a View file and a Controller file. For this example, we create a Controller "ExampleController.cs", a Model "ExampleMaster.cs" and the View file "OrderMaster.cshtml", which will be explained in detail below.

Controller file

The Controller file "ExampleController.cs" is stored in the "Controllers" folder (together with all other Controllers of the project). The Controller class needs to inherit from the class "BaseController". Addtionally, the Key Words "[Authorize]" and "[AuthorizeRole]" are put above the Signature to include Authorization features. So in total, the outer part of the Controller class looks like this:

namespace Advantech.Ctos.Web.Controllers
{
    [Authorize]
    [AuthorizeRole]
    public class ExampleController : BaseController
    {
        ...
    }
    
}    

Model file

The Model file "ExampleMaster.cs" is stored in a new folder "Example" in the Business directory of the project. The Model class needs to inherit from the class "BaseModel". So in total, the outer part of the Model class looks like this:

namespace Advantech.Ctos.Business.Example
{
    public class ExampleMaster : ModelBase
    {
        ...
    }
    
}

View file

The View file "OrderMaster.cshtml" is stored in a new folder "Example" in the "Views" folder of the project (that gets automatically created when the Controller is created). The View file needs to be linked to the Model file which is why it needs to start with the following line:

@model Advantech.Ctos.Business.Example.ExampleMaster

Furthermore, it is divided into 2 sections: the Content section (containing the <div> tags, etc) and the Script section (containing the JavaScript functions).

The Content section should only work if the Model is not null, so it needs to be encapsulated by the following:

@if (Model != null)
{
    ...
}

The Script section is located outside of this if-statement and is identified by the following tag:

<script type="text/javascript">
    ...
</script>

So, in total, the View file looks like this:

@model Advantech.Ctos.Business.Example.ExampleMaster

@if (Model != null)
{
    ...
}

<script type="text/javascript">
    ...
</script>

Model Structure

In this context, "Model Structure" refers to the main components of the Model and their relation to each other without taking the methods of the Model into consideration. The Model Structure needs to match the View hierarchy. Because the View consists of a List section and a Detail section, the Model needs to have 2 Properties, which will be named "OrderList" and "OrderDetail" in this example:

public List<ExampleDetail> OrderList { get; set; }
public ExampleDetail OrderDetail { get; set; }

Both are set to be public and both need to have the standard Getter and Setter functions, so that they can be accessed. The data type of "OrderDetail" is "ExampleDetail" and the data type of "OrderList" is a List of "ExampleDetail". "ExampleDetail" is a custom made class that is intended to represent a Work Order with all the details that are relevant to this page. It is defined by the end of the file "ExampleMaster.cs" (outside of the "ExampleMaster" class) in the following way:

public class ExampleDetail
{
    public string WorkOrder { get; set; }

    public string Delivery { get; set; }

    public string SalesOrder { get; set; }

    public string CustomerID { get; set; }

    public string Product { get; set; }

    public string Location { get; set; }

    public List<SelectListItem> Locations { get; set; }

    public List<ExamplePart> Components { get; set; }
    
}

It has 6 simple string Properties "WorkOrder", "Delivery", "SalesOrder", "CustomerID", "Product" and "Location". Furthermore, it has the Property "Locations", which is a List of "SelectListItem" (a standard class for selectable list items that should already be implemented in the "Controls" file of the project) and the Property "Components", which is a List of "ExamplePart". "ExamplePart" is also a custom made class that is defined by the end of the "ExampleMaster.cs" file:

public class ExamplePart
{
    public string Name { get; set; }

    public int Position { get; set; }

    public int Quantity { get; set; }

    public string Category { get; set; }
    
}

Finally, every class needs to have a Constructor that contains the initialization of all List Properties and Object Properties.

So for the Model class "ExampleMaster.cs", the following code needs to be added after the declaration of the Properties "OrderList" and "OrderDetail":

public ExampleMaster()
{
    OrderDetail = new ExampleDetail();
    OrderList = new List<ExampleDetail>();
}

Within the class "ExampleDetail", the following Constructor needs to be added after the declaration of the Properties:

public ExampleDetail()
{
    Components = new List<ExamplePart>();
    Locations = new List<SelectListItem>();
}

The class "ExamplePart" does not require a Constructor as it does not have Properties that have a List or an Object as data type.

View Structure (OrderMaster)

In this context, "View Structure" refers to the components of the Content section (without the Script section) of the View file. In the following, the single components will be described:

Page Title

The Page Title refers to the uppermost section of the webpage that contains the title of the webpage. It is identified by the class "content-title".

In this example, the title of the webpage is "Work Orders", so the code for the Page Title looks like this:

<div class="content-title">Work Orders</div>

Toolbar

The Toolbar refers to the section of the webpage that is located just below the Page Title and contains the buttons of the webpage. It is identified by the class "content-action" and the Buttons are recognized by the class "btn".

In this example, the webpage has one "Update" Button which should have the id "btn-update". It should be grey in color but the color should only be applied to its margins which is rendered by using the class "btn-outline-secondary". So the overall code looks like this:

<div class="content-action">
    <a id="btn-update" class="btn btn-outline-secondary"> Update</a>
</div>

Main Body

The Main Body refers to the main section of the webpage that contains the functionalities of the webpage. It is identified by the class "content-body".

In this example, the Main Body of this webpage consists of two sections (two Horizontal Flexible Containers), so it additionally needs to include the classes "d-flex" and "flex-row", which makes the code look like this:

<div class="content-body d-flex flex-row">
    <div>
        ...
    </div>
    <div>
        ...
    </div>
</div>

List Section

As a Horizontal Flexible Container, the List section needs to include the class "flex-fill". To stand out from the blue background, it should have a white-colored background, so it will include the class "content-block" as well. Furthermore, "overflow-auto" is added to enable a scroll bar when the content of the container exceeds the available window space (e.g. when shrinking the window).

<div class="content-block flex-fill overflow-auto">
    ...
</div>

The List Section contains a Table, so the <table> tag is used where the opening tag contains an id "tblOrderList" as well as the classes "display" (to adopt the standard design for Tables in the application) and "w-100" (for 100% width, i.e. using all of the space). Else, a Table needs to contain a table head (<thead>) and a table body (<tbody>), like this:

<table class="display w-100" id="tblOrderList">
    <thead>
        ...
    </thead>
    <tbody>
        ...
    </tbody>
</table>

The table head contains the column headers. Because all of them should be displayed in a single row, they are encapsulated by a <tr> tag. Every column header is encapsulated by the <th> tag. With the column headers being "WorkOrder", "Plant", "Delivery", "SalesOrder", "Product" and "Customer", the code for the table head would look like this:

    <thead>
        <tr>
            <th>WorkOrder</th>
            <th>Plant</th>
            <th>Delivery</th>
            <th>SalesOrder</th>
            <th>Product</th>
            <th>Customer</th>
        </tr>
    </thead>

However, the table of this webpage additionally has a column containing an icon. Icons are identified by <i> tags. The icon of this particular column is a list icon taken from Font Awesome, which is identified by the class "fa-list-alt". Because it should be a solid icon, it should also include the class "fa-solid". So the code for the icon itself looks like this:

<i class="fa-solid fa-list-alt"></i>

This line of code needs to be inserted in an additional <th> tag at the very beginning of the table head. Furthermore, the icon should be aligned to the center of the column (accomplished by using the class "text-center") and the column should be 20 px wide, so that there is some spacing (which is accomplished by applying the style property "width:20px"). So the overall code for the table head looks like this:

<thead>
    <tr>
        <th class="text-center" style="width:20px"><i class="fa-solid fa-list-alt"></i></th>
        <th>WorkOrder</th>
        <th>Plant</th>
        <th>Delivery</th>
        <th>SalesOrder</th>
        <th>Product</th>
        <th>Customer</th>
    </tr>
</thead>

The table body should list all the Work Orders that can be found in the "OrderList" of the Model, i.e. every item in the "OrderList" has an own row in the table. Since the number of rows depend on the number of items in "OrderList" (which cannot be known in advance), the program should create a row for each item in "OrderList". For this purpose, a "foreach" statement is used that does exactly that:

<tbody>
    @foreach (var item in Model.OrderList)
    {
        <tr>
            ...
        </tr>
    }
</tbody>

Similar to how the <th> tag was used to specify the columns in the table head, now the <td> tag is used to specify the columns in the table body. The number and content of these <td> tags need to match the headers specified for the table head. Using the dot operator on the "item" variable of the loop, the respective properties of the Work Order are accessed: "WorkOrder" for the "WorkOrder" column, "Location" for the "Plant" column, and so on.

Because the rows of the icon column all contain the same icon, the same code that was used for the header can be used here as well. However, because the icon in the rows should trigger an event when clicked, they additionally include an <a> tag that contains the class "go-detail" as well as a reference to the "WorkOrder" Property (id) of the respective item as data atrribute ("data-attr") to be able to address them in the Script section, which will be explained in detail later.

Overall, the code for the table body looks like this:

<tbody>
    @foreach (var item in Model.OrderList)
    {
        <tr>
            <td class="text-center"><a class="go-detail" data-attr="@item.WorkOrder"><i class="fa-solid fa-list-alt"></i></a></td>
            <td>@item.WorkOrder</td>
            <td>@item.Location</td>
            <td>@item.Delivery</td>
            <td>@item.SalesOrder</td>
            <td>@item.Product</td>
            <td>@item.CustomerID</td>
        </tr>
    }
</tbody>

Detail Section

Because the Detail Section of the webpage should be dynamic, i.e. changed whenever a different Work Order in the List is clicked, another View file is created specifically for this section. The View file is named "OrderDetail.cshtml" and using this name, it is being referenced within the <div> tags for the Detail Section like this:

<div>
    @Html.Partial("OrderDetail", Model.OrderDetail)
</div>

Because its content is dynamic, this section becomes an own container, so it needs an own container id. In this example, we call it "div-order-detail". Because it will also contain two sub-sections of its own (two Vertical Flexible Containers), it additionally needs to include the classes "d-flex" and "flex-column". Furthermore, its width should be fixed to 500 px (achieved through the style property "width:500px") and it should have a margin on the left to be visually more apart from the List section (achieved through the class "ms-2"). So overall, the code for the Detail section in the "OrderMaster.cshtml" View file looks like this:

<div style="width:500px;" id="div-order-detail" class="ms-2 d-flex flex-column">
    @Html.Partial("OrderDetail", Model.OrderDetail)
</div>

However, this was only the outer design of the Detail section. Its content is specified in the "OrderDetail.cshtml" file that is being referenced in the code above.

View Structure (OrderDetail)

The "OrderDetail.cshtml" is created as described above for general View files. Its general structure is also the same as the one for "OrderMaster.cshtml", however, there are 2 differences. First, it does not link to the whole Model file "ExampleMaster" but only to the class "ExampleDetail" that is defined within the "ExampleMaster" file (because the Detail section is represented by the "OrderDetail" Property of the Model which is of this data type). Second, it is not required to encapsulate the content within an if-statement. Since "OrderDetail.cshtml" is only found within the Content section of "OrderMaster.cs", which is only processed when the Model is not null, we know that if "OrderDetail" is opened, the Model has to exist, else it would not have opened in the first place. So the View file "OrderDetail" generally looks like this:

@model Advantech.Ctos.Business.Example.ExampleDetail


...


<script type="text/javascript">

    ...

</script>

The Content section of "OrderDetail.cshtml" contains the 2 Vertical Flexible Containers. The top one should have a fixed height of 250 px while the bottom one should have a stretchable height. Therefore, the top container includes the style property "height:250px" and the bottom container includes the class "flex-fill". "overflow-auto" is used here as well to enable the scroll bar functionality. Furthermore, the bottom container should stick out from the blue background by having a white one (achieved by the class "content-block") and have a padding of 2 units (achieved by the class p-2). So the overall code looks like this:

<div style="height:250px;">
    ...
</div>
<div class="content-block p-2 flex-fill overflow-auto">
    ...
</div>

The bottom section contains a List, i.e. another Data Table. It is done exactly like the table in the List section of the webpage, just with the headers "Pos", "Name", "Quantity" and "Category" and the loop iterates through the "Components" Property of the Model (which is "ExampleDetail" in this case, not "ExampleMaster"). There is no clickable icon in this table and for visual purposes, the entries for Position and Quantity are aligned to the center. So the overall bottom container looks like this:

<div class="content-block p-2 flex-fill overflow-auto">
        <table id="tblComponent" class="display w-100">
        <thead>
            <tr>
                <th>Pos</th>
                <th>Name</th>
                <th>Quantity</th>
                <th>Category</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var item in Model.Components)
            {
                <tr>
                    <td class="text-center">@item.Position</td>
                    <td>@item.Name</td>
                    <td class="text-center">@item.Quantity</td>
                    <td>@item.Category</td>
                </tr>
            }
        </tbody>
    </table>
</div>

The top section contains a Form. Similar to Tables, Forms are also done with <table> tags. However, this table is not made up of a head and a body part. It is made up of several rows (<tr>) that each have a Label and a Control Input (both encapsulated by <td> tags).

The Labels of this form should all have a width of 150px, so they all have the class "field150". For example, the Label for "Product" would look like this:

<td class="field150">Product</td>

Most of the Control Inputs are Text Boxes, so they are done by using the HTML Helper for Text Boxes. And each of them should be of 100% width, so they all have the class "text100". For example, the Control Input for "Product" would look like this:

<td>@Html.TextBoxFor(m => m.Product, new { @class = "text100" })</td>

The Property "Work Order" should not be editable but only displayed. For this purpose, it is additionally set to be "readonly":

<td>@Html.TextBoxFor(m => m.WorkOrder, new { @readonly = "readonly", @class = "text100" })</td>

The Property "Locations" (labeled "Plant Location") is not a Text Box but a DropDown List, therefore it is done by the HTML Helper for DropDown Lists. Additionally, the Property needs to be converted into an MVC List to be able to display it as DropDown. Else, it should have 100% width as well. So the code looks like this:

<td>@Html.DropDownListFor(m => m.Location, Converter.ToMvcList(Model.Locations), new { @class = "text100" })</td>

Overall, the code for the Form in top container of the Detail section looks like this:

<table class="w-100">
    <tr>
        <td class="field150">Work Order</td>
        <td>@Html.TextBoxFor(m => m.WorkOrder, new { @readonly = "readonly", @class = "text100" })</td>
    </tr>
    <tr>
        <td class="field150">Plant Location</td>
        <td>@Html.DropDownListFor(m => m.Location, Converter.ToMvcList(Model.Locations), new { @class = "text100" })</td>
    </tr>
    <tr>
        <td class="field150">Sales Order</td>
        <td>@Html.TextBoxFor(m => m.SalesOrder, new { @class = "text100" })</td>
    </tr>
    <tr>
        <td class="field150">Delivery Number</td>
        <td>@Html.TextBoxFor(m => m.Delivery, new { @class = "text100" })</td>
    </tr>
    <tr>
        <td class="field150">Product</td>
        <td>@Html.TextBoxFor(m => m.Product, new { @class = "text100" })</td>
    </tr>
    <tr>
        <td class="field150">Customer Code</td>
        <td>@Html.TextBoxFor(m => m.CustomerID, new { @class = "text100" })</td>
    </tr>
</table>

To be able to submit the changes done in the Form, the above code needs to be encapsulated by the following:

@using (Ajax.BeginForm("OrderUpdate", "Example", null,
    new AjaxOptions()
    {
        HttpMethod = "POST",
        OnBegin = "onOrderBegin",
        OnSuccess = "onOrderSuccess"
    }, new { @id = "formUpdateOrder" }))
{
    ...
}

From bottom to top: "formUpdateOrder" is the id that will be used to address this Form in the Script section, "onOrderSuccess" is the name of the function in the Script section that handles a successful submission of the Form, "onOrderBegin" is the name of the function that starts the process of submitting the Form, "POST" is the HttpMethod that is used to send the Action, "OrderUpdate" is the name of that Action in the Controller file and "Example" refers to the name of the Controller (the first part of the name "ExampleController").

The detailed workings of the Saving process will be described at the end of this article.

So the overall top container looks like this:

<div style="height:250px;">
    @using (Ajax.BeginForm("OrderUpdate", "Example", null,
        new AjaxOptions()
        {
            HttpMethod = "POST",
            OnBegin = "onOrderBegin",
            OnSuccess = "onOrderSuccess"
        }, new { @id = "formUpdateOrder" }))
    {
        <table class="w-100">
            <tr>
                <td class="field150">Work Order</td>
                <td>@Html.TextBoxFor(m => m.WorkOrder, new { @readonly = "readonly", @class = "text100" })</td>
            </tr>
            <tr>
                <td class="field150">Plant Location</td>
                <td>@Html.DropDownListFor(m => m.Location, Converter.ToMvcList(Model.Locations), new { @class = "text100" })</td>
            </tr>
            <tr>
                <td class="field150">Sales Order</td>
                <td>@Html.TextBoxFor(m => m.SalesOrder, new { @class = "text100" })</td>
            </tr>
            <tr>
                <td class="field150">Delivery Number</td>
                <td>@Html.TextBoxFor(m => m.Delivery, new { @class = "text100" })</td>
            </tr>
            <tr>
                <td class="field150">Product</td>
                <td>@Html.TextBoxFor(m => m.Product, new { @class = "text100" })</td>
            </tr>
            <tr>
                <td class="field150">Customer Code</td>
                <td>@Html.TextBoxFor(m => m.CustomerID, new { @class = "text100" })</td>
            </tr>
        </table>
    }
</div>

How to Populate Tables with Data

Now that the Structure of everything is set, we can start implementing the different functionalities of the webpage. The first and most essential functionality to be implemented is the population of the Data Table because the other ones rely on it.

Controller file

Every functionality requires an Action in the Controller that handles its request. Since the Population of the Data Table should happen every time the page loads, the Action that is associated with this functionality is the same one that "gets" the page in the first place. Accordingly, an HTTPGET method is used and the Action is named according to what we want to be displayed as the URI of the basic webpage (in this case "Orders"). Furthermore, this Action is not only used when the page loads the first time but also when it reloads after selecting a specific Work Order in the Table, thus it requires a parameter for the id of the selected Work Order.

Within the Action, a new instance of the Model is created by calling the constructor of the same name "ExampleMaster" passing the id as parameter and storing it in a variable "model". The Action returns the View by using the function "PartialView" with the name of the View file as first parameter ("OrderMaster") and the instance of the model that was just created as second parameter ("model").

[HttpGet]
public ActionResult Orders(string id)
{
    var model = new ExampleMaster(id);
    return PartialView("OrderMaster", model);
}

Because this Action is responsible for the loading of the page, we additionally have to add it in the Navigation Editor of the application (under "Settings" in the Toolbar). There, click "Add Menu" and type in the Controller and Action name (here: "example/orders") in "Action Route". As "Group/Directory" select any of the directories, e.g. "Settings" (usually a new Group would be created beforehand that is named after the Controller "Example" but since this is just an example, we just take one of the existing Groups). Then type the name that should be displayed in the Navigation for this page (for example "Example Orders"). Do not forget to check "Set Enabled ".

After clicking "Save" in the Footer of the Modal, click the "Save" Button on the Navigation page.

Model file

Now we implement the function in the Model that is actually doing the Population of the Tables. Since it happens each time the page is loaded, that function actually is the Constructor of the Model. But instead of rewriting the existing Constructor, it is easier to implement a new one that inherits from the basic one. This new Constructor also needs to have a parameter "id" since it gets passed by the Action for the case that the page gets loaded due to the selection of a specific Order. So the Signature of the new Constructor looks like this:

public ExampleMaster(string id) : this()
{
    ...
}

The data that should be populated in the Data Table is derived from the "OrderList" Property of the Model "ExampleMaster". The items in "OrderList" are of the type "ExampleDetail", which is specified in the same Model file. The data for "ExampleDetail" are taken from (but not the same as) the "Production Orders" data repository.

So the first thing that needs to be done is to fetch the Production Orders. For this purpose, the "Get" function is used. "Get" has 4 arguments:

  1. "filter" specifies which items to fetch and which to leave out. In this case, we only want Production Orders that are planned and whose "ProductionID" and "Delivery" of the "SapOrder" is not null.

  2. "orderBy" specifies the order in which the Production Orders should be stored. This is irrelevant in our case, thus it is set to null.

  3. "includeProperties" specifies which external properties should be included. Since none are required here, it is set to be empty.

  4. "rows" specifies the maximum number of Production Orders to be fetched. For our purpose, fetching 100 Production Orders is sufficient, thus it is set to 100.

The data repository where the Orders are taken from is "UnitWork.ProductionOrders" and the Orders should be stored in a variable "orderList", thus the function call is implemented like this:

var orderList = UnitWork.ProductionOrders.Get(
    x => x.IsPlan && x.SapOrder.Delivery != null && x.ProductID != null, 
    null, 
    "", 
    100);

Next, the "ExampleDetail" Objects need to be created from the "Production Order" data and inserted into the "OrderList" one by one. For this purpose, a foreach loop is used that iterates through the fetched Production Orders and for each one, it creates a new "ExampleDetail" assigning the corresponding data to the respective properties and adding it to "OrderList". The loop is implemented like this:

    foreach (var item in orderList)
    {
        OrderList.Add(new ExampleDetail()
        {
            WorkOrder = item.ID,
            Delivery = item.SapOrder.Delivery,
            CustomerID = item.SapOrder.CustId,
            SalesOrder = item.SapOrder.SalesOrder,
            Product = item.ProductID,
            Location = item.Location
        });
    }

Finally, for the case that a specific Order has been selected (i.e. the parameter "id" is not empty), the function that gets the data for the Detail section should be called. How the function itself is implemented is described in the section "How to Get Detailed Data". The function call within the Constructor is implemented like this:

    if (!string.IsNullOrWhiteSpace(id))
        OrderDetail = GetOrderDetail(id);

Taking all the above into consideration, the Constructor is implemented like this:

public ExampleMaster(string id) : this()
{
    //get list of orders
    var orderList = UnitWork.ProductionOrders.Get(x => x.IsPlan && x.SapOrder.Delivery != null && x.ProductID != null, null, "", 100);

    foreach (var item in orderList)
    {
        OrderList.Add(new ExampleDetail()
        {
            WorkOrder = item.ID,
            Delivery = item.SapOrder.Delivery,
            CustomerID = item.SapOrder.CustId,
            SalesOrder = item.SapOrder.SalesOrder,
            Product = item.ProductID,
            Location = item.Location
        });
    }

    if (!string.IsNullOrWhiteSpace(id))
        OrderDetail = GetOrderDetail(id);
}

View file

Now, the only thing left to do is to render the Data Table in the View file. Any functionality in the layout is implemented in the Script section of the View file. Since the structure of the Data Tables (i.e. the data) takes time before it is ready (because of the loops), the JavaScript that specifies the layout of the DataTable is encapsulated by the "$(document).ready" function.

The Data Table in "OrderMaster.cshtml" has the id "tblOrderList" and is specified the following way:

        $("#tblOrderList").dataTable({
            "bJQueryUI": false,
            "autoWidth": false,
            "bLengthChange": false,
            "bFilter": false,
            "iDisplayLength": 500,
            "order": [[1, "asc"]],
            "bInfo": true,
            "aoColumnDefs": [
                { "bSortable": false, "aTargets": [0] },
            ]
        }); 
  • It should not be themed by jQuery UI, thus "bJQueryUI" is set to false

  • It should not enable automatic column width handling, thus "autoWidth" is set to false.

  • It should not contain a search bar, thus "bFilter" is set to false.

  • It should contain pagination, thus "bPaginate" is not specified (because the default is true).

  • It should only display 500 entries at a time, thus "iDisplayLength" is set to 500.

  • It should not enable the user to change the display length, thus "bLengthChange" is set to false.

  • By default, the content should be ordered according to the Work Order number (2nd column) in ascending order, thus "order" is set to "1" and "asc".

  • The order should be changeable according to any of the columns except the first one (because it only contains an icon), thus "aoColumnDefs" is specified with "bSortable" set to false targeting the first column ("aTragets" set to "0"), which means only the first column should not be sortable.

  • It should display information, such as number of total entries, thus "bInfo" is set to true.

How to Get Detailed Data

The next functionality that needs to be implemented is the Population of the Detail section after a Work Order in the Data Table of the List section has been selected.

Controller file

This functionality requires an Action of its own. It is very similar to the first Action in the sense that it also uses HTTPGET as method and requires a parameter for the id of the selected Work Order. It differs from the first Action in that it only returns the Detail section, not the whole page. For this reason, the first parameter of "PartialView" is "OrderDetail" (not "OrderMaster") and the second parameter is "model.GetOrderDetail(id)" (not "model"). Naming the Action "OrderDetail", it is implemented like this:

[HttpGet]
public ActionResult OrderDetail(string id)
{
    var model = new ExampleMaster();
    return PartialView("OrderDetail", model.GetOrderDetail(id));
}

Model file

Now the function "GetOrderDetail", that is used in the Action "OrderDetail" and that is also found in the Constructor, will be implemented. Since it depends on the specific Work Order that has been selected, it needs a parameter "id". Its return value is an object of type "ExampleDetail".

First, it creates a new instance of "ExampleDetail" and stores it in a new variable "orderDetail"

Second, it fetches the Production Order corresponding to the selected Work Order into the variable "order". It does so by using the "GetById" function passing "id" as parameter. Then each Property of "orderDetail" is assigned the corresponding Property of "order". The property "Components" is a list of "ExampleParts", which is an Object, thus, for this case, a loop is used that creates a new instance of "ExamplePart" for every "Component" that "order" has, assigning the corresponding values to the Properties of "ExamplePart" and adding it to the "Components" List of "orderDetail". Because this whole fetching and assigning process necessitates that the passed "id" is not empty, this whole second step is encapsulated by an if-statement.

Finally, "orderDetail" is returned.

Overall, the function is implemented like this:

public ExampleDetail GetOrderDetail(string id)
{
    var orderDetail = new ExampleDetail();

    //get detail of work  order
    if (!string.IsNullOrWhiteSpace(id))
    {
        var order = UnitWork.ProductionOrders.GetByID(id);

        orderDetail.WorkOrder = order.ID;
        orderDetail.Delivery = order.SapOrder.Delivery;
        orderDetail.CustomerID = order.SapOrder.CustId;
        orderDetail.SalesOrder = order.SapOrder.SalesOrder;
        orderDetail.Product = order.ProductID;
        orderDetail.Location = order.Location;
        orderDetail.Locations = MiddleContext.GetUserScopes;

        //add components
        foreach (var item in order.Components)
        {
            orderDetail.Components.Add(new ExamplePart()
            {
                Name = item.Name,
                Position = item.Position,
                Quantity = item.QtyInSU,
                Category = item.Category
            });
        }
    }

    return orderDetail;
}

View file

The whole process of displaying the data of a specific Work Order in the Detail section is triggered by clicking the icon of a specific row in the first column of the Data Table. Since this functionality is also dependent on the Data Table, the JavaScript function is also encapsulated by the "$(document).ready" function together with the implementation of the Data Table.

The icon has the class "go-detail, so the click event function is applied on this class where the functionlaity is implemented in the following way:

$(".go-detail").click(function () {
    $.getPartial("example/orderDetail/" + $(this).attr("data-attr"), "div-order-detail", true, "example/orders/" + $(this).attr("data-attr"));
})

The function "$.getPartial" is used because the Partial View "OrderDetail.cshtml" should be updated. "$.getPartial" has 4 arguments:

  1. "url" requires the URI of the appropriate Action together with all necessary parameters:

    • Because all Actions of this webpage are located in the "ExampleController" file, the first part of the URI is "example/"

    • Because the Action that "gets" the Partial View is named "OrderDetail", the second part of the URI is "orderdetail/"

    • OrderDetail requires a parameter "id" that specifies the specific Work Order that should be displayed. Because this part of the URI is variable depending on which row was clicked, "+ $(this).attr("data-attr")" is added to the URI string using "data-attr" as reference that was already set to the respective Work Order of the row in the implementation of the Data Table (see "View Structure (OrderMaster)").

  2. "id" requires the id of the specific container the Partial View should be loaded into. In this case, it is "div-order-detail".

  3. "chg" is set to true because the URI should be updated along with the webpage.

  4. "newUrl" requires the updated URI which is "example/orders/" with the respective Work Order number. This is done similarly to the first parameter.

Furthermore, the Detail section also contains a Data Table. The rendering of this Table is done similarly to the other one, however, it is not located in "OrderMaster.cshtml" but in "OrderDetail.cshtml". Referring to it by its id "tblComponent", it is specified the following way:

        $("#tblComponent").dataTable({
            "bJQueryUI": false,
            "bFilter": false,
            "bPaginate": false,
            "bAutoWidth": false,
            "aoColumnDefs": [
                { "bSortable": false, "aTargets": [0] }
            ]
        });
  • It should not be themed by jQuery UI, thus "bJQueryUI" is set to false

  • It should not enable automatic column width handling, thus "bAutoWidth" is set to false.

  • It should not contain a search bar, thus "bFilter" is set to false.

  • It should not contain pagination, thus "bPaginate" is set to false.

  • The order should be changeable according to any of the columns except the first one, thus "aoColumnDefs" is specified with "bSortable" set to false targeting the first column ("aTragets" set to "0"), which means only the first column should not be sortable.

How to Edit Data

The last and the most complex functionality that needs to be implemented is the one that takes care of the saving process after a change has been made to a specific Work Order in the Detail section.

Controller file

When the AJAX header for the submission form has been created in the "OrderDetail.cshtml" View file, it has been mentioned that "OrderUpdate" is the Action corresponding to the Saving process. Since it is a functionality that updates data in the database, the HTTPPOST method is used. It also requires a parameter that passes the "ExampleDetail" instance with the updated data. The Action returns a JSON object that contains a call of the function "SaveOrder" (a method in the Model that willl be explained in detail in the next section). Because the function is implemented in the Model file, a new instance of the Model "ExampleMaster" needs to be created beforehand to access it. Overall, the Action "OrderUpdate" is implemented like this:

[HttpPost]
public ActionResult OrderUpdate(ExampleDetail data)
{
    var model = new ExampleMaster();
    return Json(new { succeed = model.SaveOrder(data) }, JsonRequestBehavior.AllowGet);
}

Model file

The function "SaveOrder" evaluates if the Update has been successful or not, thus returns a boolean value. It requires the "ExampleDetail" with the updated data as parameter as well.

First, it fetches the corresponding Production Order from the database using the "GetById" method and stores it in a variable "order". It passes the value of the Property "WorkOrder" as input parameter because this is the one that equals the id.

Then, if the corresponding order is actually found and not empty, the Properties of "order" are assigned the corresponding (updated) values from the passed "ExampleDetail" object.

Finally, it returns true, i.e. it only returns true when all of the above has been successfully executed without error.

Overall, the "SaveOrder" function is implemented like this:

public bool SaveOrder(ExampleDetail data)
{
    var order = UnitWork.ProductionOrders.GetByID(data.WorkOrder);

    if (order != null)
    {
        order.Location = data.Location;
        order.SapOrder.SalesOrder = data.SalesOrder;
        order.SapOrder.Delivery = data.Delivery;

        UnitWork.ProductionOrders.Update(order);
    }

    return true;
}

View file

The whole process of saving an edited Work Order is triggered by clicking the "Update" Button in the top of the page. When implementing the AJAX Header in the View file, it was also mentioned that there are two functions "onOrderBegin" and "onOrderSuccess" handling the submission process of the Form. So in total, the Saving process consists of three functionalities:

  1. the "Update" Button

  2. the "onOrderBegin" function

  3. the "onOrderSuccess" function

The functionality of the "Update" Button is specified in the "OrderMaster.cshtml" file because the Button is implemented there. It is also encapsulated by the "$(document).ready" function. It is an "on click" event that should trigger the submission of the form in the Detail Section that has the id "formUpdateOrder" (see View Structure (OrderDetail)), thus the functionality is implemented like this:

The functionality of the "Update" Button is encapsulated by the "$(document).ready" function. It is an "on click" event that should trigger the submission of the form that has the id "frmCustomerDetail" , thus the functionality is implemented like this:

$("#btn-update").click(function () {
    $("#formUpdateOrder").submit();
})

The functionality of the "onOrderBegin" function is specified in the "OrderDetail.cshtml" file because the AJAX header is defined there. It is NOT encapsulated by the "$(document).ready" function because it is an independent function.

While the submission process is on-going, the UI should be blocked. This is all that should be done when starting the saving process, thus the function is implemented like this:

function onOrderBegin() { $.blockUI(); }

The functionality of the "onOrderSuccess" function is specified in the "OrderDetail.cshtml" file because the AJAX header is defined there. It is NOT encapsulated by the "$(document).ready" function because it is an independent function.

When the saving process has been successful, the page should be refreshed, so the updated data will be displayed. To evaluate if the process has been successful or not, the "$.response" function is used that evaluates the data, which is passed on as parameter, and executes the refreshing of the webpage (using "$.getContent" and "$.parseURL") only if the data succeeded. The whole function is implemented like this:

function onOrderSuccess(data) {
    $.response(data, function () {
        if (data.succeed) $.getContent($.parseURL());
    })
}

Registering Actions

As a final step, the Actions that have been implemented in the Controller need to be registered before the page can run. For this purpose, navigate to "Access Assignment" under "Settings", click the "Synch" Button, select "Example" in the "Resource Group" Dropdown List, click the "Check All" Button and finally click the "Save" Button. Now you can access the page with all its functionalities.

Last updated