Repository: NeXTs/Clusterize.js Branch: master Commit: 199ff217e491 Files: 6 Total size: 16.8 KB Directory structure: gitextract_jzoo8kx6/ ├── LICENSE ├── README.md ├── clusterize.css ├── clusterize.js ├── externs.js └── package.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: LICENSE ================================================ 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. ================================================ FILE: README.md ================================================ # 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/) ================================================ FILE: clusterize.css ================================================ /* 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; } ================================================ FILE: clusterize.js ================================================ /* 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 = '