Showing posts with label DataTables. Show all posts
Showing posts with label DataTables. Show all posts

Wednesday, December 11, 2019

DataTable.js Tutorial for .Net Core Razor Pages Application - Part 2 - Full CRUD



This is the continuation of the Part 1 tutorial. The main goal here is to apply rendered elements in the datatable, which can contain various HTML tags, such as links/operations with other pages.

Technologies used:
1. Javascript, Datatables.js
2. Razor Pages, .Net Core
3. In-Memory Database in .Net Core

Prerequisites:
1. Asp.Net Core 2.2  Razor Pages, see suggested learning: https://mydev-journey.blogspot.com/2019/11/razor-pages-not-for-shaving.html
2. In-memory Database, presented in tutorial: https://exceptionnotfound.net/ef-core-inmemory-asp-net-core-store-database/
3. I was inspired by this tutorial: https://www.c-sharpcorner.com/article/using-datatables-grid-with-asp-net-mvc/
4. See Part 1, which is the more simple approach for DataTables:  https://mydev-journey.blogspot.com/
5. Link for Part 2 Repository, zipped: https://drive.google.com/open?id=1PT9Tk77m2gfZVrFmLwefSt_lqXuYyvEr
6. setup the wwwroot folder, in a similar way as for the Part 1 tutorial
7. you can view the application live online: http://datatables.azurewebsites.net/

Steps:
1. Create Razor Web Project
2. Create Base Class:
    public class InvoiceModel
    {
        [JsonProperty(PropertyName = "ID")]
        public int ID { get; set; }
        [JsonProperty(PropertyName = "InvoiceNumber")]
        public int InvoiceNumber { get; set; }
        [JsonProperty(PropertyName = "Amount")]
        public double Amount { get; set; }
        [JsonProperty(PropertyName = "CostCategory")]
        public string CostCategory { get; set; }
        [JsonProperty(PropertyName = "Period")]
        public string Period { get; set; }   
    }
3. Create and Populate in-memory database and table
Create Context:
    public class InvoiceContext : DbContext
    {
        public InvoiceContext(DbContextOptions<InvoiceContext> options)
            : base(options)
        {
        }

        public DbSet<InvoiceModel> InvoiceTable { get; set; }
    }
Create Invoice Generator Service
 public class InvoiceGenerator
    {
      public static void Initialize(IServiceProvider serviceProvider)
        {
            using (var context = new InvoiceContext(serviceProvider.GetRequiredService<DbContextOptions<InvoiceContext>>()))
            {
                // Look for any board games.
                if (context.InvoiceTable.Any())
                {
                    return;   // Data was already seeded
                }

                context.InvoiceTable.AddRange(
                new InvoiceModel() { ID=1, InvoiceNumber = 1, Amount = 10, CostCategory = "Utilities", Period = "2019_11" },
                new InvoiceModel() { ID=2, InvoiceNumber = 2, Amount = 50, CostCategory = "Telephone", Period = "2019_12" },
                new InvoiceModel() { ID = 3, InvoiceNumber = 3, Amount = 30, CostCategory = "Services", Period = "2019_11" },
                new InvoiceModel() { ID = 4, InvoiceNumber = 4, Amount = 40, CostCategory = "Consultancy", Period = "2019_11" },
                new InvoiceModel() { ID = 5, InvoiceNumber = 5, Amount = 60, CostCategory = "Raw materials", Period = "2019_10" },
                new InvoiceModel() { ID = 6, InvoiceNumber = 6, Amount = 10, CostCategory = "Raw materials", Period = "2019_11" },
                new InvoiceModel() { ID = 7, InvoiceNumber = 7, Amount = 30, CostCategory = "Raw materials", Period = "2019_11" },
                new InvoiceModel() { ID = 8, InvoiceNumber = 8, Amount = 30, CostCategory = "Services", Period = "2019_11" },
                new InvoiceModel() { ID = 9, InvoiceNumber = 8, Amount = 20, CostCategory = "Services", Period = "2019_11" },
                new InvoiceModel() { ID = 10, InvoiceNumber = 9, Amount = 2, CostCategory = "Services", Period = "2019_11" },
                new InvoiceModel() { ID = 11, InvoiceNumber = 10, Amount = 24, CostCategory = "Services", Period = "2019_11" },
                new InvoiceModel() { ID = 12, InvoiceNumber = 11, Amount = 10, CostCategory = "Telephone", Period = "2019_11" },
                new InvoiceModel() { ID = 13, InvoiceNumber = 12, Amount = 40, CostCategory = "Consultancy", Period = "2019_12" },
                new InvoiceModel() { ID = 14, InvoiceNumber = 13, Amount = 50, CostCategory = "Services", Period = "2019_11" },
                new InvoiceModel() { ID = 15, InvoiceNumber = 14, Amount = 40, CostCategory = "Utilities", Period = "2019_11" },
                new InvoiceModel() { ID = 16, InvoiceNumber = 15, Amount = 10, CostCategory = "Services", Period = "2019_11" });

                context.SaveChanges();
            }

        }


4. Register the database
within Startup cs, above add MVC command:
            services.AddDbContext<InvoiceContext>(options => options.UseInMemoryDatabase(databaseName: "InvoiceDB"));

within Program Cs, we need to make changes, see final version:
 public class Program
    {
        public static void Main(string[] args)
        {


            var host = CreateWebHostBuilder(args).Build();

            //2. Find the service layer within our scope.
            using (var scope = host.Services.CreateScope())
            {
                //3. Get the instance of BoardGamesDBContext in our services layer
                var services = scope.ServiceProvider;
                var context = services.GetRequiredService<InvoiceContext>();

                //4. Call the DataGenerator to create sample data
                InvoiceGenerator.Initialize(services);
            }
            //Continue to run the application
            host.Run();
            //CreateWebHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }
5. Using EF, the tables are populated in all Pages, see example for Index:

PageModel:
  public class IndexModel : PageModel
    {
        private InvoiceContext _context;

        public List<InvoiceModel> InvoiceList;
        public IndexModel(InvoiceContext context)
        {
            _context = context;
        }
        public void OnGet()
        {
            InvoiceList = _context.InvoiceTable.ToList();
        }
    }
CSHTML file
will be a simple listing of the InvoiceTable using foreach (actually you can scaffold this view)
6. DataTableArrayRender page:
Will contain the datatable js code, together with the rendered html elements:

@page
@model DataTableArrayRenderModel
@{
    ViewData["Title"] = "Invoice List - With Datatable - from Javascript Array";
}

    <div class="text-center">
        <h1 class="display-4">Show DataTable - from Javascript Array -  Rendered Columns</h1>
        <p>
            <a asp-page="Index">Show original Table (Html from Razor)</a>
        </p>
        <p>
            <a asp-page="InvoiceAdd" class="btn btn-info">Add New Invoice</a>
        </p>
    </div>

<script type="text/javascript" language="javascript" src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/js/jquery.dataTables.min.js"></script>

<script>
    /////////
    function convertToDataSet(responseJSON) {
        console.log(responseJSON);
        var returnList = [];
        var returnitem = [];
        for (var i = 0; i < responseJSON.length; i++) {
            console.log(responseJSON[i]);
            returnitem = [];
            returnitem.push(responseJSON[i].ID);
            returnitem.push(responseJSON[i].InvoiceNumber);
            returnitem.push(responseJSON[i].Amount);
            returnitem.push(responseJSON[i].CostCategory);
            returnitem.push(responseJSON[i].Period);
            returnList.push(returnitem);
        }
        return returnList;
    }

    function getTable() {
        return fetch('./DataTableArrayRender?handler=ArrayDataRender',
            {
                method: 'get',
                headers: {
                    'Content-Type': 'application/json;charset=UTF-8'
                }
            })
            .then(function (response) {
                if (response.ok) {
                    return response.text();
                } else {
                    throw Error('Response Not OK');
                }
            })
            .then(function (text) {
                try {
                    return JSON.parse(text);
                } catch (err) {
                    throw Error('Method Not Found');
                }
            })
            .then(function (responseJSON) {
                var dataSet = convertToDataSet(responseJSON);
                console.log(dataSet);
                $(document).ready(function () {
                    $('#example').DataTable({
                        data: dataSet,
                        "processing": true, // for show progress bar
                        "filter": true, // this is for disable filter (search box)
                        "orderMulti": false, // for disable multiple column at once
           
                        columns: [
                            { title: "ID" },
                            { title: "InvoiceNumber" },
                            { title: "Amount" },
                            { title: "CostCategory" },
                            { title: "Period" },
                            {
                                data: null, render: function (data, type, row) {                                 
                                    return '<a class="btn btn-danger" href="/InvoiceDelete?id=' + row[0] + '">Delete</a>';
                                }
                            },
                            {
                                "render": function (data, type, full, meta)
                                { return '<a class="btn btn-info" href="/InvoiceEdit?id=' + full[0] + '">Edit</a>'; }
                            },
                            {
                                "render": function (data, type, full, meta)
                                { return '<a class="btn btn-warning" href="/Index">Main Page</a>'; }
                            },
                        ]
                    });
                });
            })
    }

    getTable();
</script>

<table id="example" class="display" width="100%"></table>

7. Using the InvoiceModel, we can scaffold all pages like Delete, Create, Edit using EF model scaffolding of Razor Pages.
8. The end result will be a nice navigation table that besides the invoice data, will contain the rendered buttons/links.
End result:


DataTable.js Tutorial for .Net Core Razor Pages Application - Part 1


As I mentioned in my previous posts, my goal is to gather a number of  open source libraries, that can be implemented in my .NET Core project, alongside with Razor pages. My projects have to serve business purposes, so they need certain features that make them similar to Microsoft Excel.

Until now, I found a solution for:
a. Charting, with Chart.js, shown in tutorial: https://mydev-journey.blogspot.com/2019/12/chartjs-tutorial-with-aspnet-core-22.html
b. Pivot Table, with PivotTable.js, shown in tutorial: https://mydev-journey.blogspot.com/2019/12/pivottablejs-in-net-core-razor-pages.html
c. Fast Report, open source reporting tool, will be presented soon;
d. DataTables - present tutorial.
e. AgGrid or other grid systems - future plan.

In the Javascript opensource world, https://datatables.net/ seems to be a respected solution for grids on the frontend. This is what I want to learn and implement in my project, and in the meantime, to share with you. I would like to study later a more advanced solution with datatables, with additional features, and perhaps that will be a continuation for this tutorial, another time.


Materials to study:
I. DataTables site, and two small examples that are the backbone of this tutorial:
a. https://datatables.net/examples/data_sources/dom.html
b. https://datatables.net/examples/data_sources/js_array.html
II. My Razor projects with Chart and PivotTable, see my tutorials at a) and b) above, see link:  https://mydev-journey.blogspot.com/2019/12/pivottablejs-in-net-core-razor-pages.html
III. Download my code from zipped repo: https://drive.google.com/open?id=1g1eJkqV1dphV0iAPQiqpLJSQtt_iYjeX
IV. Download the zipped DataTables files from: https://datatables.net/download/
Download the Css and JS file specific to Datatables, and place them into the CSS and JS folder of wwwroot.

jquery.dataTables.min.css => will go to the wwwroot/css folder
jquery.dataTables.min.js => will go to the wwwroot/js folder
then, copy the images files to wwwroot/images


Steps to follow for this introductory tutorial:
1. Create a .Net Core Web Project (Razor pages)
2. Create Base Class - InvoiceModel
    public class InvoiceModel
    {
        [JsonProperty(PropertyName = "InvoiceNumber")]
        public int InvoiceNumber { get; set; }

        [JsonProperty(PropertyName = "Amount")]
        public double Amount { get; set; }
        [JsonProperty(PropertyName = "CostCategory")]
        public string CostCategory { get; set; }
        [JsonProperty(PropertyName = "Period")]
        public string Period { get; set; }   

    }

3. Create Service to Populate List of  Invoices.

 public class InvoiceService
    {
        public List<InvoiceModel> GetInvoices()
        {
            return new List<InvoiceModel>()
            {
                new InvoiceModel() {InvoiceNumber = 1, Amount = 10, CostCategory = "Utilities", Period="2019_11"},
                new InvoiceModel() {InvoiceNumber = 2, Amount = 50, CostCategory = "Telephone", Period="2019_12"},
                new InvoiceModel() {InvoiceNumber = 3, Amount = 30, CostCategory = "Services", Period="2019_11"},
                new InvoiceModel() {InvoiceNumber = 4, Amount = 40, CostCategory = "Consultancy", Period="2019_11"},
                new InvoiceModel() {InvoiceNumber = 5, Amount = 60, CostCategory = "Raw materials", Period="2019_10"},
                new InvoiceModel() {InvoiceNumber = 6, Amount = 10, CostCategory = "Raw materials", Period="2019_11"},
                new InvoiceModel() {InvoiceNumber = 7, Amount = 30, CostCategory = "Raw materials", Period="2019_11"},
                new InvoiceModel() {InvoiceNumber = 8, Amount = 30, CostCategory = "Services", Period="2019_11"},
                new InvoiceModel() {InvoiceNumber = 8, Amount = 20, CostCategory = "Services", Period="2019_11"},
                new InvoiceModel() {InvoiceNumber = 9, Amount = 2, CostCategory = "Services", Period="2019_11"},
                new InvoiceModel() {InvoiceNumber = 10, Amount = 24, CostCategory = "Services", Period="2019_11"},
                new InvoiceModel() {InvoiceNumber = 11, Amount = 10, CostCategory = "Telephone", Period="2019_11"},
                new InvoiceModel() {InvoiceNumber = 12, Amount = 40, CostCategory = "Consultancy", Period="2019_12"},
                new InvoiceModel() {InvoiceNumber = 13, Amount = 50, CostCategory = "Services", Period="2019_11"},
                new InvoiceModel() {InvoiceNumber = 14, Amount = 40, CostCategory = "Utilities", Period="2019_11"},
                new InvoiceModel() {InvoiceNumber = 15, Amount = 10, CostCategory = "Services", Period="2019_11"},
            };
        }
    }

This will be injected in the pages where the list is needed.

We need to register in the services, startup.cs, just above the AddMvc command.
            services.AddTransient<InvoiceService>();

4. Create special layout file, which inserts the necessary css file in the head of the new Layout file.
in the head, this will be inserted:
    <link rel="stylesheet" href="~/css/jquery.dataTables.min.css" /> 

See _DataTableLayout from my repo.

4. Index page: will display a html table with the elements of the list above, using the classic Razor Page syntax.


5. DataTable page will contain the JS Code to transform an existing Html table to the DataTable grid format, according to study material I - a) above.

in the Razor Page, the following Javascript code will be inserted, below the listing of the table:

<script type="text/javascript" language="javascript" src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/js/jquery.dataTables.min.js"></script>
<script>
    $(document).ready(function () {
        $('#example').DataTable({
            "order": [[3, "desc"]]
        });
    });
</script>

Important aspect here: the html table has to have the id "example" to be transformed into the grid format by the datatable js commands.

6. DataTableAjax will use AJAX Fetch in javascript to generate an array which will be used as a datasource for the array, according to study material I - b) above.

<script type="text/javascript" language="javascript" src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/js/jquery.dataTables.min.js"></script>

<script>
    /////////
    function convertToDataSet(responseJSON) {
        console.log(responseJSON);
        var returnList = [];
        var returnitem = [];
        for (var i = 0; i < responseJSON.length; i++) {
            console.log(responseJSON[i]);
            returnitem = [];
            returnitem.push(responseJSON[i].InvoiceNumber);
            returnitem.push(responseJSON[i].Amount);
            returnitem.push(responseJSON[i].CostCategory);
            returnitem.push(responseJSON[i].Period);
            returnList.push(returnitem);
        }
        return returnList;
    }

    function getTable() {
        return fetch('./DataTableArray?handler=ArrayData',
            {
                method: 'get',
                headers: {
                    'Content-Type': 'application/json;charset=UTF-8'
                }
            })
            .then(function (response) {
                if (response.ok) {
                    return response.text();
                } else {
                    throw Error('Response Not OK');
                }
            })
            .then(function (text) {
                try {
                    return JSON.parse(text);
                } catch (err) {
                    throw Error('Method Not Found');
                }
            })
            .then(function (responseJSON) {
                var dataSet = convertToDataSet(responseJSON);
                console.log(dataSet);
                $(document).ready(function () {
                    $('#example').DataTable({
                        data: dataSet,
                        columns: [
                            { title: "InvoiceNumber" },
                            { title: "Amount" },
                            { title: "CostCategory" },
                            { title: "Period" },
                        ]
                    });
                });
            })
    }

    getTable();
</script>

<table id="example" class="display" width="100%"></table>


7. The final result will be:
a. from html table:


















b. from Javascript Array: