blob: 075c9ca4ae65d91c46aa0fb12d49416984d77092 [file] [log] [blame]
Faris Ansari6163a392017-08-30 12:54:16 +05301/* eslint-disable */
Faris Ansari03e7ec22017-08-10 17:17:34 +05302/*! Clusterize.js - v0.17.6 - 2017-03-05
3* http://NeXTs.github.com/Clusterize.js/
4* Copyright (c) 2015 Denis Lukov; Licensed GPLv3 */
5
6;(function(name, definition) {
7 if (typeof module != 'undefined') module.exports = definition();
8 else if (typeof define == 'function' && typeof define.amd == 'object') define(definition);
9 else this[name] = definition();
10}('Clusterize', function() {
11 "use strict"
12
13 // detect ie9 and lower
14 // https://gist.github.com/padolsey/527683#comment-786682
15 var ie = (function(){
16 for( var v = 3,
17 el = document.createElement('b'),
18 all = el.all || [];
19 el.innerHTML = '<!--[if gt IE ' + (++v) + ']><i><![endif]-->',
20 all[0];
21 ){}
22 return v > 4 ? v : document.documentMode;
23 }()),
24 is_mac = navigator.platform.toLowerCase().indexOf('mac') + 1;
25 var Clusterize = function(data) {
26 if( ! (this instanceof Clusterize))
27 return new Clusterize(data);
28 var self = this;
29
30 var defaults = {
31 rows_in_block: 50,
32 blocks_in_cluster: 4,
33 tag: null,
34 show_no_data_row: true,
35 no_data_class: 'clusterize-no-data',
36 no_data_text: 'No data',
37 keep_parity: true,
38 callbacks: {}
39 }
40
41 // public parameters
42 self.options = {};
43 var options = ['rows_in_block', 'blocks_in_cluster', 'show_no_data_row', 'no_data_class', 'no_data_text', 'keep_parity', 'tag', 'callbacks'];
44 for(var i = 0, option; option = options[i]; i++) {
45 self.options[option] = typeof data[option] != 'undefined' && data[option] != null
46 ? data[option]
47 : defaults[option];
48 }
49
50 var elems = ['scroll', 'content'];
51 for(var i = 0, elem; elem = elems[i]; i++) {
52 self[elem + '_elem'] = data[elem + 'Id']
53 ? document.getElementById(data[elem + 'Id'])
54 : data[elem + 'Elem'];
55 if( ! self[elem + '_elem'])
56 throw new Error("Error! Could not find " + elem + " element");
57 }
58
59 // tabindex forces the browser to keep focus on the scrolling list, fixes #11
60 if( ! self.content_elem.hasAttribute('tabindex'))
61 self.content_elem.setAttribute('tabindex', 0);
62
63 // private parameters
64 var rows = isArray(data.rows)
65 ? data.rows
66 : self.fetchMarkup(),
67 cache = {},
68 scroll_top = self.scroll_elem.scrollTop;
69
70 // append initial data
71 self.insertToDOM(rows, cache);
72
73 // restore the scroll position
74 self.scroll_elem.scrollTop = scroll_top;
75
76 // adding scroll handler
77 var last_cluster = false,
78 scroll_debounce = 0,
79 pointer_events_set = false,
80 scrollEv = function() {
81 // fixes scrolling issue on Mac #3
82 if (is_mac) {
83 if( ! pointer_events_set) self.content_elem.style.pointerEvents = 'none';
84 pointer_events_set = true;
85 clearTimeout(scroll_debounce);
86 scroll_debounce = setTimeout(function () {
87 self.content_elem.style.pointerEvents = 'auto';
88 pointer_events_set = false;
89 }, 50);
90 }
91 if (last_cluster != (last_cluster = self.getClusterNum()))
92 self.insertToDOM(rows, cache);
93 if (self.options.callbacks.scrollingProgress)
94 self.options.callbacks.scrollingProgress(self.getScrollProgress());
95 },
96 resize_debounce = 0,
97 resizeEv = function() {
98 clearTimeout(resize_debounce);
99 resize_debounce = setTimeout(self.refresh, 100);
100 }
101 on('scroll', self.scroll_elem, scrollEv);
102 on('resize', window, resizeEv);
103
104 // public methods
105 self.destroy = function(clean) {
106 off('scroll', self.scroll_elem, scrollEv);
107 off('resize', window, resizeEv);
108 self.html((clean ? self.generateEmptyRow() : rows).join(''));
109 }
110 self.refresh = function(force) {
111 if(self.getRowsHeight(rows) || force) self.update(rows);
112 }
113 self.update = function(new_rows) {
114 rows = isArray(new_rows)
115 ? new_rows
116 : [];
117 var scroll_top = self.scroll_elem.scrollTop;
118 // fixes #39
119 if(rows.length * self.options.item_height < scroll_top) {
120 self.scroll_elem.scrollTop = 0;
121 last_cluster = 0;
122 }
123 self.insertToDOM(rows, cache);
124 self.scroll_elem.scrollTop = scroll_top;
125 }
126 self.clear = function() {
127 self.update([]);
128 }
129 self.getRowsAmount = function() {
130 return rows.length;
131 }
132 self.getScrollProgress = function() {
133 return this.options.scroll_top / (rows.length * this.options.item_height) * 100 || 0;
134 }
135
136 var add = function(where, _new_rows) {
137 var new_rows = isArray(_new_rows)
138 ? _new_rows
139 : [];
140 if( ! new_rows.length) return;
141 rows = where == 'append'
142 ? rows.concat(new_rows)
143 : new_rows.concat(rows);
144 self.insertToDOM(rows, cache);
145 }
146 self.append = function(rows) {
147 add('append', rows);
148 }
149 self.prepend = function(rows) {
150 add('prepend', rows);
151 }
152 }
153
154 Clusterize.prototype = {
155 constructor: Clusterize,
156 // fetch existing markup
157 fetchMarkup: function() {
158 var rows = [], rows_nodes = this.getChildNodes(this.content_elem);
159 while (rows_nodes.length) {
160 rows.push(rows_nodes.shift().outerHTML);
161 }
162 return rows;
163 },
164 // get tag name, content tag name, tag height, calc cluster height
165 exploreEnvironment: function(rows, cache) {
166 var opts = this.options;
167 opts.content_tag = this.content_elem.tagName.toLowerCase();
168 if( ! rows.length) return;
169 if(ie && ie <= 9 && ! opts.tag) opts.tag = rows[0].match(/<([^>\s/]*)/)[1].toLowerCase();
170 if(this.content_elem.children.length <= 1) cache.data = this.html(rows[0] + rows[0] + rows[0]);
171 if( ! opts.tag) opts.tag = this.content_elem.children[0].tagName.toLowerCase();
172 this.getRowsHeight(rows);
173 },
174 getRowsHeight: function(rows) {
175 var opts = this.options,
176 prev_item_height = opts.item_height;
177 opts.cluster_height = 0;
178 if( ! rows.length) return;
179 var nodes = this.content_elem.children;
180 var node = nodes[Math.floor(nodes.length / 2)];
181 opts.item_height = node.offsetHeight;
182 // consider table's border-spacing
183 if(opts.tag == 'tr' && getStyle('borderCollapse', this.content_elem) != 'collapse')
184 opts.item_height += parseInt(getStyle('borderSpacing', this.content_elem), 10) || 0;
185 // consider margins (and margins collapsing)
186 if(opts.tag != 'tr') {
187 var marginTop = parseInt(getStyle('marginTop', node), 10) || 0;
188 var marginBottom = parseInt(getStyle('marginBottom', node), 10) || 0;
189 opts.item_height += Math.max(marginTop, marginBottom);
190 }
191 opts.block_height = opts.item_height * opts.rows_in_block;
192 opts.rows_in_cluster = opts.blocks_in_cluster * opts.rows_in_block;
193 opts.cluster_height = opts.blocks_in_cluster * opts.block_height;
194 return prev_item_height != opts.item_height;
195 },
196 // get current cluster number
197 getClusterNum: function () {
198 this.options.scroll_top = this.scroll_elem.scrollTop;
199 return Math.floor(this.options.scroll_top / (this.options.cluster_height - this.options.block_height)) || 0;
200 },
201 // generate empty row if no data provided
202 generateEmptyRow: function() {
203 var opts = this.options;
204 if( ! opts.tag || ! opts.show_no_data_row) return [];
205 var empty_row = document.createElement(opts.tag),
206 no_data_content = document.createTextNode(opts.no_data_text), td;
207 empty_row.className = opts.no_data_class;
208 if(opts.tag == 'tr') {
209 td = document.createElement('td');
210 // fixes #53
211 td.colSpan = 100;
212 td.appendChild(no_data_content);
213 }
214 empty_row.appendChild(td || no_data_content);
215 return [empty_row.outerHTML];
216 },
217 // generate cluster for current scroll position
218 generate: function (rows, cluster_num) {
219 var opts = this.options,
220 rows_len = rows.length;
221 if (rows_len < opts.rows_in_block) {
222 return {
223 top_offset: 0,
224 bottom_offset: 0,
225 rows_above: 0,
226 rows: rows_len ? rows : this.generateEmptyRow()
227 }
228 }
229 var items_start = Math.max((opts.rows_in_cluster - opts.rows_in_block) * cluster_num, 0),
230 items_end = items_start + opts.rows_in_cluster,
231 top_offset = Math.max(items_start * opts.item_height, 0),
232 bottom_offset = Math.max((rows_len - items_end) * opts.item_height, 0),
233 this_cluster_rows = [],
234 rows_above = items_start;
235 if(top_offset < 1) {
236 rows_above++;
237 }
238 for (var i = items_start; i < items_end; i++) {
239 rows[i] && this_cluster_rows.push(rows[i]);
240 }
241 return {
242 top_offset: top_offset,
243 bottom_offset: bottom_offset,
244 rows_above: rows_above,
245 rows: this_cluster_rows
246 }
247 },
248 renderExtraTag: function(class_name, height) {
249 var tag = document.createElement(this.options.tag),
250 clusterize_prefix = 'clusterize-';
251 tag.className = [clusterize_prefix + 'extra-row', clusterize_prefix + class_name].join(' ');
252 height && (tag.style.height = height + 'px');
253 return tag.outerHTML;
254 },
255 // if necessary verify data changed and insert to DOM
256 insertToDOM: function(rows, cache) {
257 // explore row's height
258 if( ! this.options.cluster_height) {
259 this.exploreEnvironment(rows, cache);
260 }
261 var data = this.generate(rows, this.getClusterNum()),
262 this_cluster_rows = data.rows.join(''),
263 this_cluster_content_changed = this.checkChanges('data', this_cluster_rows, cache),
264 top_offset_changed = this.checkChanges('top', data.top_offset, cache),
265 only_bottom_offset_changed = this.checkChanges('bottom', data.bottom_offset, cache),
266 callbacks = this.options.callbacks,
267 layout = [];
268
269 if(this_cluster_content_changed || top_offset_changed) {
270 if(data.top_offset) {
271 this.options.keep_parity && layout.push(this.renderExtraTag('keep-parity'));
272 layout.push(this.renderExtraTag('top-space', data.top_offset));
273 }
274 layout.push(this_cluster_rows);
275 data.bottom_offset && layout.push(this.renderExtraTag('bottom-space', data.bottom_offset));
276 callbacks.clusterWillChange && callbacks.clusterWillChange();
277 this.html(layout.join(''));
278 this.options.content_tag == 'ol' && this.content_elem.setAttribute('start', data.rows_above);
279 callbacks.clusterChanged && callbacks.clusterChanged();
280 } else if(only_bottom_offset_changed) {
281 this.content_elem.lastChild.style.height = data.bottom_offset + 'px';
282 }
283 },
284 // unfortunately ie <= 9 does not allow to use innerHTML for table elements, so make a workaround
285 html: function(data) {
286 var content_elem = this.content_elem;
287 if(ie && ie <= 9 && this.options.tag == 'tr') {
288 var div = document.createElement('div'), last;
289 div.innerHTML = '<table><tbody>' + data + '</tbody></table>';
290 while((last = content_elem.lastChild)) {
291 content_elem.removeChild(last);
292 }
293 var rows_nodes = this.getChildNodes(div.firstChild.firstChild);
294 while (rows_nodes.length) {
295 content_elem.appendChild(rows_nodes.shift());
296 }
297 } else {
298 content_elem.innerHTML = data;
299 }
300 },
301 getChildNodes: function(tag) {
302 var child_nodes = tag.children, nodes = [];
303 for (var i = 0, ii = child_nodes.length; i < ii; i++) {
304 nodes.push(child_nodes[i]);
305 }
306 return nodes;
307 },
308 checkChanges: function(type, value, cache) {
309 var changed = value != cache[type];
310 cache[type] = value;
311 return changed;
312 }
313 }
314
315 // support functions
316 function on(evt, element, fnc) {
317 return element.addEventListener ? element.addEventListener(evt, fnc, false) : element.attachEvent("on" + evt, fnc);
318 }
319 function off(evt, element, fnc) {
320 return element.removeEventListener ? element.removeEventListener(evt, fnc, false) : element.detachEvent("on" + evt, fnc);
321 }
322 function isArray(arr) {
323 return Object.prototype.toString.call(arr) === '[object Array]';
324 }
325 function getStyle(prop, elem) {
326 return window.getComputedStyle ? window.getComputedStyle(elem)[prop] : elem.currentStyle[prop];
327 }
328
329 return Clusterize;
330}));