/* eslint-disable */ /*! Clusterize.js - v0.17.6 - 2017-03-05 * http://NeXTs.github.com/Clusterize.js/ * Copyright (c) 2015 Denis Lukov; Licensed GPLv3 */ ;(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())) 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; 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 () { this.options.scroll_top = this.scroll_elem.scrollTop; return Math.floor(this.options.scroll_top / (this.options.cluster_height - this.options.block_height)) || 0; }, // 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, cluster_num) { 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) * cluster_num, 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.getClusterNum()), 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); 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 = '' + data + '
'; while((last = content_elem.lastChild)) { content_elem.removeChild(last); } var rows_nodes = this.getChildNodes(div.firstChild.firstChild); while (rows_nodes.length) { content_elem.appendChild(rows_nodes.shift()); } } else { content_elem.innerHTML = data; } }, getChildNodes: function(tag) { var child_nodes = tag.children, nodes = []; for (var i = 0, ii = child_nodes.length; i < ii; i++) { nodes.push(child_nodes[i]); } return nodes; }, checkChanges: function(type, value, cache) { var changed = value != cache[type]; cache[type] = value; return changed; } } // support functions function on(evt, element, fnc) { return element.addEventListener ? element.addEventListener(evt, fnc, false) : element.attachEvent("on" + evt, fnc); } function off(evt, element, fnc) { return element.removeEventListener ? element.removeEventListener(evt, fnc, false) : element.detachEvent("on" + evt, fnc); } function isArray(arr) { return Object.prototype.toString.call(arr) === '[object Array]'; } function getStyle(prop, elem) { return window.getComputedStyle ? window.getComputedStyle(elem)[prop] : elem.currentStyle[prop]; } return Clusterize; }));