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