Merge lp:~ricgard/maas/vs-repeat-ui-improvement--2.2 into lp:maas/2.2

Proposed by Richard McCartney
Status: Merged
Approved by: Mike Pontillo
Approved revision: no longer in the source branch.
Merged at revision: 6066
Proposed branch: lp:~ricgard/maas/vs-repeat-ui-improvement--2.2
Merge into: lp:maas/2.2
Diff against target: 792 lines (+651/-10)
10 files modified
src/maasserver/static/js/angular/3rdparty/vs-repeat.js (+629/-0)
src/maasserver/static/js/angular/maas.js (+2/-1)
src/maasserver/static/partials/dashboard.html (+3/-1)
src/maasserver/static/partials/machines-table.html (+1/-1)
src/maasserver/static/partials/networks-list.html (+2/-2)
src/maasserver/static/partials/node-events.html (+1/-1)
src/maasserver/static/partials/nodes-list.html (+3/-3)
src/maasserver/static/partials/pods-list.html (+1/-1)
src/maasserver/templates/maasserver/js-conf.html (+3/-0)
src/maasserver/views/combo.py (+6/-0)
To merge this branch: bzr merge lp:~ricgard/maas/vs-repeat-ui-improvement--2.2
Reviewer Review Type Date Requested Status
Mike Pontillo (community) Approve
Richard McCartney (community) Needs Resubmitting
Review via email: mp+325848@code.launchpad.net

Commit message

Backport: Commit 6088: Created vertical scrolling repeat to improve the loading times of heavy table content pages such as device discovery

Description of the change

Work done
========================

- Added the vs-repeat angular module to MAAS. Module details and code can be found here https://github.com/kamilkp/angular-vs-repeat
- Applied the vs-repeat directive to the following pages:

Device discovery, Node listing, Device listing, Controller listing, Pod listing, Subnet listing, Node events page

How to test
========================

- Pull down the branch and run 'make' then 'make sampledata'
- Go to http://localhost:5240 and install MAAS via the first user journey. Login in with 'admin' 'test.
- View the device discovery page and attempt to rapidly scroll the page. The directive should pull in the table row content and improve the loading time on the page.
- Test in a similar way on other pages such as node events

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote :

I tested this and it's working great on my MAAS 2.2 setup. Nice work!

However, this cannot land unless we fulfill the licensing requirements for including this third-party code[1]. (I guess that means the original branch that landed in trunk needs to be fixed, too; I didn't review that one.)

Per the MIT license, we need to acknowledge our usage of his code by redistributing the copyright notice and MIT license stating freely-available permission to copy the work.

Perhaps we should have some kind of "About" page in MAAS which includes a list of all third-party code included in MAAS, like I've seen many other projects do.

For now, since I don't think we want to design an about page at the moment, if we just modify the 'vs-repeat.js' file to include the MIT license in JavaScript comments at the top, in my (non-lawyer) opinion we would fulfill the requirements of the license.

[1]: https://github.com/kamilkp/angular-vs-repeat/blob/master/LICENSE

review: Needs Fixing
Revision history for this message
Andres Rodriguez (andreserl) wrote :

Actually, the way we have always done this is by :

1. having the right headers
2. updating the packaging to highlight this file is with a different license.

Since we are now releasing snaps that do not include a debian/copyright file, we need to include a LICENSE file listing all those files with different licenses.

Revision history for this message
Richard McCartney (ricgard) wrote :

Mike can you just check now, I've added the license

Rich

review: Needs Resubmitting
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Looks good to me. Per the comments from Andres above, we need to also update the licenses that ship with our packaging (both snap and deb). But that can be done in a separate branch.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'src/maasserver/static/js/angular/3rdparty/vs-repeat.js'
--- src/maasserver/static/js/angular/3rdparty/vs-repeat.js 1970-01-01 00:00:00 +0000
+++ src/maasserver/static/js/angular/3rdparty/vs-repeat.js 2017-06-19 16:17:11 +0000
@@ -0,0 +1,629 @@
1// The MIT License (MIT)
2
3// Copyright (c) 2014 kamilkp
4
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22
23// Copyright Kamil Pękala http://github.com/kamilkp
24// Angular Virtual Scroll Repeat v1.1.7 2016/03/08
25//
26
27(function(window, angular) {
28 'use strict';
29 /* jshint eqnull:true */
30 /* jshint -W038 */
31
32 // DESCRIPTION:
33 // vsRepeat directive stands for Virtual Scroll Repeat. It turns a standard ngRepeated set of elements in a scrollable container
34 // into a component, where the user thinks he has all the elements rendered and all he needs to do is scroll (without any kind of
35 // pagination - which most users loath) and at the same time the browser isn't overloaded by that many elements/angular bindings etc.
36 // The directive renders only so many elements that can fit into current container's clientHeight/clientWidth.
37
38 // LIMITATIONS:
39 // - current version only supports an Array as a right-hand-side object for ngRepeat
40 // - all rendered elements must have the same height/width or the sizes of the elements must be known up front
41
42 // USAGE:
43 // In order to use the vsRepeat directive you need to place a vs-repeat attribute on a direct parent of an element with ng-repeat
44 // example:
45 // <div vs-repeat>
46 // <div ng-repeat="item in someArray">
47 // <!-- content -->
48 // </div>
49 // </div>
50 //
51 // or:
52 // <div vs-repeat>
53 // <div ng-repeat-start="item in someArray">
54 // <!-- content -->
55 // </div>
56 // <div>
57 // <!-- something in the middle -->
58 // </div>
59 // <div ng-repeat-end>
60 // <!-- content -->
61 // </div>
62 // </div>
63 //
64 // You can also measure the single element's height/width (including all paddings and margins), and then speficy it as a value
65 // of the attribute 'vs-repeat'. This can be used if one wants to override the automatically computed element size.
66 // example:
67 // <div vs-repeat="50"> <!-- the specified element height is 50px -->
68 // <div ng-repeat="item in someArray">
69 // <!-- content -->
70 // </div>
71 // </div>
72 //
73 // IMPORTANT!
74 //
75 // - the vsRepeat directive must be applied to a direct parent of an element with ngRepeat
76 // - the value of vsRepeat attribute is the single element's height/width measured in pixels. If none provided, the directive
77 // will compute it automatically
78
79 // OPTIONAL PARAMETERS (attributes):
80 // vs-repeat-container="selector" - selector for element containing ng-repeat. (defaults to the current element)
81 // vs-scroll-parent="selector" - selector to the scrollable container. The directive will look for a closest parent matching
82 // the given selector (defaults to the current element)
83 // vs-horizontal - stack repeated elements horizontally instead of vertically
84 // vs-offset-before="value" - top/left offset in pixels (defaults to 0)
85 // vs-offset-after="value" - bottom/right offset in pixels (defaults to 0)
86 // vs-excess="value" - an integer number representing the number of elements to be rendered outside of the current container's viewport
87 // (defaults to 2)
88 // vs-size - a property name of the items in collection that is a number denoting the element size (in pixels)
89 // vs-autoresize - use this attribute without vs-size and without specifying element's size. The automatically computed element style will
90 // readjust upon window resize if the size is dependable on the viewport size
91 // vs-scrolled-to-end="callback" - callback will be called when the last item of the list is rendered
92 // vs-scrolled-to-end-offset="integer" - set this number to trigger the scrolledToEnd callback n items before the last gets rendered
93 // vs-scrolled-to-beginning="callback" - callback will be called when the first item of the list is rendered
94 // vs-scrolled-to-beginning-offset="integer" - set this number to trigger the scrolledToBeginning callback n items before the first gets rendered
95
96 // EVENTS:
97 // - 'vsRepeatTrigger' - an event the directive listens for to manually trigger reinitialization
98 // - 'vsRepeatReinitialized' - an event the directive emits upon reinitialization done
99
100 var dde = document.documentElement,
101 matchingFunction = dde.matches ? 'matches' :
102 dde.matchesSelector ? 'matchesSelector' :
103 dde.webkitMatches ? 'webkitMatches' :
104 dde.webkitMatchesSelector ? 'webkitMatchesSelector' :
105 dde.msMatches ? 'msMatches' :
106 dde.msMatchesSelector ? 'msMatchesSelector' :
107 dde.mozMatches ? 'mozMatches' :
108 dde.mozMatchesSelector ? 'mozMatchesSelector' : null;
109
110 var closestElement = angular.element.prototype.closest || function (selector) {
111 var el = this[0].parentNode;
112 while (el !== document.documentElement && el != null && !el[matchingFunction](selector)) {
113 el = el.parentNode;
114 }
115
116 if (el && el[matchingFunction](selector)) {
117 return angular.element(el);
118 }
119 else {
120 return angular.element();
121 }
122 };
123
124 function getWindowScroll() {
125 if ('pageYOffset' in window) {
126 return {
127 scrollTop: pageYOffset,
128 scrollLeft: pageXOffset
129 };
130 }
131 else {
132 var sx, sy, d = document, r = d.documentElement, b = d.body;
133 sx = r.scrollLeft || b.scrollLeft || 0;
134 sy = r.scrollTop || b.scrollTop || 0;
135 return {
136 scrollTop: sy,
137 scrollLeft: sx
138 };
139 }
140 }
141
142 function getClientSize(element, sizeProp) {
143 if (element === window) {
144 return sizeProp === 'clientWidth' ? window.innerWidth : window.innerHeight;
145 }
146 else {
147 return element[sizeProp];
148 }
149 }
150
151 function getScrollPos(element, scrollProp) {
152 return element === window ? getWindowScroll()[scrollProp] : element[scrollProp];
153 }
154
155 function getScrollOffset(vsElement, scrollElement, isHorizontal) {
156 var vsPos = vsElement.getBoundingClientRect()[isHorizontal ? 'left' : 'top'];
157 var scrollPos = scrollElement === window ? 0 : scrollElement.getBoundingClientRect()[isHorizontal ? 'left' : 'top'];
158 var correction = vsPos - scrollPos +
159 (scrollElement === window ? getWindowScroll() : scrollElement)[isHorizontal ? 'scrollLeft' : 'scrollTop'];
160
161 return correction;
162 }
163
164 var vsRepeatModule = angular.module('vs-repeat', []).directive('vsRepeat', ['$compile', '$parse', function($compile, $parse) {
165 return {
166 restrict: 'A',
167 scope: true,
168 compile: function($element, $attrs) {
169 var repeatContainer = angular.isDefined($attrs.vsRepeatContainer) ? angular.element($element[0].querySelector($attrs.vsRepeatContainer)) : $element,
170 ngRepeatChild = repeatContainer.children().eq(0),
171 ngRepeatExpression,
172 childCloneHtml = ngRepeatChild[0].outerHTML,
173 expressionMatches,
174 lhs,
175 rhs,
176 rhsSuffix,
177 originalNgRepeatAttr,
178 collectionName = '$vs_collection',
179 isNgRepeatStart = false,
180 attributesDictionary = {
181 'vsRepeat': 'elementSize',
182 'vsOffsetBefore': 'offsetBefore',
183 'vsOffsetAfter': 'offsetAfter',
184 'vsScrolledToEndOffset': 'scrolledToEndOffset',
185 'vsScrolledToBeginningOffset': 'scrolledToBeginningOffset',
186 'vsExcess': 'excess'
187 };
188
189 if (ngRepeatChild.attr('ng-repeat')) {
190 originalNgRepeatAttr = 'ng-repeat';
191 ngRepeatExpression = ngRepeatChild.attr('ng-repeat');
192 }
193 else if (ngRepeatChild.attr('data-ng-repeat')) {
194 originalNgRepeatAttr = 'data-ng-repeat';
195 ngRepeatExpression = ngRepeatChild.attr('data-ng-repeat');
196 }
197 else if (ngRepeatChild.attr('ng-repeat-start')) {
198 isNgRepeatStart = true;
199 originalNgRepeatAttr = 'ng-repeat-start';
200 ngRepeatExpression = ngRepeatChild.attr('ng-repeat-start');
201 }
202 else if (ngRepeatChild.attr('data-ng-repeat-start')) {
203 isNgRepeatStart = true;
204 originalNgRepeatAttr = 'data-ng-repeat-start';
205 ngRepeatExpression = ngRepeatChild.attr('data-ng-repeat-start');
206 }
207 else {
208 throw new Error('angular-vs-repeat: no ng-repeat directive on a child element');
209 }
210
211 expressionMatches = /^\s*(\S+)\s+in\s+([\S\s]+?)(track\s+by\s+\S+)?$/.exec(ngRepeatExpression);
212 lhs = expressionMatches[1];
213 rhs = expressionMatches[2];
214 rhsSuffix = expressionMatches[3];
215
216 if (isNgRepeatStart) {
217 var index = 0;
218 var repeaterElement = repeatContainer.children().eq(0);
219 while(repeaterElement.attr('ng-repeat-end') == null && repeaterElement.attr('data-ng-repeat-end') == null) {
220 index++;
221 repeaterElement = repeatContainer.children().eq(index);
222 childCloneHtml += repeaterElement[0].outerHTML;
223 }
224 }
225
226 repeatContainer.empty();
227 return {
228 pre: function($scope, $element, $attrs) {
229 var repeatContainer = angular.isDefined($attrs.vsRepeatContainer) ? angular.element($element[0].querySelector($attrs.vsRepeatContainer)) : $element,
230 childClone = angular.element(childCloneHtml),
231 childTagName = childClone[0].tagName.toLowerCase(),
232 originalCollection = [],
233 originalLength,
234 $$horizontal = typeof $attrs.vsHorizontal !== 'undefined',
235 $beforeContent = angular.element('<' + childTagName + ' class="vs-repeat-before-content"></' + childTagName + '>'),
236 $afterContent = angular.element('<' + childTagName + ' class="vs-repeat-after-content"></' + childTagName + '>'),
237 autoSize = !$attrs.vsRepeat,
238 sizesPropertyExists = !!$attrs.vsSize || !!$attrs.vsSizeProperty,
239 $scrollParent = $attrs.vsScrollParent ?
240 $attrs.vsScrollParent === 'window' ? angular.element(window) :
241 closestElement.call(repeatContainer, $attrs.vsScrollParent) : repeatContainer,
242 $$options = 'vsOptions' in $attrs ? $scope.$eval($attrs.vsOptions) : {},
243 clientSize = $$horizontal ? 'clientWidth' : 'clientHeight',
244 offsetSize = $$horizontal ? 'offsetWidth' : 'offsetHeight',
245 scrollPos = $$horizontal ? 'scrollLeft' : 'scrollTop';
246
247 $scope.totalSize = 0;
248 if (!('vsSize' in $attrs) && 'vsSizeProperty' in $attrs) {
249 console.warn('vs-size-property attribute is deprecated. Please use vs-size attribute which also accepts angular expressions.');
250 }
251
252 if ($scrollParent.length === 0) {
253 throw 'Specified scroll parent selector did not match any element';
254 }
255 $scope.$scrollParent = $scrollParent;
256
257 if (sizesPropertyExists) {
258 $scope.sizesCumulative = [];
259 }
260
261 //initial defaults
262 $scope.elementSize = (+$attrs.vsRepeat) || getClientSize($scrollParent[0], clientSize) || 50;
263 $scope.offsetBefore = 0;
264 $scope.offsetAfter = 0;
265 $scope.excess = 2;
266
267 if ($$horizontal) {
268 $beforeContent.css('height', '100%');
269 $afterContent.css('height', '100%');
270 }
271 else {
272 $beforeContent.css('width', '100%');
273 $afterContent.css('width', '100%');
274 }
275
276 Object.keys(attributesDictionary).forEach(function(key) {
277 if ($attrs[key]) {
278 $attrs.$observe(key, function(value) {
279 // '+' serves for getting a number from the string as the attributes are always strings
280 $scope[attributesDictionary[key]] = +value;
281 reinitialize();
282 });
283 }
284 });
285
286
287 $scope.$watchCollection(rhs, function(coll) {
288 originalCollection = coll || [];
289 refresh();
290 });
291
292 function refresh() {
293 if (!originalCollection || originalCollection.length < 1) {
294 $scope[collectionName] = [];
295 originalLength = 0;
296 $scope.sizesCumulative = [0];
297 }
298 else {
299 originalLength = originalCollection.length;
300 if (sizesPropertyExists) {
301 $scope.sizes = originalCollection.map(function(item) {
302 var s = $scope.$new(false);
303 angular.extend(s, item);
304 s[lhs] = item;
305 var size = ($attrs.vsSize || $attrs.vsSizeProperty) ?
306 s.$eval($attrs.vsSize || $attrs.vsSizeProperty) :
307 $scope.elementSize;
308 s.$destroy();
309 return size;
310 });
311 var sum = 0;
312 $scope.sizesCumulative = $scope.sizes.map(function(size) {
313 var res = sum;
314 sum += size;
315 return res;
316 });
317 $scope.sizesCumulative.push(sum);
318 }
319 else {
320 setAutoSize();
321 }
322 }
323
324 reinitialize();
325 }
326
327 function setAutoSize() {
328 if (autoSize) {
329 $scope.$$postDigest(function() {
330 if (repeatContainer[0].offsetHeight || repeatContainer[0].offsetWidth) { // element is visible
331 var children = repeatContainer.children(),
332 i = 0,
333 gotSomething = false,
334 insideStartEndSequence = false;
335
336 while (i < children.length) {
337 if (children[i].attributes[originalNgRepeatAttr] != null || insideStartEndSequence) {
338 if (!gotSomething) {
339 $scope.elementSize = 0;
340 }
341
342 gotSomething = true;
343 if (children[i][offsetSize]) {
344 $scope.elementSize += children[i][offsetSize];
345 }
346
347 if (isNgRepeatStart) {
348 if (children[i].attributes['ng-repeat-end'] != null || children[i].attributes['data-ng-repeat-end'] != null) {
349 break;
350 }
351 else {
352 insideStartEndSequence = true;
353 }
354 }
355 else {
356 break;
357 }
358 }
359 i++;
360 }
361
362 if (gotSomething) {
363 reinitialize();
364 autoSize = false;
365 if ($scope.$root && !$scope.$root.$$phase) {
366 $scope.$apply();
367 }
368 }
369 }
370 else {
371 var dereg = $scope.$watch(function() {
372 if (repeatContainer[0].offsetHeight || repeatContainer[0].offsetWidth) {
373 dereg();
374 setAutoSize();
375 }
376 });
377 }
378 });
379 }
380 }
381
382 function getLayoutProp() {
383 var layoutPropPrefix = childTagName === 'tr' ? '' : 'min-';
384 var layoutProp = $$horizontal ? layoutPropPrefix + 'width' : layoutPropPrefix + 'height';
385 return layoutProp;
386 }
387
388 childClone.eq(0).attr(originalNgRepeatAttr, lhs + ' in ' + collectionName + (rhsSuffix ? ' ' + rhsSuffix : ''));
389 childClone.addClass('vs-repeat-repeated-element');
390
391 repeatContainer.append($beforeContent);
392 repeatContainer.append(childClone);
393 $compile(childClone)($scope);
394 repeatContainer.append($afterContent);
395
396 $scope.startIndex = 0;
397 $scope.endIndex = 0;
398
399 function scrollHandler() {
400 if (updateInnerCollection()) {
401 $scope.$digest();
402 }
403 }
404
405 $scrollParent.on('scroll', scrollHandler);
406
407 function onWindowResize() {
408 if (typeof $attrs.vsAutoresize !== 'undefined') {
409 autoSize = true;
410 setAutoSize();
411 if ($scope.$root && !$scope.$root.$$phase) {
412 $scope.$apply();
413 }
414 }
415 if (updateInnerCollection()) {
416 $scope.$apply();
417 }
418 }
419
420 angular.element(window).on('resize', onWindowResize);
421 $scope.$on('$destroy', function() {
422 angular.element(window).off('resize', onWindowResize);
423 $scrollParent.off('scroll', scrollHandler);
424 });
425
426 $scope.$on('vsRepeatTrigger', refresh);
427
428 $scope.$on('vsRepeatResize', function() {
429 autoSize = true;
430 setAutoSize();
431 });
432
433 var _prevStartIndex,
434 _prevEndIndex,
435 _minStartIndex,
436 _maxEndIndex;
437
438 $scope.$on('vsRenderAll', function() {//e , quantum) {
439 if($$options.latch) {
440 setTimeout(function() {
441 // var __endIndex = Math.min($scope.endIndex + (quantum || 1), originalLength);
442 var __endIndex = originalLength;
443 _maxEndIndex = Math.max(__endIndex, _maxEndIndex);
444 $scope.endIndex = $$options.latch ? _maxEndIndex : __endIndex;
445 $scope[collectionName] = originalCollection.slice($scope.startIndex, $scope.endIndex);
446 _prevEndIndex = $scope.endIndex;
447
448 $scope.$$postDigest(function() {
449 $beforeContent.css(getLayoutProp(), 0);
450 $afterContent.css(getLayoutProp(), 0);
451 });
452
453 $scope.$apply(function() {
454 $scope.$emit('vsRenderAllDone');
455 });
456 });
457 }
458 });
459
460 function reinitialize() {
461 _prevStartIndex = void 0;
462 _prevEndIndex = void 0;
463 _minStartIndex = originalLength;
464 _maxEndIndex = 0;
465 updateTotalSize(sizesPropertyExists ?
466 $scope.sizesCumulative[originalLength] :
467 $scope.elementSize * originalLength
468 );
469 updateInnerCollection();
470
471 $scope.$emit('vsRepeatReinitialized', $scope.startIndex, $scope.endIndex);
472 }
473
474 function updateTotalSize(size) {
475 $scope.totalSize = $scope.offsetBefore + size + $scope.offsetAfter;
476 }
477
478 var _prevClientSize;
479 function reinitOnClientHeightChange() {
480 var ch = getClientSize($scrollParent[0], clientSize);
481 if (ch !== _prevClientSize) {
482 reinitialize();
483 if ($scope.$root && !$scope.$root.$$phase) {
484 $scope.$apply();
485 }
486 }
487 _prevClientSize = ch;
488 }
489
490 $scope.$watch(function() {
491 if (typeof window.requestAnimationFrame === 'function') {
492 window.requestAnimationFrame(reinitOnClientHeightChange);
493 }
494 else {
495 reinitOnClientHeightChange();
496 }
497 });
498
499 function updateInnerCollection() {
500 var $scrollPosition = getScrollPos($scrollParent[0], scrollPos);
501 var $clientSize = getClientSize($scrollParent[0], clientSize);
502
503 var scrollOffset = repeatContainer[0] === $scrollParent[0] ? 0 : getScrollOffset(
504 repeatContainer[0],
505 $scrollParent[0],
506 $$horizontal
507 );
508
509 var __startIndex = $scope.startIndex;
510 var __endIndex = $scope.endIndex;
511
512 if (sizesPropertyExists) {
513 __startIndex = 0;
514 while ($scope.sizesCumulative[__startIndex] < $scrollPosition - $scope.offsetBefore - scrollOffset) {
515 __startIndex++;
516 }
517 if (__startIndex > 0) { __startIndex--; }
518
519 // Adjust the start index according to the excess
520 __startIndex = Math.max(
521 Math.floor(__startIndex - $scope.excess / 2),
522 0
523 );
524
525 __endIndex = __startIndex;
526 while ($scope.sizesCumulative[__endIndex] < $scrollPosition - $scope.offsetBefore - scrollOffset + $clientSize) {
527 __endIndex++;
528 }
529
530 // Adjust the end index according to the excess
531 __endIndex = Math.min(
532 Math.ceil(__endIndex + $scope.excess / 2),
533 originalLength
534 );
535 }
536 else {
537 __startIndex = Math.max(
538 Math.floor(
539 ($scrollPosition - $scope.offsetBefore - scrollOffset) / $scope.elementSize
540 ) - $scope.excess / 2,
541 0
542 );
543
544 __endIndex = Math.min(
545 __startIndex + Math.ceil(
546 $clientSize / $scope.elementSize
547 ) + $scope.excess,
548 originalLength
549 );
550 }
551
552 _minStartIndex = Math.min(__startIndex, _minStartIndex);
553 _maxEndIndex = Math.max(__endIndex, _maxEndIndex);
554
555 $scope.startIndex = $$options.latch ? _minStartIndex : __startIndex;
556 $scope.endIndex = $$options.latch ? _maxEndIndex : __endIndex;
557
558 var digestRequired = false;
559 if (_prevStartIndex == null) {
560 digestRequired = true;
561 }
562 else if (_prevEndIndex == null) {
563 digestRequired = true;
564 }
565
566 if (!digestRequired) {
567 if ($$options.hunked) {
568 if (Math.abs($scope.startIndex - _prevStartIndex) >= $scope.excess / 2 ||
569 ($scope.startIndex === 0 && _prevStartIndex !== 0)) {
570 digestRequired = true;
571 }
572 else if (Math.abs($scope.endIndex - _prevEndIndex) >= $scope.excess / 2 ||
573 ($scope.endIndex === originalLength && _prevEndIndex !== originalLength)) {
574 digestRequired = true;
575 }
576 }
577 else {
578 digestRequired = $scope.startIndex !== _prevStartIndex ||
579 $scope.endIndex !== _prevEndIndex;
580 }
581 }
582
583 if (digestRequired) {
584 $scope[collectionName] = originalCollection.slice($scope.startIndex, $scope.endIndex);
585
586 // Emit the event
587 $scope.$emit('vsRepeatInnerCollectionUpdated', $scope.startIndex, $scope.endIndex, _prevStartIndex, _prevEndIndex);
588 var triggerIndex;
589 if ($attrs.vsScrolledToEnd) {
590 triggerIndex = originalCollection.length - ($scope.scrolledToEndOffset || 0);
591 if (($scope.endIndex >= triggerIndex && _prevEndIndex < triggerIndex) || (originalCollection.length && $scope.endIndex === originalCollection.length)) {
592 $scope.$eval($attrs.vsScrolledToEnd);
593 }
594 }
595 if ($attrs.vsScrolledToBeginning) {
596 triggerIndex = $scope.scrolledToBeginningOffset || 0;
597 if (($scope.startIndex <= triggerIndex && _prevStartIndex > $scope.startIndex)) {
598 $scope.$eval($attrs.vsScrolledToBeginning);
599 }
600 }
601
602 _prevStartIndex = $scope.startIndex;
603 _prevEndIndex = $scope.endIndex;
604
605 var offsetCalculationString = sizesPropertyExists ?
606 '(sizesCumulative[$index + startIndex] + offsetBefore)' :
607 '(($index + startIndex) * elementSize + offsetBefore)';
608
609 var parsed = $parse(offsetCalculationString);
610 var o1 = parsed($scope, {$index: 0});
611 var o2 = parsed($scope, {$index: $scope[collectionName].length});
612 var total = $scope.totalSize;
613
614 $beforeContent.css(getLayoutProp(), o1 + 'px');
615 $afterContent.css(getLayoutProp(), (total - o2) + 'px');
616 }
617
618 return digestRequired;
619 }
620 }
621 };
622 }
623 };
624 }]);
625
626 if (typeof module !== 'undefined' && module.exports) {
627 module.exports = vsRepeatModule.name;
628 }
629})(window, window.angular);
0630
=== modified file 'src/maasserver/static/js/angular/maas.js'
--- src/maasserver/static/js/angular/maas.js 2017-03-10 18:38:46 +0000
+++ src/maasserver/static/js/angular/maas.js 2017-06-19 16:17:11 +0000
@@ -9,7 +9,8 @@
9 */9 */
1010
11angular.module('MAAS',11angular.module('MAAS',
12 ['ngRoute', 'ngCookies', 'ngSanitize', 'ngTagsInput', 'sticky']).config(12 ['ngRoute', 'ngCookies', 'ngSanitize', 'ngTagsInput', 'sticky',
13 'vs-repeat']).config(
13 function($interpolateProvider, $routeProvider, $httpProvider) {14 function($interpolateProvider, $routeProvider, $httpProvider) {
14 $interpolateProvider.startSymbol('{$');15 $interpolateProvider.startSymbol('{$');
15 $interpolateProvider.endSymbol('$}');16 $interpolateProvider.endSymbol('$}');
1617
=== modified file 'src/maasserver/static/partials/dashboard.html'
--- src/maasserver/static/partials/dashboard.html 2017-04-18 10:57:48 +0000
+++ src/maasserver/static/partials/dashboard.html 2017-06-19 16:17:11 +0000
@@ -46,7 +46,8 @@
46 No new discoveries46 No new discoveries
47 </div>47 </div>
48 </div>48 </div>
49 <div class="table__row"49 <div vs-repeat vs-scroll-parent="window">
50 <div class="table__row"
50 data-ng-repeat="discovery in discoveredDevices | orderBy:'-last_seen' track by discovery.first_seen"51 data-ng-repeat="discovery in discoveredDevices | orderBy:'-last_seen' track by discovery.first_seen"
51 data-ng-class="{'is-active' : discovery.first_seen === selectedDevice}">52 data-ng-class="{'is-active' : discovery.first_seen === selectedDevice}">
52 <div data-ng-if="discovery.first_seen !== selectedDevice"53 <div data-ng-if="discovery.first_seen !== selectedDevice"
@@ -179,6 +180,7 @@
179 </div>180 </div>
180 </maas-obj-form>181 </maas-obj-form>
181 </div>182 </div>
183 </div>
182 </div>184 </div>
183 </div>185 </div>
184 </div>186 </div>
185187
=== modified file 'src/maasserver/static/partials/machines-table.html'
--- src/maasserver/static/partials/machines-table.html 2017-04-18 11:31:25 +0000
+++ src/maasserver/static/partials/machines-table.html 2017-06-19 16:17:11 +0000
@@ -39,7 +39,7 @@
39 </th>39 </th>
40 </tr>40 </tr>
41 </thead>41 </thead>
42 <tbody>42 <tbody vs-repeat vs-scroll-parent="window">
43 <tr43 <tr
44 data-ng-repeat="node in filteredMachines = (machines | nodesFilter:search | orderBy:table.predicate:table.reverse) track by node.system_id"44 data-ng-repeat="node in filteredMachines = (machines | nodesFilter:search | orderBy:table.predicate:table.reverse) track by node.system_id"
45 data-ng-class="{ 'table--error': machineHasError({ $machine: node }), selected: node.$selected }">45 data-ng-class="{ 'table--error': machineHasError({ $machine: node }), selected: node.$selected }">
4646
=== modified file 'src/maasserver/static/partials/networks-list.html'
--- src/maasserver/static/partials/networks-list.html 2017-04-10 15:35:18 +0000
+++ src/maasserver/static/partials/networks-list.html 2017-06-19 16:17:11 +0000
@@ -147,7 +147,7 @@
147 <th class="table__column--13 align-right">Available IPs</th>147 <th class="table__column--13 align-right">Available IPs</th>
148 </tr>148 </tr>
149 </thead>149 </thead>
150 <tbody>150 <tbody vs-repeat vs-scroll-parent="window">
151 <tr class="table-listing__row" data-ng-repeat="row in group.spaces.rows">151 <tr class="table-listing__row" data-ng-repeat="row in group.spaces.rows">
152 <!-- <td class="table-listing__cell table__column--3 ng-hide">152 <!-- <td class="table-listing__cell table__column--3 ng-hide">
153 <div data-ng-if="row.space_name">153 <div data-ng-if="row.space_name">
@@ -207,7 +207,7 @@
207 <th class="table__column--25">Space</th>207 <th class="table__column--25">Space</th>
208 </tr>208 </tr>
209 </thead>209 </thead>
210 <tbody>210 <tbody vs-repeat vs-scroll-parent="window">
211 <tr class="table-listing__row" data-ng-repeat="row in group.fabrics.rows">211 <tr class="table-listing__row" data-ng-repeat="row in group.fabrics.rows">
212 <!-- <td class="table-listing__cell table__column--3 ng-hide">212 <!-- <td class="table-listing__cell table__column--3 ng-hide">
213 <div data-ng-if="row.fabric_name">213 <div data-ng-if="row.fabric_name">
214214
=== modified file 'src/maasserver/static/partials/node-events.html'
--- src/maasserver/static/partials/node-events.html 2017-04-05 02:36:06 +0000
+++ src/maasserver/static/partials/node-events.html 2017-06-19 16:17:11 +0000
@@ -35,7 +35,7 @@
35 <th class="table-col--20">Time</th>35 <th class="table-col--20">Time</th>
36 </tr>36 </tr>
37 </thead>37 </thead>
38 <tbody>38 <tbody vs-repeat vs-scroll-parent="window">
39 <tr39 <tr
40 data-ng-repeat="event in events | filter:search | orderByDate:'created':'id' track by event.id">40 data-ng-repeat="event in events | filter:search | orderByDate:'created':'id' track by event.id">
41 <td class="table-col--1 u-padding--right-none u-padding--left-none">41 <td class="table-col--1 u-padding--right-none u-padding--left-none">
4242
=== modified file 'src/maasserver/static/partials/nodes-list.html'
--- src/maasserver/static/partials/nodes-list.html 2017-04-13 10:43:13 +0000
+++ src/maasserver/static/partials/nodes-list.html 2017-06-19 16:17:11 +0000
@@ -448,7 +448,7 @@
448 <th class="table-col--5"></th>448 <th class="table-col--5"></th>
449 </tr>449 </tr>
450 </thead>450 </thead>
451 <tbody>451 <tbody vs-repeat vs-scroll-parent="window">
452 <tr data-ng-repeat="interface in device.interfaces">452 <tr data-ng-repeat="interface in device.interfaces">
453 <td class="table-col--20" aria-label="MAC address">453 <td class="table-col--20" aria-label="MAC address">
454 <input type="text" id="mac-address1" placeholder="00:00:00:00:00:00"454 <input type="text" id="mac-address1" placeholder="00:00:00:00:00:00"
@@ -853,7 +853,7 @@
853 </th>853 </th>
854 </tr>854 </tr>
855 </thead>855 </thead>
856 <tbody>856 <tbody vs-repeat vs-scroll-parent="window">
857 <!-- XXX rvba 2015-02-25 - Need to add e2e test. This really needs lots of tests. -->857 <!-- XXX rvba 2015-02-25 - Need to add e2e test. This really needs lots of tests. -->
858 <tr858 <tr
859 data-ng-repeat="device in tabs.devices.filtered_items = (devices | nodesFilter:tabs.devices.search | orderBy:tabs.devices.predicate:tabs.devices.reverse) track by device.system_id"859 data-ng-repeat="device in tabs.devices.filtered_items = (devices | nodesFilter:tabs.devices.search | orderBy:tabs.devices.predicate:tabs.devices.reverse) track by device.system_id"
@@ -917,7 +917,7 @@
917 </th>917 </th>
918 </tr>918 </tr>
919 </thead>919 </thead>
920 <tbody>920 <tbody vs-repeat vs-scroll-parent="window">
921 <!-- XXX rvba 2015-02-25 - Need to add e2e test. This really needs lots of tests. -->921 <!-- XXX rvba 2015-02-25 - Need to add e2e test. This really needs lots of tests. -->
922 <tr922 <tr
923 data-ng-repeat="controller in tabs.controllers.filtered_items = (controllers | nodesFilter:tabs.controllers.search | orderBy:tabs.controllers.predicate:tabs.controllers.reverse) track by controller.system_id"923 data-ng-repeat="controller in tabs.controllers.filtered_items = (controllers | nodesFilter:tabs.controllers.search | orderBy:tabs.controllers.predicate:tabs.controllers.reverse) track by controller.system_id"
924924
=== modified file 'src/maasserver/static/partials/pods-list.html'
--- src/maasserver/static/partials/pods-list.html 2017-05-01 20:31:46 +0000
+++ src/maasserver/static/partials/pods-list.html 2017-06-19 16:17:11 +0000
@@ -123,7 +123,7 @@
123 </div>123 </div>
124 </div>124 </div>
125 </header>125 </header>
126 <div class="table__body">126 <div class="table__body" vs-repeat vs-scroll-parent="window">
127 <div class="table__row" data-ng-repeat="pod in filteredItems = (pods | nodesFilter:search | orderBy:predicate:reverse) track by pod.id"127 <div class="table__row" data-ng-repeat="pod in filteredItems = (pods | nodesFilter:search | orderBy:predicate:reverse) track by pod.id"
128 data-ng-class="{ selected: pod.$selected, 'is-active': pod.$selected && pod.action_failed }">128 data-ng-class="{ selected: pod.$selected, 'is-active': pod.$selected && pod.action_failed }">
129 <div data-ng-if="isSuperUser()">129 <div data-ng-if="isSuperUser()">
130130
=== modified file 'src/maasserver/templates/maasserver/js-conf.html'
--- src/maasserver/templates/maasserver/js-conf.html 2017-03-10 15:30:26 +0000
+++ src/maasserver/templates/maasserver/js-conf.html 2017-06-19 16:17:11 +0000
@@ -42,6 +42,9 @@
42 src="{% url "merge" filename="sticky.js" %}?v={{files_version}}">42 src="{% url "merge" filename="sticky.js" %}?v={{files_version}}">
43</script>43</script>
44<script type="text/javascript"44<script type="text/javascript"
45 src="{% url "merge" filename="vs-repeat.js" %}?v={{files_version}}">
46</script>
47<script type="text/javascript"
45 src="{% url "merge" filename="maas-angular.js" %}?v={{files_version}}">48 src="{% url "merge" filename="maas-angular.js" %}?v={{files_version}}">
46</script>49</script>
4750
4851
=== modified file 'src/maasserver/views/combo.py'
--- src/maasserver/views/combo.py 2017-04-06 15:11:01 +0000
+++ src/maasserver/views/combo.py 2017-06-19 16:17:11 +0000
@@ -53,6 +53,12 @@
53 "js/angular/3rdparty/sticky.js",53 "js/angular/3rdparty/sticky.js",
54 ]54 ]
55 },55 },
56 "vs-repeat.js": {
57 "content_type": "text/javascript; charset=UTF-8",
58 "files": [
59 "js/angular/3rdparty/vs-repeat.js",
60 ]
61 },
56 "maas-angular.js": {62 "maas-angular.js": {
57 "content_type": "text/javascript; charset=UTF-8",63 "content_type": "text/javascript; charset=UTF-8",
58 "files": [64 "files": [

Subscribers

People subscribed via source and target branches