Faris Ansari | 6163a39 | 2017-08-30 12:54:16 +0530 | [diff] [blame] | 1 | /* eslint-disable */ |
Faris Ansari | 03e7ec2 | 2017-08-10 17:17:34 +0530 | [diff] [blame] | 2 | /*! 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 | })); |