Merge ~deadlight/maas:sticky-header-height-fix into maas:master

Proposed by Karl Williams
Status: Merged
Approved by: Andres Rodriguez
Approved revision: 0fb98a7194f35db8b44d808b475b930f1e519fba
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~deadlight/maas:sticky-header-height-fix
Merge into: maas:master
Diff against target: 1418 lines (+3/-688)
4 files modified
dev/null (+0/-684)
src/maasserver/static/js/angular/maas.js (+1/-2)
src/maasserver/static/partials/node-details.html (+1/-1)
src/maasserver/static/partials/node-events.html (+1/-1)
Reviewer Review Type Date Requested Status
Andres Rodriguez (community) Approve
Review via email: mp+340851@code.launchpad.net

Commit message

Remove sticky header javascript

Removed instances of the sticky javascript and the source files

Description of the change

Remove sticky header javascript

Removed instances of the sticky javascript and the source files

QA:
 - Go to a machine details page
 - Select an action in the header
 - Scroll the page and see that the header background is the correct size/colour

To post a comment you must log in.
Revision history for this message
Andres Rodriguez (andreserl) wrote :

lgtm!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/src/maasserver/static/js/angular/3rdparty/sticky.js b/src/maasserver/static/js/angular/3rdparty/sticky.js
0deleted file mode 1006440deleted file mode 100644
index 977c232..0000000
--- a/src/maasserver/static/js/angular/3rdparty/sticky.js
+++ /dev/null
@@ -1,682 +0,0 @@
1/**
2 * ngSticky - https://github.com/d-oliveros/ngSticky
3 *
4 * A simple, pure javascript (No jQuery required!) AngularJS directive
5 * to make elements stick when scrolling down.
6 *
7 * Credits: https://github.com/d-oliveros/ngSticky/graphs/contributors
8 */
9(function() {
10 'use strict';
11
12 var module = angular.module('sticky', []);
13
14 /**
15 * Directive: sticky
16 */
17 module.directive('sticky', ['$window', '$timeout', function($window, $timeout) {
18 return {
19 restrict: 'A', // this directive can only be used as an attribute.
20 scope: {
21 disabled: '=disabledSticky'
22 },
23 link: function linkFn($scope, $elem, $attrs) {
24
25 // Initial scope
26 var scrollableNodeTagName = 'sticky-scroll';
27 var initialPosition = $elem.css('position');
28 var initialStyle = $elem.attr('style') || '';
29 var stickyBottomLine = 0;
30 var isSticking = false;
31 var onStickyHeighUnbind;
32 var originalInitialCSS;
33 var originalOffset;
34 var placeholder;
35 var stickyLine;
36 var initialCSS;
37
38 // Optional Classes
39 var stickyClass = $attrs.stickyClass || '';
40 var unstickyClass = $attrs.unstickyClass || '';
41 var bodyClass = $attrs.bodyClass || '';
42 var bottomClass = $attrs.bottomClass || '';
43
44 // Find scrollbar
45 var scrollbar = deriveScrollingViewport ($elem);
46
47 // Define elements
48 var windowElement = angular.element($window);
49 var scrollbarElement = angular.element(scrollbar);
50 var $body = angular.element(document.body);
51
52 // Resize callback
53 var $onResize = function () {
54 if ($scope.$root && !$scope.$root.$$phase) {
55 $scope.$apply(onResize);
56 } else {
57 onResize();
58 }
59 };
60
61 // Define options
62 var usePlaceholder = ($attrs.usePlaceholder !== 'false');
63 var anchor = $attrs.anchor === 'bottom' ? 'bottom' : 'top';
64 var confine = ($attrs.confine === 'true');
65
66 // flag: can react to recalculating the initial CSS dimensions later
67 // as link executes prematurely. defaults to immediate checking
68 var isStickyLayoutDeferred = $attrs.isStickyLayoutDeferred !== undefined
69 ? ($attrs.isStickyLayoutDeferred === 'true')
70 : false;
71
72 // flag: is sticky content constantly observed for changes.
73 // Should be true if content uses ngBind to show text
74 // that may vary in size over time
75 var isStickyLayoutWatched = $attrs.isStickyLayoutWatched !== undefined
76 ? ($attrs.isStickyLayoutWatched === 'true')
77 : true;
78
79
80 var offset = $attrs.offset
81 ? parseInt ($attrs.offset.replace(/px;?/, ''))
82 : 0;
83
84 /**
85 * Trigger to initialize the sticky
86 * Because of the `timeout()` method for the call of
87 * @type {Boolean}
88 */
89 var shouldInitialize = true;
90
91 /**
92 * Initialize Sticky
93 */
94 function initSticky() {
95
96 if (shouldInitialize) {
97
98 // Listeners
99 scrollbarElement.on('scroll', checkIfShouldStick);
100 windowElement.on('resize', $onResize);
101
102 memorizeDimensions(); // remember sticky's layout dimensions
103
104 // Setup watcher on digest and change
105 $scope.$watch(onDigest, onChange);
106
107 // Clean up
108 $scope.$on('$destroy', onDestroy);
109 shouldInitialize = false;
110 }
111 };
112
113 /**
114 * need to recall sticky's DOM attributes (make sure layout has occured)
115 */
116 function memorizeDimensions() {
117 // immediate assignment, but there is the potential for wrong values if content not ready
118 initialCSS = $scope.getInitialDimensions();
119
120 // option to calculate the dimensions when layout is 'ready'
121 if (isStickyLayoutDeferred) {
122
123 // logic: when this directive link() runs before the content has had a chance to layout on browser, height could be 0
124 if (!$elem[0].getBoundingClientRect().height) {
125
126 onStickyHeighUnbind = $scope.$watch(
127 function() {
128 return $elem.height();
129 },
130
131 // state change: sticky content's height set
132 function onStickyContentLayoutInitialHeightSet(newValue, oldValue) {
133 if (newValue > 0) {
134 // now can memorize
135 initialCSS = $scope.getInitialDimensions();
136
137 if (!isStickyLayoutWatched) {
138 // preference was to do just a one-time async watch on the sticky's content; now stop watching
139 onStickyHeighUnbind();
140 }
141 }
142 }
143 );
144 }
145 }
146 }
147
148 /**
149 * Determine if the element should be sticking or not.
150 */
151 var checkIfShouldStick = function() {
152 if ($scope.disabled === true || mediaQueryMatches()) {
153 if (isSticking) unStickElement();
154 return false;
155 }
156
157 // What's the document client top for?
158 var scrollbarPosition = scrollbarYPos();
159 var shouldStick;
160
161 if (anchor === 'top') {
162 if (confine === true) {
163 shouldStick = scrollbarPosition > stickyLine && scrollbarPosition <= stickyBottomLine;
164 } else {
165 shouldStick = scrollbarPosition > stickyLine;
166 }
167 } else {
168 shouldStick = scrollbarPosition <= stickyLine;
169 }
170
171 // Switch the sticky mode if the element crosses the sticky line
172 // $attrs.stickLimit - when it's equal to true it enables the user
173 // to turn off the sticky function when the elem height is
174 // bigger then the viewport
175 var closestLine = getClosest (scrollbarPosition, stickyLine, stickyBottomLine);
176
177 if (shouldStick && !shouldStickWithLimit ($attrs.stickLimit) && !isSticking) {
178 stickElement (closestLine);
179 } else if (!shouldStick && isSticking) {
180 unStickElement(closestLine, scrollbarPosition);
181 } else if (confine && !shouldStick) {
182 // If we are confined to the parent, refresh, and past the stickyBottomLine
183 // We should 'remember' the original offset and unstick the element which places it at the stickyBottomLine
184 originalOffset = elementsOffsetFromTop ($elem[0]);
185 unStickElement (closestLine, scrollbarPosition);
186 }
187 };
188
189 /**
190 * determine the respective node that handles scrolling, defaulting to browser window
191 */
192 function deriveScrollingViewport(stickyNode) {
193 // derive relevant scrolling by ascending the DOM tree
194 var match =findAncestorTag (scrollableNodeTagName, stickyNode);
195 return (match.length === 1) ? match[0] : $window;
196 }
197
198 /**
199 * since jqLite lacks closest(), this is a pseudo emulator (by tag name)
200 */
201 function findAncestorTag(tag, context) {
202 var m = []; // nodelist container
203 var n = context.parent(); // starting point
204 var p;
205
206 do {
207 var node = n[0]; // break out of jqLite
208 // limit DOM territory
209 if (node.nodeType !== 1) {
210 break;
211 }
212
213 // success
214 if (node.tagName.toUpperCase() === tag.toUpperCase()) {
215 return n;
216 }
217
218 p = n.parent();
219 n = p; // set to parent
220 } while (p.length !== 0);
221
222 return m; // empty set
223 }
224
225 /**
226 * Seems to be undocumented functionality
227 */
228 function shouldStickWithLimit(shouldApplyWithLimit) {
229 return shouldApplyWithLimit === 'true'
230 ? ($window.innerHeight - ($elem[0].offsetHeight + parseInt(offset)) < 0)
231 : false;
232 }
233
234 /**
235 * Finds the closest value from a set of numbers in an array.
236 */
237 function getClosest(scrollTop, stickyLine, stickyBottomLine) {
238 var closest = 'top';
239 var topDistance = Math.abs(scrollTop - stickyLine);
240 var bottomDistance = Math.abs(scrollTop - stickyBottomLine);
241
242 if (topDistance > bottomDistance) {
243 closest = 'bottom';
244 }
245
246 return closest;
247 }
248
249 /**
250 * Unsticks the element
251 */
252 function unStickElement(fromDirection) {
253 if (initialStyle) {
254 $elem.attr('style', initialStyle);
255 }
256 isSticking = false;
257
258 initialCSS.width = $scope.getInitialDimensions().width;
259
260 $body.removeClass(bodyClass);
261 $elem.removeClass(stickyClass);
262 $elem.addClass(unstickyClass);
263
264 if (fromDirection === 'top') {
265 $elem.removeClass(bottomClass);
266
267 $elem
268 .css('z-index', 10)
269 .css('width', initialCSS.width)
270 .css('top', initialCSS.top)
271 .css('position', initialCSS.position)
272 .css('left', initialCSS.cssLeft)
273 .css('margin-top', initialCSS.marginTop);
274 } else if (fromDirection === 'bottom' && confine === true) {
275 $elem.addClass(bottomClass);
276
277 // It's possible to page down page and skip the 'stickElement'.
278 // In that case we should create a placeholder so the offsets don't get off.
279 createPlaceholder();
280
281 $elem
282 .css('z-index', 10)
283 .css('width', initialCSS.width)
284 .css('top', '')
285 .css('bottom', 0)
286 .css('position', 'absolute')
287 .css('left', initialCSS.cssLeft)
288 .css('margin-top', initialCSS.marginTop)
289 .css('margin-bottom', initialCSS.marginBottom);
290 }
291
292 if (placeholder && fromDirection === anchor) {
293 placeholder.remove();
294 }
295 }
296
297 /**
298 * Sticks the element
299 */
300 function stickElement(closestLine) {
301 // Set sticky state
302 isSticking = true;
303 $timeout(function() {
304 initialCSS.offsetWidth = $elem[0].offsetWidth;
305 }, 0);
306 $body.addClass(bodyClass);
307 $elem.removeClass(unstickyClass);
308 $elem.removeClass(bottomClass);
309 $elem.addClass(stickyClass);
310
311 createPlaceholder();
312
313 $elem
314 .css('z-index', '10')
315 .css('width', $elem[0].offsetWidth + 'px')
316 .css('position', 'fixed')
317 .css('left', $elem.css('left').replace('px', '') + 'px')
318 .css(anchor, (offset + elementsOffsetFromTop (scrollbar)) + 'px')
319 .css('margin-top', 0);
320
321 if (anchor === 'bottom') {
322 $elem.css('margin-bottom', 0);
323 }
324 }
325
326 /**
327 * Clean up directive
328 */
329 var onDestroy = function() {
330 scrollbarElement.off('scroll', checkIfShouldStick);
331 windowElement.off('resize', $onResize);
332
333 $onResize = null;
334
335 $body.removeClass(bodyClass);
336
337 if (placeholder) {
338 placeholder.remove();
339 }
340 };
341
342 /**
343 * Updates on resize.
344 */
345 function onResize() {
346 unStickElement (anchor);
347 checkIfShouldStick();
348 }
349
350 /**
351 * Triggered on load / digest cycle
352 * return `0` if the DOM element is hidden
353 */
354 var onDigest = function() {
355 if ($scope.disabled === true) {
356 return unStickElement();
357 }
358 var offsetFromTop = elementsOffsetFromTop ($elem[0]);
359 if (offsetFromTop === 0) {
360 return offsetFromTop;
361 }
362 if (anchor === 'top') {
363 return (originalOffset || offsetFromTop) - elementsOffsetFromTop (scrollbar) + scrollbarYPos();
364 } else {
365 return offsetFromTop - scrollbarHeight() + $elem[0].offsetHeight + scrollbarYPos();
366 }
367 };
368
369 /**
370 * Triggered on change
371 */
372 var onChange = function (newVal, oldVal) {
373
374 /**
375 * Indicate if the DOM element is showed, or not
376 * @type {boolean}
377 */
378 var elemIsShowed = !!newVal;
379
380 /**
381 * Indicate if the DOM element was showed, or not
382 * @type {boolean}
383 */
384 var elemWasHidden = !oldVal;
385 var valChange = (newVal !== oldVal || typeof stickyLine === 'undefined');
386 var notSticking = (!isSticking && !isBottomedOut());
387
388 if (valChange && notSticking && newVal > 0 && elemIsShowed) {
389 stickyLine = newVal - offset;
390 //Update dimensions of sticky element when is showed
391 if (elemIsShowed && elemWasHidden) {
392 $scope.updateStickyContentUpdateDimensions($elem[0].offsetWidth, $elem[0].offsetHeight);
393 }
394 // IF the sticky is confined, we want to make sure the parent is relatively positioned,
395 // otherwise it won't bottom out properly
396 if (confine) {
397 $elem.parent().css({
398 'position': 'relative'
399 });
400 }
401
402 // Get Parent height, so we know when to bottom out for confined stickies
403 var parent = $elem.parent()[0];
404
405 // Offset parent height by the elements height, if we're not using a placeholder
406 var parentHeight = parseInt (parent.offsetHeight) - (usePlaceholder ? 0 : $elem[0].offsetHeight);
407
408 // and now lets ensure we adhere to the bottom margins
409 // TODO: make this an attribute? Maybe like ignore-margin?
410 var marginBottom = parseInt ($elem.css('margin-bottom').replace(/px;?/, '')) || 0;
411
412 // specify the bottom out line for the sticky to unstick
413 var elementsDistanceFromTop = elementsOffsetFromTop ($elem[0]);
414 var parentsDistanceFromTop = elementsOffsetFromTop (parent)
415 var scrollbarDistanceFromTop = elementsOffsetFromTop (scrollbar);
416
417 var elementsDistanceFromScrollbarStart = elementsDistanceFromTop - scrollbarDistanceFromTop;
418 var elementsDistanceFromBottom = parentsDistanceFromTop + parentHeight - elementsDistanceFromTop;
419
420 stickyBottomLine = elementsDistanceFromScrollbarStart
421 + elementsDistanceFromBottom
422 - $elem[0].offsetHeight
423 - marginBottom
424 - offset
425 + +scrollbarYPos();
426
427 checkIfShouldStick();
428 }
429 };
430
431 /**
432 * Helper Functions
433 */
434
435 /**
436 * Create a placeholder
437 */
438 function createPlaceholder() {
439 if (usePlaceholder) {
440 // Remove the previous placeholder
441 if (placeholder) {
442 placeholder.remove();
443 }
444
445 placeholder = angular.element('<div>');
446 var elementsHeight = $elem[0].offsetHeight;
447 var computedStyle = $elem[0].currentStyle || window.getComputedStyle($elem[0]);
448 elementsHeight += parseInt(computedStyle.marginTop, 10);
449 elementsHeight += parseInt(computedStyle.marginBottom, 10);
450 elementsHeight += parseInt(computedStyle.borderTopWidth, 10);
451 elementsHeight += parseInt(computedStyle.borderBottomWidth, 10);
452 placeholder.css('height', $elem[0].offsetHeight + 'px');
453
454 $elem.after(placeholder);
455 }
456 }
457
458 /**
459 * Are we bottomed out of the parent element?
460 */
461 function isBottomedOut() {
462 if (confine && scrollbarYPos() > stickyBottomLine) {
463 return true;
464 }
465
466 return false;
467 }
468
469 /**
470 * Fetch top offset of element
471 */
472 function elementsOffsetFromTop(element) {
473 var offset = 0;
474
475 if (element.getBoundingClientRect) {
476 offset = element.getBoundingClientRect().top;
477 }
478
479 return offset;
480 }
481
482 /**
483 * Retrieves top scroll distance
484 */
485 function scrollbarYPos() {
486 var position;
487
488 if (typeof scrollbar.scrollTop !== 'undefined') {
489 position = scrollbar.scrollTop;
490 } else if (typeof scrollbar.pageYOffset !== 'undefined') {
491 position = scrollbar.pageYOffset;
492 } else {
493 position = document.documentElement.scrollTop;
494 }
495
496 return position;
497 }
498
499 /**
500 * Determine scrollbar's height
501 */
502 function scrollbarHeight() {
503 var height;
504
505 if (scrollbarElement[0] instanceof HTMLElement) {
506 // isn't bounding client rect cleaner than insane regex mess?
507 height = $window.getComputedStyle(scrollbarElement[0], null)
508 .getPropertyValue('height')
509 .replace(/px;?/, '');
510 } else {
511 height = $window.innerHeight;
512 }
513
514 return parseInt (height) || 0;
515 }
516
517 /**
518 * Checks if the media matches
519 */
520 function mediaQueryMatches() {
521 var mediaQuery = $attrs.mediaQuery || false;
522 var matchMedia = $window.matchMedia;
523
524 return mediaQuery && !(matchMedia ('(' + mediaQuery + ')').matches || matchMedia (mediaQuery).matches);
525 }
526
527 /**
528 * Get more accurate CSS values
529 */
530 function getCSS($el, prop){
531 var el = $el[0],
532 computed = window.getComputedStyle(el),
533 prevDisplay = computed.display,
534 val;
535
536 // hide the element so that we can get original css
537 // values instead of computed values
538 el.style.display = "none";
539
540 // NOTE - computed style declaration object is a reference
541 // to the element's CSSStyleDeclaration, so it will always
542 // reflect the current style of the element
543 val = computed[prop];
544
545 // restore previous display value
546 el.style.display = prevDisplay;
547
548 return val;
549 }
550
551 // public accessors for the controller to hitch into. Helps with external API access
552 $scope.getElement = function() { return $elem; };
553 $scope.getScrollbar = function() { return scrollbar; };
554 $scope.getInitialCSS = function() { return initialCSS; };
555 $scope.getAnchor = function() { return anchor; };
556 $scope.isSticking = function() { return isSticking; };
557 $scope.getOriginalInitialCSS = function() { return originalInitialCSS; };
558 // pass through aliases
559 $scope.processUnStickElement = function(anchor) { unStickElement(anchor)};
560 $scope.processCheckIfShouldStick =function() { checkIfShouldStick(); };
561
562 /**
563 * set the dimensions for the defaults of the content block occupied by the sticky element
564 */
565 $scope.getInitialDimensions = function() {
566 return {
567 zIndex: $elem.css('z-index'),
568 top: $elem.css('top'),
569 position: initialPosition, // revert to true initial state
570 marginTop: $elem.css('margin-top'),
571 marginBottom: $elem.css('margin-bottom'),
572 cssLeft: getCSS($elem, 'left'),
573 width: $elem[0].offsetWidth,
574 height: $elem.css('height')
575 };
576 };
577
578 /**
579 * only change content box dimensions
580 */
581 $scope.updateStickyContentUpdateDimensions = function(width, height) {
582 if (width && height) {
583 initSticky();
584 initialCSS.width = width + 'px';
585 initialCSS.height = height + 'px';
586 }
587 };
588
589 // ----------- configuration -----------
590
591 $timeout(function() {
592 originalInitialCSS = $scope.getInitialDimensions(); // preserve a copy
593 // Init the directive
594 initSticky();
595 },0);
596 },
597
598 /**
599 * +++++++++ public APIs+++++++++++++
600 */
601 controller: ['$scope', '$window', function($scope, $window) {
602
603 /**
604 * integration method allows for an outside client to reset the pinned state back to unpinned.
605 * Useful for when refreshing the scrollable DIV content completely
606 * if newWidth and newHeight integer values are not supplied then function will make a best guess
607 */
608 this.resetLayout = function(newWidth, newHeight) {
609
610 var scrollbar = $scope.getScrollbar(),
611 initialCSS = $scope.getInitialCSS(),
612 anchor = $scope.getAnchor();
613
614 function _resetScrollPosition() {
615
616 // reset means content is scrolled to anchor position
617 if (anchor === 'top') {
618 // window based scroller
619 if (scrollbar === $window) {
620 $window.scrollTo(0, 0);
621 // DIV based sticky scroller
622 } else {
623 if (scrollbar.scrollTop > 0) {
624 scrollbar.scrollTop = 0;
625 }
626 }
627 }
628 // todo: need bottom use case
629 }
630
631 // only if pinned, force unpinning, otherwise height is inadvertently reset to 0
632 if ($scope.isSticking()) {
633 $scope.processUnStickElement (anchor);
634 $scope.processCheckIfShouldStick();
635 }
636 // remove layout-affecting attribures that were modified by this sticky
637 $scope.getElement().css({ 'width': '', 'height': '', 'position': '', 'top': '', zIndex: '' });
638 // model resets
639 initialCSS.position = $scope.getOriginalInitialCSS().position; // revert to original state
640 delete initialCSS.offsetWidth; // stickElement affected
641
642 // use this directive element's as default, if no measurements passed in
643 if (newWidth === undefined && newHeight === undefined) {
644 var e_bcr = $scope.getElement()[0].getBoundingClientRect();
645 newWidth = e_bcr.width;
646 newHeight = e_bcr.height;
647 }
648
649 // update model with new dimensions (if supplied from client's own measurement)
650 $scope.updateStickyContentUpdateDimensions(newWidth, newHeight); // update layout dimensions only
651
652 _resetScrollPosition();
653 };
654
655 /**
656 * return a reference to the scrolling element (window or DIV with overflow)
657 */
658 this.getScrollbar = function() {
659 return $scope.getScrollbar();
660 };
661 }]
662 };
663 }]
664 );
665
666 // Shiv: matchMedia
667 window.matchMedia = window.matchMedia || (function() {
668 var warning = 'angular-sticky: This browser does not support ' +
669 'matchMedia, therefore the minWidth option will not work on ' +
670 'this browser. Polyfill matchMedia to fix this issue.';
671
672 if (window.console && console.warn) {
673 console.warn(warning);
674 }
675
676 return function() {
677 return {
678 matches: true
679 };
680 };
681 }());
682}());
diff --git a/src/maasserver/static/js/angular/3rdparty/sticky.min.js b/src/maasserver/static/js/angular/3rdparty/sticky.min.js
683deleted file mode 1006440deleted file mode 100644
index d8ae3da..0000000
--- a/src/maasserver/static/js/angular/3rdparty/sticky.min.js
+++ /dev/null
@@ -1,684 +0,0 @@
1/**
2 * ngSticky - https://github.com/d-oliveros/ngSticky
3 *
4 * A simple, pure javascript (No jQuery required!) AngularJS directive
5 * to make elements stick when scrolling down.
6 *
7 * Credits: https://github.com/d-oliveros/ngSticky/graphs/contributors
8 */
9(function() {
10 'use strict';
11
12 var module = angular.module('sticky', []);
13
14 /**
15 * Directive: sticky
16 */
17 module.directive('sticky', ['$window', '$timeout', function($window, $timeout) {
18 return {
19 restrict: 'A', // this directive can only be used as an attribute.
20 scope: {
21 disabled: '=disabledSticky'
22 },
23 link: function linkFn($scope, $elem, $attrs) {
24
25 // Initial scope
26 var scrollableNodeTagName = 'sticky-scroll';
27 var initialPosition = $elem.css('position');
28 var initialStyle = $elem.attr('style') || '';
29 var stickyBottomLine = 0;
30 var isSticking = false;
31 var onStickyHeighUnbind;
32 var originalInitialCSS;
33 var originalOffset;
34 var placeholder;
35 var stickyLine;
36 var initialCSS;
37
38 // Optional Classes
39 var stickyClass = $attrs.stickyClass || '';
40 var unstickyClass = $attrs.unstickyClass || '';
41 var bodyClass = $attrs.bodyClass || '';
42 var bottomClass = $attrs.bottomClass || '';
43
44 // Find scrollbar
45 var scrollbar = deriveScrollingViewport ($elem);
46
47 // Define elements
48 var windowElement = angular.element($window);
49 var scrollbarElement = angular.element(scrollbar);
50 var $body = angular.element(document.body);
51
52 // Resize callback
53 var $onResize = function () {
54 if ($scope.$root && !$scope.$root.$$phase) {
55 $scope.$apply(onResize);
56 } else {
57 onResize();
58 }
59 };
60
61 // Define options
62 var usePlaceholder = ($attrs.usePlaceholder !== 'false');
63 var anchor = $attrs.anchor === 'bottom' ? 'bottom' : 'top';
64 var confine = ($attrs.confine === 'true');
65
66 // flag: can react to recalculating the initial CSS dimensions later
67 // as link executes prematurely. defaults to immediate checking
68 var isStickyLayoutDeferred = $attrs.isStickyLayoutDeferred !== undefined
69 ? ($attrs.isStickyLayoutDeferred === 'true')
70 : false;
71
72 // flag: is sticky content constantly observed for changes.
73 // Should be true if content uses ngBind to show text
74 // that may vary in size over time
75 var isStickyLayoutWatched = $attrs.isStickyLayoutWatched !== undefined
76 ? ($attrs.isStickyLayoutWatched === 'true')
77 : true;
78
79
80 var offset = $attrs.offset
81 ? parseInt ($attrs.offset.replace(/px;?/, ''))
82 : 0;
83
84 /**
85 * Trigger to initialize the sticky
86 * Because of the `timeout()` method for the call of
87 * @type {Boolean}
88 */
89 var shouldInitialize = true;
90
91 /**
92 * Initialize Sticky
93 */
94 function initSticky() {
95
96 if (shouldInitialize) {
97
98 // Listeners
99 scrollbarElement.on('scroll', checkIfShouldStick);
100 windowElement.on('resize', $onResize);
101
102 memorizeDimensions(); // remember sticky's layout dimensions
103
104 // Setup watcher on digest and change
105 $scope.$watch(onDigest, onChange);
106
107 // Clean up
108 $scope.$on('$destroy', onDestroy);
109 shouldInitialize = false;
110 }
111 };
112
113 /**
114 * need to recall sticky's DOM attributes (make sure layout has occured)
115 */
116 function memorizeDimensions() {
117 // immediate assignment, but there is the potential for wrong values if content not ready
118 initialCSS = $scope.getInitialDimensions();
119
120 // option to calculate the dimensions when layout is 'ready'
121 if (isStickyLayoutDeferred) {
122
123 // logic: when this directive link() runs before the content has had a chance to layout on browser, height could be 0
124 if (!$elem[0].getBoundingClientRect().height) {
125
126 onStickyHeighUnbind = $scope.$watch(
127 function() {
128 return $elem.height();
129 },
130
131 // state change: sticky content's height set
132 function onStickyContentLayoutInitialHeightSet(newValue, oldValue) {
133 if (newValue > 0) {
134 // now can memorize
135 initialCSS = $scope.getInitialDimensions();
136
137 if (!isStickyLayoutWatched) {
138 // preference was to do just a one-time async watch on the sticky's content; now stop watching
139 onStickyHeighUnbind();
140 }
141 }
142 }
143 );
144 }
145 }
146 }
147
148 /**
149 * Determine if the element should be sticking or not.
150 */
151 var checkIfShouldStick = function() {
152 if ($scope.disabled === true || mediaQueryMatches()) {
153 if (isSticking) unStickElement();
154 return false;
155 }
156
157 // What's the document client top for?
158 var scrollbarPosition = scrollbarYPos();
159 var shouldStick;
160
161 if (anchor === 'top') {
162 if (confine === true) {
163 shouldStick = scrollbarPosition > stickyLine && scrollbarPosition <= stickyBottomLine;
164 } else {
165 shouldStick = scrollbarPosition > stickyLine;
166 }
167 } else {
168 shouldStick = scrollbarPosition <= stickyLine;
169 }
170
171 // Switch the sticky mode if the element crosses the sticky line
172 // $attrs.stickLimit - when it's equal to true it enables the user
173 // to turn off the sticky function when the elem height is
174 // bigger then the viewport
175 var closestLine = getClosest (scrollbarPosition, stickyLine, stickyBottomLine);
176
177 if (shouldStick && !shouldStickWithLimit ($attrs.stickLimit) && !isSticking) {
178 stickElement (closestLine);
179 } else if (!shouldStick && isSticking) {
180 unStickElement(closestLine, scrollbarPosition);
181 } else if (confine && !shouldStick) {
182 // If we are confined to the parent, refresh, and past the stickyBottomLine
183 // We should 'remember' the original offset and unstick the element which places it at the stickyBottomLine
184 originalOffset = elementsOffsetFromTop ($elem[0]);
185 unStickElement (closestLine, scrollbarPosition);
186 }
187 };
188
189 /**
190 * determine the respective node that handles scrolling, defaulting to browser window
191 */
192 function deriveScrollingViewport(stickyNode) {
193 // derive relevant scrolling by ascending the DOM tree
194 var match =findAncestorTag (scrollableNodeTagName, stickyNode);
195 return (match.length === 1) ? match[0] : $window;
196 }
197
198 /**
199 * since jqLite lacks closest(), this is a pseudo emulator (by tag name)
200 */
201 function findAncestorTag(tag, context) {
202 var m = []; // nodelist container
203 var n = context.parent(); // starting point
204 var p;
205
206 do {
207 var node = n[0]; // break out of jqLite
208 // limit DOM territory
209 if (node.nodeType !== 1) {
210 break;
211 }
212
213 // success
214 if (node.tagName.toUpperCase() === tag.toUpperCase()) {
215 return n;
216 }
217
218 p = n.parent();
219 n = p; // set to parent
220 } while (p.length !== 0);
221
222 return m; // empty set
223 }
224
225 /**
226 * Seems to be undocumented functionality
227 */
228 function shouldStickWithLimit(shouldApplyWithLimit) {
229 return shouldApplyWithLimit === 'true'
230 ? ($window.innerHeight - ($elem[0].offsetHeight + parseInt(offset)) < 0)
231 : false;
232 }
233
234 /**
235 * Finds the closest value from a set of numbers in an array.
236 */
237 function getClosest(scrollTop, stickyLine, stickyBottomLine) {
238 var closest = 'top';
239 var topDistance = Math.abs(scrollTop - stickyLine);
240 var bottomDistance = Math.abs(scrollTop - stickyBottomLine);
241
242 if (topDistance > bottomDistance) {
243 closest = 'bottom';
244 }
245
246 return closest;
247 }
248
249 /**
250 * Unsticks the element
251 */
252 function unStickElement(fromDirection) {
253 if (initialStyle) {
254 $elem.attr('style', initialStyle);
255 }
256 isSticking = false;
257
258 initialCSS.width = $scope.getInitialDimensions().width;
259
260 $body.removeClass(bodyClass);
261 $elem.removeClass(stickyClass);
262 $elem.addClass(unstickyClass);
263
264 if (fromDirection === 'top') {
265 $elem.removeClass(bottomClass);
266
267 $elem
268 .css('z-index', 10)
269 .css('width', initialCSS.width)
270 .css('top', initialCSS.top)
271 .css('position', initialCSS.position)
272 .css('left', initialCSS.cssLeft)
273 .css('margin-top', initialCSS.marginTop)
274 .css('height', initialCSS.height);
275 } else if (fromDirection === 'bottom' && confine === true) {
276 $elem.addClass(bottomClass);
277
278 // It's possible to page down page and skip the 'stickElement'.
279 // In that case we should create a placeholder so the offsets don't get off.
280 createPlaceholder();
281
282 $elem
283 .css('z-index', 10)
284 .css('width', initialCSS.width)
285 .css('top', '')
286 .css('bottom', 0)
287 .css('position', 'absolute')
288 .css('left', initialCSS.cssLeft)
289 .css('margin-top', initialCSS.marginTop)
290 .css('margin-bottom', initialCSS.marginBottom)
291 .css('height', initialCSS.height);
292 }
293
294 if (placeholder && fromDirection === anchor) {
295 placeholder.remove();
296 }
297 }
298
299 /**
300 * Sticks the element
301 */
302 function stickElement(closestLine) {
303 // Set sticky state
304 isSticking = true;
305 $timeout(function() {
306 initialCSS.offsetWidth = $elem[0].offsetWidth;
307 }, 0);
308 $body.addClass(bodyClass);
309 $elem.removeClass(unstickyClass);
310 $elem.removeClass(bottomClass);
311 $elem.addClass(stickyClass);
312
313 createPlaceholder();
314
315 $elem
316 .css('z-index', '10')
317 .css('width', $elem[0].offsetWidth + 'px')
318 .css('position', 'fixed')
319 .css('left', $elem.css('left').replace('px', '') + 'px')
320 .css(anchor, (offset + elementsOffsetFromTop (scrollbar)) + 'px')
321 .css('margin-top', 0);
322
323 if (anchor === 'bottom') {
324 $elem.css('margin-bottom', 0);
325 }
326 }
327
328 /**
329 * Clean up directive
330 */
331 var onDestroy = function() {
332 scrollbarElement.off('scroll', checkIfShouldStick);
333 windowElement.off('resize', $onResize);
334
335 $onResize = null;
336
337 $body.removeClass(bodyClass);
338
339 if (placeholder) {
340 placeholder.remove();
341 }
342 };
343
344 /**
345 * Updates on resize.
346 */
347 function onResize() {
348 unStickElement (anchor);
349 checkIfShouldStick();
350 }
351
352 /**
353 * Triggered on load / digest cycle
354 * return `0` if the DOM element is hidden
355 */
356 var onDigest = function() {
357 if ($scope.disabled === true) {
358 return unStickElement();
359 }
360 var offsetFromTop = elementsOffsetFromTop ($elem[0]);
361 if (offsetFromTop === 0) {
362 return offsetFromTop;
363 }
364 if (anchor === 'top') {
365 return (originalOffset || offsetFromTop) - elementsOffsetFromTop (scrollbar) + scrollbarYPos();
366 } else {
367 return offsetFromTop - scrollbarHeight() + $elem[0].offsetHeight + scrollbarYPos();
368 }
369 };
370
371 /**
372 * Triggered on change
373 */
374 var onChange = function (newVal, oldVal) {
375
376 /**
377 * Indicate if the DOM element is showed, or not
378 * @type {boolean}
379 */
380 var elemIsShowed = !!newVal;
381
382 /**
383 * Indicate if the DOM element was showed, or not
384 * @type {boolean}
385 */
386 var elemWasHidden = !oldVal;
387 var valChange = (newVal !== oldVal || typeof stickyLine === 'undefined');
388 var notSticking = (!isSticking && !isBottomedOut());
389
390 if (valChange && notSticking && newVal > 0 && elemIsShowed) {
391 stickyLine = newVal - offset;
392 //Update dimensions of sticky element when is showed
393 if (elemIsShowed && elemWasHidden) {
394 $scope.updateStickyContentUpdateDimensions($elem[0].offsetWidth, $elem[0].offsetHeight);
395 }
396 // IF the sticky is confined, we want to make sure the parent is relatively positioned,
397 // otherwise it won't bottom out properly
398 if (confine) {
399 $elem.parent().css({
400 'position': 'relative'
401 });
402 }
403
404 // Get Parent height, so we know when to bottom out for confined stickies
405 var parent = $elem.parent()[0];
406
407 // Offset parent height by the elements height, if we're not using a placeholder
408 var parentHeight = parseInt (parent.offsetHeight) - (usePlaceholder ? 0 : $elem[0].offsetHeight);
409
410 // and now lets ensure we adhere to the bottom margins
411 // TODO: make this an attribute? Maybe like ignore-margin?
412 var marginBottom = parseInt ($elem.css('margin-bottom').replace(/px;?/, '')) || 0;
413
414 // specify the bottom out line for the sticky to unstick
415 var elementsDistanceFromTop = elementsOffsetFromTop ($elem[0]);
416 var parentsDistanceFromTop = elementsOffsetFromTop (parent)
417 var scrollbarDistanceFromTop = elementsOffsetFromTop (scrollbar);
418
419 var elementsDistanceFromScrollbarStart = elementsDistanceFromTop - scrollbarDistanceFromTop;
420 var elementsDistanceFromBottom = parentsDistanceFromTop + parentHeight - elementsDistanceFromTop;
421
422 stickyBottomLine = elementsDistanceFromScrollbarStart
423 + elementsDistanceFromBottom
424 - $elem[0].offsetHeight
425 - marginBottom
426 - offset
427 + +scrollbarYPos();
428
429 checkIfShouldStick();
430 }
431 };
432
433 /**
434 * Helper Functions
435 */
436
437 /**
438 * Create a placeholder
439 */
440 function createPlaceholder() {
441 if (usePlaceholder) {
442 // Remove the previous placeholder
443 if (placeholder) {
444 placeholder.remove();
445 }
446
447 placeholder = angular.element('<div>');
448 var elementsHeight = $elem[0].offsetHeight;
449 var computedStyle = $elem[0].currentStyle || window.getComputedStyle($elem[0]);
450 elementsHeight += parseInt(computedStyle.marginTop, 10);
451 elementsHeight += parseInt(computedStyle.marginBottom, 10);
452 elementsHeight += parseInt(computedStyle.borderTopWidth, 10);
453 elementsHeight += parseInt(computedStyle.borderBottomWidth, 10);
454 placeholder.css('height', $elem[0].offsetHeight + 'px');
455
456 $elem.after(placeholder);
457 }
458 }
459
460 /**
461 * Are we bottomed out of the parent element?
462 */
463 function isBottomedOut() {
464 if (confine && scrollbarYPos() > stickyBottomLine) {
465 return true;
466 }
467
468 return false;
469 }
470
471 /**
472 * Fetch top offset of element
473 */
474 function elementsOffsetFromTop(element) {
475 var offset = 0;
476
477 if (element.getBoundingClientRect) {
478 offset = element.getBoundingClientRect().top;
479 }
480
481 return offset;
482 }
483
484 /**
485 * Retrieves top scroll distance
486 */
487 function scrollbarYPos() {
488 var position;
489
490 if (typeof scrollbar.scrollTop !== 'undefined') {
491 position = scrollbar.scrollTop;
492 } else if (typeof scrollbar.pageYOffset !== 'undefined') {
493 position = scrollbar.pageYOffset;
494 } else {
495 position = document.documentElement.scrollTop;
496 }
497
498 return position;
499 }
500
501 /**
502 * Determine scrollbar's height
503 */
504 function scrollbarHeight() {
505 var height;
506
507 if (scrollbarElement[0] instanceof HTMLElement) {
508 // isn't bounding client rect cleaner than insane regex mess?
509 height = $window.getComputedStyle(scrollbarElement[0], null)
510 .getPropertyValue('height')
511 .replace(/px;?/, '');
512 } else {
513 height = $window.innerHeight;
514 }
515
516 return parseInt (height) || 0;
517 }
518
519 /**
520 * Checks if the media matches
521 */
522 function mediaQueryMatches() {
523 var mediaQuery = $attrs.mediaQuery || false;
524 var matchMedia = $window.matchMedia;
525
526 return mediaQuery && !(matchMedia ('(' + mediaQuery + ')').matches || matchMedia (mediaQuery).matches);
527 }
528
529 /**
530 * Get more accurate CSS values
531 */
532 function getCSS($el, prop){
533 var el = $el[0],
534 computed = window.getComputedStyle(el),
535 prevDisplay = computed.display,
536 val;
537
538 // hide the element so that we can get original css
539 // values instead of computed values
540 el.style.display = "none";
541
542 // NOTE - computed style declaration object is a reference
543 // to the element's CSSStyleDeclaration, so it will always
544 // reflect the current style of the element
545 val = computed[prop];
546
547 // restore previous display value
548 el.style.display = prevDisplay;
549
550 return val;
551 }
552
553 // public accessors for the controller to hitch into. Helps with external API access
554 $scope.getElement = function() { return $elem; };
555 $scope.getScrollbar = function() { return scrollbar; };
556 $scope.getInitialCSS = function() { return initialCSS; };
557 $scope.getAnchor = function() { return anchor; };
558 $scope.isSticking = function() { return isSticking; };
559 $scope.getOriginalInitialCSS = function() { return originalInitialCSS; };
560 // pass through aliases
561 $scope.processUnStickElement = function(anchor) { unStickElement(anchor)};
562 $scope.processCheckIfShouldStick =function() { checkIfShouldStick(); };
563
564 /**
565 * set the dimensions for the defaults of the content block occupied by the sticky element
566 */
567 $scope.getInitialDimensions = function() {
568 return {
569 zIndex: $elem.css('z-index'),
570 top: $elem.css('top'),
571 position: initialPosition, // revert to true initial state
572 marginTop: $elem.css('margin-top'),
573 marginBottom: $elem.css('margin-bottom'),
574 cssLeft: getCSS($elem, 'left'),
575 width: $elem[0].offsetWidth,
576 height: $elem.css('height')
577 };
578 };
579
580 /**
581 * only change content box dimensions
582 */
583 $scope.updateStickyContentUpdateDimensions = function(width, height) {
584 if (width && height) {
585 initSticky();
586 initialCSS.width = width + 'px';
587 initialCSS.height = height + 'px';
588 }
589 };
590
591 // ----------- configuration -----------
592
593 $timeout(function() {
594 originalInitialCSS = $scope.getInitialDimensions(); // preserve a copy
595 // Init the directive
596 initSticky();
597 },0);
598 },
599
600 /**
601 * +++++++++ public APIs+++++++++++++
602 */
603 controller: ['$scope', '$window', function($scope, $window) {
604
605 /**
606 * integration method allows for an outside client to reset the pinned state back to unpinned.
607 * Useful for when refreshing the scrollable DIV content completely
608 * if newWidth and newHeight integer values are not supplied then function will make a best guess
609 */
610 this.resetLayout = function(newWidth, newHeight) {
611
612 var scrollbar = $scope.getScrollbar(),
613 initialCSS = $scope.getInitialCSS(),
614 anchor = $scope.getAnchor();
615
616 function _resetScrollPosition() {
617
618 // reset means content is scrolled to anchor position
619 if (anchor === 'top') {
620 // window based scroller
621 if (scrollbar === $window) {
622 $window.scrollTo(0, 0);
623 // DIV based sticky scroller
624 } else {
625 if (scrollbar.scrollTop > 0) {
626 scrollbar.scrollTop = 0;
627 }
628 }
629 }
630 // todo: need bottom use case
631 }
632
633 // only if pinned, force unpinning, otherwise height is inadvertently reset to 0
634 if ($scope.isSticking()) {
635 $scope.processUnStickElement (anchor);
636 $scope.processCheckIfShouldStick();
637 }
638 // remove layout-affecting attribures that were modified by this sticky
639 $scope.getElement().css({ 'width': '', 'height': '', 'position': '', 'top': '', zIndex: '' });
640 // model resets
641 initialCSS.position = $scope.getOriginalInitialCSS().position; // revert to original state
642 delete initialCSS.offsetWidth; // stickElement affected
643
644 // use this directive element's as default, if no measurements passed in
645 if (newWidth === undefined && newHeight === undefined) {
646 var e_bcr = $scope.getElement()[0].getBoundingClientRect();
647 newWidth = e_bcr.width;
648 newHeight = e_bcr.height;
649 }
650
651 // update model with new dimensions (if supplied from client's own measurement)
652 $scope.updateStickyContentUpdateDimensions(newWidth, newHeight); // update layout dimensions only
653
654 _resetScrollPosition();
655 };
656
657 /**
658 * return a reference to the scrolling element (window or DIV with overflow)
659 */
660 this.getScrollbar = function() {
661 return $scope.getScrollbar();
662 };
663 }]
664 };
665 }]
666 );
667
668 // Shiv: matchMedia
669 window.matchMedia = window.matchMedia || (function() {
670 var warning = 'angular-sticky: This browser does not support ' +
671 'matchMedia, therefore the minWidth option will not work on ' +
672 'this browser. Polyfill matchMedia to fix this issue.';
673
674 if (window.console && console.warn) {
675 console.warn(warning);
676 }
677
678 return function() {
679 return {
680 matches: true
681 };
682 };
683 }());
684}());
diff --git a/src/maasserver/static/js/angular/maas.js b/src/maasserver/static/js/angular/maas.js
index 0456a2f..cda7a27 100755
--- a/src/maasserver/static/js/angular/maas.js
+++ b/src/maasserver/static/js/angular/maas.js
@@ -9,8 +9,7 @@
9 */9 */
1010
11angular.module('MAAS',11angular.module('MAAS',
12 ['ngRoute', 'ngCookies', 'ngSanitize', 'ngTagsInput', 'sticky',12 ['ngRoute', 'ngCookies', 'ngSanitize', 'ngTagsInput', 'vs-repeat']).config(
13 'vs-repeat']).config(
14 function($interpolateProvider, $routeProvider, $httpProvider) {13 function($interpolateProvider, $routeProvider, $httpProvider) {
15 $interpolateProvider.startSymbol('{$');14 $interpolateProvider.startSymbol('{$');
16 $interpolateProvider.endSymbol('$}');15 $interpolateProvider.endSymbol('$}');
diff --git a/src/maasserver/static/partials/node-details.html b/src/maasserver/static/partials/node-details.html
index a217103..77c760c 100755
--- a/src/maasserver/static/partials/node-details.html
+++ b/src/maasserver/static/partials/node-details.html
@@ -6,7 +6,7 @@
6 </header>6 </header>
7</div>7</div>
8<div data-ng-if="loaded">8<div data-ng-if="loaded">
9 <header sticky use-placeholder="true" media-query="min-width: 769px" class="p-strip--light is-shallow page-header u-no-padding--bottom">9 <header class="p-strip--light is-shallow page-header u-no-padding--bottom">
10 <div class="row">10 <div class="row">
11 <div class="col-8">11 <div class="col-8">
12 <h1 class="page-header__title">12 <h1 class="page-header__title">
diff --git a/src/maasserver/static/partials/node-events.html b/src/maasserver/static/partials/node-events.html
index 59e3e91..d9a5c5e 100644
--- a/src/maasserver/static/partials/node-events.html
+++ b/src/maasserver/static/partials/node-events.html
@@ -6,7 +6,7 @@
6 </header>6 </header>
7</div>7</div>
8<div class="ng-hide u-no-margin--top" data-ng-show="loaded">8<div class="ng-hide u-no-margin--top" data-ng-show="loaded">
9 <header class="p-strip--light is-shallow is-bordered page-header" sticky>9 <header class="p-strip--light is-shallow is-bordered page-header">
10 <div class="row">10 <div class="row">
11 <div class="col-8">11 <div class="col-8">
12 <ul class="p-inline-list u-no-margin--top">12 <ul class="p-inline-list u-no-margin--top">

Subscribers

People subscribed via source and target branches