OfirD
OfirD

Reputation: 10490

Knockout template with both different td cells and one common td cell

I'm using the following html as a template to build a table (this is actually Knockout's simpleGrid template):

<script type='text/html' id='template'> 
    <table class="table table-bordered table-condensed" >
       <thead>
          <tr class="active" data-bind="foreach: columns">
             <th data-bind="text: headerText"></th>
          </tr>
       </thead>
       <tbody data-bind="foreach: data">
          <tr data-bind="foreach: $parent.columns">
             <td data-bind="text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText] "></td>
          </tr>
       </tbody>
    </table>
</script>

So that I can use different models to create grids with different columns. For example (taken from here, with some changes):

(function () {
    // Private function
    function getColumnsForScaffolding(data)
    {
        if ((typeof data.length !== 'number') || data.length === 0)
        {
            return [];
        }
        var columns = [];
        for (var propertyName in data[0])
        {
            columns.push({ headerText: propertyName, rowText: propertyName });
        }
        return columns;
    }

    ko.simpleGrid = {
        // Defines a view model class you can use to populate a grid
        viewModel: function (configuration)
        {
            this.data = configuration.data;
            this.columns = configuration.columns;
        }
    };

    // Templates used to render the grid
    var templateEngine = new ko.nativeTemplateEngine();

    templateEngine.addTemplate = function (templateName, templateMarkup)
    {
        document.write("<script type='text/html' id='" + templateName + "'>" + templateMarkup + "<" + "/script>");
    };

    templateEngine.addTemplate("ko_simpleGrid_grid", "\
                <table class=\"table table-bordered table-condensed\" >\
                    <thead>\
                        <tr class=\"active\" data-bind=\"foreach: columns\">\
                           <th data-bind=\"text: headerText\"></th>\
                        </tr>\
                    </thead>\
                    <tbody data-bind=\"foreach: data\">\
                       <tr data-bind=\"foreach: $parent.columns\">\
                           <td data-bind=\"text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText] \"></td>\
                        </tr>\
                    </tbody>\
                </table>");

    // The "simpleGrid" binding
    ko.bindingHandlers.simpleGrid = {
        init: function ()
        {
            return { 'controlsDescendantBindings': true };
        },
        // This method is called to initialize the node, and will also be called again if you change what the grid is bound to
        update: function (element, viewModelAccessor, allBindings)
        {
            var viewModel = viewModelAccessor();

            // Empty the element
            while (element.firstChild)
                ko.removeNode(element.firstChild);

            // Allow the default templates to be overridden
            var gridTemplateName = "ko_simpleGrid_grid",
                gridFooterTemplateName = "ko_simpleGrid_gridFooter";

            // Render the main grid
            var gridContainer = element.appendChild(document.createElement("DIV"));
            ko.renderTemplate(gridTemplateName, viewModel, { templateEngine: templateEngine }, gridContainer, "replaceNode");
        }
    };
})();


var initialData = [
    { name: "Well-Travelled Kitten", sales: 352, price: 75.95 },
    { name: "Speedy Coyote", sales: 89, price: 190.00 },
];
 
var PagedGridModel = function(items) {
    this.items = ko.observableArray(items);
    this.gridViewModel = new ko.simpleGrid.viewModel({
        data: this.items,
        columns: [
            { headerText: "Item Name", rowText: "name" },
            { headerText: "Sales Count", rowText: "sales" },
            { headerText: "Price", rowText: function (item) { return "$" + item.price.toFixed(2) } }
        ]
    });
};
 
ko.applyBindings(new PagedGridModel(initialData));
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<div data-bind='simpleGrid: gridViewModel'> </div>

Now, I'd like to be able to generate the td elements according to the model, but also to have a "common" td, that will have some buttons in it, something like that:

<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<table class="table table-bordered table-condensed">                    
  <thead>                  
    <tr class="active" data-bind="foreach: columns">                           
      <th>Item Name</th>
      <th>Sales Count</th>
      <th>Price</th>
      <th>Action</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Well-Travelled Kitten</td>
      <td>352</td>
      <td>$75.95</td>
      <td>
         <button type="button" class="btn btn-default" title="Delete">
	   <span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
         </button>
      </td>
    </tr>
    <tr>
      <td>Speedy Coyote</td>
      <td>89</td>
      <td>$190.00</td>
      <td>
         <button type="button" class="btn btn-default" title="Delete">
	   <span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
         </button>
      </td>
    </tr> 
  </tbody>
</table>

What do I need to add to the template to achieve it?

Upvotes: 1

Views: 36

Answers (1)

user3297291
user3297291

Reputation: 23372

The easiest solution is to create a virtual element binding for the table cells in your array, and add additional cells directly below. For example:

<tbody data-bind="foreach: data">
    <tr>
      <!-- ko foreach: $parent.columns -->
        <td data-bind="text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText] "></td>
      <!-- /ko -->

      <td data-bind="click: $parent.columns.remove.bind($parent)">delete</td>
    </tr>
</tbody>

Full snippet:

(function () {
    // Private function
    function getColumnsForScaffolding(data)
    {
        if ((typeof data.length !== 'number') || data.length === 0)
        {
            return [];
        }
        var columns = [];
        for (var propertyName in data[0])
        {
            columns.push({ headerText: propertyName, rowText: propertyName });
        }
        return columns;
    }

    ko.simpleGrid = {
        // Defines a view model class you can use to populate a grid
        viewModel: function (configuration)
        {
            this.data = configuration.data;
            this.columns = configuration.columns;
        }
    };

    // Templates used to render the grid
    var templateEngine = new ko.nativeTemplateEngine();

    templateEngine.addTemplate = function (templateName, templateMarkup)
    {
        document.write("<script type='text/html' id='" + templateName + "'>" + templateMarkup + "<" + "/script>");
    };

    templateEngine.addTemplate("ko_simpleGrid_grid", "\
                <table class=\"table table-bordered table-condensed\" >\
                    <thead>\
                        <tr class=\"active\">\
                        <!-- ko foreach: columns -->\
                           <th data-bind=\"text: headerText\"></th>\
                           <!-- /ko -->\
                           <th>Action</th>\
                        </tr>\
                    </thead>\
                    <tbody data-bind=\"foreach: data\">\
                       <tr>\
                           <!-- ko foreach: $parent.columns -->\
        <td data-bind=\"text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText] \"></td>\
      <!-- /ko -->\
                         <td>\
         <button type=\"button\" class=\"btn btn-default\" title=\"Delete\">\
	   <span class=\"glyphicon glyphicon-trash\"></span>\
         </button>\
      </td>\
      </tr>\
      </tbody>\
      </table>");

    // The "simpleGrid" binding
    ko.bindingHandlers.simpleGrid = {
        init: function ()
        {
            return { 'controlsDescendantBindings': true };
        },
        // This method is called to initialize the node, and will also be called again if you change what the grid is bound to
        update: function (element, viewModelAccessor, allBindings)
        {
            var viewModel = viewModelAccessor();

            // Empty the element
            while (element.firstChild)
                ko.removeNode(element.firstChild);

            // Allow the default templates to be overridden
            var gridTemplateName = "ko_simpleGrid_grid",
                gridFooterTemplateName = "ko_simpleGrid_gridFooter";

            // Render the main grid
            var gridContainer = element.appendChild(document.createElement("DIV"));
            ko.renderTemplate(gridTemplateName, viewModel, { templateEngine: templateEngine }, gridContainer, "replaceNode");
        }
    };
})();


var initialData = [
    { name: "Well-Travelled Kitten", sales: 352, price: 75.95 },
    { name: "Speedy Coyote", sales: 89, price: 190.00 },
];
 
var PagedGridModel = function(items) {
    this.items = ko.observableArray(items);
    this.gridViewModel = new ko.simpleGrid.viewModel({
        data: this.items,
        columns: [
            { headerText: "Item Name", rowText: "name" },
            { headerText: "Sales Count", rowText: "sales" },
            { headerText: "Price", rowText: function (item) { return "$" + item.price.toFixed(2) } }
        ]
    });
};
 
ko.applyBindings(new PagedGridModel(initialData));
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<div data-bind='simpleGrid: gridViewModel'> </div>

Alternatively, you could create a combined array of RegularCell and ActionCell elements and use a template binding with a dynamic template selector function to switch between different types of templates. This is well documented on the knockout site.

Upvotes: 1

Related Questions