Lý thuyết và thực hành: Trong lý thuyết không có sự khác biệt giữa lý thuyết và thực hành, nhưng trong thực tế thì có.
- Lý thuyết: mọi thứ đều rõ ràng, nhưng không có gì hoạt động;
- Thực hành: mọi thứ đều hoạt động, nhưng không có gì rõ ràng;
- Đôi khi lý thuyết đáp ứng thực tiễn: không có gì hoạt động và không có gì rõ ràng.
Đôi khi cách tiếp cận tốt nhất là một nguyên mẫu, và thấy vấn đề thú vị, tôi đã dành một ít thời gian để nấu một cái, mặc dù là một nguyên mẫu, nó thừa nhận có nhiều mụn cóc ...
Nói tóm lại, giải pháp đơn giản nhất để hạn chế tồn đọng các lần tìm nạp dữ liệu dường như chỉ đơn giản là thiết lập sự thay đổi của một người nghèo trong thói quen thực hiện việc tìm nạp. (Trong ví dụ mã bên dưới, hàm tìm nạp mô phỏng là simulateFetchOfData
.) Mutex liên quan đến việc thiết lập một biến ngoài phạm vi hàm sao cho nếu false
tìm nạp được mở để sử dụng và nếu true
quá trình tìm nạp hiện đang diễn ra.
Đó là, khi người dùng điều chỉnh thanh trượt ngang hoặc dọc để bắt đầu tìm nạp dữ liệu, chức năng tìm nạp dữ liệu trước tiên sẽ kiểm tra xem biến toàn cục mutex
có đúng không (nghĩa là quá trình tìm nạp đã được thực hiện) và nếu vậy, chỉ cần thoát . Nếu mutex
không đúng, thì nó được đặt mutex
thành đúng và sau đó tiếp tục thực hiện tìm nạp. Và tất nhiên, ở cuối chức năng tìm nạp, mutex
được đặt thành false, sao cho sự kiện đầu vào của người dùng tiếp theo sau đó sẽ đi qua phía trước kiểm tra mutex và thực hiện một lần tìm nạp khác ...
Một vài lưu ý về nguyên mẫu.
- Trong
simulateFetchOfData
chức năng, có chế độ ngủ (100) được cấu hình là một Lời hứa mô phỏng độ trễ trong việc truy xuất dữ liệu. Điều này được kẹp với một số đăng nhập vào giao diện điều khiển. Nếu bạn loại bỏ kiểm tra mutex, bạn sẽ thấy với giao diện điều khiển mở trong khi di chuyển các thanh trượt, nhiều trường hợp simulateFetchOfData
được khởi tạo và hồi hộp chờ trong khi ngủ (nghĩa là tìm nạp dữ liệu mô phỏng) để giải quyết, trong khi với kiểm tra mutex tại chỗ, chỉ có một trường hợp được bắt đầu tại một thời điểm.
- Thời gian ngủ có thể được điều chỉnh để mô phỏng độ trễ của mạng hoặc cơ sở dữ liệu lớn hơn, do đó bạn có thể cảm nhận được trải nghiệm người dùng. Ví dụ: các mạng tôi đang trải nghiệm độ trễ 90ms cho các giao dịch trên khắp lục địa Hoa Kỳ.
- Một điều đáng chú ý khác là khi hoàn tất quá trình tìm nạp và sau khi đặt lại
mutex
thành false, kiểm tra được thực hiện để xác định xem các giá trị cuộn ngang và dọc có thẳng hàng hay không. Nếu không, một lần tìm nạp khác được bắt đầu. Điều này đảm bảo rằng mặc dù một số sự kiện cuộn có thể không kích hoạt do quá trình tìm nạp đang bận, nhưng tối thiểu các giá trị cuộn cuối cùng được xử lý bằng cách kích hoạt một lần tìm nạp cuối cùng.
- Dữ liệu ô mô phỏng chỉ đơn giản là một giá trị chuỗi của số hàng-dash-cột. Ví dụ: "555-333" biểu thị hàng 555 cột 333.
- Một mảng thưa có tên
buffer
được sử dụng để giữ dữ liệu "tìm nạp". Kiểm tra nó trong bảng điều khiển sẽ cho thấy nhiều mục "trống x XXXX". Các simulateFetchOfData
chức năng được thiết lập như vậy nếu dữ liệu đã được tổ chức tại buffer
thì không "lấy" được thực hiện.
(Để xem nguyên mẫu, chỉ cần sao chép và dán toàn bộ mã vào tệp văn bản mới, đổi tên thành ".html" và mở trong trình duyệt. EDIT: Đã được thử nghiệm trên Chrome và Edge.)
<html><head>
<script>
function initialize() {
window.rowCount = 10000;
window.colCount = 5000;
window.buffer = [];
window.rowHeight = Array( rowCount ).fill( 25 ); // 20px high rows
window.colWidth = Array( colCount ).fill( 70 ); // 70px wide columns
var cellAreaCells = { row: 0, col: 0, height: 0, width: 0 };
window.contentGridCss = [ ...document.styleSheets[ 0 ].rules ].find( rule => rule.selectorText === '.content-grid' );
window.cellArea = document.getElementById( 'cells' );
// Horizontal slider will indicate the left most column.
window.hslider = document.getElementById( 'hslider' );
hslider.min = 0;
hslider.max = colCount;
hslider.oninput = ( event ) => {
updateCells();
}
// Vertical slider will indicate the top most row.
window.vslider = document.getElementById( 'vslider' );
vslider.max = 0;
vslider.min = -rowCount;
vslider.oninput = ( event ) => {
updateCells();
}
function updateCells() {
// Force a recalc of the cell height and width...
simulateFetchOfData( cellArea, cellAreaCells, { row: -parseInt( vslider.value ), col: parseInt( hslider.value ) } );
}
window.mutex = false;
window.lastSkippedRange = null;
window.addEventListener( 'resize', () => {
//cellAreaCells.height = 0;
//cellAreaCells.width = 0;
cellArea.innerHTML = '';
contentGridCss.style[ "grid-template-rows" ] = "0px";
contentGridCss.style[ "grid-template-columns" ] = "0px";
window.initCellAreaSize = { height: document.getElementById( 'cellContainer' ).clientHeight, width: document.getElementById( 'cellContainer' ).clientWidth };
updateCells();
} );
window.dispatchEvent( new Event( 'resize' ) );
}
function sleep( ms ) {
return new Promise(resolve => setTimeout( resolve, ms ));
}
async function simulateFetchOfData( cellArea, curRange, newRange ) {
//
// Global var "mutex" is true if this routine is underway.
// If so, subsequent calls from the sliders will be ignored
// until the current process is complete. Also, if the process
// is underway, capture the last skipped call so that when the
// current finishes, we can ensure that the cells align with the
// settled scroll values.
//
if ( window.mutex ) {
lastSkippedRange = newRange;
return;
}
window.mutex = true;
//
// The cellArea width and height in pixels will tell us how much
// room we have to fill.
//
// row and col is target top/left cell in the cellArea...
//
newRange.height = 0;
let rowPixelTotal = 0;
while ( newRange.row + newRange.height < rowCount && rowPixelTotal < initCellAreaSize.height ) {
rowPixelTotal += rowHeight[ newRange.row + newRange.height ];
newRange.height++;
}
newRange.width = 0;
let colPixelTotal = 0;
while ( newRange.col + newRange.width < colCount && colPixelTotal < initCellAreaSize.width ) {
colPixelTotal += colWidth[ newRange.col + newRange.width ];
newRange.width++;
}
//
// Now the range to acquire is newRange. First, check if this data
// is already available, and if not, fetch the data.
//
function isFilled( buffer, range ) {
for ( let r = range.row; r < range.row + range.height; r++ ) {
for ( let c = range.col; c < range.col + range.width; c++ ) {
if ( buffer[ r ] == null || buffer[ r ][ c ] == null) {
return false;
}
}
}
return true;
}
if ( !isFilled( buffer, newRange ) ) {
// fetch data!
for ( let r = newRange.row; r < newRange.row + newRange.height; r++ ) {
buffer[ r ] = [];
for ( let c = newRange.col; c < newRange.col + newRange.width; c++ ) {
buffer[ r ][ c ] = `${r}-${c} data`;
}
}
console.log( 'Before sleep' );
await sleep(100);
console.log( 'After sleep' );
}
//
// Now that we have the data, let's load it into the cellArea.
//
gridRowSpec = '';
for ( let r = newRange.row; r < newRange.row + newRange.height; r++ ) {
gridRowSpec += rowHeight[ r ] + 'px ';
}
gridColumnSpec = '';
for ( let c = newRange.col; c < newRange.col + newRange.width; c++ ) {
gridColumnSpec += colWidth[ c ] + 'px ';
}
contentGridCss.style[ "grid-template-rows" ] = gridRowSpec;
contentGridCss.style[ "grid-template-columns" ] = gridColumnSpec;
cellArea.innerHTML = '';
for ( let r = newRange.row; r < newRange.row + newRange.height; r++ ) {
for ( let c = newRange.col; c < newRange.col + newRange.width; c++ ) {
let div = document.createElement( 'DIV' );
div.innerText = buffer[ r ][ c ];
cellArea.appendChild( div );
}
}
//
// Let's update the reference to the current range viewed and clear the mutex.
//
curRange = newRange;
window.mutex = false;
//
// One final step. Check to see if the last skipped call to perform an update
// matches with the current scroll bars. If not, let's align the cells with the
// scroll values.
//
if ( lastSkippedRange ) {
if ( !( lastSkippedRange.row === newRange.row && lastSkippedRange.col === newRange.col ) ) {
lastSkippedRange = null;
hslider.dispatchEvent( new Event( 'input' ) );
} else {
lastSkippedRange = null;
}
}
}
</script>
<style>
/*
".range-slider" adapted from... https://codepen.io/ATC-test/pen/myPNqW
See https://www.w3schools.com/howto/howto_js_rangeslider.asp for alternatives.
*/
.range-slider-horizontal {
width: 100%;
height: 20px;
}
.range-slider-vertical {
width: 20px;
height: 100%;
writing-mode: bt-lr; /* IE */
-webkit-appearance: slider-vertical;
}
/* grid container... see https://www.w3schools.com/css/css_grid.asp */
.grid-container {
display: grid;
width: 95%;
height: 95%;
padding: 0px;
grid-gap: 2px;
grid-template-areas:
topLeft column topRight
row cells vslider
botLeft hslider botRight;
grid-template-columns: 50px 95% 27px;
grid-template-rows: 20px 95% 27px;
}
.grid-container > div {
border: 1px solid black;
}
.grid-topLeft {
grid-area: topLeft;
}
.grid-column {
grid-area: column;
}
.grid-topRight {
grid-area: topRight;
}
.grid-row {
grid-area: row;
}
.grid-cells {
grid-area: cells;
}
.grid-vslider {
grid-area: vslider;
}
.grid-botLeft {
grid-area: botLeft;
}
.grid-hslider {
grid-area: hslider;
}
.grid-botRight {
grid-area: botRight;
}
/* Adapted from... https://medium.com/evodeck/responsive-data-tables-with-css-grid-3c58ecf04723 */
.content-grid {
display: grid;
overflow: hidden;
grid-template-rows: 0px; /* Set later by simulateFetchOfData */
grid-template-columns: 0px; /* Set later by simulateFetchOfData */
border-top: 1px solid black;
border-right: 1px solid black;
}
.content-grid > div {
overflow: hidden;
white-space: nowrap;
border-left: 1px solid black;
border-bottom: 1px solid black;
}
</style>
</head><body onload='initialize()'>
<div class='grid-container'>
<div class='topLeft'> TL </div>
<div class='column' id='columns'> column </div>
<div class='topRight'> TR </div>
<div class='row' id = 'rows'> row </div>
<div class='cells' id='cellContainer'>
<div class='content-grid' id='cells'>
Cells...
</div>
</div>
<div class='vslider'> <input id="vslider" type="range" class="range-slider-vertical" step="1" value="0" min="0" max="0"> </div>
<div class='botLeft'> BL </div>
<div class='hslider'> <input id="hslider" type="range" class="range-slider-horizontal" step="1" value="0" min="0" max="0"> </div>
<div class='botRight'> BR </div>
</div>
</body></html>
Một lần nữa, đây là một nguyên mẫu để chứng minh một phương tiện để hạn chế tồn đọng các cuộc gọi dữ liệu không cần thiết. Nếu điều này được tái cấu trúc cho mục đích sản xuất, nhiều khu vực sẽ yêu cầu giải quyết, bao gồm: 1) giảm việc sử dụng không gian biến toàn cầu; 2) thêm nhãn hàng và cột; 3) thêm các nút vào thanh trượt để cuộn các hàng hoặc cột riêng lẻ; 4) có thể đệm dữ liệu liên quan, nếu cần tính toán dữ liệu; 5) v.v ...