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 |
|
25 | var Clusterize = function(data) {
|
26 | if( ! (this instanceof Clusterize))
|
27 | return new Clusterize(data);
|
28 | var self = this;
|
29 |
|
30 | var defaults = {
|
31 | item_height: 0,
|
32 | block_height: 0,
|
33 | rows_in_block: 50,
|
34 | rows_in_cluster: 0,
|
35 | cluster_height: 0,
|
36 | blocks_in_cluster: 4,
|
37 | tag: null,
|
38 | content_tag: null,
|
39 | show_no_data_row: true,
|
40 | no_data_class: 'clusterize-no-data',
|
41 | no_data_text: 'No data',
|
42 | keep_parity: true,
|
43 | verify_change: false,
|
44 | callbacks: {},
|
45 | scroll_top: 0
|
46 | }
|
47 |
|
48 |
|
49 | self.options = {};
|
50 | var options = ['rows_in_block', 'blocks_in_cluster', 'verify_change', 'show_no_data_row', 'no_data_class', 'no_data_text', 'keep_parity', 'tag', 'callbacks'];
|
51 | for(var i = 0, option; option = options[i]; i++) {
|
52 | self.options[option] = typeof data[option] != 'undefined' && data[option] != null
|
53 | ? data[option]
|
54 | : defaults[option];
|
55 | }
|
56 |
|
57 | var elems = ['scroll', 'content'];
|
58 | for(var i = 0, elem; elem = elems[i]; i++) {
|
59 | self[elem + '_elem'] = data[elem + 'Id']
|
60 | ? document.getElementById(data[elem + 'Id'])
|
61 | : data[elem + 'Elem'];
|
62 | if( ! self[elem + '_elem'])
|
63 | throw new Error("Error! Could not find " + elem + " element");
|
64 | }
|
65 |
|
66 |
|
67 | if( ! self.content_elem.hasAttribute('tabindex'))
|
68 | self.content_elem.setAttribute('tabindex', 0);
|
69 |
|
70 |
|
71 | var rows = isArray(data.rows)
|
72 | ? data.rows
|
73 | : self.fetchMarkup(),
|
74 | cache = {data: ''},
|
75 | scroll_top = self.scroll_elem.scrollTop;
|
76 |
|
77 |
|
78 | self.exploreEnvironment(rows);
|
79 |
|
80 |
|
81 | self.insertToDOM(rows, cache);
|
82 |
|
83 |
|
84 | self.scroll_elem.scrollTop = scroll_top;
|
85 |
|
86 |
|
87 | var last_cluster = false,
|
88 | scroll_debounce = 0,
|
89 | pointer_events_set = false,
|
90 | scrollEv = function() {
|
91 |
|
92 | if (is_mac) {
|
93 | if( ! pointer_events_set) self.content_elem.style.pointerEvents = 'none';
|
94 | pointer_events_set = true;
|
95 | clearTimeout(scroll_debounce);
|
96 | scroll_debounce = setTimeout(function () {
|
97 | self.content_elem.style.pointerEvents = 'auto';
|
98 | pointer_events_set = false;
|
99 | }, 50);
|
100 | }
|
101 | if (last_cluster != (last_cluster = self.getClusterNum()))
|
102 | self.insertToDOM(rows, cache);
|
103 | if (self.options.callbacks.scrollingProgress)
|
104 | self.options.callbacks.scrollingProgress(self.getScrollProgress());
|
105 | },
|
106 | resize_debounce = 0,
|
107 | resizeEv = function() {
|
108 | clearTimeout(resize_debounce);
|
109 | resize_debounce = setTimeout(self.refresh, 100);
|
110 | }
|
111 | on('scroll', self.scroll_elem, scrollEv);
|
112 | on('resize', window, resizeEv);
|
113 |
|
114 |
|
115 | self.destroy = function(clean) {
|
116 | off('scroll', self.scroll_elem, scrollEv);
|
117 | off('resize', window, resizeEv);
|
118 | self.html((clean ? self.generateEmptyRow() : rows).join(''));
|
119 | }
|
120 | self.refresh = function() {
|
121 | self.getRowsHeight(rows) && self.update(rows);
|
122 | }
|
123 | self.update = function(new_rows) {
|
124 | rows = isArray(new_rows)
|
125 | ? new_rows
|
126 | : [];
|
127 | var scroll_top = self.scroll_elem.scrollTop;
|
128 |
|
129 | if(rows.length * self.options.item_height < scroll_top) {
|
130 | self.scroll_elem.scrollTop = 0;
|
131 | last_cluster = 0;
|
132 | }
|
133 | self.insertToDOM(rows, cache);
|
134 | self.scroll_elem.scrollTop = scroll_top;
|
135 | }
|
136 | self.clear = function() {
|
137 | self.update([]);
|
138 | }
|
139 | self.getRowsAmount = function() {
|
140 | return rows.length;
|
141 | }
|
142 | self.getScrollProgress = function() {
|
143 | return this.options.scroll_top / (rows.length * this.options.item_height) * 100 || 0;
|
144 | }
|
145 |
|
146 | var add = function(where, _new_rows) {
|
147 | var new_rows = isArray(_new_rows)
|
148 | ? _new_rows
|
149 | : [];
|
150 | if( ! new_rows.length) return;
|
151 | rows = where == 'append'
|
152 | ? rows.concat(new_rows)
|
153 | : new_rows.concat(rows);
|
154 | self.insertToDOM(rows, cache);
|
155 | }
|
156 | self.append = function(rows) {
|
157 | add('append', rows);
|
158 | }
|
159 | self.prepend = function(rows) {
|
160 | add('prepend', rows);
|
161 | }
|
162 | }
|
163 |
|
164 | Clusterize.prototype = {
|
165 | constructor: Clusterize,
|
166 | // fetch existing markup
|
167 | fetchMarkup: function() {
|
168 | var rows = [], rows_nodes = this.getChildNodes(this.content_elem);
|
169 | while (rows_nodes.length) {
|
170 | rows.push(rows_nodes.shift().outerHTML);
|
171 | }
|
172 | return rows;
|
173 | },
|
174 |
|
175 | exploreEnvironment: function(rows) {
|
176 | var opts = this.options;
|
177 | opts.content_tag = this.content_elem.tagName.toLowerCase();
|
178 | if( ! rows.length) return;
|
179 | if(ie && ie <= 9 && ! opts.tag) opts.tag = rows[0].match(/<([^>\s/]*)/)[1].toLowerCase();
|
180 | if(this.content_elem.children.length <= 1) this.html(rows[0] + rows[0] + rows[0]);
|
181 | if( ! opts.tag) opts.tag = this.content_elem.children[0].tagName.toLowerCase();
|
182 | this.getRowsHeight(rows);
|
183 | },
|
184 | getRowsHeight: function(rows) {
|
185 | var opts = this.options,
|
186 | prev_item_height = opts.item_height;
|
187 | opts.cluster_height = 0
|
188 | if( ! rows.length) return;
|
189 | var nodes = this.content_elem.children;
|
190 | opts.item_height = nodes[Math.floor(nodes.length / 2)].offsetHeight;
|
191 |
|
192 | if(opts.tag == 'tr' && getStyle('borderCollapse', this.content_elem) != 'collapse')
|
193 | opts.item_height += parseInt(getStyle('borderSpacing', this.content_elem)) || 0;
|
194 | opts.block_height = opts.item_height * opts.rows_in_block;
|
195 | opts.rows_in_cluster = opts.blocks_in_cluster * opts.rows_in_block;
|
196 | opts.cluster_height = opts.blocks_in_cluster * opts.block_height;
|
197 | return prev_item_height != opts.item_height;
|
198 | },
|
199 |
|
200 | getClusterNum: function () {
|
201 | this.options.scroll_top = this.scroll_elem.scrollTop;
|
202 | return Math.floor(this.options.scroll_top / (this.options.cluster_height - this.options.block_height)) || 0;
|
203 | },
|
204 |
|
205 | generateEmptyRow: function() {
|
206 | var opts = this.options;
|
207 | if( ! opts.tag || ! opts.show_no_data_row) return [];
|
208 | var empty_row = document.createElement(opts.tag),
|
209 | no_data_content = document.createTextNode(opts.no_data_text), td;
|
210 | empty_row.className = opts.no_data_class;
|
211 | if(opts.tag == 'tr') {
|
212 | td = document.createElement('td');
|
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 | rows_above: 0,
|
225 | rows: rows_len ? rows : this.generateEmptyRow()
|
226 | }
|
227 | }
|
228 | if( ! opts.cluster_height) {
|
229 | this.exploreEnvironment(rows);
|
230 | }
|
231 | var items_start = Math.max((opts.rows_in_cluster - opts.rows_in_block) * cluster_num, 0),
|
232 | items_end = items_start + opts.rows_in_cluster,
|
233 | top_space = items_start * opts.item_height,
|
234 | bottom_space = (rows_len - items_end) * opts.item_height,
|
235 | this_cluster_rows = [],
|
236 | rows_above = items_start;
|
237 | if(top_space > 0) {
|
238 | opts.keep_parity && this_cluster_rows.push(this.renderExtraTag('keep-parity'));
|
239 | this_cluster_rows.push(this.renderExtraTag('top-space', top_space));
|
240 | } else {
|
241 | rows_above++;
|
242 | }
|
243 | for (var i = items_start; i < items_end; i++) {
|
244 | rows[i] && this_cluster_rows.push(rows[i]);
|
245 | }
|
246 | bottom_space > 0 && this_cluster_rows.push(this.renderExtraTag('bottom-space', bottom_space));
|
247 | return {
|
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 | outer_data = data.rows.join(''),
|
263 | callbacks = this.options.callbacks;
|
264 | if( ! this.options.verify_change || this.options.verify_change && this.dataChanged(outer_data, cache)) {
|
265 | callbacks.clusterWillChange && callbacks.clusterWillChange();
|
266 | this.html(outer_data);
|
267 | this.options.content_tag == 'ol' && this.content_elem.setAttribute('start', data.rows_above);
|
268 | callbacks.clusterChanged && callbacks.clusterChanged();
|
269 | }
|
270 | },
|
271 |
|
272 | html: function(data) {
|
273 | var content_elem = this.content_elem;
|
274 | if(ie && ie <= 9 && this.options.tag == 'tr') {
|
275 | var div = document.createElement('div'), last;
|
276 | div.innerHTML = '<table><tbody>' + data + '</tbody></table>';
|
277 | while((last = content_elem.lastChild)) {
|
278 | content_elem.removeChild(last);
|
279 | }
|
280 | var rows_nodes = this.getChildNodes(div.firstChild.firstChild);
|
281 | while (rows_nodes.length) {
|
282 | content_elem.appendChild(rows_nodes.shift());
|
283 | }
|
284 | } else {
|
285 | content_elem.innerHTML = data;
|
286 | }
|
287 | },
|
288 | getChildNodes: function(tag) {
|
289 | var child_nodes = tag.children,
|
290 | ie8_child_nodes_helper = [];
|
291 | for (var i = 0, ii = child_nodes.length; i < ii; i++) {
|
292 | ie8_child_nodes_helper.push(child_nodes[i]);
|
293 | }
|
294 | return Array.prototype.slice.call(ie8_child_nodes_helper);
|
295 | },
|
296 | dataChanged: function(data, cache) {
|
297 | var current_data = JSON.stringify(data),
|
298 | changed = current_data !== cache.data;
|
299 | return changed && (cache.data = current_data);
|
300 | }
|
301 | }
|
302 |
|
303 |
|
304 | function on(evt, element, fnc) {
|
305 | return element.addEventListener ? element.addEventListener(evt, fnc, false) : element.attachEvent("on" + evt, fnc);
|
306 | }
|
307 | function off(evt, element, fnc) {
|
308 | return element.removeEventListener ? element.removeEventListener(evt, fnc, false) : element.detachEvent("on" + evt, fnc);
|
309 | }
|
310 | function isArray(arr) {
|
311 | return Object.prototype.toString.call(arr) === '[object Array]';
|
312 | }
|
313 | function getStyle(prop, elem) {
|
314 | return window.getComputedStyle ? window.getComputedStyle(elem)[prop] : elem.currentStyle[prop];
|
315 | }
|
316 |
|
317 | return Clusterize;
|
318 | })); |
\ | No newline at end of file |