diff --git a/app/assets/javascripts/stick-to-window-when-scrolling.js b/app/assets/javascripts/stick-to-window-when-scrolling.js
index 9838570d8..c7296d12d 100644
--- a/app/assets/javascripts/stick-to-window-when-scrolling.js
+++ b/app/assets/javascripts/stick-to-window-when-scrolling.js
@@ -4,14 +4,15 @@
var $ = global.jQuery;
var GOVUK = global.GOVUK || {};
+ // Constructor for objects holding data for each element to have sticky behaviour
var StickyElement = function ($el, sticky) {
this._sticky = sticky;
this.$fixedEl = $el;
this._initialFixedClass = 'content-fixed-onload';
this._fixedClass = 'content-fixed';
this._appliedClass = null;
+ this._$shim = null;
this._stopped = false;
- this.scrolledFrom = this._sticky.getScrolledFrom($el);
};
StickyElement.prototype.stickyClass = function () {
return (this._sticky._initialPositionsSet) ? this._fixedClass : this._initialFixedClass;
@@ -30,17 +31,37 @@
this._appliedClass = null;
this._hasBeenCalled = true;
};
+ // When a sticky element is moved into the 'stuck' state, a shim is inserted into the
+ // page to preserve the space the element occupies in the flow.
+ StickyElement.prototype.addShim = function (position) {
+ this._$shim = $('
 
');
+ this.$fixedEl[position](this._$shim);
+ };
+ StickyElement.prototype.removeShim = function () {
+ this._$shim.remove();
+ this._$shim = null;
+ };
+ // Changes to the dimensions of a sticky element with a shim need to be passed on to the shim
+ StickyElement.prototype.updateShim = function () {
+ if (this._$shim) {
+ this._$shim.css({
+ 'height': this.verticalSpace,
+ 'width': this.horizontalSpace
+ });
+ }
+ };
StickyElement.prototype.stop = function () {
- this._stopped = true;
+ this._isStopped = true;
};
StickyElement.prototype.unstop = function () {
- this._stopped = false;
+ this._isStopped = false;
};
- StickyElement.prototype.stopped = function () {
- return this._stopped;
+ StickyElement.prototype.isStopped = function () {
+ return this._isStopped;
};
- // Stick elements to top of screen when you scroll past, documentation is in the README.md
+ // Constructor for objects collecting together all generic behaviour for controlling the state of
+ // sticky elements
var Sticky = function (selector) {
this._hasScrolled = false;
this._scrollTimeout = false;
@@ -63,100 +84,153 @@
scrollTop: $(global).scrollTop()
};
};
+ // Change state of sticky elements based on their position relative to the window
Sticky.prototype.setElementPositions = function () {
var self = this;
$.each(self._els, function (i, el) {
- var $el = el.$fixedEl;
+ var $el = el.$fixedEl,
+ windowDimensions = self.getWindowDimensions();
- var windowDimensions = self.getWindowDimensions();
+ if (self.viewportIsWideEnough(windowDimensions.width)) {
- if (self.scrolledFromInsideWindow(el.scrolledFrom)) {
- self.release(el);
- } else {
- if (self.scrolledToOutsideWindow(el, windowDimensions.height)) {
- self.stop(el);
- } else if (self.viewportIsWideEnough(windowDimensions.width)) {
- if (el.stopped) {
- self.unstop(el);
+ if (self.windowNotPastScrolledFrom(el.scrolledFrom)) {
+ self.release(el);
+ } else {
+ if (self.windowNotPastScrolledTo(el, windowDimensions.height)) {
+ self.stick(el);
+ if (el.isStopped()) {
+ self.unstop(el);
+ }
+ } else { // window past scrolledTo position
+ if (!el.isStopped()) {
+ self.stop(el);
+ }
}
- self.stick(el);
}
+
+ } else {
+
+ self.reset(el);
+
}
});
if (self._initialPositionsSet === false) { self._initialPositionsSet = true; }
};
- Sticky.prototype.setFixedTop = function (el) {
- var $siblingEl = $('');
- $siblingEl.insertBefore(el.$fixedEl);
- var fixedTop = $siblingEl.offset().top - $siblingEl.position().top;
- $siblingEl.remove();
-
- el.fixedTop = fixedTop;
- };
- Sticky.prototype.setElHeight = function (el) {
+ // Store all the dimensions for a sticky element to limit DOM queries
+ Sticky.prototype.setElementDimensions = function (el, callback) {
+ var self = this;
+ var $el = el.$fixedEl;
+ var onHeightSet = function () {
+ el.scrolledTo = self.getScrollingTo(el);
+ // if element is shim'ed, pass changes in dimension on to the shim
+ if (el._$shim) {
+ el.updateShim();
+ $el = el._$shim;
+ }
+ el.scrolledFrom = self.getScrolledFrom($el);
+ if (callback !== undefined) {
+ callback();
+ }
+ };
+
+ this.setElWidth(el);
+ this.setElHeight(el, onHeightSet);
+ };
+ // Reset element to original state in the page
+ Sticky.prototype.reset = function (el) {
+ if (el.isStuck()) {
+ this.release(el);
+ }
+ if (el.isStopped()) {
+ this.unstop(el);
+ }
+ };
+ // Recalculate stored dimensions for all sticky elements
+ Sticky.prototype.recalculate = function () {
+ var self = this;
+
+ $.each(self._els, function (i, el) {
+ self.setElementDimensions(el);
+ });
+ self.setElementPositions();
+ };
+ Sticky.prototype.setElWidth = function (el) {
+ var $el = el.$fixedEl;
+ var width = $el.parent().width();
+
+ el.horizontalSpace = width;
+ // if stuck, element won't inherit width from parent so set explicitly
+ if (el._$shim) {
+ $el.width(width);
+ }
+ };
+ Sticky.prototype.setElHeight = function (el, callback) {
var self = this;
- var fixedOffset = parseInt(el.$fixedEl.css('top'), 10);
var $el = el.$fixedEl;
var $img = $el.find('img');
- fixedOffset = isNaN(fixedOffset) ? 0 : fixedOffset;
-
if ((!self._elsLoaded) && ($img.length > 0)) {
var image = new global.Image();
image.onload = function () {
- el.height = $el.outerHeight() + fixedOffset;
- el.scrolledTo = self.getScrollingTo(el);
- self.checkElementsLoaded();
+ el.verticalSpace = $el.outerHeight(true);
+ el.height = $el.outerHeight();
+ callback();
};
image.src = $img.attr('src');
} else {
- el.height = $el.outerHeight() + fixedOffset;
- el.scrolledTo = self.getScrollingTo(el);
- self.checkElementsLoaded();
+ el.verticalSpace = $el.outerHeight(true);
+ el.height = $el.outerHeight();
+ callback();
}
};
- Sticky.prototype.checkElementsLoaded = function () {
- this._elsLoaded = $.grep(this._els, function (el) { return ('height' in el); }).length === this._els.length;
+ Sticky.prototype.allElementsLoaded = function (totalEls) {
+ return this._els.length === totalEls;
};
Sticky.prototype.init = function () {
var self = this;
var $els = $(self.CSS_SELECTOR);
+ var numOfEls = $els.length;
- if ($els.length > 0) {
+ if (numOfEls > 0) {
$els.each(function (i, el) {
var $el = $(el);
var elObj = new StickyElement($el, self);
- self.setFixedTop(elObj);
- self.setElHeight(elObj);
- self._els.push(elObj);
+ self.setElementDimensions(elObj, function () {
+ self._els.push(elObj);
+ // set positions based on initial scroll positionu
+ if (self._els.length === numOfEls) {
+ self._elsLoaded = true;
+ self.setElementPositions();
+ }
+ });
});
- // set element positions based on page scroll position on load
- self.setElementPositions();
-
+ // flag when scrolling takes place and check (and re-position) sticky elements relative to
+ // window position
if (self._scrollTimeout === false) {
$(global).scroll(function (e) { self.onScroll(); });
self._scrollTimeout = global.setInterval(function (e) { self.checkScroll(); }, 50);
}
+ // Recalculate all dimensions when the window resizes
if (self._resizeTimeout === false) {
$(global).resize(function (e) { self.onResize(); });
self._resizeTimeout = global.setInterval(function (e) { self.checkResize(); }, 50);
}
}
};
+ Sticky.prototype.viewportIsWideEnough = function (windowWidth) {
+ return windowWidth > 768;
+ };
Sticky.prototype.onScroll = function () {
this._hasScrolled = true;
};
Sticky.prototype.onResize = function () {
this._hasResized = true;
};
- Sticky.prototype.viewportIsWideEnough = function (windowWidth) {
- return windowWidth > 768;
- };
Sticky.prototype.checkScroll = function () {
var self = this;
@@ -166,41 +240,23 @@
}
};
Sticky.prototype.checkResize = function () {
- var self = this;
+ var self = this,
+ windowWidth = self.getWindowDimensions().width;
if (self._hasResized === true) {
self._hasResized = false;
- var windowDimensions = self.getWindowDimensions();
-
$.each(self._els, function (i, el) {
- var $el = el.$fixedEl;
-
- var elResize = $el.hasClass('js-self-resize');
- if (elResize) {
- var $shim = $('.shim');
- var $elParent = $el.parent('div');
- var elParentWidth = $elParent.width();
- $shim.css('width', elParentWidth);
- $el.css('width', elParentWidth);
- self.setElHeight(el);
- }
-
- if (!self.viewportIsWideEnough(windowDimensions.width)) {
- self.release($el);
+ if (!self.viewportIsWideEnough(windowWidth)) {
+ self.reset(el);
+ } else {
+ self.setElementDimensions(el);
}
});
- }
- };
- Sticky.prototype.stick = function (el) {
- if (!el.isStuck()) {
- var $el = el.$fixedEl;
- var height = Math.max($el.height(), 1);
- var width = $el.width();
- this.addShimForEl($el, width, height);
- $el.css('width', width + 'px').addClass(el.stickyClass());
- el.stick();
+ if (self.viewportIsWideEnough(windowWidth)) {
+ self.setElementPositions();
+ }
}
};
Sticky.prototype.release = function (el) {
@@ -208,15 +264,18 @@
var $el = el.$fixedEl;
$el.removeClass(el.appliedClass()).css('width', '');
- $el.siblings('.shim').remove();
+ el.removeShim();
el.release();
}
};
+ // Extension of sticky object to add behaviours specific to sticking to top of window
var stickAtTop = new Sticky('.js-stick-at-top-when-scrolling');
+ // Store top of sticky elements while unstuck
stickAtTop.getScrolledFrom = function ($el) {
return $el.offset().top;
};
+ // Store furthest point top of sticky element is allowed
stickAtTop.getScrollingTo = function (el) {
var footer = $('.js-footer:eq(0)');
if (footer.length === 0) {
@@ -224,36 +283,42 @@
}
return (footer.offset().top - 10) - el.height;
};
- stickAtTop.scrolledFromInsideWindow = function (scrolledFrom) {
+ stickAtTop.windowNotPastScrolledFrom = function (scrolledFrom) {
var windowTop = this.getWindowPositions().scrollTop;
return scrolledFrom > windowTop;
};
- stickAtTop.scrolledToOutsideWindow = function (el, windowHeight) {
+ stickAtTop.windowNotPastScrolledTo = function (el, windowHeight) {
var windowTop = this.getWindowPositions().scrollTop;
- return windowTop > el.scrolledTo;
+ return windowTop < el.scrolledTo;
};
- stickAtTop.addShimForEl = function ($el, width, height) {
- $el.before('
');
+ stickAtTop.stick = function (el) {
+ if (!el.isStuck()) {
+ var $el = el.$fixedEl;
+
+ el.addShim('before');
+ // element will be absolutely positioned so cannot rely on parent element for width
+ $el.css('width', $el.width() + 'px').addClass(el.stickyClass());
+ el.stick();
+ }
};
stickAtTop.stop = function (el) {
- if (!el.stopped()) {
- el.$fixedEl.css({ 'position': 'absolute', 'top': el.scrolledTo });
- el.stop();
- }
+ el.$fixedEl.css({ 'position': 'absolute', 'top': el.scrolledTo });
+ el.stop();
};
stickAtTop.unstop = function (el) {
- if (el.stopped()) {
- el.$fixedEl.css({ 'position': '', 'top': '' });
- el.unstop();
- }
+ el.$fixedEl.css({ 'position': '', 'top': '' });
+ el.unstop();
};
+ // Extension of sticky object to add behaviours specific to sticking to bottom of window
var stickAtBottom = new Sticky('.js-stick-at-bottom-when-scrolling');
+ // Store bottom of sticky elements while unstuck
stickAtBottom.getScrolledFrom = function ($el) {
return $el.offset().top + $el.outerHeight();
};
+ // Store furthest point bottom of sticky element is allowed
stickAtBottom.getScrollingTo = function (el) {
var header = $('.js-header:eq(0)');
if (header.length === 0) {
@@ -261,38 +326,41 @@
}
return (header.offset().top + header.outerHeight() + 10) + el.height;
};
- stickAtBottom.scrolledFromInsideWindow = function (scrolledFrom) {
+ stickAtBottom.windowNotPastScrolledFrom = function (scrolledFrom) {
var windowBottom = this.getWindowPositions().scrollTop + this.getWindowDimensions().height;
return scrolledFrom < windowBottom;
};
- stickAtBottom.scrolledToOutsideWindow = function (el, windowHeight) {
+ stickAtBottom.windowNotPastScrolledTo = function (el, windowHeight) {
var windowBottom = this.getWindowPositions().scrollTop + this.getWindowDimensions().height;
- return windowBottom < el.scrolledTo;
+ return windowBottom > el.scrolledTo;
};
- stickAtBottom.addShimForEl = function ($el, width, height) {
- $el.after(' 
');
+ stickAtBottom.stick = function (el) {
+ if (!el.isStuck()) {
+ var $el = el.$fixedEl;
+
+ el.addShim('after');
+ // element will be absolutely positioned so cannot rely on parent element for width
+ el.$fixedEl.css('width', $el.width() + 'px').addClass(el.stickyClass());
+ el.stick();
+ }
};
stickAtBottom.stop = function (el) {
- if (!el.stopped()) {
- el.$fixedEl.css({
- 'position': 'absolute',
- 'top': (el.scrolledTo - el.height),
- 'bottom': 'auto'
- });
- el.stop();
- }
+ el.$fixedEl.css({
+ 'position': 'absolute',
+ 'top': (el.scrolledTo - el.height),
+ 'bottom': 'auto'
+ });
+ el.stop();
};
stickAtBottom.unstop = function (el) {
- if (el.stopped()) {
- el.$fixedEl.css({
- 'position': '',
- 'top': '',
- 'bottom': ''
- });
- el.unstop();
- }
+ el.$fixedEl.css({
+ 'position': '',
+ 'top': '',
+ 'bottom': ''
+ });
+ el.unstop();
};
GOVUK.stickAtTopWhenScrolling = stickAtTop;
diff --git a/app/assets/javascripts/templateFolderForm.js b/app/assets/javascripts/templateFolderForm.js
index c5fa90448..8d845b9ee 100644
--- a/app/assets/javascripts/templateFolderForm.js
+++ b/app/assets/javascripts/templateFolderForm.js
@@ -37,8 +37,6 @@
this.$form.on('click', 'button.button-secondary', (event) => this.actionButtonClicked(event));
this.$form.on('change', 'input[type=checkbox]', () => this.templateFolderCheckboxChanged());
-
- this.render();
};
this.addCancelButton = function(state) {
@@ -122,6 +120,11 @@
this.states.forEach(
state => (state.key === this.currentState ? this.$stickyBottom.append(state.$el) : state.$el.detach())
);
+
+ // make sticky JS recalculate its cache of the element's position
+ if ('stickAtBottomWhenScrolling' in GOVUK) {
+ GOVUK.stickAtBottomWhenScrolling.recalculate();
+ }
};
this.nothingSelectedButtons = `