Merge lp:~ricgard/maas/vs-repeat-ui-improvement into lp:~maas-committers/maas/trunk
- vs-repeat-ui-improvement
- Merge into trunk
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 |
Related bugs: |
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:/
- 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://
- 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
MAAS Lander (maas-lander) wrote : | # |
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://
Hit:2 http://
Get:3 http://
Get:4 http://
Get:5 http://
Get:6 http://
Fetched 1,112 kB in 0s (2,080 kB/s)
Reading package lists...
sudo DEBIAN_
--no-
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~
build-essential is already the newest version (12.1ubuntu2).
debhelper is already the newest version (9.20160115ubun
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+
python-formencode is already the n...
MAAS Lander (maas-lander) wrote : | # |
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://
Hit:2 http://
Hit:3 http://
Hit:4 http://
Reading package lists...
sudo DEBIAN_
--no-
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~
build-essential is already the newest version (12.1ubuntu2).
debhelper is already the newest version (9.20160115ubun
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+
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
1 | === added file 'src/maasserver/static/js/angular/3rdparty/vs-repeat.js' | |||
2 | --- src/maasserver/static/js/angular/3rdparty/vs-repeat.js 1970-01-01 00:00:00 +0000 | |||
3 | +++ src/maasserver/static/js/angular/3rdparty/vs-repeat.js 2017-06-15 11:16:28 +0000 | |||
4 | @@ -0,0 +1,608 @@ | |||
5 | 1 | // | ||
6 | 2 | // Copyright Kamil Pękala http://github.com/kamilkp | ||
7 | 3 | // Angular Virtual Scroll Repeat v1.1.7 2016/03/08 | ||
8 | 4 | // | ||
9 | 5 | |||
10 | 6 | (function(window, angular) { | ||
11 | 7 | 'use strict'; | ||
12 | 8 | /* jshint eqnull:true */ | ||
13 | 9 | /* jshint -W038 */ | ||
14 | 10 | |||
15 | 11 | // DESCRIPTION: | ||
16 | 12 | // vsRepeat directive stands for Virtual Scroll Repeat. It turns a standard ngRepeated set of elements in a scrollable container | ||
17 | 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 | ||
18 | 14 | // pagination - which most users loath) and at the same time the browser isn't overloaded by that many elements/angular bindings etc. | ||
19 | 15 | // The directive renders only so many elements that can fit into current container's clientHeight/clientWidth. | ||
20 | 16 | |||
21 | 17 | // LIMITATIONS: | ||
22 | 18 | // - current version only supports an Array as a right-hand-side object for ngRepeat | ||
23 | 19 | // - all rendered elements must have the same height/width or the sizes of the elements must be known up front | ||
24 | 20 | |||
25 | 21 | // USAGE: | ||
26 | 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 | ||
27 | 23 | // example: | ||
28 | 24 | // <div vs-repeat> | ||
29 | 25 | // <div ng-repeat="item in someArray"> | ||
30 | 26 | // <!-- content --> | ||
31 | 27 | // </div> | ||
32 | 28 | // </div> | ||
33 | 29 | // | ||
34 | 30 | // or: | ||
35 | 31 | // <div vs-repeat> | ||
36 | 32 | // <div ng-repeat-start="item in someArray"> | ||
37 | 33 | // <!-- content --> | ||
38 | 34 | // </div> | ||
39 | 35 | // <div> | ||
40 | 36 | // <!-- something in the middle --> | ||
41 | 37 | // </div> | ||
42 | 38 | // <div ng-repeat-end> | ||
43 | 39 | // <!-- content --> | ||
44 | 40 | // </div> | ||
45 | 41 | // </div> | ||
46 | 42 | // | ||
47 | 43 | // You can also measure the single element's height/width (including all paddings and margins), and then speficy it as a value | ||
48 | 44 | // of the attribute 'vs-repeat'. This can be used if one wants to override the automatically computed element size. | ||
49 | 45 | // example: | ||
50 | 46 | // <div vs-repeat="50"> <!-- the specified element height is 50px --> | ||
51 | 47 | // <div ng-repeat="item in someArray"> | ||
52 | 48 | // <!-- content --> | ||
53 | 49 | // </div> | ||
54 | 50 | // </div> | ||
55 | 51 | // | ||
56 | 52 | // IMPORTANT! | ||
57 | 53 | // | ||
58 | 54 | // - the vsRepeat directive must be applied to a direct parent of an element with ngRepeat | ||
59 | 55 | // - the value of vsRepeat attribute is the single element's height/width measured in pixels. If none provided, the directive | ||
60 | 56 | // will compute it automatically | ||
61 | 57 | |||
62 | 58 | // OPTIONAL PARAMETERS (attributes): | ||
63 | 59 | // vs-repeat-container="selector" - selector for element containing ng-repeat. (defaults to the current element) | ||
64 | 60 | // vs-scroll-parent="selector" - selector to the scrollable container. The directive will look for a closest parent matching | ||
65 | 61 | // the given selector (defaults to the current element) | ||
66 | 62 | // vs-horizontal - stack repeated elements horizontally instead of vertically | ||
67 | 63 | // vs-offset-before="value" - top/left offset in pixels (defaults to 0) | ||
68 | 64 | // vs-offset-after="value" - bottom/right offset in pixels (defaults to 0) | ||
69 | 65 | // vs-excess="value" - an integer number representing the number of elements to be rendered outside of the current container's viewport | ||
70 | 66 | // (defaults to 2) | ||
71 | 67 | // vs-size - a property name of the items in collection that is a number denoting the element size (in pixels) | ||
72 | 68 | // vs-autoresize - use this attribute without vs-size and without specifying element's size. The automatically computed element style will | ||
73 | 69 | // readjust upon window resize if the size is dependable on the viewport size | ||
74 | 70 | // vs-scrolled-to-end="callback" - callback will be called when the last item of the list is rendered | ||
75 | 71 | // vs-scrolled-to-end-offset="integer" - set this number to trigger the scrolledToEnd callback n items before the last gets rendered | ||
76 | 72 | // vs-scrolled-to-beginning="callback" - callback will be called when the first item of the list is rendered | ||
77 | 73 | // vs-scrolled-to-beginning-offset="integer" - set this number to trigger the scrolledToBeginning callback n items before the first gets rendered | ||
78 | 74 | |||
79 | 75 | // EVENTS: | ||
80 | 76 | // - 'vsRepeatTrigger' - an event the directive listens for to manually trigger reinitialization | ||
81 | 77 | // - 'vsRepeatReinitialized' - an event the directive emits upon reinitialization done | ||
82 | 78 | |||
83 | 79 | var dde = document.documentElement, | ||
84 | 80 | matchingFunction = dde.matches ? 'matches' : | ||
85 | 81 | dde.matchesSelector ? 'matchesSelector' : | ||
86 | 82 | dde.webkitMatches ? 'webkitMatches' : | ||
87 | 83 | dde.webkitMatchesSelector ? 'webkitMatchesSelector' : | ||
88 | 84 | dde.msMatches ? 'msMatches' : | ||
89 | 85 | dde.msMatchesSelector ? 'msMatchesSelector' : | ||
90 | 86 | dde.mozMatches ? 'mozMatches' : | ||
91 | 87 | dde.mozMatchesSelector ? 'mozMatchesSelector' : null; | ||
92 | 88 | |||
93 | 89 | var closestElement = angular.element.prototype.closest || function (selector) { | ||
94 | 90 | var el = this[0].parentNode; | ||
95 | 91 | while (el !== document.documentElement && el != null && !el[matchingFunction](selector)) { | ||
96 | 92 | el = el.parentNode; | ||
97 | 93 | } | ||
98 | 94 | |||
99 | 95 | if (el && el[matchingFunction](selector)) { | ||
100 | 96 | return angular.element(el); | ||
101 | 97 | } | ||
102 | 98 | else { | ||
103 | 99 | return angular.element(); | ||
104 | 100 | } | ||
105 | 101 | }; | ||
106 | 102 | |||
107 | 103 | function getWindowScroll() { | ||
108 | 104 | if ('pageYOffset' in window) { | ||
109 | 105 | return { | ||
110 | 106 | scrollTop: pageYOffset, | ||
111 | 107 | scrollLeft: pageXOffset | ||
112 | 108 | }; | ||
113 | 109 | } | ||
114 | 110 | else { | ||
115 | 111 | var sx, sy, d = document, r = d.documentElement, b = d.body; | ||
116 | 112 | sx = r.scrollLeft || b.scrollLeft || 0; | ||
117 | 113 | sy = r.scrollTop || b.scrollTop || 0; | ||
118 | 114 | return { | ||
119 | 115 | scrollTop: sy, | ||
120 | 116 | scrollLeft: sx | ||
121 | 117 | }; | ||
122 | 118 | } | ||
123 | 119 | } | ||
124 | 120 | |||
125 | 121 | function getClientSize(element, sizeProp) { | ||
126 | 122 | if (element === window) { | ||
127 | 123 | return sizeProp === 'clientWidth' ? window.innerWidth : window.innerHeight; | ||
128 | 124 | } | ||
129 | 125 | else { | ||
130 | 126 | return element[sizeProp]; | ||
131 | 127 | } | ||
132 | 128 | } | ||
133 | 129 | |||
134 | 130 | function getScrollPos(element, scrollProp) { | ||
135 | 131 | return element === window ? getWindowScroll()[scrollProp] : element[scrollProp]; | ||
136 | 132 | } | ||
137 | 133 | |||
138 | 134 | function getScrollOffset(vsElement, scrollElement, isHorizontal) { | ||
139 | 135 | var vsPos = vsElement.getBoundingClientRect()[isHorizontal ? 'left' : 'top']; | ||
140 | 136 | var scrollPos = scrollElement === window ? 0 : scrollElement.getBoundingClientRect()[isHorizontal ? 'left' : 'top']; | ||
141 | 137 | var correction = vsPos - scrollPos + | ||
142 | 138 | (scrollElement === window ? getWindowScroll() : scrollElement)[isHorizontal ? 'scrollLeft' : 'scrollTop']; | ||
143 | 139 | |||
144 | 140 | return correction; | ||
145 | 141 | } | ||
146 | 142 | |||
147 | 143 | var vsRepeatModule = angular.module('vs-repeat', []).directive('vsRepeat', ['$compile', '$parse', function($compile, $parse) { | ||
148 | 144 | return { | ||
149 | 145 | restrict: 'A', | ||
150 | 146 | scope: true, | ||
151 | 147 | compile: function($element, $attrs) { | ||
152 | 148 | var repeatContainer = angular.isDefined($attrs.vsRepeatContainer) ? angular.element($element[0].querySelector($attrs.vsRepeatContainer)) : $element, | ||
153 | 149 | ngRepeatChild = repeatContainer.children().eq(0), | ||
154 | 150 | ngRepeatExpression, | ||
155 | 151 | childCloneHtml = ngRepeatChild[0].outerHTML, | ||
156 | 152 | expressionMatches, | ||
157 | 153 | lhs, | ||
158 | 154 | rhs, | ||
159 | 155 | rhsSuffix, | ||
160 | 156 | originalNgRepeatAttr, | ||
161 | 157 | collectionName = '$vs_collection', | ||
162 | 158 | isNgRepeatStart = false, | ||
163 | 159 | attributesDictionary = { | ||
164 | 160 | 'vsRepeat': 'elementSize', | ||
165 | 161 | 'vsOffsetBefore': 'offsetBefore', | ||
166 | 162 | 'vsOffsetAfter': 'offsetAfter', | ||
167 | 163 | 'vsScrolledToEndOffset': 'scrolledToEndOffset', | ||
168 | 164 | 'vsScrolledToBeginningOffset': 'scrolledToBeginningOffset', | ||
169 | 165 | 'vsExcess': 'excess' | ||
170 | 166 | }; | ||
171 | 167 | |||
172 | 168 | if (ngRepeatChild.attr('ng-repeat')) { | ||
173 | 169 | originalNgRepeatAttr = 'ng-repeat'; | ||
174 | 170 | ngRepeatExpression = ngRepeatChild.attr('ng-repeat'); | ||
175 | 171 | } | ||
176 | 172 | else if (ngRepeatChild.attr('data-ng-repeat')) { | ||
177 | 173 | originalNgRepeatAttr = 'data-ng-repeat'; | ||
178 | 174 | ngRepeatExpression = ngRepeatChild.attr('data-ng-repeat'); | ||
179 | 175 | } | ||
180 | 176 | else if (ngRepeatChild.attr('ng-repeat-start')) { | ||
181 | 177 | isNgRepeatStart = true; | ||
182 | 178 | originalNgRepeatAttr = 'ng-repeat-start'; | ||
183 | 179 | ngRepeatExpression = ngRepeatChild.attr('ng-repeat-start'); | ||
184 | 180 | } | ||
185 | 181 | else if (ngRepeatChild.attr('data-ng-repeat-start')) { | ||
186 | 182 | isNgRepeatStart = true; | ||
187 | 183 | originalNgRepeatAttr = 'data-ng-repeat-start'; | ||
188 | 184 | ngRepeatExpression = ngRepeatChild.attr('data-ng-repeat-start'); | ||
189 | 185 | } | ||
190 | 186 | else { | ||
191 | 187 | throw new Error('angular-vs-repeat: no ng-repeat directive on a child element'); | ||
192 | 188 | } | ||
193 | 189 | |||
194 | 190 | expressionMatches = /^\s*(\S+)\s+in\s+([\S\s]+?)(track\s+by\s+\S+)?$/.exec(ngRepeatExpression); | ||
195 | 191 | lhs = expressionMatches[1]; | ||
196 | 192 | rhs = expressionMatches[2]; | ||
197 | 193 | rhsSuffix = expressionMatches[3]; | ||
198 | 194 | |||
199 | 195 | if (isNgRepeatStart) { | ||
200 | 196 | var index = 0; | ||
201 | 197 | var repeaterElement = repeatContainer.children().eq(0); | ||
202 | 198 | while(repeaterElement.attr('ng-repeat-end') == null && repeaterElement.attr('data-ng-repeat-end') == null) { | ||
203 | 199 | index++; | ||
204 | 200 | repeaterElement = repeatContainer.children().eq(index); | ||
205 | 201 | childCloneHtml += repeaterElement[0].outerHTML; | ||
206 | 202 | } | ||
207 | 203 | } | ||
208 | 204 | |||
209 | 205 | repeatContainer.empty(); | ||
210 | 206 | return { | ||
211 | 207 | pre: function($scope, $element, $attrs) { | ||
212 | 208 | var repeatContainer = angular.isDefined($attrs.vsRepeatContainer) ? angular.element($element[0].querySelector($attrs.vsRepeatContainer)) : $element, | ||
213 | 209 | childClone = angular.element(childCloneHtml), | ||
214 | 210 | childTagName = childClone[0].tagName.toLowerCase(), | ||
215 | 211 | originalCollection = [], | ||
216 | 212 | originalLength, | ||
217 | 213 | $$horizontal = typeof $attrs.vsHorizontal !== 'undefined', | ||
218 | 214 | $beforeContent = angular.element('<' + childTagName + ' class="vs-repeat-before-content"></' + childTagName + '>'), | ||
219 | 215 | $afterContent = angular.element('<' + childTagName + ' class="vs-repeat-after-content"></' + childTagName + '>'), | ||
220 | 216 | autoSize = !$attrs.vsRepeat, | ||
221 | 217 | sizesPropertyExists = !!$attrs.vsSize || !!$attrs.vsSizeProperty, | ||
222 | 218 | $scrollParent = $attrs.vsScrollParent ? | ||
223 | 219 | $attrs.vsScrollParent === 'window' ? angular.element(window) : | ||
224 | 220 | closestElement.call(repeatContainer, $attrs.vsScrollParent) : repeatContainer, | ||
225 | 221 | $$options = 'vsOptions' in $attrs ? $scope.$eval($attrs.vsOptions) : {}, | ||
226 | 222 | clientSize = $$horizontal ? 'clientWidth' : 'clientHeight', | ||
227 | 223 | offsetSize = $$horizontal ? 'offsetWidth' : 'offsetHeight', | ||
228 | 224 | scrollPos = $$horizontal ? 'scrollLeft' : 'scrollTop'; | ||
229 | 225 | |||
230 | 226 | $scope.totalSize = 0; | ||
231 | 227 | if (!('vsSize' in $attrs) && 'vsSizeProperty' in $attrs) { | ||
232 | 228 | console.warn('vs-size-property attribute is deprecated. Please use vs-size attribute which also accepts angular expressions.'); | ||
233 | 229 | } | ||
234 | 230 | |||
235 | 231 | if ($scrollParent.length === 0) { | ||
236 | 232 | throw 'Specified scroll parent selector did not match any element'; | ||
237 | 233 | } | ||
238 | 234 | $scope.$scrollParent = $scrollParent; | ||
239 | 235 | |||
240 | 236 | if (sizesPropertyExists) { | ||
241 | 237 | $scope.sizesCumulative = []; | ||
242 | 238 | } | ||
243 | 239 | |||
244 | 240 | //initial defaults | ||
245 | 241 | $scope.elementSize = (+$attrs.vsRepeat) || getClientSize($scrollParent[0], clientSize) || 50; | ||
246 | 242 | $scope.offsetBefore = 0; | ||
247 | 243 | $scope.offsetAfter = 0; | ||
248 | 244 | $scope.excess = 2; | ||
249 | 245 | |||
250 | 246 | if ($$horizontal) { | ||
251 | 247 | $beforeContent.css('height', '100%'); | ||
252 | 248 | $afterContent.css('height', '100%'); | ||
253 | 249 | } | ||
254 | 250 | else { | ||
255 | 251 | $beforeContent.css('width', '100%'); | ||
256 | 252 | $afterContent.css('width', '100%'); | ||
257 | 253 | } | ||
258 | 254 | |||
259 | 255 | Object.keys(attributesDictionary).forEach(function(key) { | ||
260 | 256 | if ($attrs[key]) { | ||
261 | 257 | $attrs.$observe(key, function(value) { | ||
262 | 258 | // '+' serves for getting a number from the string as the attributes are always strings | ||
263 | 259 | $scope[attributesDictionary[key]] = +value; | ||
264 | 260 | reinitialize(); | ||
265 | 261 | }); | ||
266 | 262 | } | ||
267 | 263 | }); | ||
268 | 264 | |||
269 | 265 | |||
270 | 266 | $scope.$watchCollection(rhs, function(coll) { | ||
271 | 267 | originalCollection = coll || []; | ||
272 | 268 | refresh(); | ||
273 | 269 | }); | ||
274 | 270 | |||
275 | 271 | function refresh() { | ||
276 | 272 | if (!originalCollection || originalCollection.length < 1) { | ||
277 | 273 | $scope[collectionName] = []; | ||
278 | 274 | originalLength = 0; | ||
279 | 275 | $scope.sizesCumulative = [0]; | ||
280 | 276 | } | ||
281 | 277 | else { | ||
282 | 278 | originalLength = originalCollection.length; | ||
283 | 279 | if (sizesPropertyExists) { | ||
284 | 280 | $scope.sizes = originalCollection.map(function(item) { | ||
285 | 281 | var s = $scope.$new(false); | ||
286 | 282 | angular.extend(s, item); | ||
287 | 283 | s[lhs] = item; | ||
288 | 284 | var size = ($attrs.vsSize || $attrs.vsSizeProperty) ? | ||
289 | 285 | s.$eval($attrs.vsSize || $attrs.vsSizeProperty) : | ||
290 | 286 | $scope.elementSize; | ||
291 | 287 | s.$destroy(); | ||
292 | 288 | return size; | ||
293 | 289 | }); | ||
294 | 290 | var sum = 0; | ||
295 | 291 | $scope.sizesCumulative = $scope.sizes.map(function(size) { | ||
296 | 292 | var res = sum; | ||
297 | 293 | sum += size; | ||
298 | 294 | return res; | ||
299 | 295 | }); | ||
300 | 296 | $scope.sizesCumulative.push(sum); | ||
301 | 297 | } | ||
302 | 298 | else { | ||
303 | 299 | setAutoSize(); | ||
304 | 300 | } | ||
305 | 301 | } | ||
306 | 302 | |||
307 | 303 | reinitialize(); | ||
308 | 304 | } | ||
309 | 305 | |||
310 | 306 | function setAutoSize() { | ||
311 | 307 | if (autoSize) { | ||
312 | 308 | $scope.$$postDigest(function() { | ||
313 | 309 | if (repeatContainer[0].offsetHeight || repeatContainer[0].offsetWidth) { // element is visible | ||
314 | 310 | var children = repeatContainer.children(), | ||
315 | 311 | i = 0, | ||
316 | 312 | gotSomething = false, | ||
317 | 313 | insideStartEndSequence = false; | ||
318 | 314 | |||
319 | 315 | while (i < children.length) { | ||
320 | 316 | if (children[i].attributes[originalNgRepeatAttr] != null || insideStartEndSequence) { | ||
321 | 317 | if (!gotSomething) { | ||
322 | 318 | $scope.elementSize = 0; | ||
323 | 319 | } | ||
324 | 320 | |||
325 | 321 | gotSomething = true; | ||
326 | 322 | if (children[i][offsetSize]) { | ||
327 | 323 | $scope.elementSize += children[i][offsetSize]; | ||
328 | 324 | } | ||
329 | 325 | |||
330 | 326 | if (isNgRepeatStart) { | ||
331 | 327 | if (children[i].attributes['ng-repeat-end'] != null || children[i].attributes['data-ng-repeat-end'] != null) { | ||
332 | 328 | break; | ||
333 | 329 | } | ||
334 | 330 | else { | ||
335 | 331 | insideStartEndSequence = true; | ||
336 | 332 | } | ||
337 | 333 | } | ||
338 | 334 | else { | ||
339 | 335 | break; | ||
340 | 336 | } | ||
341 | 337 | } | ||
342 | 338 | i++; | ||
343 | 339 | } | ||
344 | 340 | |||
345 | 341 | if (gotSomething) { | ||
346 | 342 | reinitialize(); | ||
347 | 343 | autoSize = false; | ||
348 | 344 | if ($scope.$root && !$scope.$root.$$phase) { | ||
349 | 345 | $scope.$apply(); | ||
350 | 346 | } | ||
351 | 347 | } | ||
352 | 348 | } | ||
353 | 349 | else { | ||
354 | 350 | var dereg = $scope.$watch(function() { | ||
355 | 351 | if (repeatContainer[0].offsetHeight || repeatContainer[0].offsetWidth) { | ||
356 | 352 | dereg(); | ||
357 | 353 | setAutoSize(); | ||
358 | 354 | } | ||
359 | 355 | }); | ||
360 | 356 | } | ||
361 | 357 | }); | ||
362 | 358 | } | ||
363 | 359 | } | ||
364 | 360 | |||
365 | 361 | function getLayoutProp() { | ||
366 | 362 | var layoutPropPrefix = childTagName === 'tr' ? '' : 'min-'; | ||
367 | 363 | var layoutProp = $$horizontal ? layoutPropPrefix + 'width' : layoutPropPrefix + 'height'; | ||
368 | 364 | return layoutProp; | ||
369 | 365 | } | ||
370 | 366 | |||
371 | 367 | childClone.eq(0).attr(originalNgRepeatAttr, lhs + ' in ' + collectionName + (rhsSuffix ? ' ' + rhsSuffix : '')); | ||
372 | 368 | childClone.addClass('vs-repeat-repeated-element'); | ||
373 | 369 | |||
374 | 370 | repeatContainer.append($beforeContent); | ||
375 | 371 | repeatContainer.append(childClone); | ||
376 | 372 | $compile(childClone)($scope); | ||
377 | 373 | repeatContainer.append($afterContent); | ||
378 | 374 | |||
379 | 375 | $scope.startIndex = 0; | ||
380 | 376 | $scope.endIndex = 0; | ||
381 | 377 | |||
382 | 378 | function scrollHandler() { | ||
383 | 379 | if (updateInnerCollection()) { | ||
384 | 380 | $scope.$digest(); | ||
385 | 381 | } | ||
386 | 382 | } | ||
387 | 383 | |||
388 | 384 | $scrollParent.on('scroll', scrollHandler); | ||
389 | 385 | |||
390 | 386 | function onWindowResize() { | ||
391 | 387 | if (typeof $attrs.vsAutoresize !== 'undefined') { | ||
392 | 388 | autoSize = true; | ||
393 | 389 | setAutoSize(); | ||
394 | 390 | if ($scope.$root && !$scope.$root.$$phase) { | ||
395 | 391 | $scope.$apply(); | ||
396 | 392 | } | ||
397 | 393 | } | ||
398 | 394 | if (updateInnerCollection()) { | ||
399 | 395 | $scope.$apply(); | ||
400 | 396 | } | ||
401 | 397 | } | ||
402 | 398 | |||
403 | 399 | angular.element(window).on('resize', onWindowResize); | ||
404 | 400 | $scope.$on('$destroy', function() { | ||
405 | 401 | angular.element(window).off('resize', onWindowResize); | ||
406 | 402 | $scrollParent.off('scroll', scrollHandler); | ||
407 | 403 | }); | ||
408 | 404 | |||
409 | 405 | $scope.$on('vsRepeatTrigger', refresh); | ||
410 | 406 | |||
411 | 407 | $scope.$on('vsRepeatResize', function() { | ||
412 | 408 | autoSize = true; | ||
413 | 409 | setAutoSize(); | ||
414 | 410 | }); | ||
415 | 411 | |||
416 | 412 | var _prevStartIndex, | ||
417 | 413 | _prevEndIndex, | ||
418 | 414 | _minStartIndex, | ||
419 | 415 | _maxEndIndex; | ||
420 | 416 | |||
421 | 417 | $scope.$on('vsRenderAll', function() {//e , quantum) { | ||
422 | 418 | if($$options.latch) { | ||
423 | 419 | setTimeout(function() { | ||
424 | 420 | // var __endIndex = Math.min($scope.endIndex + (quantum || 1), originalLength); | ||
425 | 421 | var __endIndex = originalLength; | ||
426 | 422 | _maxEndIndex = Math.max(__endIndex, _maxEndIndex); | ||
427 | 423 | $scope.endIndex = $$options.latch ? _maxEndIndex : __endIndex; | ||
428 | 424 | $scope[collectionName] = originalCollection.slice($scope.startIndex, $scope.endIndex); | ||
429 | 425 | _prevEndIndex = $scope.endIndex; | ||
430 | 426 | |||
431 | 427 | $scope.$$postDigest(function() { | ||
432 | 428 | $beforeContent.css(getLayoutProp(), 0); | ||
433 | 429 | $afterContent.css(getLayoutProp(), 0); | ||
434 | 430 | }); | ||
435 | 431 | |||
436 | 432 | $scope.$apply(function() { | ||
437 | 433 | $scope.$emit('vsRenderAllDone'); | ||
438 | 434 | }); | ||
439 | 435 | }); | ||
440 | 436 | } | ||
441 | 437 | }); | ||
442 | 438 | |||
443 | 439 | function reinitialize() { | ||
444 | 440 | _prevStartIndex = void 0; | ||
445 | 441 | _prevEndIndex = void 0; | ||
446 | 442 | _minStartIndex = originalLength; | ||
447 | 443 | _maxEndIndex = 0; | ||
448 | 444 | updateTotalSize(sizesPropertyExists ? | ||
449 | 445 | $scope.sizesCumulative[originalLength] : | ||
450 | 446 | $scope.elementSize * originalLength | ||
451 | 447 | ); | ||
452 | 448 | updateInnerCollection(); | ||
453 | 449 | |||
454 | 450 | $scope.$emit('vsRepeatReinitialized', $scope.startIndex, $scope.endIndex); | ||
455 | 451 | } | ||
456 | 452 | |||
457 | 453 | function updateTotalSize(size) { | ||
458 | 454 | $scope.totalSize = $scope.offsetBefore + size + $scope.offsetAfter; | ||
459 | 455 | } | ||
460 | 456 | |||
461 | 457 | var _prevClientSize; | ||
462 | 458 | function reinitOnClientHeightChange() { | ||
463 | 459 | var ch = getClientSize($scrollParent[0], clientSize); | ||
464 | 460 | if (ch !== _prevClientSize) { | ||
465 | 461 | reinitialize(); | ||
466 | 462 | if ($scope.$root && !$scope.$root.$$phase) { | ||
467 | 463 | $scope.$apply(); | ||
468 | 464 | } | ||
469 | 465 | } | ||
470 | 466 | _prevClientSize = ch; | ||
471 | 467 | } | ||
472 | 468 | |||
473 | 469 | $scope.$watch(function() { | ||
474 | 470 | if (typeof window.requestAnimationFrame === 'function') { | ||
475 | 471 | window.requestAnimationFrame(reinitOnClientHeightChange); | ||
476 | 472 | } | ||
477 | 473 | else { | ||
478 | 474 | reinitOnClientHeightChange(); | ||
479 | 475 | } | ||
480 | 476 | }); | ||
481 | 477 | |||
482 | 478 | function updateInnerCollection() { | ||
483 | 479 | var $scrollPosition = getScrollPos($scrollParent[0], scrollPos); | ||
484 | 480 | var $clientSize = getClientSize($scrollParent[0], clientSize); | ||
485 | 481 | |||
486 | 482 | var scrollOffset = repeatContainer[0] === $scrollParent[0] ? 0 : getScrollOffset( | ||
487 | 483 | repeatContainer[0], | ||
488 | 484 | $scrollParent[0], | ||
489 | 485 | $$horizontal | ||
490 | 486 | ); | ||
491 | 487 | |||
492 | 488 | var __startIndex = $scope.startIndex; | ||
493 | 489 | var __endIndex = $scope.endIndex; | ||
494 | 490 | |||
495 | 491 | if (sizesPropertyExists) { | ||
496 | 492 | __startIndex = 0; | ||
497 | 493 | while ($scope.sizesCumulative[__startIndex] < $scrollPosition - $scope.offsetBefore - scrollOffset) { | ||
498 | 494 | __startIndex++; | ||
499 | 495 | } | ||
500 | 496 | if (__startIndex > 0) { __startIndex--; } | ||
501 | 497 | |||
502 | 498 | // Adjust the start index according to the excess | ||
503 | 499 | __startIndex = Math.max( | ||
504 | 500 | Math.floor(__startIndex - $scope.excess / 2), | ||
505 | 501 | 0 | ||
506 | 502 | ); | ||
507 | 503 | |||
508 | 504 | __endIndex = __startIndex; | ||
509 | 505 | while ($scope.sizesCumulative[__endIndex] < $scrollPosition - $scope.offsetBefore - scrollOffset + $clientSize) { | ||
510 | 506 | __endIndex++; | ||
511 | 507 | } | ||
512 | 508 | |||
513 | 509 | // Adjust the end index according to the excess | ||
514 | 510 | __endIndex = Math.min( | ||
515 | 511 | Math.ceil(__endIndex + $scope.excess / 2), | ||
516 | 512 | originalLength | ||
517 | 513 | ); | ||
518 | 514 | } | ||
519 | 515 | else { | ||
520 | 516 | __startIndex = Math.max( | ||
521 | 517 | Math.floor( | ||
522 | 518 | ($scrollPosition - $scope.offsetBefore - scrollOffset) / $scope.elementSize | ||
523 | 519 | ) - $scope.excess / 2, | ||
524 | 520 | 0 | ||
525 | 521 | ); | ||
526 | 522 | |||
527 | 523 | __endIndex = Math.min( | ||
528 | 524 | __startIndex + Math.ceil( | ||
529 | 525 | $clientSize / $scope.elementSize | ||
530 | 526 | ) + $scope.excess, | ||
531 | 527 | originalLength | ||
532 | 528 | ); | ||
533 | 529 | } | ||
534 | 530 | |||
535 | 531 | _minStartIndex = Math.min(__startIndex, _minStartIndex); | ||
536 | 532 | _maxEndIndex = Math.max(__endIndex, _maxEndIndex); | ||
537 | 533 | |||
538 | 534 | $scope.startIndex = $$options.latch ? _minStartIndex : __startIndex; | ||
539 | 535 | $scope.endIndex = $$options.latch ? _maxEndIndex : __endIndex; | ||
540 | 536 | |||
541 | 537 | var digestRequired = false; | ||
542 | 538 | if (_prevStartIndex == null) { | ||
543 | 539 | digestRequired = true; | ||
544 | 540 | } | ||
545 | 541 | else if (_prevEndIndex == null) { | ||
546 | 542 | digestRequired = true; | ||
547 | 543 | } | ||
548 | 544 | |||
549 | 545 | if (!digestRequired) { | ||
550 | 546 | if ($$options.hunked) { | ||
551 | 547 | if (Math.abs($scope.startIndex - _prevStartIndex) >= $scope.excess / 2 || | ||
552 | 548 | ($scope.startIndex === 0 && _prevStartIndex !== 0)) { | ||
553 | 549 | digestRequired = true; | ||
554 | 550 | } | ||
555 | 551 | else if (Math.abs($scope.endIndex - _prevEndIndex) >= $scope.excess / 2 || | ||
556 | 552 | ($scope.endIndex === originalLength && _prevEndIndex !== originalLength)) { | ||
557 | 553 | digestRequired = true; | ||
558 | 554 | } | ||
559 | 555 | } | ||
560 | 556 | else { | ||
561 | 557 | digestRequired = $scope.startIndex !== _prevStartIndex || | ||
562 | 558 | $scope.endIndex !== _prevEndIndex; | ||
563 | 559 | } | ||
564 | 560 | } | ||
565 | 561 | |||
566 | 562 | if (digestRequired) { | ||
567 | 563 | $scope[collectionName] = originalCollection.slice($scope.startIndex, $scope.endIndex); | ||
568 | 564 | |||
569 | 565 | // Emit the event | ||
570 | 566 | $scope.$emit('vsRepeatInnerCollectionUpdated', $scope.startIndex, $scope.endIndex, _prevStartIndex, _prevEndIndex); | ||
571 | 567 | var triggerIndex; | ||
572 | 568 | if ($attrs.vsScrolledToEnd) { | ||
573 | 569 | triggerIndex = originalCollection.length - ($scope.scrolledToEndOffset || 0); | ||
574 | 570 | if (($scope.endIndex >= triggerIndex && _prevEndIndex < triggerIndex) || (originalCollection.length && $scope.endIndex === originalCollection.length)) { | ||
575 | 571 | $scope.$eval($attrs.vsScrolledToEnd); | ||
576 | 572 | } | ||
577 | 573 | } | ||
578 | 574 | if ($attrs.vsScrolledToBeginning) { | ||
579 | 575 | triggerIndex = $scope.scrolledToBeginningOffset || 0; | ||
580 | 576 | if (($scope.startIndex <= triggerIndex && _prevStartIndex > $scope.startIndex)) { | ||
581 | 577 | $scope.$eval($attrs.vsScrolledToBeginning); | ||
582 | 578 | } | ||
583 | 579 | } | ||
584 | 580 | |||
585 | 581 | _prevStartIndex = $scope.startIndex; | ||
586 | 582 | _prevEndIndex = $scope.endIndex; | ||
587 | 583 | |||
588 | 584 | var offsetCalculationString = sizesPropertyExists ? | ||
589 | 585 | '(sizesCumulative[$index + startIndex] + offsetBefore)' : | ||
590 | 586 | '(($index + startIndex) * elementSize + offsetBefore)'; | ||
591 | 587 | |||
592 | 588 | var parsed = $parse(offsetCalculationString); | ||
593 | 589 | var o1 = parsed($scope, {$index: 0}); | ||
594 | 590 | var o2 = parsed($scope, {$index: $scope[collectionName].length}); | ||
595 | 591 | var total = $scope.totalSize; | ||
596 | 592 | |||
597 | 593 | $beforeContent.css(getLayoutProp(), o1 + 'px'); | ||
598 | 594 | $afterContent.css(getLayoutProp(), (total - o2) + 'px'); | ||
599 | 595 | } | ||
600 | 596 | |||
601 | 597 | return digestRequired; | ||
602 | 598 | } | ||
603 | 599 | } | ||
604 | 600 | }; | ||
605 | 601 | } | ||
606 | 602 | }; | ||
607 | 603 | }]); | ||
608 | 604 | |||
609 | 605 | if (typeof module !== 'undefined' && module.exports) { | ||
610 | 606 | module.exports = vsRepeatModule.name; | ||
611 | 607 | } | ||
612 | 608 | })(window, window.angular); | ||
613 | 0 | 609 | ||
614 | === modified file 'src/maasserver/static/js/angular/maas.js' | |||
615 | --- src/maasserver/static/js/angular/maas.js 2017-03-10 18:38:46 +0000 | |||
616 | +++ src/maasserver/static/js/angular/maas.js 2017-06-15 11:16:28 +0000 | |||
617 | @@ -9,7 +9,8 @@ | |||
618 | 9 | */ | 9 | */ |
619 | 10 | 10 | ||
620 | 11 | angular.module('MAAS', | 11 | angular.module('MAAS', |
622 | 12 | ['ngRoute', 'ngCookies', 'ngSanitize', 'ngTagsInput', 'sticky']).config( | 12 | ['ngRoute', 'ngCookies', 'ngSanitize', 'ngTagsInput', 'sticky', |
623 | 13 | 'vs-repeat']).config( | ||
624 | 13 | function($interpolateProvider, $routeProvider, $httpProvider) { | 14 | function($interpolateProvider, $routeProvider, $httpProvider) { |
625 | 14 | $interpolateProvider.startSymbol('{$'); | 15 | $interpolateProvider.startSymbol('{$'); |
626 | 15 | $interpolateProvider.endSymbol('$}'); | 16 | $interpolateProvider.endSymbol('$}'); |
627 | 16 | 17 | ||
628 | === modified file 'src/maasserver/static/partials/dashboard.html' | |||
629 | --- src/maasserver/static/partials/dashboard.html 2017-04-18 10:57:48 +0000 | |||
630 | +++ src/maasserver/static/partials/dashboard.html 2017-06-15 11:16:28 +0000 | |||
631 | @@ -46,7 +46,8 @@ | |||
632 | 46 | No new discoveries | 46 | No new discoveries |
633 | 47 | </div> | 47 | </div> |
634 | 48 | </div> | 48 | </div> |
636 | 49 | <div class="table__row" | 49 | <div vs-repeat vs-scroll-parent="window"> |
637 | 50 | <div class="table__row" | ||
638 | 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" |
639 | 51 | data-ng-class="{'is-active' : discovery.first_seen === selectedDevice}"> | 52 | data-ng-class="{'is-active' : discovery.first_seen === selectedDevice}"> |
640 | 52 | <div data-ng-if="discovery.first_seen !== selectedDevice" | 53 | <div data-ng-if="discovery.first_seen !== selectedDevice" |
641 | @@ -179,6 +180,7 @@ | |||
642 | 179 | </div> | 180 | </div> |
643 | 180 | </maas-obj-form> | 181 | </maas-obj-form> |
644 | 181 | </div> | 182 | </div> |
645 | 183 | </div> | ||
646 | 182 | </div> | 184 | </div> |
647 | 183 | </div> | 185 | </div> |
648 | 184 | </div> | 186 | </div> |
649 | 185 | 187 | ||
650 | === modified file 'src/maasserver/static/partials/machines-table.html' | |||
651 | --- src/maasserver/static/partials/machines-table.html 2017-04-18 11:31:25 +0000 | |||
652 | +++ src/maasserver/static/partials/machines-table.html 2017-06-15 11:16:28 +0000 | |||
653 | @@ -39,7 +39,7 @@ | |||
654 | 39 | </th> | 39 | </th> |
655 | 40 | </tr> | 40 | </tr> |
656 | 41 | </thead> | 41 | </thead> |
658 | 42 | <tbody> | 42 | <tbody vs-repeat vs-scroll-parent="window"> |
659 | 43 | <tr | 43 | <tr |
660 | 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" |
661 | 45 | data-ng-class="{ 'table--error': machineHasError({ $machine: node }), selected: node.$selected }"> | 45 | data-ng-class="{ 'table--error': machineHasError({ $machine: node }), selected: node.$selected }"> |
662 | 46 | 46 | ||
663 | === modified file 'src/maasserver/static/partials/networks-list.html' | |||
664 | --- src/maasserver/static/partials/networks-list.html 2017-04-10 15:35:18 +0000 | |||
665 | +++ src/maasserver/static/partials/networks-list.html 2017-06-15 11:16:28 +0000 | |||
666 | @@ -147,7 +147,7 @@ | |||
667 | 147 | <th class="table__column--13 align-right">Available IPs</th> | 147 | <th class="table__column--13 align-right">Available IPs</th> |
668 | 148 | </tr> | 148 | </tr> |
669 | 149 | </thead> | 149 | </thead> |
671 | 150 | <tbody> | 150 | <tbody vs-repeat vs-scroll-parent="window"> |
672 | 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"> |
673 | 152 | <!-- <td class="table-listing__cell table__column--3 ng-hide"> | 152 | <!-- <td class="table-listing__cell table__column--3 ng-hide"> |
674 | 153 | <div data-ng-if="row.space_name"> | 153 | <div data-ng-if="row.space_name"> |
675 | @@ -207,7 +207,7 @@ | |||
676 | 207 | <th class="table__column--25">Space</th> | 207 | <th class="table__column--25">Space</th> |
677 | 208 | </tr> | 208 | </tr> |
678 | 209 | </thead> | 209 | </thead> |
680 | 210 | <tbody> | 210 | <tbody vs-repeat vs-scroll-parent="window"> |
681 | 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"> |
682 | 212 | <!-- <td class="table-listing__cell table__column--3 ng-hide"> | 212 | <!-- <td class="table-listing__cell table__column--3 ng-hide"> |
683 | 213 | <div data-ng-if="row.fabric_name"> | 213 | <div data-ng-if="row.fabric_name"> |
684 | 214 | 214 | ||
685 | === modified file 'src/maasserver/static/partials/node-events.html' | |||
686 | --- src/maasserver/static/partials/node-events.html 2017-04-05 02:36:06 +0000 | |||
687 | +++ src/maasserver/static/partials/node-events.html 2017-06-15 11:16:28 +0000 | |||
688 | @@ -35,7 +35,7 @@ | |||
689 | 35 | <th class="table-col--20">Time</th> | 35 | <th class="table-col--20">Time</th> |
690 | 36 | </tr> | 36 | </tr> |
691 | 37 | </thead> | 37 | </thead> |
693 | 38 | <tbody> | 38 | <tbody vs-repeat vs-scroll-parent="window"> |
694 | 39 | <tr | 39 | <tr |
695 | 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"> |
696 | 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"> |
697 | 42 | 42 | ||
698 | === modified file 'src/maasserver/static/partials/nodes-list.html' | |||
699 | --- src/maasserver/static/partials/nodes-list.html 2017-05-25 13:56:07 +0000 | |||
700 | +++ src/maasserver/static/partials/nodes-list.html 2017-06-15 11:16:28 +0000 | |||
701 | @@ -452,7 +452,7 @@ | |||
702 | 452 | <th class="table-col--5"></th> | 452 | <th class="table-col--5"></th> |
703 | 453 | </tr> | 453 | </tr> |
704 | 454 | </thead> | 454 | </thead> |
706 | 455 | <tbody> | 455 | <tbody vs-repeat vs-scroll-parent="window"> |
707 | 456 | <tr data-ng-repeat="interface in device.interfaces"> | 456 | <tr data-ng-repeat="interface in device.interfaces"> |
708 | 457 | <td class="table-col--20" aria-label="MAC address"> | 457 | <td class="table-col--20" aria-label="MAC address"> |
709 | 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" |
710 | @@ -857,7 +857,7 @@ | |||
711 | 857 | </th> | 857 | </th> |
712 | 858 | </tr> | 858 | </tr> |
713 | 859 | </thead> | 859 | </thead> |
715 | 860 | <tbody> | 860 | <tbody vs-repeat vs-scroll-parent="window"> |
716 | 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. --> |
717 | 862 | <tr | 862 | <tr |
718 | 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" |
719 | @@ -921,7 +921,7 @@ | |||
720 | 921 | </th> | 921 | </th> |
721 | 922 | </tr> | 922 | </tr> |
722 | 923 | </thead> | 923 | </thead> |
724 | 924 | <tbody> | 924 | <tbody vs-repeat vs-scroll-parent="window"> |
725 | 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. --> |
726 | 926 | <tr | 926 | <tr |
727 | 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" |
728 | 928 | 928 | ||
729 | === modified file 'src/maasserver/static/partials/pods-list.html' | |||
730 | --- src/maasserver/static/partials/pods-list.html 2017-05-01 20:31:46 +0000 | |||
731 | +++ src/maasserver/static/partials/pods-list.html 2017-06-15 11:16:28 +0000 | |||
732 | @@ -123,7 +123,7 @@ | |||
733 | 123 | </div> | 123 | </div> |
734 | 124 | </div> | 124 | </div> |
735 | 125 | </header> | 125 | </header> |
737 | 126 | <div class="table__body"> | 126 | <div class="table__body" vs-repeat vs-scroll-parent="window"> |
738 | 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" |
739 | 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 }"> |
740 | 129 | <div data-ng-if="isSuperUser()"> | 129 | <div data-ng-if="isSuperUser()"> |
741 | 130 | 130 | ||
742 | === modified file 'src/maasserver/templates/maasserver/js-conf.html' | |||
743 | --- src/maasserver/templates/maasserver/js-conf.html 2017-03-10 15:30:26 +0000 | |||
744 | +++ src/maasserver/templates/maasserver/js-conf.html 2017-06-15 11:16:28 +0000 | |||
745 | @@ -42,6 +42,9 @@ | |||
746 | 42 | src="{% url "merge" filename="sticky.js" %}?v={{files_version}}"> | 42 | src="{% url "merge" filename="sticky.js" %}?v={{files_version}}"> |
747 | 43 | </script> | 43 | </script> |
748 | 44 | <script type="text/javascript" | 44 | <script type="text/javascript" |
749 | 45 | src="{% url "merge" filename="vs-repeat.js" %}?v={{files_version}}"> | ||
750 | 46 | </script> | ||
751 | 47 | <script type="text/javascript" | ||
752 | 45 | src="{% url "merge" filename="maas-angular.js" %}?v={{files_version}}"> | 48 | src="{% url "merge" filename="maas-angular.js" %}?v={{files_version}}"> |
753 | 46 | </script> | 49 | </script> |
754 | 47 | 50 | ||
755 | 48 | 51 | ||
756 | === modified file 'src/maasserver/views/combo.py' | |||
757 | --- src/maasserver/views/combo.py 2017-05-25 13:56:07 +0000 | |||
758 | +++ src/maasserver/views/combo.py 2017-06-15 11:16:28 +0000 | |||
759 | @@ -53,6 +53,12 @@ | |||
760 | 53 | "js/angular/3rdparty/sticky.js", | 53 | "js/angular/3rdparty/sticky.js", |
761 | 54 | ] | 54 | ] |
762 | 55 | }, | 55 | }, |
763 | 56 | "vs-repeat.js": { | ||
764 | 57 | "content_type": "text/javascript; charset=UTF-8", | ||
765 | 58 | "files": [ | ||
766 | 59 | "js/angular/3rdparty/vs-repeat.js", | ||
767 | 60 | ] | ||
768 | 61 | }, | ||
769 | 56 | "maas-angular.js": { | 62 | "maas-angular.js": { |
770 | 57 | "content_type": "text/javascript; charset=UTF-8", | 63 | "content_type": "text/javascript; charset=UTF-8", |
771 | 58 | "files": [ | 64 | "files": [ |
This is awesome! Thanks for doing it.