Search code examples
jqueryasp.net-mvcasp.net-mvc-5jquery-jtable

Integrating jTable (jQuery) into an ASP.NET MVC 5 project that uses a default layout


I'm having difficulty integrating a jTable into an ASP.NET MVC 5 project that uses a default Layout (_Layout.cshtml). Here's my attempt at an MCVE:

I created a new ASP.NET MVC project named "mvc5_jTable" in Visual Studio Community 2013. I created a model, "Client.cs":

namespace mvc5_jTable.Models
{
    public class Client
    {
        public int ID { get; set; }
        public string LastName { get; set; }

        public Client(int newID, string newLastName) 
        {
            ID = newID;
            LastName = newLastName;
        }
    }
}

I modified the Index() action of the HomeController:

using mvc5_jTable.Models;
using System.Collections.Generic;
using System.Web.Mvc;

namespace mvc5_jTable.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            List<Client> clients = new List<Client>();
            clients.Add(new Client(1, "Starsky"));
            clients.Add(new Client(2, "Hutch"));
            return View(clients);
        }

        public ActionResult About()
        {
            ViewBag.Message = "Your application description page.";

            return View();
        }

        public ActionResult Contact()
        {
            ViewBag.Message = "Your contact page.";

            return View();
        }
    }
}

To verify that things were working to this point I replaced the boilerplate in ~/Views/Home/Index.cshtml with code to produce a plain old HTML table:

@model IEnumerable<mvc5_jTable.Models.Client>
@{
    ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
    <table class="table">
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.ID)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.LastName)
            </th>
        </tr>

        @foreach (var item in Model)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.ID)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.LastName)
                </td>
            </tr>
        }

    </table>

When I run the project I see this, which indicates that we're good so far:

Index1.png

Now, I want to display the data in a jTable so I launch NuGet via "Manage NuGet Packages for Solution..." and search for jTable. NuGet finds v2.4.0 for me (which I verified to be the latest stable version according to http://jtable.org/), so I clicked the "Install" button. When the install is complete I check the "Installed packages" in NuGet and in that list I see

jQuery 1.10.2
jQuery UI (Combined Library) 1.9.2
jTable 2.4.0

After much searching and several unsuccessful attempts I finally got something working using a combination of

AJAX based CRUD tables using ASP.NET MVC 3 and jTable jQuery plug-in

and the answer to the question in another forum here

jquery file error while loading jtable in MVC 4 application ASP.net

I replaced the contents of the Index.cshtml view with

@{
    Layout = null;
    ViewBag.Title = "Index";
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
    <link href="~/Scripts/jtable/themes/metro/blue/jtable.min.css" rel="stylesheet" />
    <link rel="stylesheet" href="http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css">
    <script src="~/Scripts/jquery-1.10.2.js"></script>
    <script src="~/Scripts/jquery-ui-1.9.2.js"></script>
    <script src="~/Scripts/jtable/jquery.jtable.js"></script>
</head>
<body>
    <div id="ClientTableContainer1"></div>
    <script type="text/javascript">

    $(document).ready(function () {

     $('#ClientTableContainer1').jtable({
         title: 'Client List',
         actions: {
            listAction: '@Url.Action("ClientList")',
            deleteAction: '@Url.Action("DeleteClient")',
            updateAction: '@Url.Action("UpdateClient")',
            createAction: '@Url.Action("CreateClient")'
        },
        fields: {
            ID: {
                key: true,
                create: false,
                edit: false,
                list: true
            },
            LastName: {
                title: 'LastName',
                width: '12%'

            }
        }
    });

      //Load student list from server
      $('#ClientTableContainer1').jtable('load');
  });

    </script>
</body>
</html>

and I added the following method to HomeController.cs:

[HttpPost]
public JsonResult ClientList()
{
    try
    {
        List<Client> clients = new List<Client>();
        clients.Add(new Client(1, "Starsky"));
        clients.Add(new Client(2, "Hutch"));
        return Json(new { Result = "OK", Records = clients });
    }
    catch (Exception ex)
    {
        return Json(new { Result = "ERROR", Message = ex.Message });
    }
}

That works, too

Index2.png

but it is a standalone page, not taking advantage of the overall layout for the site (_Layout.cshtml). When I try to re-work Index.cshtml as a partial view by moving the stylesheet and script references from the <head> section of Index.cshtml into the <head> section of _Layout.cshtml, commenting out the Layout = null; and leaving just the body content in Index.cshtml

@{
    //Layout = null;
    ViewBag.Title = "Index";
}
    <div id="ClientTableContainer1"></div>
    <script type="text/javascript">

    $(document).ready(function () {

     $('#ClientTableContainer1').jtable({
         title: 'Client List',
         actions: {
            listAction: '@Url.Action("ClientList")',
            deleteAction: '@Url.Action("DeleteClient")',
            updateAction: '@Url.Action("UpdateClient")',
            createAction: '@Url.Action("CreateClient")'
        },
        fields: {
            ID: {
                key: true,
                create: false,
                edit: false,
                list: true
            },
            LastName: {
                title: 'LastName',
                width: '12%'
            }
        }
    });

      //Load student list from server
      $('#ClientTableContainer1').jtable('load');
  });

    </script>

I get the standard header and footer from _Layout.cshtml but nothing in between.

Index3.png

When I check the JavaScript console in Chrome it shows the error

Uncaught TypeError: undefined is not a function

with the offending line being line 53 of my (revised) Index.cshtml

$('#ClientTableContainer1').jtable({

I think that perhaps the issue relates to _Layout.cshtml enclosing the body content in another <div>

<div class="container body-content">
    @RenderBody()
    <hr />
    <footer>
        <p>&copy; @DateTime.Now.Year - My ASP.NET Application</p>
    </footer>
</div>

but I'm guessing at this point.


Solution

  • It could possibly have to do with your bundles config references and loading of the scripts through your Layout.cshtml, since it works when you hard code the references in your view.

    With these plugins it is important to have the order of files loaded correctly and also the references pointed in the right direction.

    For example, in the header of your Layout.cshtml:

    <head>
        <meta charset="utf-8" />
        <title>@ViewBag.Title</title>
        <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
        <meta name="viewport" content="width=device-width" />
            @Styles.Render("~/Content/css")
            @Styles.Render("~/Content/themes/base/css")
            @Scripts.Render("~/bundles/modernizr")
            @Scripts.Render("~/bundles/jquery")
            @Scripts.Render("~/bundles/jqueryui")
            @Scripts.Render("~/bundles/jquery.jtable")
    

    And then in the bundles.config, make sure that you have these references pointed to the right place - e.g. use of {version} and * to ensure the next time you update through nuget it will still be OK.

    bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
                        "~/Scripts/jquery-ui-{version}.js"));
    
    bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                        "~/Scripts/jquery.unobtrusive*",
                        "~/Scripts/jquery.validate*"));
    

    After your header, you will also need to render scripts within the body by including this in your layout view and then adding a section to your index view.

    In Layout:

        <div id="body">
            <section class="content-wrapper main-content clear-fix">
                @RenderBody()
            </section>
        </div>
        @RenderSection("scripts", required: false)
    </body>
    

    In Index:

    @model ARAWeb.Models.PurchaseOrder
    @{
        ViewBag.Title = "Index";
     }
    
    @*Links to css*@
    <link rel="stylesheet" href="http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css">
    
    @section scripts {
        <script type="text/javascript">