1 |
|
2 |
|
3 |
|
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 |
|
13 |
|
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 | item_height: 0,
|
31 | block_height: 0,
|
32 | rows_in_block: 50,
|
33 | rows_in_cluster: 0,
|
34 | cluster_height: 0,
|
35 | blocks_in_cluster: 4,
|
36 | tag: null,
|
37 | content_tag: null,
|
38 | show_no_data_row: true,
|
39 | no_data_class: 'clusterize-no-data',
|
40 | no_data_text: 'No data',
|
41 | keep_parity: true,
|
42 | callbacks: {},
|
43 | scroll_top: 0
|
44 | }
|
45 |
|
46 |
|
47 | self.options = {};
|
48 | var options = ['rows_in_block', 'blocks_in_cluster', 'show_no_data_row', 'no_data_class', 'no_data_text', 'keep_parity', 'tag', 'callbacks'];
|
49 | for(var i = 0, option; option = options[i]; i++) {
|
50 | self.options[option] = typeof data[option] != 'undefined' && data[option] != null
|
51 | ? data[option]
|
52 | : defaults[option];
|
53 | }
|
54 |
|
55 | var elems = ['scroll', 'content'];
|
56 | for(var i = 0, elem; elem = elems[i]; i++) {
|
57 | self[elem + '_elem'] = data[elem + 'Id']
|
58 | ? document.getElementById(data[elem + 'Id'])
|
59 | : data[elem + 'Elem'];
|
60 | if( ! self[elem + '_elem'])
|
61 | throw new Error("Error! Could not find " + elem + " element");
|
62 | }
|
63 |
|
64 |
|
65 | if( ! self.content_elem.hasAttribute('tabindex'))
|
66 | self.content_elem.setAttribute('tabindex', 0);
|
67 |
|
68 |
|
69 | var rows = isArray(data.rows)
|
70 | ? data.rows
|
71 | : self.fetchMarkup(),
|
72 | cache = {data: '', bottom: 0},
|
73 | scroll_top = self.scroll_elem.scrollTop;
|
74 |
|
75 |
|
76 | self.exploreEnvironment(rows);
|
77 |
|
78 |
|
79 | self.insertToDOM(rows, cache);
|
80 |
|
81 |
|
82 | self.scroll_elem.scrollTop = scroll_top;
|
83 |
|
84 |
|
85 | var last_cluster = false,
|
86 | scroll_debounce = 0,
|
87 | pointer_events_set = false,
|
88 | scrollEv = function() {
|
89 |
|
90 | if (is_mac) {
|
91 | if( ! pointer_events_set) self.content_elem.style.pointerEvents = 'none';
|
92 | pointer_events_set = true;
|
93 | clearTimeout(scroll_debounce);
|
94 | scroll_debounce = setTimeout(function () {
|
95 | self.content_elem.style.pointerEvents = 'auto';
|
96 | pointer_events_set = false;
|
97 | }, 50);
|
98 | }
|
99 | if (last_cluster != (last_cluster = self.getClusterNum()))
|
100 | self.insertToDOM(rows, cache);
|
101 | if (self.options.callbacks.scrollingProgress)
|
102 | self.options.callbacks.scrollingProgress(self.getScrollProgress());
|
103 | },
|
104 | resize_debounce = 0,
|
105 | resizeEv = function() {
|
106 | clearTimeout(resize_debounce);
|
107 | resize_debounce = setTimeout(self.refresh, 100);
|
108 | }
|
109 | on('scroll', self.scroll_elem, scrollEv);
|
110 | on('resize', window, resizeEv);
|
111 |
|
112 |
|
113 | self.destroy = function(clean) {
|
114 | off('scroll', self.scroll_elem, scrollEv);
|
115 | off('resize', window, resizeEv);
|
116 | self.html((clean ? self.generateEmptyRow() : rows).join(''));
|
117 | }
|
118 | self.refresh = function() {
|
119 | self.getRowsHeight(rows) && self.update(rows);
|
120 | }
|
121 | self.update = function(new_rows) {
|
122 | rows = isArray(new_rows)
|
123 | ? new_rows
|
124 | : [];
|
125 | var scroll_top = self.scroll_elem.scrollTop;
|
126 |
|
127 | if(rows.length * self.options.item_height < scroll_top) {
|
128 | self.scroll_elem.scrollTop = 0;
|
129 | last_cluster = 0;
|
130 | }
|
131 | self.insertToDOM(rows, cache);
|
132 | self.scroll_elem.scrollTop = scroll_top;
|
133 | }
|
134 | self.clear = function() {
|
135 | self.update([]);
|
136 | }
|
137 | self.getRowsAmount = function() {
|
138 | return rows.length;
|
139 | }
|
140 | self.getScrollProgress = function() {
|
141 | return this.options.scroll_top / (rows.length * this.options.item_height) * 100 || 0;
|
142 | }
|
143 |
|
144 | var add = function(where, _new_rows) {
|
145 | var new_rows = isArray(_new_rows)
|
146 | ? _new_rows
|
147 | : [];
|
148 | if( ! new_rows.length) return;
|
149 | rows = where == 'append'
|
150 | ? rows.concat(new_rows)
|
151 | : new_rows.concat(rows);
|
152 | self.insertToDOM(rows, cache);
|
153 | }
|
154 | self.append = function(rows) {
|
155 | add('append', rows);
|
156 | }
|
157 | self.prepend = function(rows) {
|
158 | add('prepend', rows);
|
159 | }
|
160 | }
|
161 |
|
162 | Clusterize.prototype = {
|
163 | constructor: Clusterize,
|
164 | // fetch existing markup
|
165 | fetchMarkup: function() {
|
166 | var rows = [], rows_nodes = this.getChildNodes(this.content_elem);
|
167 | while (rows_nodes.length) {
|
168 | rows.push(rows_nodes.shift().outerHTML);
|
169 | }
|
170 | return rows;
|
171 | },
|
172 |
|
173 | exploreEnvironment: function(rows) {
|
174 | var opts = this.options;
|
175 | opts.content_tag = this.content_elem.tagName.toLowerCase();
|
176 | if( ! rows.length) return;
|
177 | if(ie && ie <= 9 && ! opts.tag) opts.tag = rows[0].match(/<([^>\s/]*)/)[1].toLowerCase();
|
178 | if(this.content_elem.children.length <= 1) this.html(rows[0] + rows[0] + rows[0]);
|
179 | if( ! opts.tag) opts.tag = this.content_elem.children[0].tagName.toLowerCase();
|
180 | this.getRowsHeight(rows);
|
181 | },
|
182 | getRowsHeight: function(rows) {
|
183 | var opts = this.options,
|
184 | prev_item_height = opts.item_height;
|
185 | opts.cluster_height = 0
|
186 | if( ! rows.length) return;
|
187 | var nodes = this.content_elem.children;
|
188 | opts.item_height = nodes[Math.floor(nodes.length / 2)].offsetHeight;
|
189 |
|
190 | if(opts.tag == 'tr' && getStyle('borderCollapse', this.content_elem) != 'collapse')
|
191 | opts.item_height += parseInt(getStyle('borderSpacing', this.content_elem), 10) || 0;
|
192 | opts.block_height = opts.item_height * opts.rows_in_block;
|
193 | opts.rows_in_cluster = opts.blocks_in_cluster * opts.rows_in_block;
|
194 | opts.cluster_height = opts.blocks_in_cluster * opts.block_height;
|
195 | return prev_item_height != opts.item_height;
|
196 | },
|
197 |
|
198 | getClusterNum: function () {
|
199 | this.options.scroll_top = this.scroll_elem.scrollTop;
|
200 | return Math.floor(this.options.scroll_top / (this.options.cluster_height - this.options.block_height)) || 0;
|
201 | },
|
202 |
|
203 | generateEmptyRow: function() {
|
204 | var opts = this.options;
|
205 | if( ! opts.tag || ! opts.show_no_data_row) return [];
|
206 | var empty_row = document.createElement(opts.tag),
|
207 | no_data_content = document.createTextNode(opts.no_data_text), td;
|
208 | empty_row.className = opts.no_data_class;
|
209 | if(opts.tag == 'tr') {
|
210 | td = document.createElement('td');
|
211 |
|
212 | td.colSpan = 100;
|
213 | td.appendChild(no_data_content);
|
214 | }
|
215 | empty_row.appendChild(td || no_data_content);
|
216 | return [empty_row.outerHTML];
|
217 | },
|
218 |
|
219 | generate: function (rows, cluster_num) {
|
220 | var opts = this.options,
|
221 | rows_len = rows.length;
|
222 | if (rows_len < opts.rows_in_block) {
|
223 | return {
|
224 | top_offset: 0,
|
225 | bottom_offset: 0,
|
226 | rows_above: 0,
|
227 | rows: rows_len ? rows : this.generateEmptyRow()
|
228 | }
|
229 | }
|
230 | if( ! opts.cluster_height) {
|
231 | this.exploreEnvironment(rows);
|
232 | }
|
233 | var items_start = Math.max((opts.rows_in_cluster - opts.rows_in_block) * cluster_num, 0),
|
234 | items_end = items_start + opts.rows_in_cluster,
|
235 | top_offset = Math.max(items_start * opts.item_height, 0),
|
236 | bottom_offset = Math.max((rows_len - items_end) * opts.item_height, 0),
|
237 | this_cluster_rows = [],
|
238 | rows_above = items_start;
|
239 | if(top_offset < 1) {
|
240 | rows_above++;
|
241 | }
|
242 | for (var i = items_start; i < items_end; i++) {
|
243 | rows[i] && this_cluster_rows.push(rows[i]);
|
244 | }
|
245 | return {
|
246 | top_offset: top_offset,
|
247 | bottom_offset: bottom_offset,
|
248 | rows_above: rows_above,
|
249 | rows: this_cluster_rows
|
250 | }
|
251 | },
|
252 | renderExtraTag: function(class_name, height) {
|
253 | var tag = document.createElement(this.options.tag),
|
254 | clusterize_prefix = 'clusterize-';
|
255 | tag.className = [clusterize_prefix + 'extra-row', clusterize_prefix + class_name].join(' ');
|
256 | height && (tag.style.height = height + 'px');
|
257 | return tag.outerHTML;
|
258 | },
|
259 |
|
260 | insertToDOM: function(rows, cache) {
|
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 | 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) {
|
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 |
|
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 |
|
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 | })); |
\ | No newline at end of file |