`.
99 |
100 | ## Dynamic data
101 |
102 | The clusterize instance emits two events to get dynamic data:
103 | ```html
104 |
105 | ```
106 | ```js
107 | methods:
108 | # For the first datapiece, first and last will be 0
109 | getData: function(first,last,cb) {
110 | # somehow get data
111 | cb(data)
112 | }
113 | getDataCount: function(cb) {
114 | cb(dataCount)
115 | }
116 | ```
117 |
118 | To issue a manual redraw, call `redraw()` on the clusterize instance.
119 |
120 | If you want to enforce a scroll-to-top use the `scrollTop` prop.
121 |
122 | ## Flex
123 |
124 | When using the `flex` prop, the usage changes. You will now recieve a array of row items per row which you can use in a `v-for`:
125 | ```html
126 |
127 |
130 |
131 | ```
132 | The row height, items per row and rows per cluster will be recalculated on resize of clusterize.
133 |
134 | # Development
135 | Clone repository.
136 | ```sh
137 | npm install
138 | npm run test
139 | ```
140 | Browse to `http://localhost:8080/`.
141 |
142 | ## To-Do
143 | - use html5 history mode or document.store to save scroll position
144 |
145 | ## License
146 | Copyright (c) 2016 Paul Pflugradt
147 | Licensed under the MIT license.
148 |
--------------------------------------------------------------------------------
/clusterize-cluster.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mixins: [require("vue-mixins/vue")],
3 | props: {
4 | "bindingName": {
5 | type: String,
6 | "default": "data"
7 | },
8 | "loading": {
9 | type: Number,
10 | "default": 0
11 | },
12 | "nr": {
13 | type: Number
14 | },
15 | "index": {
16 | type: Number
17 | },
18 | "height": {
19 | type: Number
20 | },
21 | "data": {
22 | type: Array,
23 | "default": function() {
24 | return [];
25 | }
26 | },
27 | "rowWatchers": {
28 | type: Object
29 | },
30 | "parentVm": {
31 | type: Object
32 | }
33 | },
34 | data: function() {
35 | return {
36 | isCluster: true,
37 | factory: null,
38 | Vue: null,
39 | end: null,
40 | frags: []
41 | };
42 | },
43 | ready: function() {
44 | var key, ref, results, val;
45 | this.end = this.Vue.util.createAnchor('clusterize-cluster-end');
46 | this.$el.appendChild(this.end);
47 | ref = this.rowWatchers;
48 | results = [];
49 | for (key in ref) {
50 | val = ref[key];
51 | results.push(this.initRowWatchers(key, val));
52 | }
53 | return results;
54 | },
55 | methods: {
56 | createFrag: function(i) {
57 | var frag, key, parentScope, ref, scope, val;
58 | parentScope = this.parentVm;
59 | scope = Object.create(parentScope);
60 | scope.$refs = Object.create(parentScope.$refs);
61 | scope.$els = Object.create(parentScope.$els);
62 | scope.$parent = parentScope;
63 | scope.$forContext = this;
64 | this.Vue.util.defineReactive(scope, this.bindingName, this.data[i]);
65 | this.Vue.util.defineReactive(scope, "loading", this.loading);
66 | this.Vue.util.defineReactive(scope, "index", this.index + i);
67 | ref = this.rowWatchers;
68 | for (key in ref) {
69 | val = ref[key];
70 | this.Vue.util.defineReactive(scope, key, val.vm[val.prop]);
71 | scope[key] = val.vm[val.prop];
72 | }
73 | frag = this.factory.create(this, scope, this.$options._frag);
74 | frag.before(this.end);
75 | return this.frags[i] = frag;
76 | },
77 | updateIndex: function() {
78 | var frag, i, j, len, ref, results;
79 | ref = this.frags;
80 | results = [];
81 | for (i = j = 0, len = ref.length; j < len; i = ++j) {
82 | frag = ref[i];
83 | results.push(frag.scope.index = this.index + i);
84 | }
85 | return results;
86 | },
87 | destroyFrag: function(i) {
88 | return this.frags[i].remove();
89 | },
90 | initRowWatchers: function(key, obj) {
91 | var self;
92 | self = this;
93 | return obj.vm.$watch(obj.prop, function(val) {
94 | var frag, j, len, ref, results;
95 | ref = self.frags;
96 | results = [];
97 | for (j = 0, len = ref.length; j < len; j++) {
98 | frag = ref[j];
99 | results.push(frag.scope[key] = val);
100 | }
101 | return results;
102 | });
103 | },
104 | redraw: function() {
105 | var i, j, ref, results;
106 | if (this.frags.length > 0) {
107 | results = [];
108 | for (i = j = 0, ref = this.frags.length; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {
109 | this.destroyFrag(i);
110 | results.push(this.createFrag(i));
111 | }
112 | return results;
113 | }
114 | }
115 | },
116 | watch: {
117 | "index": "updateIndex",
118 | "factory": "redraw",
119 | "rowWatchers": function(newRW, oldRW) {
120 | var key, results, val;
121 | results = [];
122 | for (key in newRW) {
123 | val = newRW[key];
124 | if (oldRW[key] == null) {
125 | results.push(this.initRowWatchers(key, val));
126 | } else {
127 | results.push(void 0);
128 | }
129 | }
130 | return results;
131 | },
132 | data: function(newData, oldData) {
133 | var diff, frag, i, index, j, k, l, len, ref, ref1, ref2, results;
134 | diff = newData.length - oldData.length;
135 | if (diff > 0) {
136 | for (i = j = 0, ref = diff; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {
137 | this.createFrag(oldData.length + i);
138 | }
139 | } else if (diff < 0) {
140 | for (i = k = ref1 = diff; ref1 <= 0 ? k < 0 : k > 0; i = ref1 <= 0 ? ++k : --k) {
141 | this.destroyFrag(oldData.length + i);
142 | }
143 | }
144 | ref2 = this.frags;
145 | results = [];
146 | for (index = l = 0, len = ref2.length; l < len; index = ++l) {
147 | frag = ref2[index];
148 | results.push(frag.scope.data = newData[index]);
149 | }
150 | return results;
151 | },
152 | loading: function(newLoading) {
153 | var frag, j, len, ref, results;
154 | ref = this.frags;
155 | results = [];
156 | for (j = 0, len = ref.length; j < len; j++) {
157 | frag = ref[j];
158 | results.push(frag.scope.loading = newLoading);
159 | }
160 | return results;
161 | }
162 | }
163 | };
164 |
165 | if (module.exports.__esModule) module.exports = module.exports.default
166 | ;(typeof module.exports === "function"? module.exports.options: module.exports).template = ""
167 |
--------------------------------------------------------------------------------
/clusterize.js:
--------------------------------------------------------------------------------
1 | var modulo = function(a, b) { return (+a % (b = +b) + b) % b; };
2 |
3 | module.exports = {
4 | mixins: [require("vue-mixins/onElementResize"), require("vue-mixins/vue"), require("vue-mixins/fragToString")],
5 | components: {
6 | "clusterize-cluster": require("./clusterize-cluster")
7 | },
8 | props: {
9 | "bindingName": {
10 | type: String,
11 | "default": "data"
12 | },
13 | "height": {
14 | type: Number
15 | },
16 | "autoHeight": {
17 | type: Boolean,
18 | "default": false
19 | },
20 | "manualStart": {
21 | type: Boolean,
22 | "default": false
23 | },
24 | "data": {
25 | type: Array
26 | },
27 | "scrollTop": {
28 | type: Number,
29 | "default": 0
30 | },
31 | "scrollLeft": {
32 | type: Number,
33 | "default": 0
34 | },
35 | "clusterSizeFac": {
36 | type: Number,
37 | "default": 1.5
38 | },
39 | "rowHeight": {
40 | type: Number
41 | },
42 | "template": {
43 | type: String
44 | },
45 | "style": {
46 | type: Object
47 | },
48 | "rowWatchers": {
49 | type: Object,
50 | "default": function() {
51 | return {
52 | height: {
53 | vm: this,
54 | prop: "rowHeight"
55 | }
56 | };
57 | }
58 | },
59 | "parentVm": {
60 | type: Object,
61 | "default": function() {
62 | return this.$parent;
63 | }
64 | },
65 | "flex": {
66 | type: Boolean,
67 | "default": false
68 | },
69 | "flexInitial": {
70 | type: Number,
71 | "default": 20
72 | },
73 | "flexFac": {
74 | type: Number,
75 | "default": 1
76 | }
77 | },
78 | computed: {
79 | position: function() {
80 | if (this.autoHeight) {
81 | if (this.disposeResizeCb == null) {
82 | this.disposeResizeCb = this.onElementResize(this.$el, this.updateHeight);
83 | }
84 | return "absolute";
85 | } else if (this.flex) {
86 | if (this.disposeResizeCb == null) {
87 | this.disposeResizeCb = this.onElementResize(this.$el, this.updateHeight);
88 | }
89 | } else {
90 | if (typeof this.disposeResizeCb === "function") {
91 | this.disposeResizeCb();
92 | }
93 | }
94 | return null;
95 | },
96 | computedStyle: function() {
97 | var key, ref, style, val;
98 | if (!this.state.started) {
99 | return null;
100 | }
101 | style = {
102 | height: this.height + 'px',
103 | position: this.position,
104 | top: this.autoHeight ? 0 : null,
105 | bottom: this.autoHeight ? 0 : null,
106 | left: this.autoHeight ? 0 : null,
107 | right: this.autoHeight ? 0 : null,
108 | overflow: "auto"
109 | };
110 | if (this.style != null) {
111 | ref = this.style;
112 | for (key in ref) {
113 | val = ref[key];
114 | style[key] = val;
115 | }
116 | }
117 | return style;
118 | }
119 | },
120 | data: function() {
121 | return {
122 | clusters: [],
123 | firstRowHeight: null,
124 | lastRowHeight: null,
125 | rowCount: null,
126 | rowsCount: null,
127 | itemsPerRow: 1,
128 | clustersCount: null,
129 | clusterHeight: null,
130 | clusterSize: null,
131 | clustersBelow: 2,
132 | clusterVisible: 0,
133 | clusterVisibleLast: -1,
134 | offsetHeight: 0,
135 | itemWidth: 0,
136 | minHeight: null,
137 | lastScrollTop: this.scrollTop,
138 | lastScrollLeft: this.scrollLeft,
139 | state: {
140 | started: false,
141 | startFinished: false,
142 | loading: false
143 | }
144 | };
145 | },
146 | methods: {
147 | updateHeight: function() {
148 | var changedHeight, changedWidth, process;
149 | process = (function(_this) {
150 | return function() {
151 | var data, l, len, oldData, ref, tmp;
152 | if (_this.flex) {
153 | oldData = _this.clusters[0].data;
154 | tmp = [];
155 | ref = _this.clusters[0].data;
156 | for (l = 0, len = ref.length; l < len; l++) {
157 | data = ref[l];
158 | tmp = tmp.concat(data);
159 | if (tmp.length >= _this.flexInitial) {
160 | break;
161 | }
162 | }
163 | _this.clusters[0].data = [tmp];
164 | return _this.$nextTick(function() {
165 | _this.calcRowHeight();
166 | return _this.processClusterChange(_this.$el.scrollTop, true);
167 | });
168 | } else {
169 | _this.calcClusterSize();
170 | return _this.processClusterChange(_this.$el.scrollTop, true);
171 | }
172 | };
173 | })(this);
174 | if (this.state.startFinished && this.rowHeight > -1) {
175 | changedHeight = Math.abs(this.offsetHeight - this.$el.offsetHeight) / this.clusterHeight * this.clusterSizeFac > 0.2;
176 | if (this.flex) {
177 | changedWidth = this.$el.clientWidth - this.itemsPerRow * this.itemWidth;
178 | if (changedWidth > this.itemWidth || changedWidth < 1) {
179 | return process();
180 | }
181 | } else if (changedHeight) {
182 | return process();
183 | }
184 | }
185 | },
186 | start: function(top) {
187 | var count;
188 | if (top == null) {
189 | top = this.$el.scrollTop;
190 | }
191 | this.state.started = true;
192 | this.processTemplate();
193 | this.state.loading = true;
194 | if (this.data != null) {
195 | this.$watch("data", this.processData);
196 | }
197 | count = 0;
198 | if (this.flex) {
199 | count = this.flexInitial;
200 | }
201 | if (!this.rowHeight || this.flex) {
202 | return this.getData(0, count, (function(_this) {
203 | return function(data) {
204 | _this.getAndProcessDataCount();
205 | _this.clusters[0].index = 0;
206 | if (_this.flex) {
207 | _this.clusters[0].data = [data];
208 | } else {
209 | _this.clusters[0].data = data;
210 | }
211 | return _this.$nextTick(function() {
212 | _this.calcRowHeight();
213 | _this.processScroll(top);
214 | return _this.state.startFinished = true;
215 | });
216 | };
217 | })(this));
218 | } else {
219 | this.getAndProcessDataCount();
220 | return this.$nextTick((function(_this) {
221 | return function() {
222 | _this.calcClusterSize();
223 | _this.processScroll(top);
224 | return _this.state.startFinished = true;
225 | };
226 | })(this));
227 | }
228 | },
229 | getData: function(first, last, cb) {
230 | if (this.data != null) {
231 | return cb(this.data.slice(first, +last + 1 || 9e9));
232 | } else {
233 | return this.$emit("get-data", first, last, cb);
234 | }
235 | },
236 | getAndProcessDataCount: function() {
237 | var getDataCount, processDataCount;
238 | getDataCount = (function(_this) {
239 | return function(cb) {
240 | if (_this.data != null) {
241 | return cb(_this.data.length);
242 | } else {
243 | return _this.$emit("get-data-count", cb);
244 | }
245 | };
246 | })(this);
247 | processDataCount = (function(_this) {
248 | return function(count) {
249 | if (count > 0) {
250 | _this.dataCount = count;
251 | _this.clustersCount = Math.ceil(_this.dataCount / _this.itemsPerRow / _this.clusterSize);
252 | return _this.updateLastRowHeight();
253 | }
254 | };
255 | })(this);
256 | return getDataCount(processDataCount);
257 | },
258 | calcRowHeight: function() {
259 | var child, el, height, i, items, itemsPerRow, itemsPerRowLast, j, k, l, lastTop, maxHeights, rect, ref, row, style, width;
260 | if (this.flex) {
261 | maxHeights = [0];
262 | el = this.clusters[0].$el;
263 | lastTop = Number.MIN_VALUE;
264 | itemsPerRow = [];
265 | itemsPerRowLast = 0;
266 | row = el.children[1];
267 | items = row.children.length - 1;
268 | width = 0;
269 | k = 0;
270 | for (i = l = 1, ref = items; 1 <= ref ? l <= ref : l >= ref; i = 1 <= ref ? ++l : --l) {
271 | child = row.children[i];
272 | if (!child) {
273 | return;
274 | }
275 | rect = child.getBoundingClientRect();
276 | style = window.getComputedStyle(child);
277 | height = rect.height + parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10);
278 | width += rect.width;
279 | if (rect.top > lastTop + maxHeights[k] * 1 / 3 && i > 1) {
280 | j = i - 1;
281 | k++;
282 | itemsPerRow.push(j - itemsPerRowLast);
283 | itemsPerRowLast = j;
284 | lastTop = rect.top;
285 | maxHeights.push(height);
286 | } else {
287 | if (lastTop < rect.top) {
288 | lastTop = rect.top;
289 | }
290 | if (maxHeights[maxHeights.length - 1] < height) {
291 | maxHeights[maxHeights.length - 1] = height;
292 | }
293 | }
294 | }
295 | itemsPerRow.shift();
296 | maxHeights.shift();
297 | if (itemsPerRow.length > 0) {
298 | this.itemsPerRow = Math.floor(itemsPerRow.reduce(function(a, b) {
299 | return a + b;
300 | }) / itemsPerRow.length * this.flexFac);
301 | } else {
302 | this.itemsPerRow = items;
303 | }
304 | if (this.itemsPerRow === 0) {
305 | this.itemsPerRow = 1;
306 | }
307 | this.itemWidth = width / items;
308 | if (maxHeights.length > 0) {
309 | this.rowHeight = maxHeights.reduce(function(a, b) {
310 | return a + b;
311 | }) / maxHeights.length;
312 | } else {
313 | this.rowHeight = height;
314 | }
315 | } else {
316 | this.rowHeight = this.clusters[0].$el.children[1].getBoundingClientRect().height;
317 | }
318 | return this.calcClusterSize();
319 | },
320 | calcClusterSize: function() {
321 | var cluster, l, len, ref, results;
322 | this.offsetHeight = this.$el.offsetHeight;
323 | this.clusterSize = Math.ceil(this.$el.offsetHeight / this.rowHeight * this.clusterSizeFac) * this.itemsPerRow;
324 | if (this.dataCount) {
325 | this.clustersCount = Math.ceil(this.dataCount / this.itemsPerRow / this.clusterSize);
326 | if (this.clustersCount < 3) {
327 | this.clustersCount = 3;
328 | }
329 | this.updateLastRowHeight();
330 | }
331 | this.clusterHeight = this.rowHeight * this.clusterSize / this.itemsPerRow;
332 | ref = this.clusters;
333 | results = [];
334 | for (l = 0, len = ref.length; l < len; l++) {
335 | cluster = ref[l];
336 | results.push(cluster.height = this.clusterHeight);
337 | }
338 | return results;
339 | },
340 | updateLastRowHeight: function() {
341 | var newHeight;
342 | if (this.dataCount && this.clusterSize) {
343 | newHeight = (this.dataCount - (this.clusterVisible + this.clustersBelow + 1) * this.clusterSize) * this.rowHeight / this.itemsPerRow;
344 | if (newHeight > 0) {
345 | return this.lastRowHeight = newHeight;
346 | } else {
347 | return this.lastRowHeight = 0;
348 | }
349 | }
350 | },
351 | processScroll: function(top) {
352 | this.clusterVisible = Math.floor(top / this.clusterHeight + 0.5);
353 | if (this.clusterVisibleLast !== this.clusterVisible) {
354 | this.processClusterChange(top);
355 | return this.clusterVisibleLast = this.clusterVisible;
356 | }
357 | },
358 | processClusterChange: function(top, repaint) {
359 | var absI, absIs, down, l, len, m, n, position, ref, ref1, relI, results, results1;
360 | if (top == null) {
361 | top = this.$el.scrollTop;
362 | }
363 | if (repaint == null) {
364 | repaint = false;
365 | }
366 | down = this.clusterVisibleLast < this.clusterVisible;
367 | if (this.clusterVisible === 0) {
368 | this.clustersBelow = 2;
369 | } else if (this.clusterVisible === this.clustersCount - 1) {
370 | this.clustersBelow = 0;
371 | } else {
372 | this.clustersBelow = 1;
373 | }
374 | position = this.clusterVisible + this.clustersBelow;
375 | if (down) {
376 | absIs = (function() {
377 | results = [];
378 | for (var l = ref = position - 2; ref <= position ? l <= position : l >= position; ref <= position ? l++ : l--){ results.push(l); }
379 | return results;
380 | }).apply(this);
381 | } else {
382 | absIs = (function() {
383 | results1 = [];
384 | for (var m = position, ref1 = position - 2; position <= ref1 ? m <= ref1 : m >= ref1; position <= ref1 ? m++ : m--){ results1.push(m); }
385 | return results1;
386 | }).apply(this);
387 | }
388 | for (n = 0, len = absIs.length; n < len; n++) {
389 | absI = absIs[n];
390 | relI = absI % 3;
391 | if (this.clusters[relI].nr !== absI || repaint) {
392 | if (down) {
393 | this.clusters[relI].$before(this.$els.lastRow);
394 | } else {
395 | this.clusters[relI].$after(this.$els.firstRow);
396 | }
397 | this.clusters[relI].nr = absI;
398 | this.clusters[relI].index = absI * this.clusterSize;
399 | this.fillClusterWithData(this.clusters[relI], absI * this.clusterSize, (absI + 1) * this.clusterSize - 1);
400 | }
401 | }
402 | this.updateFirstRowHeight();
403 | return this.updateLastRowHeight();
404 | },
405 | fillClusterWithData: function(cluster, first, last) {
406 | var loading;
407 | if (this.state.loading) {
408 | this.state.loading = false;
409 | this.$emit("clusterize-loaded");
410 | }
411 | cluster.loading += 1;
412 | loading = cluster.loading;
413 | this.$emit("cluster-loading", cluster.nr);
414 | return this.getData(first, last, (function(_this) {
415 | return function(data) {
416 | var currentData, d, data2, i, l, len;
417 | if (cluster.loading === loading) {
418 | if (data.length !== _this.clusterSize) {
419 | cluster.height = data.length * _this.rowHeight / _this.itemsPerRow;
420 | } else {
421 | cluster.height = _this.clusterHeight;
422 | }
423 | if (_this.flex) {
424 | data2 = [];
425 | currentData = [];
426 | for (i = l = 0, len = data.length; l < len; i = ++l) {
427 | d = data[i];
428 | if (modulo(i, _this.itemsPerRow) === 0) {
429 | currentData = [];
430 | data2.push(currentData);
431 | }
432 | currentData.push(d);
433 | }
434 | cluster.data = data2;
435 | } else {
436 | cluster.data = data;
437 | }
438 | cluster.loading = 0;
439 | return _this.$emit("cluster-loaded", cluster.nr);
440 | }
441 | };
442 | })(this));
443 | },
444 | updateFirstRowHeight: function() {
445 | var newHeight;
446 | newHeight = (this.clusterVisible - (2 - this.clustersBelow)) * this.clusterHeight;
447 | if (newHeight > 0) {
448 | return this.firstRowHeight = newHeight;
449 | } else {
450 | return this.firstRowHeight = 0;
451 | }
452 | },
453 | onScroll: function(e) {
454 | var top;
455 | top = this.$el.scrollTop;
456 | this.$emit("scroll-y", top);
457 | this.$emit("scroll-x", this.$el.scrollLeft);
458 | if (this.lastScrollTop !== top) {
459 | this.lastScrollTop = top;
460 | return this.processScroll(top);
461 | }
462 | },
463 | processData: function(newData, oldData) {
464 | if (newData !== oldData) {
465 | return this.redraw();
466 | }
467 | },
468 | redraw: function() {
469 | this.getAndProcessDataCount();
470 | return this.processClusterChange(this.$el.scrollTop, true);
471 | },
472 | processTemplate: function() {
473 | var cluster, factory, l, len, ref, results;
474 | if (this.state.started) {
475 | if (!this.template) {
476 | this.template = this.fragToString(this._slotContents["default"]);
477 | }
478 | factory = new this.Vue.FragmentFactory(this.parentVm, this.template);
479 | ref = this.clusters;
480 | results = [];
481 | for (l = 0, len = ref.length; l < len; l++) {
482 | cluster = ref[l];
483 | results.push(cluster.factory = factory);
484 | }
485 | return results;
486 | }
487 | }
488 | },
489 | ready: function() {
490 | var child, l, len, ref;
491 | ref = this.$children;
492 | for (l = 0, len = ref.length; l < len; l++) {
493 | child = ref[l];
494 | if (child.isCluster) {
495 | this.clusters.push(child);
496 | }
497 | }
498 | if (!this.manualStart) {
499 | return this.start();
500 | }
501 | },
502 | watch: {
503 | "height": "updateHeight",
504 | "scrollTop": function(val) {
505 | if (val !== this.$el.scrollTop) {
506 | this.$el.scrollTop = val;
507 | return this.processScroll(val);
508 | }
509 | },
510 | "template": "processTemplate",
511 | "rowWatchers": function(val) {
512 | if (val.height == null) {
513 | val.height = {
514 | vm: this,
515 | prop: "rowHeight"
516 | };
517 | }
518 | return val;
519 | }
520 | }
521 | };
522 |
523 | if (module.exports.__esModule) module.exports = module.exports.default
524 | ;(typeof module.exports === "function"? module.exports.options: module.exports).template = ""
525 |
--------------------------------------------------------------------------------
/dev/autoheight.vue:
--------------------------------------------------------------------------------
1 |
2 | .container
3 | a(href="https://vue-comps.github.io/vue-clusterize/blob/master/dev/autoheight.vue") source
4 | p resize the window
5 | p rows per cluster: {{clusterSize}}
6 | .scrollcontainer(style="height:80%;position:relative")
7 | clusterize(v-ref:clusterize v-bind:data="rowsData" auto-height v-bind:style="{width:'200px',border:'solid 1px black'}")
8 | div.clusterize-row {{data}}
9 |
10 |
11 |
32 |
33 |
41 |
--------------------------------------------------------------------------------
/dev/basic.vue:
--------------------------------------------------------------------------------
1 |
2 | .container
3 | a(href="https://vue-comps.github.io/vue-clusterize/blob/master/dev/basic.vue") source
4 | clusterize(v-ref:clusterize v-bind:data="rowsData" v-bind:height="400" v-bind:style="{width:'200px'}")
5 | div.clusterize-row {{data}} - index: {{index}}
6 |
7 |
8 |
15 |
16 |
24 |
--------------------------------------------------------------------------------
/dev/flex.vue:
--------------------------------------------------------------------------------
1 |
2 | .container
3 | a(href="https://vue-comps.github.io/vue-clusterize/blob/master/dev/autoheight.vue") source
4 | p resize the window
5 | p Same size // items per cluster: {{clusterSize}}
6 | p(style="margin-left:50%") Different size with display:flex // items per cluster: {{clusterSize2}}
7 | .scrollcontainer(style="height:80%;position:relative;width:40%;float:left")
8 | clusterize(v-ref:clusterize v-bind:data="rowsData" flex v-bind:flex-initial="100" auto-height v-bind:style="{border:'solid 1px black'}")
9 | div.clusterize-row(style="clear:both")
10 | span(style="float:left;padding:5px" v-for="d in data") {{d}}
11 | .flexcontainer(style="height:80%;position:relative;width:40%;float:left")
12 | clusterize(v-ref:clusterize2 @get-data="getData" @get-data-count="getDataCount" flex v-bind:flex-fac=0.96 v-bind:flex-initial="40" auto-height v-bind:style="{border:'solid 1px black'}")
13 | div.clusterize-row(style="display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between")
14 | div(style="text-align:center;margin:5px",v-bind:style="{background:d.color,height:d.height,width:d.width}",v-for="d in data") {{d.i}}
15 |
16 |
17 |
60 |
61 |
69 |
--------------------------------------------------------------------------------
/dev/loading.vue:
--------------------------------------------------------------------------------
1 |
2 | .container
3 | a(href="https://vue-comps.github.io/vue-clusterize/blob/master/dev/loading.vue") source
4 | p 0.5 sec delay for data
5 | clusterize(v-ref:clusterize @get-data="getData" @get-data-count="getDataCount" v-bind:height="400" v-bind:style="{width:'200px'}")
6 | div.clusterize-row(v-if="!loading") {{data}}
7 | p(slot="loading") loading
8 |
9 |
10 |
24 |
25 |
37 |
--------------------------------------------------------------------------------
/dev/presetRowHeight.vue:
--------------------------------------------------------------------------------
1 |
2 | .container
3 | a(href="https://vue-comps.github.io/vue-clusterize/blob/master/dev/basic.vue") source
4 | clusterize(v-ref:clusterize v-bind:data="rowsData" v-bind:row-height="25" v-bind:height="400" v-bind:style="{width:'200px'}")
5 | div.clusterize-row(v-bind:style="{height:height+'px'}") {{data}}
6 |
7 |
8 |
15 |
16 |
24 |
--------------------------------------------------------------------------------
/dev/webpack.config.coffee:
--------------------------------------------------------------------------------
1 | module.exports =
2 | module:
3 | loaders: [
4 | { test: /\.vue$/, loader: "vue-loader"}
5 | { test: /\.html$/, loader: "html"}
6 | { test: /\.css$/, loader: "style-loader!css-loader" }
7 | ]
8 | resolve:
9 | extensions: ["",".js",".vue",".coffee"]
10 |
--------------------------------------------------------------------------------
/dev/withOtherComponentInside.vue:
--------------------------------------------------------------------------------
1 |
2 | .container
3 | a(href="https://vue-comps.github.io/vue-clusterize/blob/master/dev/basic.vue") source
4 | clusterize(v-ref:clusterize v-bind:data="rowsData" v-bind:height="400" v-bind:style="{width:'200px'}")
5 | div.clusterize-row {{data}}
6 | tooltip(position="body") index: {{index}}
7 |
8 |
9 |
17 |
18 |
30 |
--------------------------------------------------------------------------------
/karma.conf.coffee:
--------------------------------------------------------------------------------
1 | module.exports = (config) ->
2 | config.set
3 | preprocessors:
4 | "**/*.coffee": ["webpack",'sourcemap']
5 | webpack:
6 | devtool: 'inline-source-map'
7 | resolve:
8 | extensions: ["",".js",".coffee",".vue"]
9 | module:
10 | loaders: [
11 | { test: /\.coffee$/, loader: "coffee-loader" }
12 | { test: /\.vue$/, loader: "vue-loader" }
13 | { test: /\.html$/, loader: "html"}
14 | { test: /\.css$/, loader: "style-loader!css-loader" }
15 | ]
16 | webpackMiddleware:
17 | noInfo: true
18 | files: ["test/*.coffee"]
19 | frameworks: ["mocha","chai-dom","chai-spies","chai","vue-component"]
20 | plugins: [
21 | require("karma-chai")
22 | require("karma-chai-dom")
23 | require("karma-chrome-launcher")
24 | require("karma-firefox-launcher")
25 | require("karma-mocha")
26 | require("karma-webpack")
27 | require("karma-sourcemap-loader")
28 | require("karma-spec-reporter")
29 | require("karma-chai-spies")
30 | require("karma-vue-component")
31 | ]
32 | browsers: ["Chrome","Firefox"]
33 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-clusterize",
3 | "description": "clusterize - done in vue",
4 | "version": "0.5.1",
5 | "homepage": "https://github.com/paulpflug",
6 | "author": {
7 | "name": "Paul Pflugradt",
8 | "email": "paul.pflugradt@gmail.com"
9 | },
10 | "license": "MIT",
11 | "main": "clusterize.js",
12 | "repository": {
13 | "type": "git",
14 | "url": "git://github.com/paulpflug/vue-clusterize"
15 | },
16 | "engines": {
17 | "node": "*"
18 | },
19 | "dependencies": {
20 | "vue-mixins": "^0.2.10"
21 | },
22 | "devDependencies": {
23 | "chai": "^3.5.0",
24 | "chai-spies": "^0.7.1",
25 | "coffee-loader": "^0.7.2",
26 | "coffee-script": "^1.10.0",
27 | "css-loader": "^0.23.1",
28 | "gh-pages": "^0.11.0",
29 | "karma": "^0.13.22",
30 | "karma-chai": "^0.1.0",
31 | "karma-chai-dom": "^1.1.0",
32 | "karma-chai-spies": "^0.1.4",
33 | "karma-chrome-launcher": "^1.0.1",
34 | "karma-firefox-launcher": "^1.0.0",
35 | "karma-mocha": "^1.0.1",
36 | "karma-sourcemap-loader": "^0.3.7",
37 | "karma-spec-reporter": "0.0.26",
38 | "karma-vue-component": "^0.1.0",
39 | "karma-webpack": "^1.7.0",
40 | "mocha": "^2.5.3",
41 | "pug": "^2.0.0-beta3",
42 | "template-html-loader": "0.0.3",
43 | "vue": "^1.0.25",
44 | "vue-compiler": "^0.3.0",
45 | "vue-comps-tooltip": "^0.2.0",
46 | "vue-dev-server": "^0.2.10",
47 | "vue-html-loader": "^1.2.2",
48 | "vue-loader": "^8.5.2",
49 | "webpack": "^1.13.1"
50 | },
51 | "keywords": [
52 | "clusterize",
53 | "vue",
54 | "invisible pagination"
55 | ],
56 | "readmeFilename": "README.md",
57 | "scripts": {
58 | "build": "NODE_ENV=production vue-compiler --out . src/*.vue",
59 | "dev": "vue-dev-server",
60 | "watch": "karma start --browsers Chrome --auto-watch --reporters spec",
61 | "test": "karma start --single-run",
62 | "preversion": "npm test",
63 | "version": "npm run build && git add .",
64 | "postversion": "git push && git push --tags && npm publish",
65 | "ghpages": "vue-dev-server --static static/ && gh-pages -d static"
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/clusterize-cluster.vue:
--------------------------------------------------------------------------------
1 | // out: ..
2 |
3 | .clusterize-cluster(
4 | :class="{loading:loading}",
5 | :style="{height:height+'px',overflow:'visible',position:'relative',margin:0,padding:0}"
6 | )
7 | .clusterize-cluster-loading(
8 | v-el:loading,
9 | v-show="loading"
10 | )
11 | slot loading...
12 |
13 |
14 |
109 |
--------------------------------------------------------------------------------
/src/clusterize.vue:
--------------------------------------------------------------------------------
1 | // out: ..
2 |
3 | .clusterize(
4 | :style="computedStyle",
5 | :class="{'loading':state.loading, 'not-started':!state.started}",
6 | @scroll="onScroll"
7 | )
8 | .clusterize-first-row(v-el:first-row v-bind:style="{height:firstRowHeight+'px'}")
9 | clusterize-cluster(v-bind:binding-name="bindingName" v-bind:row-watchers="rowWatchers" v-bind:parent-vm="parentVm")
10 | slot(name="loading")
11 | clusterize-cluster(v-bind:binding-name="bindingName" v-bind:row-watchers="rowWatchers" v-bind:parent-vm="parentVm")
12 | slot(name="loading")
13 | clusterize-cluster(v-bind:binding-name="bindingName" v-bind:row-watchers="rowWatchers" v-bind:parent-vm="parentVm")
14 | slot(name="loading")
15 | .clusterize-last-row(v-el:last-row v-bind:style="{height:lastRowHeight+'px'}")
16 |
17 |
18 |
375 |
--------------------------------------------------------------------------------
/test/clusterize.coffee:
--------------------------------------------------------------------------------
1 | env = null
2 | cl = null
3 | clel = null
4 | overallHeight = null
5 | describe "clusterize", ->
6 |
7 |
8 | describe "basic env", ->
9 |
10 | before ->
11 | env = loadComp(require("../dev/basic.vue"))
12 | cl = env.$refs.clusterize
13 |
14 | after ->
15 | unloadComp(env)
16 |
17 | it "should render clusterize", ->
18 | should.exist(cl)
19 | should.exist(cl.$el)
20 | clel = cl.$el
21 |
22 | it "should have class clusterize", ->
23 | clel.should.have.class "clusterize"
24 |
25 | it "should have element clusterize-first-row and last-row", ->
26 | clel.should.contain "div.clusterize-first-row"
27 | clel.should.contain "div.clusterize-last-row"
28 |
29 |
30 | it "should contain three clusters", ->
31 | clel.querySelectorAll("div.clusterize-cluster").should.have.length(3)
32 |
33 | it "should emit event clusterize-loaded", (done) ->
34 | cl.$once "clusterize-loaded", done
35 |
36 |
37 | describe "after loaded", ->
38 |
39 |
40 | describe "clusterize-first-row", ->
41 |
42 | it "should have a height of 0", ->
43 | clfrel = clel.querySelector "div.clusterize-first-row"
44 | clfrel.should.exist
45 | clfrel.should.have.attr("style","height: 0px;")
46 |
47 |
48 | describe "clusterize-last-row", ->
49 |
50 | it "should have a height", ->
51 | cllrel = clel.querySelector "div.clusterize-last-row"
52 | cllrel.should.exist
53 | overallHeight = cl.rowHeight * 10000
54 | cllrel.should.have.attr("style","height: #{overallHeight-3*cl.clusterSize*cl.rowHeight}px;")
55 |
56 |
57 | describe "the clusters", ->
58 | clsels = null
59 |
60 | it "should have a height", ->
61 | clsels = clel.querySelectorAll("div.clusterize-cluster")
62 | for clusterel in clsels
63 | clusterel.should.have.attr("style").match(new RegExp("height: #{cl.clusterSize*cl.rowHeight}px;"))
64 |
65 | it "should have cl.clusterSize rows", ->
66 | for clusterel in clsels
67 | clusterel.querySelectorAll("div.clusterize-row").should.have.length(cl.clusterSize)
68 |
69 | it "should have the right data", ->
70 | i = 1
71 | for clusterel in clsels
72 | rowels = clusterel.querySelectorAll("div.clusterize-row")
73 | for row in rowels
74 | row.should.have.text "#{i} - index: #{i-1}"
75 | i++
76 |
77 |
78 |
79 |
80 | describe "scrolling", ->
81 |
82 | it "should scroll up 3/2 clustersize without change", ->
83 | cl.scrollTop = Math.floor(3/2*cl.clusterHeight-1)
84 | clel.querySelector("div.clusterize-first-row").should.have.attr("style","height: 0px;")
85 | i = 1
86 | for clusterel in clel.querySelectorAll("div.clusterize-cluster")
87 | rowels = clusterel.querySelectorAll("div.clusterize-row")
88 | for row in rowels
89 | row.should.have.text "#{i} - index: #{i-1}"
90 | i++
91 |
92 | it "should transit at scroll top 918", (done) ->
93 | cl.$once "cluster-loading", (nr) ->
94 | nr.should.equal 3
95 | cl.scrollTop = Math.floor(3/2*cl.clusterHeight)
96 | cl.$nextTick ->
97 | clel.querySelector("div.clusterize-first-row").should.have.attr("style","height: #{cl.clusterSize*cl.rowHeight}px;")
98 | clel.querySelector("div.clusterize-last-row").should.have.attr("style","height: #{overallHeight-4*cl.clusterSize*cl.rowHeight}px;")
99 | i = cl.clusterSize+1
100 | for clusterel in clel.querySelectorAll("div.clusterize-cluster")
101 | rowels = clusterel.querySelectorAll("div.clusterize-row")
102 | for row in rowels
103 | row.should.have.text "#{i} - index: #{i-1}"
104 | i++
105 | cl.scrollTop = 0
106 | cl.$nextTick done
107 |
108 |
109 | describe "size change", ->
110 |
111 | it "should change clustersize on size change", (done) ->
112 | cl.height = 200
113 | cl.$nextTick ->
114 | clsels = clel.querySelectorAll("div.clusterize-cluster")
115 | clel.querySelector("div.clusterize-last-row").should.have.attr("style")
116 | .match(new RegExp("height: #{overallHeight-3*cl.clusterSize*cl.rowHeight}px;"))
117 | i = 1
118 | for clusterel in clsels
119 | clusterel.should.have.attr("style").match(new RegExp("height: #{cl.clusterSize*cl.rowHeight}px;"))
120 | rowels = clusterel.querySelectorAll("div.clusterize-row")
121 | rowels.should.have.length(cl.clusterSize)
122 | for row in rowels
123 | row.should.have.text "#{i} - index: #{i-1}"
124 | i++
125 | done()
126 |
127 | describe "loading env", ->
128 |
129 | before ->
130 | env = loadComp(require("../dev/loading.vue"))
131 | cl = env.$refs.clusterize
132 | should.exist(cl)
133 | should.exist(cl.$el)
134 | clel = cl.$el
135 |
136 | after ->
137 | unloadComp(env)
138 |
139 | it "should dispatch event clusterize-loaded", (done) ->
140 | cl.$once "clusterize-loaded", done
141 |
142 | it "should be loading", ->
143 | clsels = clel.querySelectorAll("div.clusterize-cluster")
144 | for clusterel in clsels
145 | clusterel.should.contain "div.clusterize-cluster-loading>p[slot='loading']"
146 | clusterel.should.have.attr("style").match(new RegExp("height: #{cl.clusterSize*cl.rowHeight}px;"))
147 |
148 | it "should contain data once loaded", (done) ->
149 | clsels = clel.querySelectorAll("div.clusterize-cluster")
150 | j = 0
151 | cl.$on "cluster-loaded", (nr) ->
152 | clel.querySelector("div.clusterize-last-row").should.have.attr("style").match(new RegExp("height: #{overallHeight-3*cl.clusterSize*cl.rowHeight}px;"))
153 | env.$nextTick ->
154 | i = 1+nr*cl.clusterSize
155 | rowels = clsels[nr].querySelectorAll("div.clusterize-row")
156 | rowels.should.have.length(cl.clusterSize)
157 | for row in rowels
158 | row.should.have.text "#{i}"
159 | i++
160 | j++
161 | done() if j == 3
162 |
163 | it "should have a hidden loading item afterwards", ->
164 | clsels = clel.querySelectorAll("div.clusterize-cluster")
165 | for clusterel in clsels
166 | lel = clusterel.querySelector("div.clusterize-cluster-loading[style='display: none;']")
167 | lel.should.exit
168 | lel.should.contain "p[slot='loading']"
169 | lel.should.have.text "loading"
170 |
--------------------------------------------------------------------------------