Wednesday, 10 August 2016

Bootstrap DataTables and Modal Dialog Forms (CRUD)

So I've got this HTML snippet to generate the table above:

<div 
  class="container">
    <table 
      id="actionTabDataTable" 
      class="table table-striped table-bordered" 
      cellspacing="0" 
      width="100%"></table>
</div>
<!-- Modal -->
<div 
  class="modal fade" 
  id="myModal" 
  tabindex="-1" 
  role="dialog" 
  aria-labelledby="myModalLabel">
    <div 
      class="modal-dialog" 
      role="document">
        <div 
          class="modal-content">
            <div 
              class="modal-header">
                <button 
                  type="button" 
                  class="close" 
                  data-dismiss="modal" 
                  aria-label="Close">
                    <span 
                      aria-hidden="true">
                        &times;
                    </span>
                </button>
                <h4 
                  class="modal-title" 
                  id="myModalLabel">
                    Please tell us about the job 
                    <span 
                      id="name"></span> 
                    has:
                </h4>
            </div>
            <div 
              class="modal-body">
                <form>
                    <div 
                      class="form-group">
                        <label 
                          for="job">
                            Job title
                        </label>
                        <input 
                          type="text" 
                          class="form-control" 
                          id="job" 
                          name="job" />
                    </div>
                </form>
            </div>
            <div 
              class="modal-footer">
                <button 
                  type="button" 
                  class="btn btn-default" 
                  data-dismiss="modal">
                    Close
                </button>
                <button 
                  type="button" 
                  class="btn btn-primary">
                    Add
                </button>
            </div>
        </div>
    </div>
</div>

And I want to be able to interact with the underlying data which is an array of objects generated from a form further back in the mists of time... or at least earlier in the process anyway. So I use the following snippet of JavaScript:

var data = [{
    "name": "John Smith",
    "jobs": [
        "Bottle Washer",
        "Bus Boy"
    ]
}, {
    "name": "Jane Smith",
    "jobs": [
        "Head Chef",
        "Barmaid"
    ]
}, {
    "name": "Barry Smith"
}];
$(function(){
    var table = $("#actionTabDataTable").DataTable({
        "data": data,
        "columns": [{
            "title": "Name",
            "data": "name"
        }, {
            "title": "Jobs",
            "orderable": false,
            "data": "jobs",
            "render": function(d) {
                if (d) {
                    return $("<ul></ul>", {
                        "class": "list-group"
                    }).append(function() {
                        var lis = [];
                        for (var i = 0; i < d.length; i++) {
                            lis.push($("<li></li>", {
                                "text": d[i],
                                "class": "list-group-item"
                            }).append($("<i></i>", {
                                "class": "glyphicon glyphicon-remove"
                            })).append($("<i></i>", {
                                "class": "glyphicon glyphicon-edit",
                                "data-toggle": "modal",
                                "data-target": "#myModal"
                            })));
                        }
                        return lis;
                    }).prop("outerHTML");
                } else {
                    return "No jobs";
                }
            }
        }, {
            "title": "Action",
            "orderable": false,
            "render": function() {
                return $("<button></button>", {
                    "class": "btn btn-primary",
                    "text": "Add",
                    "data-toggle": "modal",
                    "data-target": "#myModal"
                }).append($("<i></i>", {
                    "class": "glyphicon glyphicon-plus"
                })).prop("outerHTML");
            }
        }]
    });

    $("#actionTabDataTable tbody").on("click", ".glyphicon-remove", function() {
        var d = table.row($(this).parents("tr")).data();
        var job = $(this).parents("li").text();
        $.each(data, function(k, v) {
            if (v.name === d.name) {
                console.log(v.jobs.length);
                for (var i = 0; i < v.jobs.length; i++) {
                    if (v.jobs[i] === job) {
                        v.jobs.splice(i, 1);
                        !v.jobs.length && delete v.jobs;
                        break;
                    }
                }
            }
        });
        table.clear().rows.add(data).draw();
    }).on("click", ".glyphicon-edit", function() {
        var d = table.row($(this).parents("tr")).data();
        var job = $(this).parents("li").text();
        $("#name").text(d.name);
        $("#job").val(job);
        $("#myModal").data({
            "original": d,
            "job": job
        }).find(".btn-primary").text("Update");
    }).on("click", ".btn-primary", function() {
        var d = table.row($(this).parents("tr")).data();
        $("#myModal").data("original", d);
        $("#name").text(d.name);
    });
    $("#myModal").on("click", ".btn-primary", function() {
        var d = $("#myModal").data("original");
        var j = $("#myModal").data("job");
        $.each(data, function(k, v) {
            if (v.name === d.name) {
                if ($("#myModal").find(".btn-primary").text() === "Update") {
                    $.each(v.jobs, function(a, b) {
                        if (b === j) {
                            v.jobs[a] = $("#job").val();
                        }
                    });
                } else {
                    if (v.hasOwnProperty("jobs") && Array.isArray(v.jobs)) {
                        v.jobs.push($("#job").val());
                    } else {
                        v.jobs = [$("#job").val()];
                    }
                }
            }
        });
        table.clear().rows.add(data).draw();
        $("#myModal").modal("hide");
    }).on("hidden.bs.modal", function() {
        $("#job").val("");
        $("#myModal")
         .removeData("original")
            .removeData("job")
            .find(".btn-primary")
             .text("Add");
    });
});

Basically this allows us to interact with the underlying data used to generate the table without having to worry about rendering the data again. It's up and running here.

I've had to tweak the CSS a little as well, here it is:

.list-group {
    text-align: left;
    margin-bottom:0;
}
.glyphicon {
    padding: 0 0 0 10px;
    cursor: pointer;
    float:right;
}