diff --git a/app/assets/javascripts/stick-to-window-when-scrolling.js b/app/assets/javascripts/stick-to-window-when-scrolling.js
index c7296d12d..63914fce0 100644
--- a/app/assets/javascripts/stick-to-window-when-scrolling.js
+++ b/app/assets/javascripts/stick-to-window-when-scrolling.js
@@ -3,6 +3,42 @@
var $ = global.jQuery;
var GOVUK = global.GOVUK || {};
+ var _mode = 'default';
+
+ // Object collecting together methods for dealing with marking the edge of a sticky, or group of
+ // sticky elements (as seen in dialog mode)
+ var oppositeEdge = {
+ _classes: {
+ 'top': 'content-fixed__top',
+ 'bottom': 'content-fixed__bottom'
+ },
+ _getClassForEdge: function (edge) {
+ return this._classes[edge];
+ },
+ mark: function (sticky) {
+ var edgeClass = this._getClassForEdge(sticky.edge);
+ var els;
+
+ if (_mode === 'dialog') {
+ els = [dialog.getElementAtOppositeEnd(sticky)];
+ } else {
+ els = sticky._els;
+ }
+
+ els = $.grep(els, function (el) { return el.isStuck(); });
+
+ $.each(els, function (i, el) {
+ el.$fixedEl.addClass(edgeClass);
+ });
+ },
+ unmark: function (sticky) {
+ var edgeClass = this._getClassForEdge(sticky.edge);
+
+ $.each(sticky._els, function (i, el) {
+ el.$fixedEl.removeClass(edgeClass);
+ });
+ }
+ };
// Constructor for objects holding data for each element to have sticky behaviour
var StickyElement = function ($el, sticky) {
@@ -13,6 +49,20 @@
this._appliedClass = null;
this._$shim = null;
this._stopped = false;
+ this._hasLoaded = false;
+ this._canBeStuck = true;
+ this.verticalMargins = {
+ 'top': parseInt(this.$fixedEl.css('margin-top'), 10),
+ 'bottom': parseInt(this.$fixedEl.css('margin-bottom'), 10),
+ };
+ };
+ StickyElement.prototype._getShimCSS = function () {
+ return {
+ 'width': this.horizontalSpace + 'px',
+ 'height': this.height + 'px',
+ 'margin-top': this.verticalMargins.top + 'px',
+ 'margin-bottom': this.verticalMargins.bottom + 'px'
+ };
};
StickyElement.prototype.stickyClass = function () {
return (this._sticky._initialPositionsSet) ? this._fixedClass : this._initialFixedClass;
@@ -20,44 +70,192 @@
StickyElement.prototype.appliedClass = function () {
return this._appliedClass;
};
+ StickyElement.prototype.removeStickyClasses = function (sticky) {
+ this.$fixedEl.removeClass([
+ this._initialFixedClass,
+ this._fixedClass
+ ].join(' '));
+ };
StickyElement.prototype.isStuck = function () {
return this._appliedClass !== null;
};
- StickyElement.prototype.stick = function () {
+ StickyElement.prototype.stick = function (sticky) {
this._appliedClass = this.stickyClass();
+ this.$fixedEl.addClass(this._appliedClass);
this._hasBeenCalled = true;
};
- StickyElement.prototype.release = function () {
+ StickyElement.prototype.release = function (sticky) {
this._appliedClass = null;
+ this.removeStickyClasses(sticky);
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._$shim = $(' 
');
+ this._$shim.css(this._getShimCSS());
this.$fixedEl[position](this._$shim);
};
StickyElement.prototype.removeShim = function () {
- this._$shim.remove();
- this._$shim = null;
+ if (this._$shim !== null) {
+ 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
- });
+ this._$shim.css(this._getShimCSS());
}
};
StickyElement.prototype.stop = function () {
- this._isStopped = true;
+ this._stopped = true;
};
StickyElement.prototype.unstop = function () {
- this._isStopped = false;
+ this._stopped = false;
};
StickyElement.prototype.isStopped = function () {
- return this._isStopped;
+ return this._stopped;
+ };
+ StickyElement.prototype.isInPage = function () {
+ var node = this.$fixedEl.get(0);
+
+ return (node === document.body) ? false : document.body.contains(node);
+ };
+ StickyElement.prototype.canBeStuck = function (val) {
+ if (val !== undefined) {
+ this._canBeStuck = val;
+ } else {
+ return this._canBeStuck;
+ }
+ };
+ StickyElement.prototype.hasLoaded = function (val) {
+ if (val !== undefined) {
+ this._hasLoaded = val;
+ } else {
+ return this._hasLoaded;
+ }
+ };
+
+ // Object collecting together methods for treating sticky elements as if they
+ // were wrapped by a dialog component
+ var dialog = {
+ _hasResized: false,
+ _getTotalHeight: function (els) {
+ var reducer = function (accumulator, currentValue) {
+ return accumulator + currentValue;
+ };
+ return $.map(els, function (el) { return el.height; }).reduce(reducer);
+ },
+ _elsThatCanBeStuck: function (els) {
+ return $.grep(els, function (el) { return el.canBeStuck(); });
+ },
+ getOffsetFromEdge: function (el, sticky) {
+ var els = this._elsThatCanBeStuck(sticky._els).slice();
+ var elIdx;
+
+ // els must be arranged furtherest from window edge is stuck to first
+ // default direction is order in document
+ if (sticky.edge === 'top') {
+ els.reverse();
+ }
+
+ elIdx = els.indexOf(el);
+
+ // if next to window edge the dialog is stuck to, no offset
+ if (elIdx === (els.length - 1)) { return 0; }
+
+ // get all els between this one and the window edge
+ els = els.slice(elIdx + 1);
+
+ return this._getTotalHeight(els);
+ },
+ getOffsetFromEnd: function (el, sticky) {
+ var els = this._elsThatCanBeStuck(sticky._els).slice();
+ var elIdx;
+
+ // els must be arranged furtherest from window edge is stuck to first
+ // default direction is order in document
+ if (sticky.edge === 'bottom') {
+ els.reverse();
+ }
+
+ elIdx = els.indexOf(el);
+
+ // if next to opposite edge to the one the dialog is stuck to, no offset
+ if (elIdx === (els.length - 1)) { return 0; }
+
+ // get all els between this one and the opposite edge
+ els = els.slice(elIdx + 1);
+
+ return this._getTotalHeight(els);
+ },
+ // checks total height of all this._sticky elements against a height
+ // unsticks each that won't fit and marks them as unstickable
+ fitToHeight: function (sticky) {
+ var self = this;
+ var els = sticky._els.slice();
+ var height = sticky.getWindowDimensions().height;
+ var totalStickyHeight = function () {
+ return self._getTotalHeight(self._elsThatCanBeStuck(els));
+ };
+ var dialogFitsHeight = function () {
+ return totalStickyHeight() <= height;
+ };
+
+ // els must be arranged furtherest from window edge is stuck to first
+ // default direction is order in document
+ if (sticky.edge === 'top') {
+ els.reverse();
+ }
+
+ // reset elements
+ $.each(els, function (i, el) { el.canBeStuck(true); });
+
+ while (self._elsThatCanBeStuck(els).length && !dialogFitsHeight()) {
+ var currentEl = self._elsThatCanBeStuck(els)[0];
+
+ sticky.reset(currentEl);
+ currentEl.canBeStuck(false);
+
+ if (!sticky._hasResized) { sticky._hasResized = true; }
+ }
+
+ return this._getTotalHeight(els);
+ },
+ getElementAtStickyEdge: function (sticky) {
+ var els = this._elsThatCanBeStuck(sticky._els);
+ var idx = (sticky.edge === 'top') ? 0 : els.length - 1;
+
+ return els[idx];
+ },
+ // get element at the end opposite the sticky edge
+ getElementAtOppositeEnd: function (sticky) {
+ var els = this._elsThatCanBeStuck(sticky._els);
+ var idx = (sticky.edge === 'top') ? els.length - 1 : 0;
+
+ return els[idx];
+ },
+ getInPageEdgePosition: function (sticky) {
+ return this.getElementAtStickyEdge(sticky).inPageEdgePosition;
+ },
+ getHeight: function (els) {
+ return this._getTotalHeight(this._elsThatCanBeStuck(els));
+ },
+ adjustForResize: function (sticky) {
+ var windowHeight = sticky.getWindowDimensions().height;
+
+ if (sticky.edge === 'top') {
+ $(window).scrollTop(this.getInPageEdgePosition(sticky));
+ } else {
+ $(window).scrollTop(this.getInPageEdgePosition(sticky) - windowHeight);
+ }
+
+ sticky._hasResized = false;
+ },
+ releaseEl: function (el, sticky) {
+ el.$fixedEl.css(sticky.edge, '');
+ }
};
// Constructor for objects collecting together all generic behaviour for controlling the state of
@@ -72,6 +270,7 @@
this._els = [];
this.CSS_SELECTOR = selector;
+ this.STOP_PADDING = 10;
};
Sticky.prototype.getWindowDimensions = function () {
return {
@@ -86,26 +285,30 @@
};
// 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,
- windowDimensions = self.getWindowDimensions();
+ var self = this,
+ windowDimensions = self.getWindowDimensions(),
+ windowTop = self.getWindowPositions().scrollTop,
+ windowPositions = {
+ 'top': windowTop,
+ 'bottom': windowTop + windowDimensions.height
+ };
+ var _setElementPosition = function (el) {
if (self.viewportIsWideEnough(windowDimensions.width)) {
- if (self.windowNotPastScrolledFrom(el.scrolledFrom)) {
- self.release(el);
- } else {
- if (self.windowNotPastScrolledTo(el, windowDimensions.height)) {
+ if (self.windowNotPastScrolledFrom(windowPositions, self.getScrolledFrom(el))) {
+ self.reset(el);
+ } else { // past the point it sits in the document
+ if (self.windowNotPastScrollingTo(windowPositions, self.getScrollingTo(el))) {
self.stick(el);
if (el.isStopped()) {
self.unstop(el);
}
- } else { // window past scrolledTo position
- if (!el.isStopped()) {
- self.stop(el);
+ } else { // window past scrollingTo position
+ if (!el.isStuck()) {
+ self.stick(el);
}
+ self.stop(el);
}
}
@@ -114,8 +317,20 @@
self.reset(el);
}
+ };
+
+ // clean up any existing styles marking the edges of sticky elements
+ oppositeEdge.unmark(self);
+
+ $.each(self._els, function (i, el) {
+ if (el.canBeStuck()) {
+ _setElementPosition(el);
+ }
});
+ // add styles to mark the edge of sticky elements opposite to that stuck to the window
+ oppositeEdge.mark(self);
+
if (self._initialPositionsSet === false) { self._initialPositionsSet = true; }
};
// Store all the dimensions for a sticky element to limit DOM queries
@@ -123,13 +338,10 @@
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();
}
@@ -140,21 +352,28 @@
};
// 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);
}
+ if (el.isStuck()) {
+ this.release(el);
+ }
};
// Recalculate stored dimensions for all sticky elements
- Sticky.prototype.recalculate = function () {
+ Sticky.prototype.recalculate = function (opts) {
var self = this;
+ var onSyncComplete = function () {
+ self.setEvents();
+ if (_mode === 'dialog') {
+ dialog.fitToHeight(self);
+ dialog.adjustForResize(self);
+ }
+ self.setElementPositions();
+ };
- $.each(self._els, function (i, el) {
- self.setElementDimensions(el);
- });
- self.setElementPositions();
+ if ((opts !== undefined) && ('mode' in opts)) { _mode = opts.mode; }
+
+ this.syncWithDOM(onSyncComplete);
};
Sticky.prototype.setElWidth = function (el) {
var $el = el.$fixedEl;
@@ -170,56 +389,119 @@
var self = this;
var $el = el.$fixedEl;
var $img = $el.find('img');
+ var onload = function () {
+ el.height = $el.outerHeight();
+ // if element has a shim, the shim's offset represents the element's in-page position
+ if (el._$shim) {
+ el.inPageEdgePosition = self.getInPageEdgePosition(el._$shim);
+ } else {
+ el.inPageEdgePosition = self.getInPageEdgePosition($el);
+ }
+ callback();
+ };
- if ((!self._elsLoaded) && ($img.length > 0)) {
+ if ((!el.hasLoaded()) && ($img.length > 0)) {
var image = new global.Image();
image.onload = function () {
- el.verticalSpace = $el.outerHeight(true);
- el.height = $el.outerHeight();
- callback();
+ onload();
};
image.src = $img.attr('src');
} else {
- el.verticalSpace = $el.outerHeight(true);
- el.height = $el.outerHeight();
- callback();
+ onload();
}
};
Sticky.prototype.allElementsLoaded = function (totalEls) {
return this._els.length === totalEls;
};
- Sticky.prototype.init = function () {
+ Sticky.prototype.hasEl = function (node) {
+ return !!$.grep(this._els, function (el) { return el.$fixedEl.is(node); }).length;
+ };
+ Sticky.prototype.add = function (el, setPositions, cb) {
+ var self = this;
+ var $el = $(el);
+ var onDimensionsSet;
+ var elObj;
+
+ // guard against adding elements already stored
+ if (this.hasEl(el)) { return; }
+
+ onDimensionsSet = function () {
+ elObj.hasLoaded(true);
+ self._els.push(elObj);
+ if (setPositions) {
+ self.setElementPositions();
+ }
+ if (cb !== undefined) {
+ cb();
+ }
+ };
+
+ elObj = new StickyElement($el, self);
+
+ self.setElementDimensions(elObj, onDimensionsSet);
+ };
+ Sticky.prototype.remove = function (el) {
+ if ($.inArray(el, this._els) !== -1) {
+
+ // reset DOM node to original state
+ this.reset(el);
+
+ // remove sticky element object
+ this._els = $.grep(this._els, function (_el) { return _el !== el; });
+ }
+ };
+ // gets all sticky elements in the DOM and removes any in this._els no longer in attached to it
+ Sticky.prototype.syncWithDOM = function (callback) {
var self = this;
var $els = $(self.CSS_SELECTOR);
var numOfEls = $els.length;
+ var onLoaded;
- if (numOfEls > 0) {
- $els.each(function (i, el) {
- var $el = $(el);
- var elObj = new StickyElement($el, self);
+ onLoaded = function () {
+ if (self._els.length === numOfEls) {
+ self.endOfScrollArea = self.getEndOfScrollArea();
+ if (callback !== undefined) {
+ callback();
+ }
+ }
+ };
- 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();
- }
- });
+ // remove any els no longer in the DOM
+ if (this._els.length) {
+ $.each(this._els, function (i, el) {
+ if (!el.isInPage()) {
+ self.remove(el);
+ }
});
+ }
- // 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);
- }
+ if (numOfEls) {
+ // reset flag marking page load
+ this._initialPositionsSet = false;
- // 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);
- }
+ $els.each(function (i, el) {
+ // delay setting position until all stickys are loaded
+ self.add(el, false, onLoaded);
+ });
+ }
+ };
+ Sticky.prototype.init = function (opts) {
+ this.recalculate(opts);
+ };
+ Sticky.prototype.setEvents = function () {
+ var self = this;
+
+ // 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) {
@@ -236,7 +518,7 @@
if (self._hasScrolled === true) {
self._hasScrolled = false;
- self.setElementPositions(true);
+ self.setElementPositions();
}
};
Sticky.prototype.checkResize = function () {
@@ -255,6 +537,10 @@
});
if (self.viewportIsWideEnough(windowWidth)) {
+ if (_mode === 'dialog') {
+ dialog.fitToHeight(self);
+ dialog.adjustForResize(self);
+ }
self.setElementPositions();
}
}
@@ -263,102 +549,182 @@
if (el.isStuck()) {
var $el = el.$fixedEl;
- $el.removeClass(el.appliedClass()).css('width', '');
+ el.removeStickyClasses(this);
+ $el.css('width', '');
+ if (_mode === 'dialog') {
+ dialog.releaseEl(el, this);
+ }
el.removeShim();
- el.release();
+ el.release(this);
}
};
// 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) {
+ stickAtTop.edge = 'top';
+ // Store furthest point sticky elements are allowed
+ stickAtTop.getEndOfScrollArea = function () {
var footer = $('.js-footer:eq(0)');
if (footer.length === 0) {
return 0;
}
- return (footer.offset().top - 10) - el.height;
+ return footer.offset().top - this.STOP_PADDING;
};
- stickAtTop.windowNotPastScrolledFrom = function (scrolledFrom) {
- var windowTop = this.getWindowPositions().scrollTop;
+ // position of the bottom edge when in the page flow
+ stickAtTop.getInPageEdgePosition = function ($el) {
+ return $el.offset().top;
+ };
+ stickAtTop.getScrolledFrom = function (el) {
+ if (_mode === 'dialog') {
+ return dialog.getInPageEdgePosition(this);
+ } else {
+ return el.inPageEdgePosition;
+ }
+ };
+ stickAtTop.getScrollingTo = function (el) {
+ var height = el.height;
- return scrolledFrom > windowTop;
+ if (_mode === 'dialog') {
+ height = dialog.getHeight(this._els);
+ }
+
+ return this.endOfScrollArea - height;
};
- stickAtTop.windowNotPastScrolledTo = function (el, windowHeight) {
- var windowTop = this.getWindowPositions().scrollTop;
-
- return windowTop < el.scrolledTo;
+ stickAtTop.getStoppingPosition = function (el) {
+ var offset = 0;
+
+ if (_mode === 'dialog') {
+ offset = dialog.getOffsetFromEnd(el, this);
+ }
+
+ return (this.endOfScrollArea - offset) - el.height;
+ };
+ stickAtTop.windowNotPastScrolledFrom = function (windowPositions, scrolledFrom) {
+ return scrolledFrom > windowPositions.top;
+ };
+ stickAtTop.windowNotPastScrollingTo = function (windowPositions, scrollingTo) {
+ return windowPositions.top < scrollingTo;
};
stickAtTop.stick = function (el) {
if (!el.isStuck()) {
var $el = el.$fixedEl;
+ var offset = 0;
+
+ if (_mode === 'dialog') {
+ offset = dialog.getOffsetFromEdge(el, this);
+ }
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();
+ $el.css({
+ // element will be absolutely positioned so cannot rely on parent element for width
+ 'width': $el.width() + 'px',
+ 'top': offset + 'px'
+ });
+ el.stick(this);
}
};
stickAtTop.stop = function (el) {
- el.$fixedEl.css({ 'position': 'absolute', 'top': el.scrolledTo });
- el.stop();
+ if (!el.isStopped()) {
+ el.$fixedEl.css({
+ 'position': 'absolute',
+ 'top': this.getStoppingPosition(el)
+ });
+ el.stop();
+ }
};
stickAtTop.unstop = function (el) {
- el.$fixedEl.css({ 'position': '', 'top': '' });
+ 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) {
+ stickAtBottom.edge = 'bottom';
+ // Store furthest point sticky elements are allowed
+ stickAtBottom.getEndOfScrollArea = function () {
var header = $('.js-header:eq(0)');
if (header.length === 0) {
return 0;
}
- return (header.offset().top + header.outerHeight() + 10) + el.height;
+ return (header.offset().top + header.outerHeight()) + this.STOP_PADDING;
};
- stickAtBottom.windowNotPastScrolledFrom = function (scrolledFrom) {
- var windowBottom = this.getWindowPositions().scrollTop + this.getWindowDimensions().height;
-
- return scrolledFrom < windowBottom;
+ // position of the bottom edge when in the page flow
+ stickAtBottom.getInPageEdgePosition = function ($el) {
+ return $el.offset().top + $el.outerHeight();
};
- stickAtBottom.windowNotPastScrolledTo = function (el, windowHeight) {
- var windowBottom = this.getWindowPositions().scrollTop + this.getWindowDimensions().height;
+ stickAtBottom.getScrolledFrom = function (el) {
+ if (_mode === 'dialog') {
+ return dialog.getInPageEdgePosition(this);
+ } else {
+ return el.inPageEdgePosition;
+ }
+ };
+ stickAtBottom.getScrollingTo = function (el) {
+ var height = el.height;
- return windowBottom > el.scrolledTo;
+ if (_mode === 'dialog') {
+ height = dialog.getHeight(this._els);
+ }
+
+ return this.endOfScrollArea + height;
+ };
+ stickAtBottom.getStoppingPosition = function (el) {
+ var offset = 0;
+
+ if (_mode === 'dialog') {
+ offset = dialog.getOffsetFromEnd(el, this);
+ }
+
+ return this.endOfScrollArea + offset;
+ };
+ stickAtBottom.windowNotPastScrolledFrom = function (windowPositions, scrolledFrom) {
+ return scrolledFrom < windowPositions.bottom;
+ };
+ stickAtBottom.windowNotPastScrollingTo = function (windowPositions, scrollingTo) {
+ return windowPositions.bottom > scrollingTo;
};
stickAtBottom.stick = function (el) {
if (!el.isStuck()) {
var $el = el.$fixedEl;
+ var offset = 0;
+
+ if (_mode === 'dialog') {
+ offset = dialog.getOffsetFromEdge(el, this);
+ }
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();
+ $el.css({
+ // element will be absolutely positioned so cannot rely on parent element for width
+ 'width': $el.width() + 'px',
+ 'bottom': offset + 'px'
+ });
+ el.stick(this);
}
};
stickAtBottom.stop = function (el) {
- el.$fixedEl.css({
- 'position': 'absolute',
- 'top': (el.scrolledTo - el.height),
- 'bottom': 'auto'
- });
- el.stop();
+ if (!el.isStopped()) {
+ el.$fixedEl.css({
+ 'position': 'absolute',
+ 'top': this.getStoppingPosition(el),
+ 'bottom': 'auto'
+ });
+ el.stop();
+ }
};
stickAtBottom.unstop = function (el) {
+ var offset = 0;
+
+ if (_mode === 'dialog') {
+ offset = dialog.getOffsetFromEdge(el, this);
+ }
+
el.$fixedEl.css({
'position': '',
'top': '',
- 'bottom': ''
+ 'bottom': offset + 'px'
});
el.unstop();
};
diff --git a/app/assets/javascripts/templateFolderForm.js b/app/assets/javascripts/templateFolderForm.js
index 5f9079fbf..dcd91a5a1 100644
--- a/app/assets/javascripts/templateFolderForm.js
+++ b/app/assets/javascripts/templateFolderForm.js
@@ -29,6 +29,9 @@
this.states.filter(state => state.cancellable).forEach((x) => this.addCancelButton(x));
this.states.filter(state => state.key === 'items-selected-buttons').forEach(x => this.addClearButton(x));
+ // activate stickiness of elements in each state
+ this.activateStickyElements();
+
// first off show the new template / new folder buttons
this.currentState = this.$form.data('prev-state') || 'unknown';
if (this.currentState === 'unknown') {
@@ -41,6 +44,18 @@
this.$form.on('change', 'input[type=checkbox]', () => this.templateFolderCheckboxChanged());
};
+ this.activateStickyElements = function() {
+ var oldClass = 'js-will-stick-at-bottom-when-scrolling';
+ var newClass = 'js-stick-at-bottom-when-scrolling';
+
+ this.states.forEach(state => {
+ state.$el
+ .find('.' + oldClass)
+ .removeClass(oldClass)
+ .addClass(newClass);
+ });
+ };
+
this.addCancelButton = function(state) {
let $cancel = this.makeButton('Cancel', () => {
@@ -130,30 +145,33 @@
);
// make sticky JS recalculate its cache of the element's position
- if ('stickAtBottomWhenScrolling' in GOVUK) {
- GOVUK.stickAtBottomWhenScrolling.recalculate();
- }
+ // use dialog mode for states which contain more than one form control
+ GOVUK.stickAtBottomWhenScrolling.recalculate({ 'mode': 'dialog' });
};
- this.nothingSelectedButtons = `
+ this.nothingSelectedButtons = $(`
-
-
-
- `;
+ `).get(0);
- this.itemsSelectedButtons = `
+ this.itemsSelectedButtons = $(`
-
-
-
- `;
+ `).get(0);
};
})(window.GOVUK.Modules);
diff --git a/app/assets/stylesheets/components/stick-at-top-when-scrolling.scss b/app/assets/stylesheets/components/stick-at-top-when-scrolling.scss
index 062d900f7..9c5cea1f1 100644
--- a/app/assets/stylesheets/components/stick-at-top-when-scrolling.scss
+++ b/app/assets/stylesheets/components/stick-at-top-when-scrolling.scss
@@ -72,6 +72,7 @@
background: $white;
z-index: 100;
padding-right: $gutter-half;
+ margin-top: 0;
.back-to-top-link {
opacity: 1;
@@ -85,6 +86,11 @@
top: 0;
margin-top: 0;
+
+}
+
+.js-stick-at-top-when-scrolling.content-fixed__top {
+
border-bottom: 1px solid $border-colour;
box-shadow: 0 2px 0 0 rgba($border-colour, 0.2);
@@ -101,6 +107,11 @@
top: auto; // cancel `top: 0;` inherited from govuk-template
bottom: 0;
+
+}
+
+.js-stick-at-bottom-when-scrolling.content-fixed__bottom {
+
border-top: 1px solid $border-colour;
box-shadow: 0 -2px 0 0 rgba($border-colour, 0.2);
diff --git a/app/templates/components/live-search.html b/app/templates/components/live-search.html
index bbc31f675..6c7b1fad8 100644
--- a/app/templates/components/live-search.html
+++ b/app/templates/components/live-search.html
@@ -9,7 +9,7 @@
{%- set search_label = label or form.search.label.text %}
{% if show %}