diff --git a/formats/datatables/Api.php b/formats/datatables/Api.php index d83cfb75..7af53934 100644 --- a/formats/datatables/Api.php +++ b/formats/datatables/Api.php @@ -36,10 +36,6 @@ public function execute() { $datatableData = $data['datatableData']; $settings = $data['settings']; - if ( empty( $datatableData['length'] ) ) { - $datatableData['length'] = $settings['defer-each']; - } - if ( empty( $datatableData['start'] ) ) { $datatableData['start'] = 0; } @@ -68,18 +64,17 @@ public function execute() { [ // *** important !! 'format' => 'datatables', - "apicall" => "apicall", - // @see https://datatables.net/manual/server-side - // array length will be sliced client side if greater - // than the required datatables length - "limit" => max( $datatableData['length'], $settings['defer-each'] ), - "offset" => $datatableData['start'], - - "sort" => implode( ',', array_map( static function ( $value ) use( $datatableData ) { + 'apicall' => 'apicall', + + // @TODO limit taking into account PreloaData + 'limit' => max( $datatableData['length'], $settings['limit'] ), + 'offset' => $datatableData['start'], + + 'sort' => implode( ',', array_map( static function ( $value ) use( $datatableData ) { return $datatableData['columns'][$value['column']]['name']; }, $datatableData['order'] ) ), - "order" => implode( ',', array_map( static function ( $value ) { + 'order' => implode( ',', array_map( static function ( $value ) { return $value['dir']; }, $datatableData['order'] ) ) @@ -298,7 +293,8 @@ public function execute() { 'recordsTotal' => $settings['count'], 'recordsFiltered' => $count, 'cacheKey' => $data['cacheKey'], - 'datalength' => $datatableData['length'] + 'datalength' => $datatableData['length'], + 'start' => $datatableData['start'] ]; if ( $settings['displayLog'] ) { diff --git a/formats/datatables/DataTables.php b/formats/datatables/DataTables.php index 9c7a42ee..a7c2772c 100644 --- a/formats/datatables/DataTables.php +++ b/formats/datatables/DataTables.php @@ -99,11 +99,10 @@ public function getParamDefinitions( array $definitions ) { 'values' => [ 'all', 'subject', 'none', 'auto' ], ]; - $params['defer-each'] = [ + $params['limit'] = [ 'type' => 'integer', - 'message' => 'smw-paramdesc-defer-each', - // $GLOBALS['smwgQMaxLimit'] - 'default' => 0, + 'message' => 'smw-paramdesc-limit', + 'default' => 100, ]; // *** only used internally, do not use in query diff --git a/formats/datatables/Hooks.php b/formats/datatables/Hooks.php index a6413f71..4a985a84 100644 --- a/formats/datatables/Hooks.php +++ b/formats/datatables/Hooks.php @@ -34,12 +34,11 @@ public static function onSMWStoreBeforeQueryResultLookupComplete( $store, $query } $inlineLimit = $query->getLimit(); + $count = self::getCount( $query, $queryEngine ); - // $limit = ( !empty( $params['defer-each'] ) ? $params['defer-each'] : $inlineLimit ); if ( empty( $params['noajax'] ) ) { - // $lengthmenuMax = max( $params['datatables-lengthmenu'] ); - $limit = max( $params['datatables-pagelength'], $params['defer-each'], $inlineLimit ); + $limit = max( $params['datatables-pagelength'], $inlineLimit ); } else { $limit = $count; diff --git a/formats/datatables/resources/ext.srf.formats.datatables.js b/formats/datatables/resources/ext.srf.formats.datatables.js index b3b2d668..740a6767 100644 --- a/formats/datatables/resources/ext.srf.formats.datatables.js +++ b/formats/datatables/resources/ext.srf.formats.datatables.js @@ -9,7 +9,7 @@ */ (function ($, mw, srf) { - "use strict"; + 'use strict'; /* Private methods and objects */ @@ -46,7 +46,96 @@ * @return {string} */ getID: function (container) { - return container.attr("id"); + return container.attr('id'); + }, + + getNestedProp: function (path, obj) { + return path.reduce((xs, x) => (xs && xs[x] ? xs[x] : null), obj); + }, + + objectValues: function (obj) { + return Object.keys(obj).map(function (e) { + return obj[e]; + }); + }, + objectEntries: function (obj) { + var keys = Object.keys(obj), + i = keys.length, + ret = new Array(i); + while (i--) { + ret[i] = [keys[i], obj[keys[i]]]; + } + return ret; + }, + + getCacheLimit: function (obj) { + return _cacheLimit; + }, + + getCacheKey: function (obj) { + // this ensures that the preload key + // and the dynamic key match + // this does not work: "searchPanes" in obj && Object.entries(obj.searchPanes).find(x => Object.keys(x).length ) ? obj.searchPanes : {}, + if ('searchPanes' in obj) { + for (var i in obj.searchPanes) { + if (!Object.keys(obj.searchPanes[i]).length) { + delete obj.searchPanes[i]; + } + } + } + + return objectHash.sha1({ + order: obj.order.map((x) => { + return { column: x.column, dir: x.dir }; + }), + // search: obj.search, + searchPanes: + 'searchPanes' in obj && + _datatables + .objectEntries(obj.searchPanes) + .find((x) => Object.keys(x).length) + ? obj.searchPanes + : {}, + searchBuilder: 'searchBuilder' in obj ? obj.searchBuilder : {}, + }); + }, + + getCacheData: function (count, preloadData, cacheKey, datatableData) { + if (!(cacheKey in preloadData)) { + return false; + } + + var data = []; + for ( + var i = datatableData.start; + i < datatableData.start + datatableData.length; + i++ + ) { + if (i >= count) { + break; + } + if (!(i in preloadData[cacheKey].data)) { + return false; + } + data.push(preloadData[cacheKey].data[i]); + } + + return { count: preloadData[cacheKey].count, data }; + }, + + setCacheData: function (preloadData, json) { + var cacheKey = json.cacheKey; + if (!(cacheKey in preloadData)) { + preloadData[cacheKey] = { data: {} }; + } + + var n = json.start; + for (var row of json.data) { + preloadData[cacheKey].data[n] = row; + n++; + } + + preloadData[cacheKey].count = json.recordsFiltered; }, /** @@ -87,22 +176,22 @@ * @param {Object} table */ initColumnSort: function (table) { - var column = table.data("column-sort"); + var column = table.data('column-sort'); var order = []; // SMW allows descending and ascending but those are unknown to DataTables var orderMap = { - descending: "desc", - ascending: "asc", - asc: "asc", - desc: "desc", + descending: 'desc', + ascending: 'asc', + asc: 'asc', + desc: 'desc', }; // https://datatables.net/reference/api/order() // [1, 'asc'], [2, 'desc'] $.map(column.sort, function (val, i) { - if (val === "") { + if (val === '') { i = 0; } @@ -112,26 +201,26 @@ order.push([ $.inArray(val, column.list), // Find matchable index from the list - column.order[i] === undefined ? "asc" : orderMap[column.order[i]], + column.order[i] === undefined ? 'asc' : orderMap[column.order[i]], ]); }); if (order.length > 0) { - table.data("order", order); + table.data('order', order); } else { // default @see https://datatables.net/reference/option/order - table.data("order", [[0, "asc"]]); + table.data('order', [[0, 'asc']]); } }, initSearchPanesColumns(columnDefs, options) { for (var i in columnDefs) { - if (!("searchPanes" in columnDefs[i])) { + if (!('searchPanes' in columnDefs[i])) { columnDefs[i].searchPanes = {}; } if ( - "show" in columnDefs[i].searchPanes && + 'show' in columnDefs[i].searchPanes && columnDefs[i].searchPanes.show === false ) { delete columnDefs[i].searchPanes; @@ -139,7 +228,7 @@ } if ( - "columns" in options.searchPanes && + 'columns' in options.searchPanes && options.searchPanes.columns.length && $.inArray(i * 1, options.searchPanes.columns) < 0 ) { @@ -153,10 +242,10 @@ getPanesOptions: function (data, columnDefs, options) { var ret = {}; var dataLength = {}; - var div = document.createElement("div"); + var div = document.createElement('div'); for (var i in columnDefs) { - if ("searchPanes" in columnDefs[i]) { + if ('searchPanes' in columnDefs[i]) { ret[i] = {}; dataLength[i] = 0; } @@ -172,14 +261,14 @@ var label; if (options.searchPanes.htmlLabels === false) { div.innerHTML = cellData.display; - label = div.textContent || div.innerText || ""; + label = div.textContent || div.innerText || ''; } else { label = cellData.display; } // this will exclude images as well if // options.searchPanes.htmlLabels === false - if (label === "") { + if (label === '') { continue; } @@ -197,7 +286,7 @@ for (var i in ret) { var threshold = - "threshold" in columnDefs[i].searchPanes + 'threshold' in columnDefs[i].searchPanes ? columnDefs[i].searchPanes.threshold : options.searchPanes.threshold; @@ -237,9 +326,9 @@ for (let i in searchPanesOptions) { // @see https://datatables.net/reference/option/columns.searchPanes.combiner columnDefs[i].searchPanes.combiner = - "combiner" in columnDefs[i].searchPanes + 'combiner' in columnDefs[i].searchPanes ? columnDefs[i].searchPanes.combiner - : "or"; + : 'or'; columnDefs[i].searchPanes.options = []; // @see https://datatables.net/reference/option/columns.searchPanes.options @@ -262,9 +351,9 @@ columnDefs, options ) { - var div = document.createElement("div"); + var div = document.createElement('div'); for (var i in searchPanesOptions) { - if (!("searchPanes" in columnDefs[i])) { + if (!('searchPanes' in columnDefs[i])) { columnDefs[i].searchPanes = {}; } columnDefs[i].searchPanes.show = @@ -274,7 +363,7 @@ if (options.searchPanes.htmlLabels === false) { div.innerHTML = searchPanesOptions[i][ii].label; searchPanesOptions[i][ii].label = - div.textContent || div.innerText || ""; + div.textContent || div.innerText || ''; } searchPanesOptions[i][ii].total = searchPanesOptions[i][ii].count; @@ -282,7 +371,7 @@ } for (var i in columnDefs) { - if ("searchPanes" in columnDefs[i] && !(i in searchPanesOptions)) { + if ('searchPanes' in columnDefs[i] && !(i in searchPanesOptions)) { delete columnDefs[i].searchPanes; } } @@ -298,44 +387,38 @@ displayLog ) { var payload = { - action: "ext.srf.datatables.api", - format: "json", + action: 'ext.srf.datatables.api', + format: 'json', data: JSON.stringify(data), }; new mw.Api() .post(payload) .done(function (results) { - var json = results["datatables-json"]; + var json = results['datatables-json']; if (displayLog) { - console.log("results log", json.log); + console.log('results log', json.log); } // cache all retrieved rows for each sorting // dimension (column/dir), up to a fixed - // threshold (_cacheLimit) - - if (data.datatableData.search.value === "") { - preloadData[json.cacheKey] = { - data: preloadData[json.cacheKey]["data"] - .slice(0, data.datatableData.start) - .concat(json.data), - count: json.recordsFiltered, - }; + // threshold (CacheLimit) + if (json.cacheKey) { + _datatables.setCacheData(preloadData, json); } // we retrieve more than "length" // expected by datatables, so return the // sliced result - json.data = json.data.slice(0, data.datatableData.datalength); + json.data = json.data.slice(0, data.datalength); json.searchPanes = { options: searchPanesOptions, }; callback(json); }) .fail(function (error) { - console.log("error", error); + console.log('error', error); }); }, @@ -348,65 +431,66 @@ */ oLanguage: { oAria: { - sSortAscending: mw.msg("srf-ui-datatables-label-oAria-sSortAscending"), + sSortAscending: mw.msg('srf-ui-datatables-label-oAria-sSortAscending'), sSortDescending: mw.msg( - "srf-ui-datatables-label-oAria-sSortDescending" + 'srf-ui-datatables-label-oAria-sSortDescending' ), }, oPaginate: { - sFirst: mw.msg("srf-ui-datatables-label-oPaginate-sFirst"), - sLast: mw.msg("srf-ui-datatables-label-oPaginate-sLast"), - sNext: mw.msg("srf-ui-datatables-label-oPaginate-sNext"), - sPrevious: mw.msg("srf-ui-datatables-label-oPaginate-sPrevious"), + sFirst: mw.msg('srf-ui-datatables-label-oPaginate-sFirst'), + sLast: mw.msg('srf-ui-datatables-label-oPaginate-sLast'), + sNext: mw.msg('srf-ui-datatables-label-oPaginate-sNext'), + sPrevious: mw.msg('srf-ui-datatables-label-oPaginate-sPrevious'), }, - sEmptyTable: mw.msg("srf-ui-datatables-label-sEmptyTable"), - sInfo: mw.msg("srf-ui-datatables-label-sInfo"), - sInfoEmpty: mw.msg("srf-ui-datatables-label-sInfoEmpty"), - sInfoFiltered: mw.msg("srf-ui-datatables-label-sInfoFiltered"), - sInfoPostFix: mw.msg("srf-ui-datatables-label-sInfoPostFix"), - sInfoThousands: mw.msg("srf-ui-datatables-label-sInfoThousands"), - sLengthMenu: mw.msg("srf-ui-datatables-label-sLengthMenu"), - sLoadingRecords: mw.msg("srf-ui-datatables-label-sLoadingRecords"), + sEmptyTable: mw.msg('srf-ui-datatables-label-sEmptyTable'), + sInfo: mw.msg('srf-ui-datatables-label-sInfo'), + sInfoEmpty: mw.msg('srf-ui-datatables-label-sInfoEmpty'), + sInfoFiltered: mw.msg('srf-ui-datatables-label-sInfoFiltered'), + sInfoPostFix: mw.msg('srf-ui-datatables-label-sInfoPostFix'), + sInfoThousands: mw.msg('srf-ui-datatables-label-sInfoThousands'), + sLengthMenu: mw.msg('srf-ui-datatables-label-sLengthMenu'), + sLoadingRecords: mw.msg('srf-ui-datatables-label-sLoadingRecords'), // *** hide "processing" label above the indicator // sProcessing: mw.msg("srf-ui-datatables-label-sProcessing"), - sSearch: mw.msg("srf-ui-datatables-label-sSearch"), - sZeroRecords: mw.msg("srf-ui-datatables-label-sZeroRecords"), + sSearch: mw.msg('srf-ui-datatables-label-sSearch'), + sZeroRecords: mw.msg('srf-ui-datatables-label-sZeroRecords'), + searchBuilder: { title: { // Format condition count into info chip - _: `${ mw.msg( 'srf-ui-datatables-label-conditions' ) } %d`, - 0: mw.msg( 'srf-ui-datatables-label-conditions' ) - } + _: `${mw.msg('srf-ui-datatables-label-conditions')} %d`, + 0: mw.msg('srf-ui-datatables-label-conditions'), + }, }, searchPanes: { title: { // Format filter count into info chip - _: `${ mw.msg( 'srf-ui-datatables-label-filters' ) } %d`, - 0: mw.msg( 'srf-ui-datatables-label-filters' ) - } - } + _: `${mw.msg('srf-ui-datatables-label-filters')} %d`, + 0: mw.msg('srf-ui-datatables-label-filters'), + }, + }, }, // we don't need it anymore, however keep it as // a reference for other use showNotice: function (context, container, msg) { var cookieKey = - "srf-ui-datatables-searchpanes-notice-" + - mw.config.get("wgUserName") + - "-" + - mw.config.get("wgArticleId"); + 'srf-ui-datatables-searchpanes-notice-' + + mw.config.get('wgUserName') + + '-' + + mw.config.get('wgArticleId'); if ( - mw.config.get("wgUserName") != context.data("editor") || + mw.config.get('wgUserName') != context.data('editor') || mw.cookie.get(cookieKey) ) { return; } var messageWidget = new OO.ui.MessageWidget({ - type: "warning", + type: 'warning', label: new OO.ui.HtmlSnippet(mw.msg(msg)), // *** this does not work before ooui v0.43.0 showClose: true, @@ -415,23 +499,23 @@ // 1 month var expires = 1 * 30 * 24 * 3600; mw.cookie.set(cookieKey, true, { - path: "/", + path: '/', expires: expires, }); $(messageWidget.$element).parent().remove(); }; - messageWidget.on("close", closeFunction); - $(context).prepend($("