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