define(['jquery', 'lodash', 'i18n', 'core/pluginifier', 'handlebars', 'lib/handlebars/helpers', 'ui/datatable/filterStrategy/filterStrategy', 'ui/pagination', 'layout/loading-bar', 'core/logger', 'util/httpErrorParser', 'ui/pageSizeSelector', 'css!ui/datatable/css/datatable.css', 'lib/dompurify/purify'], function ($$1, _, __, Pluginifier, Handlebars, Helpers0, filterStrategyFactory, paginationComponent, loadingBar, loggerFactory, httpErrorParser, pageSizeSelector, datatable_css, DOMPurify) { 'use strict';

    $$1 = $$1 && Object.prototype.hasOwnProperty.call($$1, 'default') ? $$1['default'] : $$1;
    _ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _;
    __ = __ && Object.prototype.hasOwnProperty.call(__, 'default') ? __['default'] : __;
    Pluginifier = Pluginifier && Object.prototype.hasOwnProperty.call(Pluginifier, 'default') ? Pluginifier['default'] : Pluginifier;
    Handlebars = Handlebars && Object.prototype.hasOwnProperty.call(Handlebars, 'default') ? Handlebars['default'] : Handlebars;
    Helpers0 = Helpers0 && Object.prototype.hasOwnProperty.call(Helpers0, 'default') ? Helpers0['default'] : Helpers0;
    filterStrategyFactory = filterStrategyFactory && Object.prototype.hasOwnProperty.call(filterStrategyFactory, 'default') ? filterStrategyFactory['default'] : filterStrategyFactory;
    paginationComponent = paginationComponent && Object.prototype.hasOwnProperty.call(paginationComponent, 'default') ? paginationComponent['default'] : paginationComponent;
    loadingBar = loadingBar && Object.prototype.hasOwnProperty.call(loadingBar, 'default') ? loadingBar['default'] : loadingBar;
    loggerFactory = loggerFactory && Object.prototype.hasOwnProperty.call(loggerFactory, 'default') ? loggerFactory['default'] : loggerFactory;
    httpErrorParser = httpErrorParser && Object.prototype.hasOwnProperty.call(httpErrorParser, 'default') ? httpErrorParser['default'] : httpErrorParser;
    pageSizeSelector = pageSizeSelector && Object.prototype.hasOwnProperty.call(pageSizeSelector, 'default') ? pageSizeSelector['default'] : pageSizeSelector;
    DOMPurify = DOMPurify && Object.prototype.hasOwnProperty.call(DOMPurify, 'default') ? DOMPurify['default'] : DOMPurify;

    if (!Helpers0.__initialized) {
      Helpers0(Handlebars);
      Helpers0.__initialized = true;
    }
    var Template = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
      this.compilerInfo = [4,'>= 1.0.0'];
    helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
      var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this, helperMissing=helpers.helperMissing;

    function program1(depth0,data) {
      
      var buffer = "", stack1;
      buffer += "\n    <aside class=\"filter\" data-column=\""
        + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.filter)),stack1 == null || stack1 === false ? stack1 : stack1.columns)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\">\n        <input type=\"text\" value=\"\" name=\"filter\" placeholder=\""
        + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.labels)),stack1 == null || stack1 === false ? stack1 : stack1.filter)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\">\n        <button class=\"icon-find\" type=\"button\"></button>\n    </aside>\n    ";
      return buffer;
      }

    function program3(depth0,data,depth1) {
      
      var buffer = "", stack1, helper;
      buffer += "\n        <h2>\n            <span class=\"empty-list hidden\">";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.empty), {hash:{},inverse:self.programWithDepth(6, program6, data, depth1),fn:self.program(4, program4, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "</span>\n            <span class=\"available-list hidden\">\n                <span class=\"text\">";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.available), {hash:{},inverse:self.programWithDepth(10, program10, data, depth1),fn:self.program(8, program8, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "</span>:\n                <span class=\"count\">";
      if (helper = helpers.count) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.count); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "</span>\n            </span>\n            <span class=\"loading\">";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.loading), {hash:{},inverse:self.programWithDepth(14, program14, data, depth1),fn:self.program(12, program12, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "...</span>\n        </h2>\n        ";
      return buffer;
      }
    function program4(depth0,data) {
      
      var stack1, helper;
      if (helper = helpers.empty) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.empty); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      return escapeExpression(stack1);
      }

    function program6(depth0,data,depth2) {
      
      var stack1;
      return escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth2 && depth2.options)),stack1 == null || stack1 === false ? stack1 : stack1.labels)),stack1 == null || stack1 === false ? stack1 : stack1.empty)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
      }

    function program8(depth0,data) {
      
      var stack1, helper;
      if (helper = helpers.available) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.available); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      return escapeExpression(stack1);
      }

    function program10(depth0,data,depth2) {
      
      var stack1;
      return escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth2 && depth2.options)),stack1 == null || stack1 === false ? stack1 : stack1.labels)),stack1 == null || stack1 === false ? stack1 : stack1.available)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
      }

    function program12(depth0,data) {
      
      var stack1, helper;
      if (helper = helpers.loading) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.loading); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      return escapeExpression(stack1);
      }

    function program14(depth0,data,depth2) {
      
      var stack1;
      return escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth2 && depth2.options)),stack1 == null || stack1 === false ? stack1 : stack1.labels)),stack1 == null || stack1 === false ? stack1 : stack1.loading)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
      }

    function program16(depth0,data) {
      
      
      return "\n        <div class=\"toolbox-container\"></div>\n        ";
      }

    function program18(depth0,data) {
      
      var buffer = "", stack1;
      buffer += "\n    <aside class=\"action-bar clearfix\">\n        ";
      stack1 = helpers.each.call(depth0, ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.tools), {hash:{},inverse:self.noop,fn:self.program(19, program19, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n    </aside>\n    ";
      return buffer;
      }
    function program19(depth0,data) {
      
      var buffer = "", stack1;
      buffer += "\n            <button class=\"btn-info small tool-";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.id), {hash:{},inverse:self.program(22, program22, data),fn:self.program(20, program20, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.massAction), {hash:{},inverse:self.noop,fn:self.program(24, program24, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\"\n                ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.title), {hash:{},inverse:self.noop,fn:self.program(26, program26, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += ">\n                <span class=\"icon-";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.icon), {hash:{},inverse:self.program(30, program30, data),fn:self.program(28, program28, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\"></span>\n                ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.label), {hash:{},inverse:self.program(35, program35, data),fn:self.program(33, program33, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n            </button>\n        ";
      return buffer;
      }
    function program20(depth0,data) {
      
      var stack1, helper;
      if (helper = helpers.id) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.id); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      return escapeExpression(stack1);
      }

    function program22(depth0,data) {
      
      var stack1;
      return escapeExpression(((stack1 = (data == null || data === false ? data : data.index)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
      }

    function program24(depth0,data) {
      
      
      return " invisible";
      }

    function program26(depth0,data) {
      
      var buffer = "", stack1, helper;
      buffer += " title=\"";
      if (helper = helpers.title) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.title); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\"";
      return buffer;
      }

    function program28(depth0,data) {
      
      var stack1, helper;
      if (helper = helpers.icon) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.icon); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      return escapeExpression(stack1);
      }

    function program30(depth0,data) {
      
      var stack1;
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.id), {hash:{},inverse:self.program(31, program31, data),fn:self.program(20, program20, data),data:data});
      if(stack1 || stack1 === 0) { return stack1; }
      else { return ''; }
      }
    function program31(depth0,data) {
      
      var stack1;
      return escapeExpression(((stack1 = (data == null || data === false ? data : data.key)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
      }

    function program33(depth0,data) {
      
      var stack1, helper;
      if (helper = helpers.label) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.label); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      return escapeExpression(stack1);
      }

    function program35(depth0,data) {
      
      var stack1;
      stack1 = helpers.unless.call(depth0, (depth0 && depth0.id), {hash:{},inverse:self.noop,fn:self.program(31, program31, data),data:data});
      if(stack1 || stack1 === 0) { return stack1; }
      else { return ''; }
      }

    function program37(depth0,data) {
      
      
      return "\n                <col/>\n                ";
      }

    function program39(depth0,data) {
      
      
      return "\n                </col>\n                ";
      }

    function program41(depth0,data) {
      
      
      return "\n                    <th class=\"checkboxes\"><input type=\"checkbox\" name=\"checkall\" value=\"1\" /></th>\n                    ";
      }

    function program43(depth0,data) {
      
      var buffer = "", stack1, helper;
      buffer += "\n                    <th";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.type), {hash:{},inverse:self.noop,fn:self.program(44, program44, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += ">\n                        <div\n                            ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.sortable), {hash:{},inverse:self.noop,fn:self.program(46, program46, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += ">";
      if (helper = helpers.label) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.label); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1);
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.alias), {hash:{},inverse:self.noop,fn:self.program(51, program51, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.comment), {hash:{},inverse:self.noop,fn:self.program(53, program53, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "</div>\n                        ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.filterable), {hash:{},inverse:self.noop,fn:self.program(55, program55, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                    </th>\n                    ";
      return buffer;
      }
    function program44(depth0,data) {
      
      
      return " class=\"actions\"";
      }

    function program46(depth0,data) {
      
      var buffer = "", stack1;
      buffer += "\n                                class=\"sortable\"\n                                data-sort-by=\"";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.sortId), {hash:{},inverse:self.program(20, program20, data),fn:self.program(47, program47, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\"\n                                ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.sorttype), {hash:{},inverse:self.noop,fn:self.program(49, program49, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                                tabindex=\"0\"\n                            ";
      return buffer;
      }
    function program47(depth0,data) {
      
      var stack1, helper;
      if (helper = helpers.sortId) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.sortId); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      return escapeExpression(stack1);
      }

    function program49(depth0,data) {
      
      var buffer = "", stack1, helper;
      buffer += "data-sort-type=\"";
      if (helper = helpers.sorttype) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.sorttype); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\"";
      return buffer;
      }

    function program51(depth0,data) {
      
      var buffer = "", stack1, helper;
      buffer += " <span class=\"alias\">(";
      if (helper = helpers.alias) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.alias); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + ")</span>";
      return buffer;
      }

    function program53(depth0,data) {
      
      var buffer = "", stack1, helper;
      buffer += " <span class=\"comment\">/ ";
      if (helper = helpers.comment) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.comment); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "</span>";
      return buffer;
      }

    function program55(depth0,data) {
      
      var buffer = "", stack1, helper;
      buffer += "\n                        <aside data-column=\"";
      if (helper = helpers.id) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.id); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\" class=\"filter column\n                            ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.customFilter), {hash:{},inverse:self.program(58, program58, data),fn:self.program(56, program56, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                        </aside>\n                        ";
      return buffer;
      }
    function program56(depth0,data) {
      
      var buffer = "", stack1;
      buffer += " customInput\" >\n                                ";
      stack1 = ((stack1 = ((stack1 = (depth0 && depth0.customFilter)),stack1 == null || stack1 === false ? stack1 : stack1.template)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1);
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                            ";
      return buffer;
      }

    function program58(depth0,data) {
      
      var buffer = "", stack1, helper;
      buffer += " \">\n                                <input type=\"text\" value=\"\" name=\"filter[";
      if (helper = helpers.id) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.id); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "]\" placeholder=\""
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.filterable)),stack1 == null || stack1 === false ? stack1 : stack1.placeholder)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\">\n                                <button class=\"icon-find\" type=\"button\"></button>\n                            ";
      return buffer;
      }

    function program60(depth0,data) {
      
      var buffer = "", stack1;
      buffer += "\n                    <th class=\"actions\">"
        + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.labels)),stack1 == null || stack1 === false ? stack1 : stack1.actions)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "</th>\n                    ";
      return buffer;
      }

    function program62(depth0,data,depth1) {
      
      var buffer = "", stack1, helper;
      buffer += "\n                    <tr data-item-identifier=\"";
      if (helper = helpers.id) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.id); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\">\n                        ";
      stack1 = helpers['if'].call(depth0, ((stack1 = (depth1 && depth1.options)),stack1 == null || stack1 === false ? stack1 : stack1.selectable), {hash:{},inverse:self.noop,fn:self.program(63, program63, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n\n                        ";
      stack1 = helpers.each.call(depth0, ((stack1 = (depth1 && depth1.options)),stack1 == null || stack1 === false ? stack1 : stack1.model), {hash:{},inverse:self.noop,fn:self.programWithDepth(65, program65, data, depth0),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n\n                        ";
      stack1 = helpers['with'].call(depth0, ((stack1 = (depth1 && depth1.options)),stack1 == null || stack1 === false ? stack1 : stack1.actions), {hash:{},inverse:self.noop,fn:self.programWithDepth(82, program82, data, depth0),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                    </tr>\n                ";
      return buffer;
      }
    function program63(depth0,data) {
      
      var buffer = "", stack1, helper;
      buffer += "\n                        <td class=\"checkboxes\"><input type=\"checkbox\" name=\"cb[";
      if (helper = helpers.id) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.id); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "]\" value=\"1\" /></td>\n                        ";
      return buffer;
      }

    function program65(depth0,data,depth1) {
      
      var buffer = "", stack1;
      buffer += "\n                            ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.type), {hash:{},inverse:self.programWithDepth(80, program80, data, depth1),fn:self.programWithDepth(66, program66, data, depth0, depth1),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n\n                        ";
      return buffer;
      }
    function program66(depth0,data,depth1,depth2) {
      
      var buffer = "", stack1, helper;
      buffer += "\n                            <td class=\"actions ";
      if (helper = helpers.id) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.id); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\">\n                                ";
      stack1 = helpers.each.call(depth0, (depth1 && depth1.actions), {hash:{},inverse:self.noop,fn:self.programWithDepth(67, program67, data, depth2),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                            </td>\n                            ";
      return buffer;
      }
    function program67(depth0,data,depth3) {
      
      var buffer = "", stack1;
      buffer += "\n                                    ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.id), {hash:{},inverse:self.program(77, program77, data),fn:self.programWithDepth(68, program68, data, depth3),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n\n                                ";
      return buffer;
      }
    function program68(depth0,data,depth4) {
      
      var buffer = "", stack1;
      buffer += "\n                                        ";
      stack1 = helpers['with'].call(depth0, depth4, {hash:{},inverse:self.noop,fn:self.programWithDepth(69, program69, data, depth0),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                                    ";
      return buffer;
      }
    function program69(depth0,data,depth1) {
      
      var buffer = "", stack1;
      buffer += "\n                                            ";
      stack1 = helpers.unless.call(depth0, (depth1 && depth1.hidden), {hash:{},inverse:self.noop,fn:self.programWithDepth(70, program70, data, depth1),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                                        ";
      return buffer;
      }
    function program70(depth0,data,depth2) {
      
      var buffer = "", stack1;
      buffer += "\n                                <button class=\"btn-info small "
        + escapeExpression(((stack1 = (depth2 && depth2.cls)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + " "
        + escapeExpression(((stack1 = (depth2 && depth2.id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\"\n                                    ";
      stack1 = helpers['if'].call(depth0, (depth2 && depth2.title), {hash:{},inverse:self.noop,fn:self.programWithDepth(71, program71, data, depth2),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                                    ";
      stack1 = helpers['if'].call(depth0, (depth2 && depth2.disabled), {hash:{},inverse:self.noop,fn:self.program(73, program73, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += ">\n                                    ";
      stack1 = helpers['if'].call(depth0, (depth2 && depth2.icon), {hash:{},inverse:self.noop,fn:self.programWithDepth(75, program75, data, depth2),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                                    "
        + escapeExpression(((stack1 = (depth2 && depth2.label)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\n                                </button>\n                                            ";
      return buffer;
      }
    function program71(depth0,data,depth3) {
      
      var buffer = "", stack1;
      buffer += " title=\""
        + escapeExpression(((stack1 = (depth3 && depth3.title)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\"";
      return buffer;
      }

    function program73(depth0,data) {
      
      
      return " disabled=\"disabled\"";
      }

    function program75(depth0,data,depth3) {
      
      var buffer = "", stack1;
      buffer += "<span class=\"icon-"
        + escapeExpression(((stack1 = (depth3 && depth3.icon)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\"></span>";
      return buffer;
      }

    function program77(depth0,data) {
      
      var buffer = "", stack1, helper;
      buffer += "\n                                <button class=\"btn-info small "
        + escapeExpression(((stack1 = (data == null || data === false ? data : data.key)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\"\n                                    ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.title), {hash:{},inverse:self.noop,fn:self.program(26, program26, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += ">\n                                    ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.icon), {hash:{},inverse:self.noop,fn:self.program(78, program78, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                                    ";
      if (helper = helpers.label) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.label); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\n                                </button>\n                                    ";
      return buffer;
      }
    function program78(depth0,data) {
      
      var buffer = "", stack1, helper;
      buffer += "<span class=\"icon-";
      if (helper = helpers.icon) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.icon); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\"></span> ";
      return buffer;
      }

    function program80(depth0,data,depth2) {
      
      var buffer = "", stack1, helper, options;
      buffer += "\n                            <td class=\"";
      if (helper = helpers.id) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.id); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\">";
      stack1 = (helper = helpers.property || (depth0 && depth0.property),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.id), depth2, options) : helperMissing.call(depth0, "property", (depth0 && depth0.id), depth2, options));
      stack1 = (helper = helpers.dompurify || (depth0 && depth0.dompurify),options={hash:{},data:data},helper ? helper.call(depth0, stack1, options) : helperMissing.call(depth0, "dompurify", stack1, options));
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "</td>\n                            ";
      return buffer;
      }

    function program82(depth0,data,depth1) {
      
      var buffer = "", stack1;
      buffer += "\n                        <td class=\"actions\">\n                            ";
      stack1 = helpers.each.call(depth0, depth0, {hash:{},inverse:self.noop,fn:self.programWithDepth(83, program83, data, depth1),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                        </td>\n                        ";
      return buffer;
      }
    function program83(depth0,data,depth2) {
      
      var buffer = "", stack1;
      buffer += "\n                                ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.id), {hash:{},inverse:self.program(88, program88, data),fn:self.programWithDepth(84, program84, data, depth2),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                            ";
      return buffer;
      }
    function program84(depth0,data,depth3) {
      
      var buffer = "", stack1;
      buffer += "\n                                    ";
      stack1 = helpers['with'].call(depth0, depth3, {hash:{},inverse:self.noop,fn:self.programWithDepth(85, program85, data, depth0),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                                ";
      return buffer;
      }
    function program85(depth0,data,depth1) {
      
      var buffer = "", stack1;
      buffer += "\n                                        ";
      stack1 = helpers.unless.call(depth0, (depth1 && depth1.hidden), {hash:{},inverse:self.noop,fn:self.programWithDepth(86, program86, data, depth1),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                                    ";
      return buffer;
      }
    function program86(depth0,data,depth2) {
      
      var buffer = "", stack1;
      buffer += "\n                            <button class=\"btn-info small "
        + escapeExpression(((stack1 = (depth2 && depth2.cls)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + " "
        + escapeExpression(((stack1 = (depth2 && depth2.id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\"\n                                ";
      stack1 = helpers['if'].call(depth0, (depth2 && depth2.title), {hash:{},inverse:self.noop,fn:self.programWithDepth(71, program71, data, depth2),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                                ";
      stack1 = helpers['if'].call(depth0, (depth2 && depth2.disabled), {hash:{},inverse:self.noop,fn:self.program(73, program73, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += ">\n                                ";
      stack1 = helpers['if'].call(depth0, (depth2 && depth2.icon), {hash:{},inverse:self.noop,fn:self.programWithDepth(75, program75, data, depth2),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                                "
        + escapeExpression(((stack1 = (depth2 && depth2.label)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\n                            </button>\n                                        ";
      return buffer;
      }

    function program88(depth0,data) {
      
      var buffer = "", stack1;
      buffer += "\n                            <button class=\"btn-info small "
        + escapeExpression(((stack1 = (data == null || data === false ? data : data.key)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\"><span class=\"icon-"
        + escapeExpression(((stack1 = (data == null || data === false ? data : data.key)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\"></span> "
        + escapeExpression(((stack1 = (data == null || data === false ? data : data.key)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "</button>\n                                ";
      return buffer;
      }

    function program90(depth0,data) {
      
      var buffer = "", stack1;
      buffer += "\n            ";
      stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.emptyText), {hash:{},inverse:self.noop,fn:self.program(91, program91, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n        ";
      return buffer;
      }
    function program91(depth0,data) {
      
      var buffer = "", stack1;
      buffer += "\n                <div class=\"empty\">\n                    "
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.emptyText)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\n                </div>\n            ";
      return buffer;
      }

      buffer += "<div class=\"datatable-wrapper\">\n\n    ";
      stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.filter), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n\n    <div class=\"datatable-header\">\n        ";
      stack1 = helpers['with'].call(depth0, ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.status), {hash:{},inverse:self.noop,fn:self.programWithDepth(3, program3, data, depth0),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n\n        ";
      stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.pageSizeSelector), {hash:{},inverse:self.noop,fn:self.program(16, program16, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n    </div>\n\n    ";
      stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.tools), {hash:{},inverse:self.noop,fn:self.program(18, program18, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n\n    <div class=\"datatable-pagination-top\"></div>\n\n    <div class=\"datatable-container\">\n        <table class=\"matrix datatable\">\n            <colgroup>\n                ";
      stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.selectable), {hash:{},inverse:self.noop,fn:self.program(37, program37, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                ";
      stack1 = helpers.each.call(depth0, ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.model), {hash:{},inverse:self.noop,fn:self.program(37, program37, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                ";
      stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.actions), {hash:{},inverse:self.noop,fn:self.program(39, program39, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n            </colgroup>\n            <thead>\n                <tr>\n                    ";
      stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.selectable), {hash:{},inverse:self.noop,fn:self.program(41, program41, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                    ";
      stack1 = helpers.each.call(depth0, ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.model), {hash:{},inverse:self.noop,fn:self.program(43, program43, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                    ";
      stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.actions), {hash:{},inverse:self.noop,fn:self.program(60, program60, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                </tr>\n            </thead>\n            <tbody>\n                ";
      stack1 = helpers.each.call(depth0, ((stack1 = (depth0 && depth0.dataset)),stack1 == null || stack1 === false ? stack1 : stack1.data), {hash:{},inverse:self.noop,fn:self.programWithDepth(62, program62, data, depth0),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n            </tbody>\n        </table>\n        ";
      stack1 = helpers.unless.call(depth0, ((stack1 = (depth0 && depth0.dataset)),stack1 == null || stack1 === false ? stack1 : stack1.data), {hash:{},inverse:self.noop,fn:self.program(90, program90, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n    </div>\n    <div class=\"datatable-pagination-bottom\"></div>\n</div>\n";
      return buffer;
      });
    function layout(data, options, asString) {
      var html = Template(data, options);
      return (asString || true) ? html : $(html);
    }

    if (!Helpers0.__initialized) {
      Helpers0(Handlebars);
      Helpers0.__initialized = true;
    }
    var Template$1 = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
      this.compilerInfo = [4,'>= 1.0.0'];
    helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
      var buffer = "", stack1, helper, functionType="function", escapeExpression=this.escapeExpression, self=this;

    function program1(depth0,data) {
      
      var buffer = "", stack1, helper;
      buffer += " title=\"";
      if (helper = helpers.title) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.title); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\"";
      return buffer;
      }

    function program3(depth0,data) {
      
      
      return " disabled=\"disabled\"";
      }

    function program5(depth0,data) {
      
      var buffer = "", stack1, helper;
      buffer += "<span class=\"icon-";
      if (helper = helpers.icon) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.icon); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\"></span>";
      return buffer;
      }

    function program7(depth0,data) {
      
      var stack1, helper;
      if (helper = helpers.label) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.label); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      return escapeExpression(stack1);
      }

      buffer += "<button class=\"btn-info small ";
      if (helper = helpers.cls) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.cls); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + " ";
      if (helper = helpers.id) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.id); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\"\n  ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.title), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n  ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.disabled), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += ">\n  ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.icon), {hash:{},inverse:self.noop,fn:self.program(5, program5, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n  ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.label), {hash:{},inverse:self.noop,fn:self.program(7, program7, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n</button>\n";
      return buffer;
      });
    function buttonTpl(data, options, asString) {
      var html = Template$1(data, options);
      return (asString || true) ? html : $(html);
    }

    /**
     * This program is free software; you can redistribute it and/or
     * modify it under the terms of the GNU General Public License
     * as published by the Free Software Foundation; under version 2
     * of the License (non-upgradable).
     *
     * This program is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with this program; if not, write to the Free Software
     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
     *
     * Copyright (c) 2015-2020 (original work) Open Assessment Technologies SA;
     */
    const ns = 'datatable';
    const dataNs = 'ui.' + ns;
    const defaults = {
      atomicUpdate: false,
      start: 0,
      rows: 25,
      page: 1,
      sortby: 'id',
      sortorder: 'asc',
      sorttype: 'string',
      paginationStrategyTop: 'none',
      paginationStrategyBottom: 'simple',
      labels: {
        filter: __('Filter'),
        empty: __('Nothing to list!'),
        available: __('Available'),
        loading: __('Loading'),
        actions: __('Actions')
      },
      pageSizeSelector: false
    };
    const logger = loggerFactory('ui/datatable');

    /**
     * The CSS class used to hide an element
     * @type {String}
     */
    const hiddenCls = 'hidden';

    /**
     * Deactivate pagination's
     * @param {Array} pagination
     */
    const disablePagination = pagination => {
      if (pagination && pagination.length) {
        pagination.forEach(step => {
          step.disable();
        });
      }
    };

    /**
     * Activate pagination's
     * @param {Array} pagination
     */
    const enablePagination = pagination => {
      if (pagination && pagination.length) {
        pagination.forEach(step => {
          step.enable();
        });
      }
    };

    /**
     *  Helper for reading actions value in context
     *
     * @param {String} property
     * @param {Object} action
     * @param {Object} context
     */
    const getPropertyValue = (property, action, context) => {
      const value = action[property];
      return _.isFunction(value) ? value.apply(context) : value;
    };

    /**
     * Update the data table status in the header
     *
     * @param {Object} options
     * @param {jQueryElement} $container
     * @param {Object} dataset
     */
    const updateHeaderStatus = (options, $container, dataset) => {
      if (!options.status) {
        return;
      }
      const $statusEmpty = $container.find('.empty-list');
      const $statusAvailable = $container.find('.available-list');
      const $statusCount = $statusAvailable.find('.count');
      $container.find('.loading').addClass(hiddenCls);

      // when the status is enabled, the response must contain the total amount of records
      const amount = dataset.amount || dataset.length;
      if (amount) {
        $statusCount.text(amount);
        $statusAvailable.removeClass(hiddenCls);
        $statusEmpty.addClass(hiddenCls);
      } else {
        $statusEmpty.removeClass(hiddenCls);
        $statusAvailable.addClass(hiddenCls);
      }
    };

    /**
     * The dataTable component makes you able to browse items and bind specific
     * actions to undertake for edition and removal of them.
     *
     * Parameters that will be send to backend by component:
     *
     * Pagination
     * @param {Number} rows - count of rows, that should be returned from backend, in other words limit.
     * @param {Number} page - number of page, that should be requested.
     *
     * Sorting
     * @param {String} sortby - name of column
     * @param {String} sortorder - order of sorting, can be 'asc' or 'desc' for ascending sorting and descending sorting respectively.
     * @param {String} sorttype - type of sorting, can be 'string' or 'numeric' for proper sorting numeric and string values.
     *
     * Filtering
     * @param {String} filterstrategy - filtering strategy. Default is single (see ui/datatable/filterStrategy/single.js).
     * @param {String} filterquery - query string for filtering of rows.
     * @param {String[]} filtercolumns[] - array of columns, in which will be implemented search during filtering process.
     * For column filter it will be only one item with column name, but component has ability define list of columns for default filter (in top toolbar).
     * Backend should correctly receive this list of columns and do search in accordance with this parameters.
     * By default, columns are not defined, so this parameter not will be sent. If filtercolumns[] not exists, backend should search by all columns.
     *
     * @example of query (GET): rows=25&page=1&sortby=login&sortorder=asc&filterquery=loginame&filtercolumns[]=login
     *
     * @exports ui/datatable
     */
    const dataTable = {
      /**
       * Used for generating action button action button
       * @typedef Action
       * @type {Object}
       * @property {String} id ID is added to the button class
       * @property {String} [title] Button title
       * @property {Boolean} [disabled] When present, button should be disabled
       * @property {String} [icon] Generate button icon
       * @property {Boolean} [hidden] When present, button is hidden
       * @property {Function} [action] Handler on button click
       */

      /**
       * Used for generating action button from Object
       * @deprecated
       * @typedef  {{
       *  [key: Action.id & Action.icon & Action.title ]: Action.action,
       * }} ActionsObject
       *
       * @example
       * {
       *  actions: {
       *    edit: editUser,
       *    remove: removeUser,
       *  }
       * }
       *
       * ! IMPORTANT USE INSTEAD:
       * {
       *   actions: [
       *     {
       *       id: "edit",
       *       title: __("Edit"),
       *       icon: "edit",
       *       action: editUser
       *     },
       *     {
       *       id: "edit",
       *       title: __("Edit"),
       *       icon: "edit",
       *       action: editUser
       *     }
       *   ]
       * }
       */

      /**
       * Initialize the plugin.
       *
       * Called the jQuery way once registered by the Pluginifier.
       * @example $('selector').datatable([], {});
       *
       * @constructor
       * @param {Object} options - the plugin options.
       * @param {String} options.url - the URL of the service used to retrieve the resources.
       * @param {Object[]} options.model - the model definition.
       * @param {ActionsObject | Action[]} options.actions - Generates action buttons
       * @param {Function} options.listeners.xxx - the callback function for event xxx, parameters depends to event trigger call.
       * @param {Boolean} options.selectable - enables the selection of rows using checkboxes.
       * @param {Boolean} options.rowSelection - enables the selection of rows by clicking on them.
       * @param {Object} options.tools - a list of tool buttons to display above the table.
       * @param {Object|Boolean} options.status - allow to display a status bar.
       * @param {Object|Boolean} options.filter - allow to display a filter bar.
       * @param {String} options.filterStrategy - 'multiple' | 'single'  -- filtered by all filters together or filtering allowed only by one field at the moment (default 'single'),
       * @param {String} options.filterSelector - css selector for search of filter inputs, by defaul 'select, input'
       * @param {String} options.filterTransform - transform filter value before send to server.
       * @param {String[]} options.filter.columns - a list of columns that will be used for default filter. Can be overridden by column filter.
       * @param {String} options.filterquery - a query string for filtering, using only in runtime.
       * @param {String[]} options.filtercolumns - a list of columns, in that should be done search, using only in runtime.
       * @param {String} options.paginationStrategyTop  - 'none' | 'pages' | 'simple' -- 'none' by default (next/prev), 'pages' show pages and extended control for pagination
       * @param {String} options.paginationStrategyBottom  - 'none' | 'pages' | 'simple' -- 'simple' by default (next/prev), 'pages' show pages and extended control for pagination
       * @param {Object} options.labels - list of labels in datatable interface, that can be overridden by incoming options
       * @param {String} options.emptyText - text that will be shown when no data found for showing in the grid.
       * @param {Boolean} options.pageSizeSelector - flag that indicates if control for changing page size should be displayed
       * @param {Boolean} options.atomicUpdate - allowed to keep the datatable state to be able on "render" event, compare with new state and atomically update the table cells.
       * @param {Function} options.requestInterceptor - Intercept sending AJAX request. The call of function must returns promise with provided data.
       * @param {Object} [data] - inject predefined data to avoid the first query.
       * @fires dataTable#create.datatable
       * @returns {jQueryElement} for chaining
       */
      init(options, data) {
        options = _.defaults(options, defaults);
        return this.each(function () {
          const $elt = $$1(this);
          const currentOptions = $elt.data(dataNs);
          if (options.atomicUpdate && data) {
            $elt.data(`${dataNs}state`, data.data);
          }
          // implement encapsulated pages for the datatable
          $elt.paginations = [];
          if (!currentOptions) {
            //add data to the element
            $elt.data(dataNs, options);
            $elt.one('load.' + ns, function () {
              /**
               * @event dataTable#create.datatable
               */
              $elt.trigger('create.' + ns);
            });
            if (data) {
              dataTable._render($elt, data);
            } else {
              dataTable._query($elt);
            }
          } else {
            // update existing options
            $elt.data(dataNs, _.merge(currentOptions, options));
            dataTable._refresh($elt, data);
          }
        });
      },
      /**
       * Refresh the data table using current options
       *
       * Called the jQuery way once registered by the Pluginifier.
       * @example $('selector').datatable('refresh');
       *
       * @param {jQueryElement} $elt - plugin's element
       * @param {Object} [data] - Data to render immediately, prevents the query to be made.
       */
      _refresh($elt, data) {
        if (data) {
          this._render($elt, data);
        } else {
          this._query($elt);
        }
      },
      /**
       * Query the server for data and load the table.
       *
       * @private
       * @param {jQueryElement} $elt - plugin's element
       * @param $filter
       * @fires dataTable#query.datatable
       */
      _query($elt, $filter) {
        const self = this;
        let options = $elt.data(dataNs);
        loadingBar.start();
        if (!$filter) {
          $filter = $$1('.filter', $elt);
        }
        options = _.assign({}, options, this._getFilterStrategy($elt).getQueryData($elt, $filter, options));
        const parameters = _.merge({}, _.pick(options, ['rows', 'page', 'sortby', 'sortorder', 'sorttype', 'filterquery', 'filtercolumns']), options.params || {});
        const ajaxConfig = {
          url: options.url,
          data: parameters,
          dataType: 'json',
          type: options.querytype || 'GET'
        };

        // disable pagination to not press multiple on it
        disablePagination($elt.paginations);

        /**
         * @event dataTable#query.datatable
         * @param {Object} ajaxConfig - The config object used to setup the AJAX request
         */
        $elt.trigger('query.' + ns, [ajaxConfig]);

        // display the loading state
        if (options.status) {
          $elt.find('.loading').removeClass(hiddenCls);
        }
        if (typeof options.requestInterceptor === 'function') {
          Promise.resolve(options.requestInterceptor(parameters)).then(data => {
            self._render($elt, data);
          }).catch(error => {
            $elt.trigger('error.' + ns, [error]);
            self._render($elt, {});
          });
          return;
        }
        $$1.ajax(ajaxConfig).done(function (response) {
          if ($elt && typeof $elt.data(dataNs) !== 'object') {
            return $elt.trigger(`error.${ns}`, [new Error(`Unable to load data attached to the element`)]);
          }
          self._render($elt, response);
        }).fail(function (response, option, err) {
          const requestErr = httpErrorParser.parse(response, option, err);
          logger.error(requestErr.message);
          requestErr.code = response.status;
          enablePagination(this.paginations);
          $elt.trigger('error.' + ns, [requestErr]);
          self._render($elt, {});
        });
      },
      /**
       * Renders the table using the provided data set
       *
       * @param {jQueryElement} $elt - plugin's element
       * @param {Object} dataset - the data set to render
       * @private
       * @fires dataTable#beforeload.datatable
       * @fires dataTable#load.datatable
       */
      _render($elt) {
        let dataset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        const self = this;
        let options = _.cloneDeep($elt.data(dataNs));
        if (typeof options === 'undefined') {
          return $elt.trigger(`error.${ns}`, [new Error(`Unable to load data attached to the element`)]);
        }
        const model = [];
        let $massActionBtns = $$1();

        /**
         * @event dataTable#beforeload.datatable
         * @param {Object} dataset - The data set object used to render the table
         */
        $elt.trigger('beforeload.' + ns, [_.cloneDeep(dataset)]);

        // overrides column options
        _.forEach(options.model, function (field, key) {
          if (!options.filter) {
            field.filterable = false;
          }
          if (_.isUndefined(field.order)) {
            field.order = key + 1;
          }
          if (field.filterable && typeof field.filterable !== 'object') {
            field.filterable = {
              placeholder: __('Filter')
            };
          }
          if (typeof field.visible === 'undefined') {
            model.push(field);
          } else if (typeof field.visible === 'function' && field.visible()) {
            model.push(field);
          } else if (field.visible === true) {
            model.push(field);
          }
        });
        model.sort(function (a, b) {
          return a.order - b.order;
        });
        if (options.sortby) {
          options = this._sortOptions($elt, options.sortby, options.sortorder, options.sorttype);
        }

        // process data by model rules
        if (_.some(model, 'transform')) {
          const transforms = _.filter(model, 'transform');
          _.forEach(dataset.data, (row, index) => {
            _.forEach(transforms, field => {
              if (_.isFunction(field.transform)) {
                row[field.id] = field.transform(row[field.id], row, field, index, dataset.data);
              }
            });
          });
        }
        options.model = model;
        if (options.atomicUpdate) {
          const skipForceUpdate = this._shallowUpdate($elt, dataset, options);
          if (skipForceUpdate) {
            updateHeaderStatus(options, $elt, dataset);
            loadingBar.stop();
            $elt.trigger(`load.${ns}`, [dataset]);
            return;
          }
        }

        // Call the rendering
        const $rendering = $$1(layout({
          options: options,
          dataset: dataset
        }));

        // the readonly property contains an associative array where keys are the ids of the items (lines)
        // the value can be a boolean (true for disable buttons, false to enable)
        // it can also bo an array that let you disable/enable the action you want
        // readonly = {
        //  id1 : {'view':true, 'delete':false},
        //  id2 : true
        //}
        _.forEach(dataset.readonly, function (values, id) {
          if (values === true) {
            $$1('[data-item-identifier="' + id + '"] button', $rendering).addClass('disabled');
          } else if (values && typeof values === 'object') {
            for (const action in values) {
              if (Object.prototype.hasOwnProperty.call(values, action) && values[action] === true) {
                $$1(`[data-item-identifier="${id}"] button.${action}`, $rendering).addClass('disabled');
              }
            }
          }
        });

        /**
         * Attach handlers on the action buttons
         * @param {ActionsObject | Action[]} actions
         */
        const attachActionListeners = actions => {
          // Attach a listener to every action button created
          _.forEach(actions, (action, name) => {
            if (!_.isFunction(action)) {
              name = action.id || name;
              action = action.action || function () {};
            }
            const css = `.${name}`;
            $rendering.off('click', css).on('click', css, function (e) {
              e.preventDefault();
              const $btn = $$1(this);
              if (!$btn.hasClass('disabled')) {
                const identifier = $btn.closest('[data-item-identifier]').data('item-identifier');
                action.apply($btn, [identifier, _.head(_.filter(dataset.data, {
                  id: identifier
                }))]);
              }
            });
          });
        };
        if (options.actions) {
          attachActionListeners(options.actions);
        }

        // Attach listeners to model.type = action
        if (_.some(options.model, 'type')) {
          const types = _.filter(options.model, 'type');
          _.forEach(types, field => {
            if (field.type === 'actions' && field.actions) {
              attachActionListeners(field.actions);
            }
          });
        }

        // Attach a listener to every tool button created
        _.forEach(options.tools, (action, name) => {
          let isMassAction = true;
          if (!_.isFunction(action)) {
            name = action.id || name;
            isMassAction = action.massAction;
            action = action.action || function () {};
          }
          const css = `.tool-${name}`;
          if (isMassAction) {
            $massActionBtns = $massActionBtns.add($rendering.find(css));
          }
          $rendering.off('click', css).on('click', css, function (e) {
            e.preventDefault();
            const $btn = $$1(this);
            if (!$btn.hasClass('disabled')) {
              action.apply($btn, [self._selection($elt)]);
            }
          });
        });

        // bind listeners to events
        _.forEach(options.listeners, (callback, event) => {
          const ev = [event, ns].join('.');
          $elt.off(ev).on(ev, callback);
        });
        function renderPagination($container, mode) {
          return paginationComponent({
            mode: mode,
            activePage: dataset.page,
            totalPages: dataset.total
          }).on('change', function () {
            self._setPage($elt, this.getActivePage());
          }).on('prev', function () {
            /**
             * @event dataTable#backward.dataTable
             */
            $elt.trigger('backward.' + ns);
          }).on('next', function () {
            /**
             * @event dataTable#forward.dataTable
             */
            $elt.trigger('forward.' + ns);
          }).render($container);
        }
        $elt.paginations = [];
        if (options.paginationStrategyTop !== 'none') {
          // bind pagination component to the datatable
          $elt.paginations.push(renderPagination($$1('.datatable-pagination-top', $rendering), options.paginationStrategyTop));
        }
        if (options.paginationStrategyBottom !== 'none') {
          // bind pagination component to the datatable
          $elt.paginations.push(renderPagination($$1('.datatable-pagination-bottom', $rendering), options.paginationStrategyBottom));
        }
        disablePagination($elt.paginations);

        // Now $rendering takes the place of $elt...
        const $rows = $rendering.find('tbody tr');
        const $sortBy = $rendering.find('th [data-sort-by]');
        const $sortElement = $rendering.find('[data-sort-by="' + options.sortby + '"]');
        const $checkAll = $rendering.find('th.checkboxes input');
        const $checkboxes = $rendering.find('td.checkboxes input');
        if (options.rowSelection) {
          $$1('table.datatable', $rendering).addClass('hoverable');
          $rendering.on('click', 'tbody td', function (e) {
            // exclude from processing columns with actions
            if ($$1(e.target).hasClass('checkboxes') || $$1(e.target).hasClass('actions')) {
              return false;
            }
            const currentRow = $$1(this).parent();
            $rows.removeClass('selected');
            currentRow.toggleClass('selected');
            $elt.trigger('selected.' + ns, _.filter(dataset.data, {
              id: currentRow.data('item-identifier')
            }));
          });
        }
        $sortBy.on('click keyup', function (e) {
          if (e.type === 'keyup' && e.keyCode !== 13) {
            return;
          }
          e.preventDefault();
          const column = $$1(this).data('sort-by');
          const type = $$1(this).data('sort-type');
          self._sort($elt, column, void 0, type);
        });

        // Add the filter behavior
        if (options.filter) {
          self._getFilterStrategy($elt).render($rendering, options);
          _.forEach($$1('.filter', $rendering), function (filter) {
            const $filter = $$1(filter);
            const $filterBtn = $$1('button', $filter);
            const $filterInput = $$1('select, input', $filter);
            if ($filterInput.is('select')) {
              $filterInput.on('change', function () {
                self._filter($elt, $filter);
              });
            } else {
              // clicking the button trigger the request
              $filterBtn.off('click').on('click', function (e) {
                e.preventDefault();
                self._filter($elt, $filter);
              });

              // or press ENTER
              $filterInput.off('keypress').on('keypress', function (e) {
                if (e.which === 13) {
                  e.preventDefault();
                  self._filter($elt, $filter);
                }
              });
            }
          });
        }

        // check/uncheck all checkboxes
        $checkAll.click(function () {
          if (this.checked) {
            $checkAll.prop('checked', true);
            $checkboxes.prop('checked', true);
          } else {
            $checkAll.prop('checked', false);
            $checkboxes.prop('checked', false);
          }
          if ($massActionBtns.length) {
            $massActionBtns.toggleClass('invisible', !$checkboxes.filter(':checked').length);
          }

          /**
           * @event dataTable#select.dataTable
           */
          $elt.trigger('select.' + ns);
        });

        // when check/uncheck a box, toggle the check/uncheck all
        $checkboxes.click(function () {
          const $checked = $checkboxes.filter(':checked');
          if ($checked.length === $checkboxes.length) {
            $checkAll.prop('checked', true);
          } else {
            $checkAll.prop('checked', false);
          }
          if ($massActionBtns.length) {
            $massActionBtns.toggleClass('invisible', !$checkboxes.filter(':checked').length);
          }

          /**
           * @event dataTable#select.dataTable
           */
          $elt.trigger('select.' + ns);
        });

        // Remove sorted class from all th
        $$1('th.sorted', $rendering).removeClass('sorted');
        // Add the sorted class to the sorted element and the order class
        $sortElement.addClass('sorted').addClass('sorted_' + options.sortorder);

        // Update the status

        updateHeaderStatus(options, $rendering, dataset);
        $elt.html($rendering);

        // if the filter is enabled and a value is present, set the focus on the input field
        if (options.filter && options.filterquery) {
          $rendering.find('[name=filter].focused').focus();
        }

        // restore pagination's after data loaded
        enablePagination($elt.paginations);
        if (options.pageSizeSelector) {
          pageSizeSelector({
            renderTo: $$1('.toolbox-container', $rendering),
            defaultSize: options.rows
          }).on('change', function (val) {
            self._setRows($elt, val);
          });
        }
        loadingBar.stop();

        /**
         * @event dataTable#load.dataTable
         * @param {Object} dataset - The data set used to render the table
         */
        $elt.trigger('load.' + ns, [dataset]);
      },
      /**
       * Query set new page
       *
       * @param $elt
       * @param page
       * @fires dataTable#setpage.datatable
       */
      _setPage($elt, page) {
        const options = $elt.data(dataNs);
        if (options.page !== page) {
          // set new page value
          options.page = page;

          //rebind options to the elt
          $elt.data(dataNs, options);

          /**
           * @event dataTable#setpage.dataTable
           */
          $elt.trigger('setpage.' + ns);

          // Call the query
          this._query($elt);
        }
      },
      /**
       * Query filtered list of items
       *
       * @param {jQueryElement} $elt - plugin's element
       * @param {jQueryElement} $filter - the filter input
       * @fires dataTable#filter.datatable
       * @fires dataTable#sort.datatable
       * @private
       */
      _filter($elt, $filter) {
        const options = $elt.data(dataNs);
        const filtersData = this._getFilterStrategy($elt).getFiltersData($elt, $filter, options);
        options.page = 1;
        $elt.data(dataNs, _.assign(options, filtersData));

        /**
         * @event dataTable#filter.datatable
         * @param {Object} options - The options list
         */
        $elt.trigger('filter.' + ns, [options]);

        // Call the query
        this._query($elt, $filter);
      },
      _getFilterStrategy($elt) {
        const options = $elt.data(dataNs);
        return filterStrategyFactory(options);
      },
      /**
       * Query the previous page
       *
       * Called the jQuery way once registered by the Pluginifier.
       * @example $('selector').datatable('sort', 'firstname', false);
       *
       * @param {jQueryElement} $elt - plugin's element
       * @param {String} sortBy - the model id of the col to sort
       * @param {Boolean} [asc] - sort direction true for asc of deduced
       * @param {String} sortType - type of sorting, numeric or string
       * @fires dataTable#sort.datatable
       */
      _sort($elt, sortBy, asc, sortType) {
        const options = this._sortOptions($elt, sortBy, asc, sortType);

        /**
         * @event dataTable#sort.datatable
         * @param {String} column - The name of the column to sort
         * @param {String} direction - The sort direction
         * @param {String} type - The type of sorting field, string or numeric
         */
        $elt.trigger('sort.' + ns, [options.sortby, options.sortorder, options.sorttype]);
        this._query($elt);
      },
      /**
       * Compared current and next number of row and identifiers order of rows.
       *
       * @param {dataset} currentState
       * @param {dataset} nextState
       * @returns {Boolean}
       */
      _canApplyShallowUpdate(currentState, nextState) {
        const isStatesHasData = _.has(currentState, 'data') && _.has(nextState, 'data');
        if (!isStatesHasData) {
          return false;
        }
        const currentData = currentState.data;
        const nextData = nextState.data;
        if (currentData.length !== nextData.length) {
          return false;
        }
        return _.isEqual(currentData.map(data => data.id), nextData.map(data => data.id));
      },
      /**
       * Check possibility of atomic update data in datatable.
       *
       * @param {jQueryElement} $container Data table container element
       * @param {Object} nextState Data to be set to the data table
       * @param {Object} options Data table options
       * @returns {Boolean} Return true when data in table can be atomically updated
       */
      _shallowUpdate($container, nextState, options) {
        const currentState = $container.data(`${dataNs}state`);

        // Always update data state
        $container.data(`${dataNs}state`, nextState);
        if (!this._canApplyShallowUpdate(currentState, nextState)) {
          return;
        }

        // NOTE: The code above generate the table cell. With updating handlebars to the version > 2.*, please move it to the dedicated template to reuse it in layout.tpl as well
        nextState.data.forEach(nextData => {
          const $row = $container.find(`tr[data-item-identifier="${nextData.id}"]`);
          options.model.forEach(model => {
            const cellId = model.id;
            if (model.type) {
              const $actionCell = $row.find(`td.actions.${cellId}`);
              $actionCell.html('');
              model.actions.forEach(action => {
                const id = action.id;
                const hidden = getPropertyValue('hidden', action, nextData);
                const title = getPropertyValue('title', action, nextData);
                const disabled = getPropertyValue('disabled', action, nextData);
                const icon = getPropertyValue('icon', action, nextData);
                const label = getPropertyValue('label', action, nextData);
                const cls = getPropertyValue('cls', action, nextData);
                const $actionButton = $$1(buttonTpl({
                  id,
                  icon,
                  label,
                  title,
                  disabled,
                  cls
                }));
                if (!hidden) {
                  $actionCell.append('\n').append($actionButton);
                }
              });
            } else {
              const nextContent = nextData[cellId];
              $row.find(`td.${cellId}`).html(DOMPurify.sanitize(nextContent));
            }
          });
        });
        return true;
      },
      /**
       * Set the sort options.
       *
       * @param {jQueryElement} $elt - plugin's element
       * @param {String} sortBy - the model id of the col to sort
       * @param {Boolean|String} [asc] - sort direction true for asc of deduced
       * @param {String} sortType - sorting type, numeric or string sorting
       * @returns {Object} - returns the options
       * @private
       */
      _sortOptions($elt, sortBy, asc, sortType) {
        const options = $elt.data(dataNs);
        if (typeof asc !== 'undefined') {
          if ('asc' !== asc && 'desc' !== asc) {
            asc = asc ? 'asc' : 'desc';
          }
          options.sortorder = asc;
        } else if (options.sortorder === 'asc' && options.sortby === sortBy) {
          // If I already sort asc this element
          options.sortorder = 'desc';
        } else {
          // If I never sort by this element or
          // I sort by this element & the order was desc
          options.sortorder = 'asc';
        }

        // Change the sorting element anyway.
        options.sortby = sortBy;

        // define sorting type
        options.sorttype = sortType;

        //rebind options to the elt
        $elt.data(dataNs, options);
        return _.cloneDeep(options);
      },
      /**
       * Gets the selected items. Returns an array of identifiers.
       *
       * @param {jQueryElement} $elt - plugin's element
       * @returns {Array} - Returns an array of identifiers.
       */
      _selection($elt) {
        const $selected = $elt.find('[data-item-identifier]').has('td.checkboxes input:checked');
        const selection = [];
        $selected.each(function () {
          selection.push($$1(this).data('item-identifier'));
        });
        return selection;
      },
      _highlightRows($elt, rowIds) {
        $elt.find('[data-item-identifier]').removeClass('highlight');
        rowIds.forEach(rowId => {
          this._highlightRow($elt, rowId);
        });
      },
      /**
       * Highlight the row with identifier
       *
       * @param $elt
       * @param rowId
       * @deprecated Use highlightRows instead
       */
      _highlightRow($elt, rowId) {
        this._addRowClass($elt, rowId, 'highlight');
      },
      /**
       * Css class add to the row with id
       *
       * @param $elt
       * @param rowId
       * @param className
       * @private
       */
      _addRowClass($elt, rowId, className) {
        const $row = $elt.find('[data-item-identifier="' + rowId + '"]');
        if (!$row.hasClass(className)) {
          $row.addClass(className);
        }
      },
      /**
       * Css class remove from the row with id
       *
       * @param $elt
       * @param rowId
       * @param className
       * @private
       */
      _removeRowClass($elt, rowId, className) {
        const $row = $elt.find('[data-item-identifier="' + rowId + '"]');
        if ($row.hasClass(className)) {
          $row.removeClass(className);
        }
      },
      /**
       * Update amount items per page
       *
       * @param $elt
       * @param rows
       * @fires dataTable#setpage.datatable
       */
      _setRows($elt, rows) {
        const options = $elt.data(dataNs);
        if (options.rows !== rows) {
          // set new amount of items per page
          options.rows = rows;

          // set page to the first one
          options.page = 1;

          //rebind options to the elt
          $elt.data(dataNs, options);

          /**
           * @event dataTable#setpage.dataTable
           */
          $elt.trigger('setpage.' + ns);

          // Call the query
          this._query($elt);
        }
      }
    };
    Pluginifier.register(ns, dataTable, {
      expose: ['refresh', 'sort', 'filter', 'selection', 'render', 'highlightRow', 'highlightRows', 'addRowClass', 'removeRowClass']
    });

});
