");
+
+ /* Pop the first elements, they are empty strings */
+ x_array.shift();
+ y_array.shift();
+
+ x_array = jQuery.map(x_array, function (a) {
+ return parse_callnumber(a);
+ });
+ y_array = jQuery.map(y_array, function (a) {
+ return parse_callnumber(a);
+ });
+
+ x_array.sort();
+ y_array.sort();
+
+ x = x_array.shift();
+ y = y_array.shift();
+
+ if (!x) {
+ x = "";
+ }
+ if (!y) {
+ y = "";
+ }
+
+ return x < y ? -1 : x > y ? 1 : 0;
+};
+
+jQuery.fn.dataTableExt.oSort["callnumbers-desc"] = function (x, y) {
+ var x_array = x.split("
");
+ var y_array = y.split("
");
+
+ /* Pop the first elements, they are empty strings */
+ x_array.shift();
+ y_array.shift();
+
+ x_array = jQuery.map(x_array, function (a) {
+ return parse_callnumber(a);
+ });
+ y_array = jQuery.map(y_array, function (a) {
+ return parse_callnumber(a);
+ });
+
+ x_array.sort();
+ y_array.sort();
+
+ x = x_array.pop();
+ y = y_array.pop();
+
+ if (!x) {
+ x = "";
+ }
+ if (!y) {
+ y = "";
+ }
+
+ return x < y ? 1 : x > y ? -1 : 0;
+};
+
+function parse_callnumber(html) {
+ var array = html.split('');
+ if (array[1]) {
+ array = array[1].split("");
+ return array[0];
+ } else {
+ return "";
+ }
+}
+
+// see http://www.datatables.net/examples/advanced_init/footer_callback.html
+function footer_column_sum(api, column_numbers) {
+ // Remove the formatting to get integer data for summation
+ var intVal = function (i) {
+ if (typeof i === "number") {
+ if (isNaN(i)) return 0;
+ return i;
+ } else if (typeof i === "string") {
+ var value = i.replace(/[a-zA-Z ,.]/g, "") * 1;
+ if (isNaN(value)) return 0;
+ return value;
+ }
+ return 0;
+ };
+
+ for (var indice = 0; indice < column_numbers.length; indice++) {
+ var column_number = column_numbers[indice];
+
+ var total = 0;
+ var cells = api
+ .column(column_number, { page: "current" })
+ .nodes()
+ .to$()
+ .find("span.total_amount");
+ var budgets_totaled = [];
+ $(cells).each(function () {
+ budgets_totaled.push($(this).data("self_id"));
+ });
+ $(cells).each(function () {
+ if (
+ $(this).data("parent_id") &&
+ $.inArray($(this).data("parent_id"), budgets_totaled) > -1
+ ) {
+ return;
+ } else {
+ total += intVal($(this).html());
+ }
+ });
+ total /= 100; // Hard-coded decimal precision
+
+ // Update footer
+ $(api.column(column_number).footer()).html(total.format_price());
+ }
+}
+
+function filterDataTable(table, column, term) {
+ if (column) {
+ table.column(column).search(term).draw("page");
+ } else {
+ table.search(term).draw("page");
+ }
+}
+
+jQuery.fn.dataTable.ext.errMode = function (settings, note, message) {
+ if (settings && settings.jqXHR) {
+ console.log(
+ "Got %s (%s)".format(
+ settings.jqXHR.status,
+ settings.jqXHR.statusText
+ )
+ );
+ alert(
+ __(
+ "Something went wrong when loading the table.\n%s: %s. \n%s"
+ ).format(
+ settings.jqXHR.status,
+ settings.jqXHR.statusText,
+ settings.jqXHR.responseJSON &&
+ settings.jqXHR.responseJSON.errors
+ ? settings.jqXHR.responseJSON.errors
+ .map(m => m.message)
+ .join("\n")
+ : ""
+ )
+ );
+ } else {
+ alert(__("Something went wrong when loading the table."));
+ }
+ console.log(message);
+};
+
+function _dt_default_ajax(params) {
+ let default_filters = params.default_filters;
+ let options = params.options;
+
+ if (
+ !options.criteria ||
+ ["contains", "starts_with", "ends_with", "exact"].indexOf(
+ options.criteria.toLowerCase()
+ ) === -1
+ )
+ options.criteria = "contains";
+ options.criteria = options.criteria.toLowerCase();
+
+ return {
+ type: "GET",
+ cache: true,
+ dataSrc: "data",
+ beforeSend: function (xhr, settings) {
+ this._xhr = xhr;
+ if (options.embed) {
+ xhr.setRequestHeader(
+ "x-koha-embed",
+ Array.isArray(options.embed)
+ ? options.embed.join(",")
+ : options.embed
+ );
+ }
+ },
+ dataFilter: function (data, type) {
+ var json = { data: JSON.parse(data) };
+ if ((total = this._xhr.getResponseHeader("x-total-count"))) {
+ json.recordsTotal = total;
+ json.recordsFiltered = total;
+ }
+ if ((total = this._xhr.getResponseHeader("x-base-total-count"))) {
+ json.recordsTotal = total;
+ }
+ if ((draw = this._xhr.getResponseHeader("x-koha-request-id"))) {
+ json.draw = draw;
+ }
+
+ return JSON.stringify(json);
+ },
+ data: function (data, settings) {
+ var length = data.length;
+ var start = data.start;
+
+ let api = new $.fn.dataTable.Api(settings);
+ const global_search = api.search();
+
+ var dataSet = {
+ _page: Math.floor(start / length) + 1,
+ _per_page: length,
+ };
+
+ function build_query(col, value) {
+ var parts = [];
+ var attributes = col.data.split(":");
+ for (var i = 0; i < attributes.length; i++) {
+ var part = {};
+ var attr = attributes[i];
+ let default_build = true;
+ let criteria = options.criteria;
+ if (value.match(/^\^(.*)\$$/)) {
+ value = value.replace(/^\^/, "").replace(/\$$/, "");
+ criteria = "exact";
+ } else {
+ // escape SQL LIKE special characters %
+ value = value.replace(/(\%|\\)/g, "\\$1");
+ }
+
+ let built_value;
+ if (col.type == "date") {
+ let rfc3339 = $date_to_rfc3339(value);
+ if (rfc3339 != "Invalid Date") {
+ built_value = rfc3339;
+ }
+ }
+
+ if (col.datatype !== undefined) {
+ default_build = false;
+ let coded_datatype =
+ col.datatype.match(/^coded_value:(.*)/);
+ if (col.datatype == "related-object") {
+ let query_term = value;
+
+ if (criteria != "exact") {
+ query_term = {
+ like:
+ (["contains", "ends_with"].indexOf(
+ criteria
+ ) !== -1
+ ? "%"
+ : "") +
+ value +
+ (["contains", "starts_with"].indexOf(
+ criteria
+ ) !== -1
+ ? "%"
+ : ""),
+ };
+ }
+
+ part = {
+ [col.related + "." + col.relatedKey]:
+ col.relatedValue,
+ [col.related + "." + col.relatedSearchOn]:
+ query_term,
+ };
+ } else if (
+ coded_datatype &&
+ coded_datatype.length > 1
+ ) {
+ if (global_search.length || value.length) {
+ coded_datatype = coded_datatype[1];
+ const search_value = value.length
+ ? value
+ : global_search;
+ const regex = new RegExp(
+ `^${search_value}`,
+ "i"
+ );
+ if (
+ coded_values &&
+ coded_values.hasOwnProperty(coded_datatype)
+ ) {
+ let codes = [
+ ...coded_values[
+ coded_datatype
+ ].entries(),
+ ]
+ .filter(([label]) => regex.test(label))
+ .map(([, code]) => code);
+
+ if (codes.length) {
+ part[
+ !attr.includes(".")
+ ? "me." + attr
+ : attr
+ ] = codes;
+ } else {
+ // Coded value not found using the description, fallback to code
+ default_build = true;
+ }
+ } else {
+ console.log(
+ "coded datatype %s not supported yet".format(
+ coded_datatype
+ )
+ );
+ }
+ } else {
+ default_build = true;
+ }
+ } else {
+ console.log(
+ "datatype %s not supported yet".format(
+ col.datatype
+ )
+ );
+ }
+ }
+ if (default_build) {
+ let value_part;
+ if (criteria === "exact") {
+ value_part = built_value
+ ? [value, built_value]
+ : value;
+ } else {
+ let like = {
+ like:
+ (["contains", "ends_with"].indexOf(
+ criteria
+ ) !== -1
+ ? "%"
+ : "") +
+ value +
+ (["contains", "starts_with"].indexOf(
+ criteria
+ ) !== -1
+ ? "%"
+ : ""),
+ };
+ value_part = built_value
+ ? [like, built_value]
+ : like;
+ }
+
+ part[!attr.includes(".") ? "me." + attr : attr] =
+ value_part;
+ }
+
+ if (Object.keys(part).length) parts.push(part);
+ }
+ return parts;
+ }
+
+ var filter = data.search.value;
+ // Build query for each column filter
+ var and_query_parameters = settings.aoColumns
+ .filter(function (col) {
+ return (
+ col.bSearchable &&
+ typeof col.data == "string" &&
+ data.columns[col.idx].search.value != ""
+ );
+ })
+ .map(function (col) {
+ var value = data.columns[col.idx].search.value;
+ return build_query(col, value);
+ })
+ .map(function r(e) {
+ return $.isArray(e) ? $.map(e, r) : e;
+ });
+
+ // Build query for the global search filter
+ var or_query_parameters = settings.aoColumns
+ .filter(function (col) {
+ return col.bSearchable && filter != "";
+ })
+ .map(function (col) {
+ var value = filter;
+ return build_query(col, value);
+ })
+ .map(function r(e) {
+ return $.isArray(e) ? $.map(e, r) : e;
+ });
+
+ if (default_filters) {
+ let additional_filters = {};
+ for (f in default_filters) {
+ let k;
+ let v;
+ if (typeof default_filters[f] === "function") {
+ let val = default_filters[f]();
+ if (val != undefined && val != "") {
+ k = f;
+ v = val;
+ }
+ } else {
+ k = f;
+ v = default_filters[f];
+ }
+
+ // Pass to -or if you want a separate OR clause
+ // It's not the usual DBIC notation!
+ if (f == "-or") {
+ if (v) or_query_parameters.push(v);
+ } else if (f == "-and") {
+ if (v) and_query_parameters.push(v);
+ } else if (v) {
+ additional_filters[k] = v;
+ }
+ }
+ if (Object.keys(additional_filters).length) {
+ and_query_parameters.push(additional_filters);
+ }
+ }
+ query_parameters = and_query_parameters;
+ if (or_query_parameters.length) {
+ query_parameters.push(or_query_parameters);
+ }
+
+ if (query_parameters.length) {
+ query_parameters = JSON.stringify(
+ query_parameters.length === 1
+ ? query_parameters[0]
+ : { "-and": query_parameters }
+ );
+ dataSet.q = query_parameters;
+ }
+
+ dataSet._match = options.criteria;
+
+ if (data["draw"] !== undefined) {
+ settings.ajax.headers = { "x-koha-request-id": data.draw };
+ }
+
+ if (options.columns) {
+ var order = data.order;
+ var orderArray = new Array();
+ order.forEach(function (e, i) {
+ var order_col = e.column;
+ var order_by = options.columns[order_col].data;
+ order_by = order_by.split(":");
+ var order_dir = e.dir == "asc" ? "+" : "-";
+ Array.prototype.push.apply(
+ orderArray,
+ order_by.map(
+ x => order_dir + (!x.includes(".") ? "me." + x : x)
+ )
+ );
+ });
+ dataSet._order_by = orderArray
+ .filter((v, i, a) => a.indexOf(v) === i)
+ .join(",");
+ }
+
+ return dataSet;
+ },
+ };
+}
+
+function build_url_with_state(dt, table_settings) {
+ let table_key = "DataTables_%s_%s_%s".format(
+ table_settings.module,
+ table_settings.page,
+ table_settings.table
+ );
+
+ let state = JSON.stringify(dt.state());
+ delete state.time;
+ let searchParams = new URLSearchParams(window.location.search);
+ searchParams.set(table_key + "_state", btoa(state));
+
+ return (
+ window.location.origin +
+ window.location.pathname +
+ "?" +
+ searchParams.toString() +
+ window.location.hash
+ );
+}
+
function _dt_buttons(params) {
let settings = params.settings || {};
let table_settings = params.table_settings;
var exportColumns = ":visible:not(.no-export)";
+ const export_format_spreadsheet = {
+ body: function (data, row, column, node) {
+ var newnode = $(node);
+
+ if (newnode.find(".no-export").length > 0) {
+ newnode = newnode.clone();
+ newnode.find(".no-export").remove();
+ }
+ let trimmed_str = newnode.text().replace(/\n/g, " ").trim();
+ const unsafeCharacters = /^[=+\-@\t\r]/;
+ if (unsafeCharacters.test(trimmed_str)) {
+ trimmed_str = "'" + trimmed_str;
+ }
+ return trimmed_str;
+ },
+ };
+
var export_format = {
body: function (data, row, column, node) {
var newnode = $(node);
@@ -130,14 +828,14 @@ function _dt_buttons(params) {
extend: "excelHtml5",
exportOptions: {
columns: exportColumns,
- format: export_format,
+ format: export_format_spreadsheet,
},
},
{
extend: "csvHtml5",
exportOptions: {
columns: exportColumns,
- format: export_format,
+ format: export_format_spreadsheet,
},
},
{
@@ -156,46 +854,21 @@ function _dt_buttons(params) {
},
];
- let buttons = [
- {
- fade: 100,
- className: "dt_button_clear_filter",
- titleAttr: __("Clear filter"),
- enabled: false,
- text:
- ' ' +
- __("Clear filter") +
- "",
- action: function (e, dt, node, config) {
- dt.search("").draw("page");
- node.addClass("disabled");
- },
+ let buttons = [];
+ buttons.push({
+ fade: 100,
+ className: "dt_button_clear_filter",
+ titleAttr: __("Clear filter"),
+ enabled: false,
+ text:
+ ' ' +
+ __("Clear filter") +
+ "",
+ action: function (e, dt, node, config) {
+ dt.search("").draw("page");
+ node.addClass("disabled");
},
- {
- extend: "csvHtml5",
- text: __("CSV"),
- exportOptions: {
- columns: exportColumns,
- format: export_format,
- },
- },
- {
- extend: "copyHtml5",
- text: __("Copy"),
- exportOptions: {
- columns: exportColumns,
- format: export_format,
- },
- },
- {
- extend: "print",
- text: __("Print"),
- exportOptions: {
- columns: exportColumns,
- format: export_format,
- },
- },
- ];
+ });
// Retrieving bKohaColumnsUseNames from the options passed to the constructor, not DT's settings
// But ideally should be retrieved using table.data()
@@ -223,7 +896,7 @@ function _dt_buttons(params) {
className: "columns_controls",
titleAttr: __("Columns settings"),
text:
- ' ' +
+ ' ' +
__("Columns") +
"",
exportOptions: {
@@ -232,6 +905,45 @@ function _dt_buttons(params) {
});
}
+ buttons.push({
+ extend: "collection",
+ autoClose: true,
+ fade: 100,
+ className: "export_controls",
+ titleAttr: __("Export or print"),
+ text:
+ ' ' +
+ __("Export") +
+ "",
+ buttons: export_buttons,
+ });
+
+ if (table_settings) {
+ const writeToClipboard = async (text, node) => {
+ await navigator.clipboard.writeText(text);
+ $(node)
+ .tooltip({ trigger: "manual", title: __("Copied!") })
+ .tooltip("show");
+ };
+ buttons.push({
+ autoClose: true,
+ fade: 100,
+ className: "copyConditions_controls",
+ titleAttr: __("Copy shareable link"),
+ text:
+ ' ' +
+ __("Copy shareable link") +
+ "",
+ action: function (e, dt, node, config) {
+ const url = build_url_with_state(dt, table_settings);
+
+ if (navigator.clipboard && navigator.clipboard.writeText) {
+ writeToClipboard(url, node);
+ }
+ },
+ });
+ }
+
return buttons;
}
@@ -266,6 +978,329 @@ function _dt_visibility(table_settings, table_dt) {
return hidden_ids;
}
+function _dt_on_visibility(add_filters, table_node, table_dt) {
+ // FIXME REPLACE ME
+ if (typeof columnsInit == "function") {
+ // This function can be created separately and used to trigger
+ // an event after the DataTable has loaded AND column visibility
+ // has been updated according to the table's configuration
+ columnsInit();
+ }
+}
+
+function _dt_add_filters(table_node, table_dt, filters_options = {}) {
+ if (!$(table_node).length) return;
+
+ $(table_node).find("thead tr:eq(1)").remove(); // Remove if one exists already
+ $(table_node)
+ .find("thead tr")
+ .clone()
+ .appendTo($(table_node).find("thead"));
+
+ let visibility = table_dt.columns().visible();
+ let columns = table_dt.settings()[0].aoColumns;
+ table_dt.columns().every(function () {
+ var column = this;
+ let i = column.index();
+ var visible_i = table_dt.column.index("fromData", i);
+ let th = $(table_node).find(
+ "thead tr:eq(1) th:eq(%s)".format(visible_i)
+ );
+ var is_searchable = columns[i].bSearchable;
+ $(th)
+ .removeClass("sorting")
+ .removeClass("sorting_asc")
+ .removeClass("sorting_desc");
+ if (is_searchable) {
+ let input_type = "input";
+ let existing_search = column.search();
+ if ($(th).data("filter") || filters_options.hasOwnProperty(i)) {
+ input_type = "select";
+ let filter_type = $(th).data("filter");
+ let select = $(
+ '%s'.format(
+ this._id,
+ this._str
+ )
+ );
+ // Compare with lc, or selfreg won't match ^SELFREG$ for instance, see bug 32517
+ // This is only for category, we might want to apply it only in this case.
+ existing_search = existing_search.toLowerCase();
+ if (
+ existing_search === this._id ||
+ (existing_search &&
+ this._id.toLowerCase().match(existing_search))
+ ) {
+ o.prop("selected", "selected");
+ }
+ o.appendTo(select);
+ });
+ $(th).html(select);
+ } else {
+ var title = $(th).text();
+ if (existing_search) {
+ $(th).html(
+ ''.format(
+ existing_search
+ )
+ );
+ } else {
+ var search_title = __("%s search").format(title);
+ $(th).html(
+ ''.format(
+ search_title
+ )
+ );
+ }
+ }
+ } else {
+ $(th).html("");
+ }
+ });
+ _dt_add_delay_filters(table_dt, table_node);
+ table_dt.fixedHeader.adjust();
+}
+
+function _dt_add_delay(table_dt, table_node) {
+ let delay_ms = 500;
+
+ let search = DataTable.util.debounce(function (val) {
+ table_dt.search(val);
+ table_dt.draw();
+ }, delay_ms);
+
+ $("#" + table_node.attr("id") + "_wrapper")
+ .find(".dt-input")
+ .unbind()
+ .bind("keyup", search(this.value));
+}
+
+function _dt_add_delay_filters(table_dt, table_node) {
+ let delay_ms = 500;
+
+ let col_input_search = DataTable.util.debounce(function (i, val) {
+ table_dt.column(i).search(val).draw();
+ }, delay_ms);
+ let col_select_search = DataTable.util.debounce(function (i, val) {
+ table_dt.column(i).search(val, true, false).draw();
+ }, delay_ms);
+
+ $(table_node)
+ .find("thead tr:eq(1) th")
+ .each(function (visible_i) {
+ var i = table_dt.column.index("fromVisible", visible_i);
+ $(this)
+ .find("input")
+ .unbind()
+ .bind("keyup change", function (e) {
+ if (e.keyCode === undefined) return;
+ col_input_search(i, this.value);
+ });
+
+ $(this)
+ .find("select")
+ .unbind()
+ .bind("keyup change", function () {
+ let value = this.value.length ? "^" + this.value + "$" : "";
+ col_select_search(i, this.value);
+ });
+ });
+}
+
+function _dt_save_restore_state(table_settings, external_filter_nodes = {}) {
+ let table_key = "DataTables_%s_%s_%s".format(
+ table_settings.module,
+ table_settings.page,
+ table_settings.table
+ );
+
+ let default_save_state = table_settings.default_save_state;
+ let default_save_state_search = table_settings.default_save_state_search;
+
+ let stateSaveCallback = function (settings, data) {
+ localStorage.setItem(table_key, JSON.stringify(data));
+ };
+
+ function set_default(table_settings, table_dt) {
+ let columns = new Array(table_dt.columns()[0].length).fill({
+ visible: true,
+ });
+ let hidden_ids = _dt_visibility(table_settings, table_dt);
+ hidden_ids.forEach((id, i) => {
+ columns[id] = { visible: false };
+ });
+ // State is not loaded if time is not passed
+ return { columns, time: new Date() };
+ }
+ let stateLoadCallback = function (settings) {
+ // Load state from URL
+ const url = new URL(window.location.href);
+ let state_from_url = url.searchParams.get(table_key + "_state");
+ if (state_from_url) {
+ $("#" + settings.nTable.id).data("loaded_from_state", true);
+ return JSON.parse(atob(state_from_url));
+ }
+
+ if (!default_save_state) return set_default(table_settings, this.api());
+
+ let state = localStorage.getItem(table_key);
+ if (!state) return set_default(table_settings, this.api());
+
+ state = JSON.parse(state);
+
+ if (default_save_state_search) {
+ $("#" + settings.nTable.id).data("loaded_from_state", true);
+ } else {
+ delete state.search;
+ state.columns.forEach(c => delete c.search);
+ }
+ return state;
+ };
+
+ let stateSaveParams = function (settings, data) {
+ // FIXME Selector in on the whole DOM, we don't know where the filters are
+ // If others exist on the same page this will lead to unexpected behaviours
+ // Should be safe so far as: patron search use the same code for the different searches
+ // but only the main one has the table settings (and so the state saved)
+ data.external_filters = Object.keys(external_filter_nodes).reduce(
+ (r, k) => {
+ let node = $(external_filter_nodes[k]);
+ let tag_name = node.prop("tagName");
+ if (tag_name == "INPUT" && node.prop("type") == "checkbox") {
+ r[k] = $(external_filter_nodes[k]).prop("checked");
+ } else if (tag_name == "INPUT" || tag_name == "SELECT") {
+ r[k] = $(external_filter_nodes[k]).val();
+ } else {
+ console.log(
+ "Tag '%s' not supported yet for DT state".format(
+ tag_name
+ )
+ );
+ }
+ return r;
+ },
+ {}
+ );
+ };
+ let stateLoadParams = function (settings, data) {
+ if (!$("#" + settings.nTable.id).data("loaded_from_state")) return;
+
+ if (data.external_filters) {
+ Object.keys(external_filter_nodes).forEach((k, i) => {
+ if (data.external_filters.hasOwnProperty(k)) {
+ let node = $(external_filter_nodes[k]);
+ let tag_name = node.prop("tagName");
+ let value = data.external_filters[k];
+ if (
+ tag_name == "INPUT" &&
+ node.prop("type") == "checkbox"
+ ) {
+ node.prop("checked", value);
+ } else if (
+ tag_name == "INPUT" &&
+ node.hasClass("flatpickr")
+ ) {
+ const fp = document.querySelector(
+ external_filter_nodes[k]
+ )._flatpickr;
+ fp.setDate(value);
+ } else if (tag_name == "INPUT" || tag_name == "SELECT") {
+ node.val(value);
+ } else {
+ console.log(
+ "Tag '%s' not supported yet for DT state".format(
+ tag_name
+ )
+ );
+ }
+ }
+ });
+ }
+ };
+
+ return {
+ stateSave: true,
+ stateSaveCallback,
+ stateLoadCallback,
+ stateSaveParams,
+ stateLoadParams,
+ };
+}
+
+function update_search_description(
+ table_node,
+ table_dt,
+ additional_search_descriptions
+) {
+ let description_node = additional_search_descriptions.node;
+ let description_fns =
+ additional_search_descriptions.additional_search_descriptions;
+ let description = [];
+ let global_search = table_dt.search();
+ if (global_search.length) {
+ description.push(__("Anywhere: %s").format(global_search));
+ }
+ let additional_searches = [];
+ for (f in description_fns) {
+ let d = description_fns[f]();
+ if (d.length) {
+ additional_searches.push(d);
+ }
+ }
+ if (additional_searches.length) {
+ description.push(additional_searches.join(", "));
+ }
+ let column_searches = [];
+ table_dt
+ .columns()
+ .search()
+ .each((search, i) => {
+ if (search.length) {
+ // Retrieve the value from the dropdown list if there is one
+ var visible_i = table_dt.column.index("fromData", i);
+ let option = $(table_node).find(
+ "thead tr:eq(1) th:eq(%s) select option:selected".format(
+ visible_i
+ )
+ );
+ if (option.length) {
+ search = $(option).text();
+ }
+
+ column_searches.push(
+ "%s=%s".format(
+ table_dt.column(i).header().innerHTML,
+ search
+ )
+ );
+ }
+ });
+ if (column_searches.length) {
+ description.push(column_searches.join(", "));
+ }
+
+ if (description.length) {
+ let display = description.length ? "block" : "none";
+ let description_str = $(description_node)
+ .data("prefix")
+ .format(description.join("
"));
+ $(description_node).html(description_str).show();
+ } else {
+ $(description_node).html("").hide();
+ }
+}
+
(function ($) {
/**
* Create a new dataTables instance that uses the Koha RESTful API's as a data source
@@ -276,11 +1311,33 @@ function _dt_visibility(table_settings, table_dt) {
* Supports `contains`, `starts_with`, `ends_with` and `exact` match
* @param {string} [options.columns.*.type Data type the field is stored in so we may impose some additional
* manipulation to search strings. Supported types are currenlty 'date'
+ * @param {string} [options.columns.*.datatype Data type the field is stored in so we may impose some additional
+ * manipulation logic to search. Supported types are currently 'related-object',
+ * for implimenting joins in api search queries, and 'coded_value:TABLE' to allow
+ * for clientside translations of description to code to reduce join requirements.
+ * See bug 39011 for an example implimentation.
* @param {Object} table_settings The arrayref as returned by TableSettings.GetTableSettings function
* available from the columns_settings template toolkit include
+ * @param {Boolean} add_filters Add a filters row as the top row of the table
+ * @param {Object} default_filters Add a set of default search filters to apply at table initialisation
+ * @param {Object} filters_options Build selects for the filters row, and pass their value.
+ * eg. { 1 => vendors } will build a select with the vendor list in the second column
+ * @param {Object} show_search_descriptions Whether or not display the search descriptions when the table is drawn
+ * Expect a node in which to place the descriptions. It must have a data-prefix attribute to build the description properly.
+ * @param {Object} aditional_search_descriptions Pass additional descriptions
+ * eg. { surname => () => { 'Surname with '.format($("#surname_form").val()) } }
* @return {Object} The dataTables instance
*/
- $.fn.kohaTable = function (options = {}, table_settings = undefined) {
+ $.fn.kohaTable = function (
+ options = {},
+ table_settings,
+ add_filters,
+ default_filters,
+ filters_options,
+ external_filter_nodes,
+ show_search_descriptions,
+ additional_search_descriptions
+ ) {
// Early return if the node does not exist
if (!this.length) return;
@@ -293,6 +1350,17 @@ function _dt_visibility(table_settings, table_dt) {
dataTablesDefaults.initComplete(settings, json);
};
}
+
+ if (options.ajax && !options.bKohaAjaxSVC) {
+ options.ajax = Object.assign(
+ {},
+ options.ajax,
+ _dt_default_ajax({ default_filters, options })
+ );
+ options.serverSide = true;
+ options.processing = true;
+ options.pagingType = "full_numbers";
+ }
}
var settings = $.extend(
@@ -300,6 +1368,7 @@ function _dt_visibility(table_settings, table_dt) {
{},
dataTablesDefaults,
{
+ paging: true,
searching: true,
language: {
emptyTable: options.emptyTable
@@ -312,7 +1381,17 @@ function _dt_visibility(table_settings, table_dt) {
settings["buttons"] = _dt_buttons({ settings, table_settings });
+ if (add_filters) {
+ settings["orderCellsTop"] = true;
+ }
+
if (table_settings) {
+ let state_settings = _dt_save_restore_state(
+ table_settings,
+ external_filter_nodes
+ );
+ settings = { ...settings, ...state_settings };
+
if (
table_settings.hasOwnProperty("default_display_length") &&
table_settings["default_display_length"] != null
@@ -343,9 +1422,19 @@ function _dt_visibility(table_settings, table_dt) {
}
$(this).data("bKohaColumnsUseNames", settings.bKohaColumnsUseNames);
+ $(this).data("bKohaAjaxSVC", settings.bKohaAjaxSVC); // Must not be used for new tables!
var table = $(this).dataTable(settings);
var table_dt = table.DataTable();
+ if (add_filters) {
+ _dt_add_filters(this, table_dt, filters_options);
+ }
+
+ table_dt.on("column-visibility.dt", function () {
+ if (add_filters) {
+ _dt_add_filters(this, table_dt, filters_options);
+ }
+ });
table_dt.on("search.dt", function (e, settings) {
// When the DataTables search function is triggered,
@@ -354,18 +1443,18 @@ function _dt_visibility(table_settings, table_dt) {
toggledClearFilter(table_dt.search(), settings.nTable.id);
});
- let hidden_ids = _dt_visibility(table_settings, table_dt);
- table_dt
- .on("column-visibility.dt", function () {
- if (typeof columnsInit == "function") {
- // This function can be created separately and used to trigger
- // an event after the DataTable has loaded AND column visibility
- // has been updated according to the table's configuration
- columnsInit();
- }
- })
- .columns(hidden_ids)
- .visible(false);
+ if (show_search_descriptions !== undefined) {
+ table_dt.on("draw", function (e, settings) {
+ update_search_description(table, table_dt, {
+ node: show_search_descriptions,
+ additional_search_descriptions,
+ });
+ });
+ update_search_description(table, table_dt, {
+ node: show_search_descriptions,
+ additional_search_descriptions,
+ });
+ }
return table;
};