pax_global_header 0000666 0000000 0000000 00000000064 14365645332 0014525 g ustar 00root root 0000000 0000000 52 comment=199ff217e491d5f2371c3bdf7f45f2d0ef365b4e LICENSE 0000664 0000000 0000000 00000002065 14365645332 0012101 0 ustar 00root root 0000000 0000000 The MIT License (MIT) Copyright (c) 2015 Denis Lukov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. README.md 0000664 0000000 0000000 00000002125 14365645332 0012350 0 ustar 00root root 0000000 0000000 # Clusterize.js [](https://www.npmjs.com/package/clusterize.js) [](http://packagequality.com/#?package=clusterize.js) [](https://cdn.jsdelivr.net/npm/clusterize.js/clusterize.min.js) [](https://packagephobia.now.sh/result?p=clusterize.js) [](https://www.npmjs.com/package/clusterize.js) [](https://gitter.im/NeXTs/Clusterize.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) > Tiny vanilla JS plugin to display large data sets easily [Demo, usage, etc…](https://clusterize.js.org/) [](https://clusterize.js.org/) clusterize.css 0000664 0000000 0000000 00000001737 14365645332 0014004 0 ustar 00root root 0000000 0000000 /* max-height - the only parameter in this file that needs to be edited. * Change it to suit your needs. The rest is recommended to leave as is. */ .clusterize-scroll{ max-height: 200px; overflow: auto; } /** * Avoid vertical margins for extra tags * Necessary for correct calculations when rows have nonzero vertical margins */ .clusterize-extra-row{ margin-top: 0 !important; margin-bottom: 0 !important; } /* By default extra tag .clusterize-keep-parity added to keep parity of rows. * Useful when used :nth-child(even/odd) */ .clusterize-extra-row.clusterize-keep-parity{ display: none; } /* During initialization clusterize adds tabindex to force the browser to keep focus * on the scrolling list, see issue #11 * Outline removes default browser's borders for focused elements. */ .clusterize-content{ outline: 0; counter-reset: clusterize-counter; } /* Centering message that appears when no data provided */ .clusterize-no-data td{ text-align: center; } clusterize.js 0000664 0000000 0000000 00000030552 14365645332 0013625 0 ustar 00root root 0000000 0000000 /* Clusterize.js - v1.0.0 - 2023-01-22 http://NeXTs.github.com/Clusterize.js/ Copyright (c) 2015 Denis Lukov; Licensed MIT */ ;(function(name, definition) { if (typeof module != 'undefined') module.exports = definition(); else if (typeof define == 'function' && typeof define.amd == 'object') define(definition); else this[name] = definition(); }('Clusterize', function() { "use strict" // detect ie9 and lower // https://gist.github.com/padolsey/527683#comment-786682 var ie = (function(){ for( var v = 3, el = document.createElement('b'), all = el.all || []; el.innerHTML = '', all[0]; ){} return v > 4 ? v : document.documentMode; }()), is_mac = navigator.platform.toLowerCase().indexOf('mac') + 1; var Clusterize = function(data) { if( ! (this instanceof Clusterize)) return new Clusterize(data); var self = this; var defaults = { rows_in_block: 50, blocks_in_cluster: 4, tag: null, show_no_data_row: true, no_data_class: 'clusterize-no-data', no_data_text: 'No data', keep_parity: true, callbacks: {} } // public parameters self.options = {}; var options = ['rows_in_block', 'blocks_in_cluster', 'show_no_data_row', 'no_data_class', 'no_data_text', 'keep_parity', 'tag', 'callbacks']; for(var i = 0, option; option = options[i]; i++) { self.options[option] = typeof data[option] != 'undefined' && data[option] != null ? data[option] : defaults[option]; } var elems = ['scroll', 'content']; for(var i = 0, elem; elem = elems[i]; i++) { self[elem + '_elem'] = data[elem + 'Id'] ? document.getElementById(data[elem + 'Id']) : data[elem + 'Elem']; if( ! self[elem + '_elem']) throw new Error("Error! Could not find " + elem + " element"); } // tabindex forces the browser to keep focus on the scrolling list, fixes #11 if( ! self.content_elem.hasAttribute('tabindex')) self.content_elem.setAttribute('tabindex', 0); // private parameters var rows = isArray(data.rows) ? data.rows : self.fetchMarkup(), cache = {}, scroll_top = self.scroll_elem.scrollTop; // append initial data self.insertToDOM(rows, cache); // restore the scroll position self.scroll_elem.scrollTop = scroll_top; // adding scroll handler var last_cluster = false, scroll_debounce = 0, pointer_events_set = false, scrollEv = function() { // fixes scrolling issue on Mac #3 if (is_mac) { if( ! pointer_events_set) self.content_elem.style.pointerEvents = 'none'; pointer_events_set = true; clearTimeout(scroll_debounce); scroll_debounce = setTimeout(function () { self.content_elem.style.pointerEvents = 'auto'; pointer_events_set = false; }, 50); } if (last_cluster != (last_cluster = self.getClusterNum(rows))) self.insertToDOM(rows, cache); if (self.options.callbacks.scrollingProgress) self.options.callbacks.scrollingProgress(self.getScrollProgress()); }, resize_debounce = 0, resizeEv = function() { clearTimeout(resize_debounce); resize_debounce = setTimeout(self.refresh, 100); } on('scroll', self.scroll_elem, scrollEv); on('resize', window, resizeEv); // public methods self.destroy = function(clean) { off('scroll', self.scroll_elem, scrollEv); off('resize', window, resizeEv); self.html((clean ? self.generateEmptyRow() : rows).join('')); } self.refresh = function(force) { if(self.getRowsHeight(rows) || force) self.update(rows); } self.update = function(new_rows) { rows = isArray(new_rows) ? new_rows : []; var scroll_top = self.scroll_elem.scrollTop; // fixes #39 if(rows.length * self.options.item_height < scroll_top) { self.scroll_elem.scrollTop = 0; last_cluster = 0; } self.insertToDOM(rows, cache); self.scroll_elem.scrollTop = scroll_top; } self.clear = function() { self.update([]); } self.getRowsAmount = function() { return rows.length; } self.getScrollProgress = function() { return this.options.scroll_top / (rows.length * this.options.item_height) * 100 || 0; } var add = function(where, _new_rows) { var new_rows = isArray(_new_rows) ? _new_rows : []; if( ! new_rows.length) return; rows = where == 'append' ? rows.concat(new_rows) : new_rows.concat(rows); self.insertToDOM(rows, cache); } self.append = function(rows) { add('append', rows); } self.prepend = function(rows) { add('prepend', rows); } } Clusterize.prototype = { constructor: Clusterize, // fetch existing markup fetchMarkup: function() { var rows = [], rows_nodes = this.getChildNodes(this.content_elem); while (rows_nodes.length) { rows.push(rows_nodes.shift().outerHTML); } return rows; }, // get tag name, content tag name, tag height, calc cluster height exploreEnvironment: function(rows, cache) { var opts = this.options; opts.content_tag = this.content_elem.tagName.toLowerCase(); if( ! rows.length) return; if(ie && ie <= 9 && ! opts.tag) opts.tag = rows[0].match(/<([^>\s/]*)/)[1].toLowerCase(); if(this.content_elem.children.length <= 1) cache.data = this.html(rows[0] + rows[0] + rows[0]); if( ! opts.tag) opts.tag = this.content_elem.children[0].tagName.toLowerCase(); this.getRowsHeight(rows); }, getRowsHeight: function(rows) { var opts = this.options, prev_item_height = opts.item_height; opts.cluster_height = 0; if( ! rows.length) return; var nodes = this.content_elem.children; if( ! nodes.length) return; var node = nodes[Math.floor(nodes.length / 2)]; opts.item_height = node.offsetHeight; // consider table's border-spacing if(opts.tag == 'tr' && getStyle('borderCollapse', this.content_elem) != 'collapse') opts.item_height += parseInt(getStyle('borderSpacing', this.content_elem), 10) || 0; // consider margins (and margins collapsing) if(opts.tag != 'tr') { var marginTop = parseInt(getStyle('marginTop', node), 10) || 0; var marginBottom = parseInt(getStyle('marginBottom', node), 10) || 0; opts.item_height += Math.max(marginTop, marginBottom); } opts.block_height = opts.item_height * opts.rows_in_block; opts.rows_in_cluster = opts.blocks_in_cluster * opts.rows_in_block; opts.cluster_height = opts.blocks_in_cluster * opts.block_height; return prev_item_height != opts.item_height; }, // get current cluster number getClusterNum: function (rows) { var opts = this.options; opts.scroll_top = this.scroll_elem.scrollTop; var cluster_divider = opts.cluster_height - opts.block_height; var current_cluster = Math.floor(opts.scroll_top / cluster_divider); var max_cluster = Math.floor((rows.length * opts.item_height) / cluster_divider); return Math.min(current_cluster, max_cluster); }, // generate empty row if no data provided generateEmptyRow: function() { var opts = this.options; if( ! opts.tag || ! opts.show_no_data_row) return []; var empty_row = document.createElement(opts.tag), no_data_content = document.createTextNode(opts.no_data_text), td; empty_row.className = opts.no_data_class; if(opts.tag == 'tr') { td = document.createElement('td'); // fixes #53 td.colSpan = 100; td.appendChild(no_data_content); } empty_row.appendChild(td || no_data_content); return [empty_row.outerHTML]; }, // generate cluster for current scroll position generate: function (rows) { var opts = this.options, rows_len = rows.length; if (rows_len < opts.rows_in_block) { return { top_offset: 0, bottom_offset: 0, rows_above: 0, rows: rows_len ? rows : this.generateEmptyRow() } } var items_start = Math.max((opts.rows_in_cluster - opts.rows_in_block) * this.getClusterNum(rows), 0), items_end = items_start + opts.rows_in_cluster, top_offset = Math.max(items_start * opts.item_height, 0), bottom_offset = Math.max((rows_len - items_end) * opts.item_height, 0), this_cluster_rows = [], rows_above = items_start; if(top_offset < 1) { rows_above++; } for (var i = items_start; i < items_end; i++) { rows[i] && this_cluster_rows.push(rows[i]); } return { top_offset: top_offset, bottom_offset: bottom_offset, rows_above: rows_above, rows: this_cluster_rows } }, renderExtraTag: function(class_name, height) { var tag = document.createElement(this.options.tag), clusterize_prefix = 'clusterize-'; tag.className = [clusterize_prefix + 'extra-row', clusterize_prefix + class_name].join(' '); height && (tag.style.height = height + 'px'); return tag.outerHTML; }, // if necessary verify data changed and insert to DOM insertToDOM: function(rows, cache) { // explore row's height if( ! this.options.cluster_height) { this.exploreEnvironment(rows, cache); } var data = this.generate(rows), this_cluster_rows = data.rows.join(''), this_cluster_content_changed = this.checkChanges('data', this_cluster_rows, cache), top_offset_changed = this.checkChanges('top', data.top_offset, cache), only_bottom_offset_changed = this.checkChanges('bottom', data.bottom_offset, cache), callbacks = this.options.callbacks, layout = []; if(this_cluster_content_changed || top_offset_changed) { if(data.top_offset) { this.options.keep_parity && layout.push(this.renderExtraTag('keep-parity')); layout.push(this.renderExtraTag('top-space', data.top_offset)); } layout.push(this_cluster_rows); data.bottom_offset && layout.push(this.renderExtraTag('bottom-space', data.bottom_offset)); callbacks.clusterWillChange && callbacks.clusterWillChange(); this.html(layout.join('')); this.options.content_tag == 'ol' && this.content_elem.setAttribute('start', data.rows_above); this.content_elem.style['counter-increment'] = 'clusterize-counter ' + (data.rows_above-1); callbacks.clusterChanged && callbacks.clusterChanged(); } else if(only_bottom_offset_changed) { this.content_elem.lastChild.style.height = data.bottom_offset + 'px'; } }, // unfortunately ie <= 9 does not allow to use innerHTML for table elements, so make a workaround html: function(data) { var content_elem = this.content_elem; if(ie && ie <= 9 && this.options.tag == 'tr') { var div = document.createElement('div'), last; div.innerHTML = '