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

Proposed by Richard McCartney
Status: Merged
Approved by: Mike Pontillo
Approved revision: no longer in the source branch.
Merged at revision: 6088
Proposed branch: lp:~ricgard/maas/vs-repeat-ui-improvement
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 771 lines (+630/-10)
10 files modified
src/maasserver/static/js/angular/3rdparty/vs-repeat.js (+608/-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
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Review via email: mp+325562@code.launchpad.net

Commit message

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
Blake Rouse (blake-rouse) wrote :

This is awesome! Thanks for doing it.

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (227.8 KiB)

The attempt to merge lp:~ricgard/maas/vs-repeat-ui-improvement into lp:maas failed. Below is the output from the failed tests.

Get:1 http://security.ubuntu.com/ubuntu xenial-security InRelease [102 kB]
Hit:2 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial InRelease
Get:3 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates InRelease [102 kB]
Get:4 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-backports InRelease [102 kB]
Get:5 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates/main Sources [252 kB]
Get:6 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages [553 kB]
Fetched 1,112 kB in 0s (2,080 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
    --no-install-recommends install apache2 archdetect-deb authbind avahi-utils bash bind9 bind9utils build-essential bzr bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools git gjs ipython isc-dhcp-common isc-dhcp-server libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libnss-wrapper libpq-dev make nodejs-legacy npm postgresql psmisc pxelinux python3-all python3-apt python3-attr python3-bson python3-convoy python3-crochet python3-cssselect python3-curtin python3-dev python3-distro-info python3-django python3-django-nose python3-django-piston3 python3-dnspython python3-docutils python3-formencode python3-hivex python3-httplib2 python3-jinja2 python3-jsonschema python3-lxml python3-netaddr python3-netifaces python3-novaclient python3-oauth python3-oauthlib python3-openssl python3-paramiko python3-petname python3-pexpect python3-psycopg2 python3-pyinotify python3-pyparsing python3-pyvmomi python3-requests python3-seamicroclient python3-setuptools python3-simplestreams python3-sphinx python3-tempita python3-twisted python3-txtftp python3-tz python3-yaml python3-zope.interface python-bson python-crochet python-django python-django-piston python-djorm-ext-pgarray python-formencode python-lxml python-netaddr python-netifaces python-pocket-lint python-psycopg2 python-simplejson python-tempita python-twisted python-yaml socat syslinux-common tgt ubuntu-cloudimage-keyring wget xvfb
Reading package lists...
Building dependency tree...
Reading state information...
authbind is already the newest version (2.1.1+nmu1).
avahi-utils is already the newest version (0.6.32~rc+dfsg-1ubuntu2).
build-essential is already the newest version (12.1ubuntu2).
debhelper is already the newest version (9.20160115ubuntu3).
distro-info is already the newest version (0.14build1).
libjs-angularjs is already the newest version (1.2.28-1ubuntu2).
libjs-jquery is already the newest version (1.11.3+dfsg-4).
libjs-yui3-full is already the newest version (3.5.1-1ubuntu3).
libjs-yui3-min is already the newest version (3.5.1-1ubuntu3).
make is already the newest version (4.1-6).
postgresql is already the newest version (9.5+173).
psmisc is already the newest version (22.21-2.1build1).
pxelinux is already the newest version (3:6.03+dfsg-11ubuntu1).
python-formencode is already the n...

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (125.6 KiB)

The attempt to merge lp:~ricgard/maas/vs-repeat-ui-improvement into lp:maas failed. Below is the output from the failed tests.

Hit:1 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial InRelease
Hit:2 http://security.ubuntu.com/ubuntu xenial-security InRelease
Hit:3 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates InRelease
Hit:4 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-backports InRelease
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
    --no-install-recommends install apache2 archdetect-deb authbind avahi-utils bash bind9 bind9utils build-essential bzr bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools git gjs ipython isc-dhcp-common isc-dhcp-server libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libnss-wrapper libpq-dev make nodejs-legacy npm postgresql psmisc pxelinux python3-all python3-apt python3-attr python3-bson python3-convoy python3-crochet python3-cssselect python3-curtin python3-dev python3-distro-info python3-django python3-django-nose python3-django-piston3 python3-dnspython python3-docutils python3-formencode python3-hivex python3-httplib2 python3-jinja2 python3-jsonschema python3-lxml python3-netaddr python3-netifaces python3-novaclient python3-oauth python3-oauthlib python3-openssl python3-paramiko python3-petname python3-pexpect python3-psycopg2 python3-pyinotify python3-pyparsing python3-pyvmomi python3-requests python3-seamicroclient python3-setuptools python3-simplestreams python3-sphinx python3-tempita python3-twisted python3-txtftp python3-tz python3-yaml python3-zope.interface python-bson python-crochet python-django python-django-piston python-djorm-ext-pgarray python-formencode python-lxml python-netaddr python-netifaces python-pocket-lint python-psycopg2 python-simplejson python-tempita python-twisted python-yaml socat syslinux-common tgt ubuntu-cloudimage-keyring wget xvfb
Reading package lists...
Building dependency tree...
Reading state information...
authbind is already the newest version (2.1.1+nmu1).
avahi-utils is already the newest version (0.6.32~rc+dfsg-1ubuntu2).
build-essential is already the newest version (12.1ubuntu2).
debhelper is already the newest version (9.20160115ubuntu3).
distro-info is already the newest version (0.14build1).
libjs-angularjs is already the newest version (1.2.28-1ubuntu2).
libjs-jquery is already the newest version (1.11.3+dfsg-4).
libjs-yui3-full is already the newest version (3.5.1-1ubuntu3).
libjs-yui3-min is already the newest version (3.5.1-1ubuntu3).
make is already the newest version (4.1-6).
postgresql is already the newest version (9.5+173).
psmisc is already the newest version (22.21-2.1build1).
pxelinux is already the newest version (3:6.03+dfsg-11ubuntu1).
python-formencode is already the newest version (1.3.0-0ubuntu5).
python-lxml is already the newest version (3.5.0-1build1).
python-netaddr is already the newest version (0.7.18-1).
python-netifaces is already the newest version (0.10.4-0.1build2).
python-psycopg2 is already the newest version (2.6.1-1b...

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