Explorar o código

fixed ajax requests for large tables - count total as separate request

Piotr Labudda %!s(int64=7) %!d(string=hai) anos
pai
achega
7e8268bd70

+ 9 - 1
SE/se-lib/TableAjax.php

@@ -452,6 +452,7 @@ class TableAjax extends ViewAjax {
 			'URI_BASE' => Request::getPathUri(),
 			'URI_WPS' => Request::getPathUri() . 'wps.php',
 			'RENDER_AS_REACT' => V::get('TEST_RENDER_AS_REACT', '', $_GET),
+			'SKIP_TOTAL' => true, // TEST: ('1' === V::get('SKIP_TOTAL', '', $_GET)),
 			'DBG' => ('1' === V::get('DBG_JS', '', $_GET)),
 		]);
 		$filterInit = $this->_filterInit;
@@ -2056,6 +2057,7 @@ jQuery(document).ready(function(){
 	}
 
 	public function ajaxData($args) {// executed from url: "{$this->syncUrl"&_hash={$this->_htmlID}&_task=loadDataAjax"
+		session_write_close();
 		$acl = $this->_acl;
 
 		$pageSize = V::get('pageSize', $this->_pageSize, $args, 'int');
@@ -2289,7 +2291,13 @@ jQuery(document).ready(function(){
 			$params['f_is_instance'] = $acl->getNamespace();
 		}
 		$queryFeatures = $acl->buildQuery($params);
-		$jsonData->total = $queryFeatures->getTotal();
+		$jsonData->total = ('1' === V::get('SKIP_TOTAL', '', $args)) ? -1 : $queryFeatures->getTotal();
+		if ('1' === V::get('ONLY_TOTAL', '', $args)) {
+			sleep(2);
+			$jsonData->type = 'success';
+			$jsonData->msg = 'pobrano nowe dane';
+			return $jsonData;
+		}
 		$listItems = $queryFeatures->getItems();
 		$primaryKeyField = $acl->getPrimaryKeyField();
 		$items = []; foreach ($listItems as $item) $items[ $item[$primaryKeyField] ] = $item;

+ 93 - 35
SE/se-lib/TableAjax.php.TableAjax.js

@@ -3,6 +3,7 @@ if (!URI_WPS) throw "Missing URI_WPS";
 if (!global.jQuery) throw "Missing jQuery"
 var $ = global.jQuery
 var RENDER_AS_REACT = Boolean(RENDER_AS_REACT) || false
+var SKIP_TOTAL = Boolean(SKIP_TOTAL) || false
 var DBG = DBG || false;
 var DBG1 = true;
 
@@ -764,7 +765,6 @@ var TableAjax = function() {
 
 	var _state = {};// state - to replace variables below (date, ...)
 	var _data; //columns and rows
-	var _totalPages; // total pages
 	var _currDpOp; // clicked datetimepicker operator
 	var _filterTimeout; // timer for delayed filtering
 	var _checkToggleChecked = false; // check-all toggle state
@@ -1169,29 +1169,14 @@ var TableAjax = function() {
 			// _uiNode$Table.find('tbody > tr').remove();
 			_bodyNode.find('tr').remove();
 
-			// find out what rows to show next...
-			var rowsAdded = 0;
-
-			// slice out the chunk of data we need and create rows
-			$.each(_data.rows, function(index, props) {
+			var rowsAdded = Math.min(_data.rows.length, priv.options.pageSize);
+			_data.rows.slice(0, rowsAdded).forEach(function (props) {
 				var rowNode = priv.renderRow(props);
 				if (rowNode) rowNode.appendTo(_bodyNode);
-				rowsAdded++;
-				if (rowsAdded >= priv.options.pageSize) { // enough rows created
-					return false;
-				}
-			});
-
-			if (_state.page == 1) {
-				_totalPages = Math.ceil(_data.total / priv.options.pageSize);
-			}
+			})
 
-			if (_state.page == _totalPages) { // pad with empty rows if we're at last page.
-				while (rowsAdded < priv.options.pageSize) {
-					var rowNode = priv.renderRowEmptyNode();
-					if (rowNode) rowNode.appendTo(_bodyNode);
-					rowsAdded++;
-				}
+			for (var i = rowsAdded; i < priv.options.pageSize; i++) {
+				priv.renderRowEmptyNode().appendTo(_bodyNode);
 			}
 		}
 
@@ -2043,10 +2028,28 @@ var TableAjax = function() {
 		DBG && console.log('DBG:TableAjax::renderFooterInfo', { _uiNodeFooterInfo: _uiNodeFooterInfo });
 		var fromRow = Math.max(_state.page - 1, 0) * _state.pageSize;
 		var total = _data.total;
-		var toRow = Math.min(fromRow + _state.pageSize, total);
-		if (_data.total > 0) {
+		var renderedRowsCount = Math.min(_data.rows.length, _state.pageSize);
+		var toRow = fromRow + renderedRowsCount;
+		var outTotal = (_state.isLoadingTotal) ? '...' : total;
+		DBG && console.log('DBG:TableAjax::renderFooterInfo', { total: total, 
+			asyncFetchTotal__lastSentReqId: _state.asyncFetchTotal__lastSentReqId,
+			asyncFetchTotal__lastReceivedId: _state.asyncFetchTotal__lastReceivedId,
+			isLoadingTotal: _state.isLoadingTotal
+		});
+		if (_state.isLoadingTotal) {
+			outTotal = '...';
+			_uiNodeFooterInfo.innerHTML = '<div style="font-size:12px; line-height:1.4em;">' +
+				'<strong>Łącznie wyników:</strong> <span class="hide-on-loading">' + outTotal + '</span>' +
+				'<br>' +
+				(	(toRow > 0)
+					? '<small class="hide-on-loading" style="color:#999; font-size:10px">' + p5Utils__format('Wyświetlane wiersze: od {0} do {1}', [fromRow + 1, toRow]) + '</small>'
+					: ''
+				) +
+			'</div>';
+		} else if (total > 0) {
+			outTotal = total;
 			_uiNodeFooterInfo.innerHTML = '<div style="font-size:12px; line-height:1.4em;">' +
-				'<strong>Łącznie wyników:</strong> <span class="hide-on-loading">' + total + '</span>' +
+				'<strong>Łącznie wyników:</strong> <span class="hide-on-loading">' + outTotal + '</span>' +
 				'<br>' +
 				'<small class="hide-on-loading" style="color:#999; font-size:10px">' + p5Utils__format('Wyświetlane wiersze: od {0} do {1}', [fromRow + 1, toRow]) + '</small>' +
 			'</div>';
@@ -2059,10 +2062,15 @@ var TableAjax = function() {
 				currentNode = $(_uiNodeCont).next('.foot').find('.' + nodeClass),
 				node;
 		var currPage = _state.page;
-		var totalPages = Math.ceil(_data.total / _state.pageSize);
 		if (priv.options.debug || DBG) console.log('Render: ', nodeClass, '_data.total', _data.total, 'currPage', currPage);
-		if (_data.total > 0) {
+		if (_state.isLoadingTotal || _data.total > 0) {
+			var fromRow = Math.max(_state.page - 1, 0) * _state.pageSize;
+			var renderedRowsCount = Math.min(_data.rows.length, _state.pageSize);
+			var toRow = fromRow + renderedRowsCount;
+			var total = (_data.total > 0) ? _data.total : toRow;
+
 			node = $('<div class="btn-group ' + nodeClass + '"></div>');
+			var totalPages = Math.ceil(total / _state.pageSize);
 			var lowerPage = currPage - 2;
 			var upperPage = currPage + 2;
 			if (upperPage > totalPages) {
@@ -2073,6 +2081,12 @@ var TableAjax = function() {
 			if (lowerPage < 1) lowerPage = 1;
 			if (upperPage < 5) upperPage = 5;
 
+			var enableNextPageLink = (
+				(_state.isLoadingTotal && renderedRowsCount === _state.pageSize)
+				|| (!(_state.isLoadingTotal) && currPage < totalPages)
+			);
+			var enableLastPageLink = ( !(_state.isLoadingTotal) && (currPage < totalPages) );
+
 			//$(p5Utils__format('<li class="{0}"><a href="#">&lt;&lt;</a></li>', [currPage == 1 ? 'disabled' : '']))
 			$(p5Utils__format('<button type="button" class="btn btn-default{0}">&lt;&lt;</button>', [currPage == 1 ? ' disabled' : '']))
 				.on('click', {pageIndex: 1}, priv.pageChanged)
@@ -2095,11 +2109,11 @@ var TableAjax = function() {
 				}
 			}
 			//$(p5Utils__format('<li class="{0}"><a href="#">&gt;</a></li>', [currPage == totalPages ? 'disabled' : '']))
-			$(p5Utils__format('<button type="button" class="btn btn-default{0}">&gt;</button>', [currPage == totalPages ? ' disabled' : '']))
+			$(p5Utils__format('<button type="button" class="btn btn-default{0}">&gt;</button>', [enableNextPageLink ? '' : ' disabled']))
 				.on('click', {pageIndex: currPage + 1}, priv.pageChanged)
 				.appendTo(node);
 			//$(p5Utils__format('<li class="{0}"><a href="#">&gt;&gt;</a></li>', [currPage == totalPages ? 'disabled' : '']))
-			$(p5Utils__format('<button type="button" class="btn btn-default{0}">&gt;&gt;</button>', [currPage == totalPages ? ' disabled' : '']))
+			$(p5Utils__format('<button type="button" class="btn btn-default{0}">&gt;&gt;</button>', [enableLastPageLink ? '' : ' disabled']))
 				.on('click', {pageIndex: totalPages}, priv.pageChanged)
 				.appendTo(node);
 		} else {
@@ -2112,7 +2126,7 @@ var TableAjax = function() {
 				currentNode = $(_uiNodeCont).next('.foot').find('.' + nodeClass),
 				node;
 		if (priv.options.debug || DBG) console.log('Render: ', nodeClass);
-		if (_data.total > 0 && priv.options.pageSizes.length > 0) {
+		if (priv.options.pageSizes.length > 0) {
 			node = $('<div class="btn-group dropup pagesize ' + nodeClass + '"></div>');
 			var btn = $('<button class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#">Liczba wierszy&nbsp;</button>').appendTo(node);
 			var span = $('<span class="caret"></span>').appendTo(btn);
@@ -2414,6 +2428,7 @@ var TableAjax = function() {
 		if (priv.options.debug || DBG) console.log(p5Utils__format('requesting data from url:{0}', [priv.options.url]));
 
 		var initUrlAdd = '&' + priv.filter_getFullFilterQuery();
+		if (SKIP_TOTAL) initUrlAdd += '&' + 'SKIP_TOTAL=1';
 		DBG && console.warn('DBG:update... - FETCH_DATA', { initUrlAdd, '_state._filterQuery': _state._filterQuery, '_state._specialFilterQuery': _state._specialFilterQuery, _state });
 
 		// p5UI__notifyAjaxCallback({type: 'info', msg: 'pobieranie danych (init) ...'});
@@ -2448,7 +2463,9 @@ var TableAjax = function() {
 			state.pageSize = data.pageSize || priv.options.pageSize;
 			state.filters = data.filters || {};
 			DBG && console.warn('DBG:update... - FETCH_DATA END priv.setState(state);', { state });
+			if (SKIP_TOTAL) _state.isLoadingTotal = true;
 			priv.setState(state);
+			if (SKIP_TOTAL) priv.asyncFetchTotal((priv.options.url + initUrlAdd).replace('SKIP_TOTAL=1', 'ONLY_TOTAL=1'))
 
 			if (typeof callback == "function") {
 				callback.call(this);
@@ -2551,11 +2568,6 @@ var TableAjax = function() {
 
 		_data = data;
 		_data.rowsOrg = _data.rows;
-
-		//we might have more/less data now. Recalculate stuff.
-		if (_state.page > 1) {
-			_totalPages = Math.ceil(_data.total / priv.options.pageSize);
-		}
 	};
 
 	/*
@@ -2807,12 +2819,24 @@ var TableAjax = function() {
 	 */
 	priv.pageChanged = function(e) {
 		e.preventDefault();
+
 		var totalPages = Math.ceil(_data.total / _state.pageSize);
+		if (_state.isLoadingTotal) {
+			var fromRow = Math.max(_state.page - 1, 0) * _state.pageSize;
+			var renderedRowsCount = Math.min(_data.rows.length, _state.pageSize);
+			var toRow = fromRow + renderedRowsCount;
+			var total = toRow;
+			if (_state.isLoadingTotal && renderedRowsCount === _state.pageSize) {
+				totalPages = Math.ceil(total / _state.pageSize) + 1; // allow load next page
+			}
+		}
+		DBG && console.warn('DBG:priv.pageChanged:?', { loadPage: e.data.pageIndex, totalPages });
 		if (e.data.pageIndex < 1 || e.data.pageIndex > totalPages) return;
 
 		//set the new page
 		_state.page = e.data.pageIndex;
 
+		DBG && console.warn('DBG:priv.pageChanged:', { loadPage: e.data.pageIndex });
 		publ.loadPage(_state.page);
 	};
 
@@ -3458,6 +3482,37 @@ var TableAjax = function() {
 	publ.loadPage = function(page, pageSize) {
 		priv.loadPage(page, pageSize);
 	}
+	priv.asyncFetchTotal = function(url) {
+		var this__asyncFetchTotal__reqId = (_state.asyncFetchTotal__lastSentReqId || 0) + 1;
+
+		{ // prevent to fetch more then 2 requests at the same time
+			var lastReceivedId = _state.asyncFetchTotal__lastReceivedId || 0;
+			DBG && console.log('DBG:priv.asyncFetchTotal skip', { skip: (this__asyncFetchTotal__reqId > lastReceivedId + 2), lastSentReqId: this__asyncFetchTotal__reqId, lastReceivedId: lastReceivedId })
+			if (this__asyncFetchTotal__reqId > lastReceivedId + 2) return;
+		}
+
+		_state.asyncFetchTotal__lastSentReqId = this__asyncFetchTotal__reqId;
+
+		window.fetch(url, {
+			method: priv.options.urlPost ? 'POST' : 'GET',
+			credentials: 'same-origin',
+		}).then(function(response) {
+			return response.json()
+		}).then(function (data) {
+			if ('success' == data.type) {
+				_data.total = data.total || -1;
+				DBG && console.log('DBG:priv.asyncFetchTotal fetch success', { reqId: this__asyncFetchTotal__reqId, data: data })
+				if (SKIP_TOTAL) _state.asyncFetchTotal__lastReceivedId = this__asyncFetchTotal__reqId;
+				if (SKIP_TOTAL) _state.isLoadingTotal = (_state.asyncFetchTotal__lastReceivedId < _state.asyncFetchTotal__lastSentReqId);
+				jQuery(_uiNodeCont).trigger('TableAjax:render', [ 'foot_pagination' ]);
+			} else {
+				p5UI__notifyAjaxCallback(data);
+				// priv.showFailFetchDataMsg();
+			}
+		}).catch(function (e) {
+			console.log('asyncFetchTotal:fetch: ERR:', e);
+		})
+	}
 	priv.loadPage = function(page, pageSize) {
 		var resetChecked = false;
 		var reqData = {
@@ -3471,12 +3526,13 @@ var TableAjax = function() {
 		urlAdd += '&' + 'pageSize=' + (pageSize || priv.options.pageSize);
 		DBG && console.log('DBG::loadPage... FETCH_DATA', { '_state._filterQuery': _state._filterQuery, '_state._specialFilterQuery': _state._specialFilterQuery, _state });
 		urlAdd += '&' + priv.filter_getFullFilterQuery();
+		if (SKIP_TOTAL) urlAdd += '&' + 'SKIP_TOTAL=1';
 
 		_uiNode$Table.parent().parent().addClass('AjaxTable-loading');
 
 		// p5UI__notifyAjaxCallback({type: 'info', msg: 'pobieranie danych...'});
 		window.fetch(priv.options.url + urlAdd, {
-		  method: priv.options.urlPost ? 'POST' : 'GET',
+			method: priv.options.urlPost ? 'POST' : 'GET',
 			credentials: 'same-origin',// add cookies
 		}).then(function (response) {
 			return response.json()
@@ -3493,8 +3549,10 @@ var TableAjax = function() {
 				state.pageSize = data.pageSize || priv.options.pageSize;
 				state.filters = data.filters || {};
 				DBG && console.log('DBG::loadPage... - FETCH_DATA END priv.setState(state);', { state });
+				if (SKIP_TOTAL) _state.isLoadingTotal = true;
 				priv.setState(state);
 				_uiNode$Table.parent().parent().removeClass('AjaxTable-loading');
+				if (SKIP_TOTAL) priv.asyncFetchTotal((priv.options.url + urlAdd).replace('SKIP_TOTAL=1', 'ONLY_TOTAL=1'))
 			} else {
 				p5UI__notifyAjaxCallback(data);
 				priv.showFailFetchDataMsg();

+ 2 - 0
SE/se-lib/TableAjax.php.style.css

@@ -52,6 +52,8 @@
 
  .AjaxTableCont .pagination { margin:0; }
  .AjaxTableCont .pagination a { line-height:16px; }
+ .AjaxTableCont .tblAjax__footer__toolbar__pagination .btn.disabled,
+ .AjaxTableCont .tblAjax__footer__toolbar__pagination .btn[disabled] { opacity:0.5 }
 
 .AjaxTableCont .AjaxTableEdit-label { display:block; margin:0 0 3px 0; font-size:12px !important; line-height:16px !important; }
 .AjaxTableCont .AjaxTableEdit-label code { padding:0; white-space:nowrap; background-color:transparent; border:none; color:#777; font-size:10px !important; line-height:14px !important; }