From 7b3ac5510398da7924896d8316ef0064928cb618 Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Mon, 7 Jan 2019 16:51:28 +0000 Subject: [PATCH 01/13] Add method for adding a sticky element --- .../stick-to-window-when-scrolling.js | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/stick-to-window-when-scrolling.js b/app/assets/javascripts/stick-to-window-when-scrolling.js index c7296d12d..9ef98e56b 100644 --- a/app/assets/javascripts/stick-to-window-when-scrolling.js +++ b/app/assets/javascripts/stick-to-window-when-scrolling.js @@ -188,24 +188,38 @@ Sticky.prototype.allElementsLoaded = function (totalEls) { return this._els.length === totalEls; }; + Sticky.prototype.add = function (el, setPositions, cb) { + var self = this; + var $el = $(el); + var elObj = new StickyElement($el, self); + + self.setElementDimensions(elObj, function () { + self._els.push(elObj); + if (setPositions) { + self.setElementPositions(); + } + if (cb !== undefined) { + cb(); + } + }); + }; Sticky.prototype.init = function () { var self = this; var $els = $(self.CSS_SELECTOR); var numOfEls = $els.length; + var onAllLoaded = function () { + if (self._els.length === numOfEls) { + self._elsLoaded = true; + + // set positions based on initial scroll position + self.setElementPositions(); + } + }; if (numOfEls > 0) { $els.each(function (i, el) { - var $el = $(el); - var elObj = new StickyElement($el, self); - - 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(); - } - }); + // delay setting position until all stickys are loaded + self.add(el, false, onAllLoaded); }); // flag when scrolling takes place and check (and re-position) sticky elements relative to From c0706b9cefe64ce90512e7f2fba8269ff5205692 Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Tue, 8 Jan 2019 15:57:55 +0000 Subject: [PATCH 02/13] Split move_to forms into separate sticky elements --- app/assets/javascripts/templateFolderForm.js | 35 +++++++++++++------- app/templates/views/templates/_move_to.html | 14 ++++++-- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/app/assets/javascripts/templateFolderForm.js b/app/assets/javascripts/templateFolderForm.js index 5f9079fbf..93bf26d80 100644 --- a/app/assets/javascripts/templateFolderForm.js +++ b/app/assets/javascripts/templateFolderForm.js @@ -13,22 +13,33 @@ this.$stickyBottom = this.$form.find('#sticky_template_forms'); this.$stickyBottom.append(this.nothingSelectedButtons); + GOVUK.stickAtBottomWhenScrolling.add(this.nothingSelectedButtons, true); this.$stickyBottom.append(this.itemsSelectedButtons); + GOVUK.stickAtBottomWhenScrolling.add(this.itemsSelectedButtons, true); // all the diff states that we want to show or hide this.states = [ - {key: 'nothing-selected-buttons', $el: this.$form.find('#nothing_selected'), cancellable: false}, - {key: 'items-selected-buttons', $el: this.$form.find('#items_selected'), cancellable: false}, - {key: 'move-to-existing-folder', $el: this.$form.find('#move_to_folder_radios'), cancellable: true}, - {key: 'move-to-new-folder', $el: this.$form.find('#move_to_new_folder_form'), cancellable: true}, - {key: 'add-new-folder', $el: this.$form.find('#add_new_folder_form'), cancellable: true}, - {key: 'add-new-template', $el: this.$form.find('#add_new_template_form'), cancellable: true} + {key: 'nothing-selected-buttons', $el: this.$form.find('#nothing_selected'), cancellable: false, isStickyGroup: false}, + {key: 'items-selected-buttons', $el: this.$form.find('#items_selected'), cancellable: false, isStickyGroup: false}, + {key: 'move-to-existing-folder', $el: this.$form.find('#move_to_folder_radios'), cancellable: true, isStickyGroup: true}, + {key: 'move-to-new-folder', $el: this.$form.find('#move_to_new_folder_form'), cancellable: true, isStickyGroup: false}, + {key: 'add-new-folder', $el: this.$form.find('#add_new_folder_form'), cancellable: true, isStickyGroup: false}, + {key: 'add-new-template', $el: this.$form.find('#add_new_template_form'), cancellable: true, isStickyGroup: true} ]; // cancel/clear buttons only relevant if JS enabled, so 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)); + // add all sticky elements from states + this.states.forEach(state => { + if (state.isStickyGroup) { + state.$el.find(".js-stick-at-bottom-when-scrolling").each((idx, el) => { + GOVUK.stickAtBottomWhenScrolling.add(el, false); + }); + } + }); + // first off show the new template / new folder buttons this.currentState = this.$form.data('prev-state') || 'unknown'; if (this.currentState === 'unknown') { @@ -135,25 +146,25 @@ } }; - this.nothingSelectedButtons = ` -
+ this.nothingSelectedButtons = $(` +
Nothing selected
- `; + `).get(0); - this.itemsSelectedButtons = ` -
+ this.itemsSelectedButtons = $(` +
1 selected
- `; + `).get(0); }; })(window.GOVUK.Modules); diff --git a/app/templates/views/templates/_move_to.html b/app/templates/views/templates/_move_to.html index 543905884..cf752d9b8 100644 --- a/app/templates/views/templates/_move_to.html +++ b/app/templates/views/templates/_move_to.html @@ -1,15 +1,19 @@ {% from "components/radios.html" import radios, radios_nested %} {% from "components/page-footer.html" import page_footer %} -
+
{% if templates_and_folders_form.move_to.choices and template_list.templates_to_show %}
+
{{ radios_nested(templates_and_folders_form.move_to, move_to_children, option_hints=option_hints) }} +
+
{{ page_footer('Move', button_name='operation', button_value='move-to-existing-folder') }} +
-
+
Move to a new folder {{ textbox(templates_and_folders_form.move_to_new_folder_name) }} @@ -17,7 +21,7 @@
{% endif %} -
+
Add a new folder {{ textbox(templates_and_folders_form.add_new_folder_name) }} @@ -27,8 +31,12 @@
Add a new template +
{{ radios(templates_and_folders_form.add_template_by_template_type) }} +
+
{{ page_footer('Continue', button_name='operation', button_value='add-new-template') }} +
From ea675f45da518428487bcc732a57bccd7c971236 Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Tue, 22 Jan 2019 10:18:25 +0000 Subject: [PATCH 03/13] Make the sticky states more robust If the screen resizes, sticky elements can end up moving from one part of the screen to another, sometimes without passing through their different states (in-page, stuck, stopped) in the normal order. This makes the methods that change their states better at dealing with those state changes. --- .../stick-to-window-when-scrolling.js | 79 ++++++++++--------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/app/assets/javascripts/stick-to-window-when-scrolling.js b/app/assets/javascripts/stick-to-window-when-scrolling.js index 9ef98e56b..31bc45d48 100644 --- a/app/assets/javascripts/stick-to-window-when-scrolling.js +++ b/app/assets/javascripts/stick-to-window-when-scrolling.js @@ -86,26 +86,30 @@ }; // Change state of sticky elements based on their position relative to the window Sticky.prototype.setElementPositions = function () { - var self = this; + var self = this, + windowDimensions = self.getWindowDimensions(), + windowTop = self.getWindowPositions().scrollTop, + windowPositions = { + 'top': windowTop, + 'bottom': windowTop + windowDimensions.height + }; $.each(self._els, function (i, el) { - var $el = el.$fixedEl, - windowDimensions = self.getWindowDimensions(); - 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); } } @@ -140,12 +144,12 @@ }; // 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 () { @@ -297,15 +301,11 @@ } return (footer.offset().top - 10) - el.height; }; - stickAtTop.windowNotPastScrolledFrom = function (scrolledFrom) { - var windowTop = this.getWindowPositions().scrollTop; - - return scrolledFrom > windowTop; + stickAtTop.windowNotPastScrolledFrom = function (windowPositions, scrolledFrom) { + return scrolledFrom > windowPositions.top; }; - stickAtTop.windowNotPastScrolledTo = function (el, windowHeight) { - var windowTop = this.getWindowPositions().scrollTop; - - return windowTop < el.scrolledTo; + stickAtTop.windowNotPastScrollingTo = function (windowPositions, scrollingTo) { + return windowPositions.top < scrollingTo; }; stickAtTop.stick = function (el) { if (!el.isStuck()) { @@ -318,8 +318,13 @@ } }; stickAtTop.stop = function (el) { - el.$fixedEl.css({ 'position': 'absolute', 'top': el.scrolledTo }); - el.stop(); + if (!el.isStopped()) { + el.$fixedEl.css({ + 'position': 'absolute', + 'top': el.scrolledTo + }); + el.stop(); + } }; stickAtTop.unstop = function (el) { el.$fixedEl.css({ 'position': '', 'top': '' }); @@ -340,15 +345,11 @@ } return (header.offset().top + header.outerHeight() + 10) + el.height; }; - stickAtBottom.windowNotPastScrolledFrom = function (scrolledFrom) { - var windowBottom = this.getWindowPositions().scrollTop + this.getWindowDimensions().height; - - return scrolledFrom < windowBottom; + stickAtBottom.windowNotPastScrolledFrom = function (windowPositions, scrolledFrom) { + return scrolledFrom < windowPositions.bottom; }; - stickAtBottom.windowNotPastScrolledTo = function (el, windowHeight) { - var windowBottom = this.getWindowPositions().scrollTop + this.getWindowDimensions().height; - - return windowBottom > el.scrolledTo; + stickAtBottom.windowNotPastScrollingTo = function (windowPositions, scrollingTo) { + return windowPositions.bottom > scrollingTo; }; stickAtBottom.stick = function (el) { if (!el.isStuck()) { @@ -361,12 +362,14 @@ } }; 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': (el.scrolledTo - el.height), + 'bottom': 'auto' + }); + el.stop(); + } }; stickAtBottom.unstop = function (el) { el.$fixedEl.css({ From 03e38dfdef9490b67ad56a6fce1964144e0964f3 Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Tue, 8 Jan 2019 17:30:46 +0000 Subject: [PATCH 04/13] Add mode for grouping sticky elements as a dialog We want a mode for when a single task is shared between all sticky elements on the page and that task has the highest priority on the page. In that case: - they should stack together into a single block, attached to the top/bottom of the viewport - that block should adjust to the vertical space available This should also adjust to the height of the viewport, dropping whatever elements that don't fit back into the page flow. When this happens, we scroll the page so all the parts of the dialog are seen together at the start of the task. --- .../stick-to-window-when-scrolling.js | 309 ++++++++++++++++-- app/assets/javascripts/templateFolderForm.js | 14 +- .../stick-at-top-when-scrolling.scss | 1 + 3 files changed, 280 insertions(+), 44 deletions(-) diff --git a/app/assets/javascripts/stick-to-window-when-scrolling.js b/app/assets/javascripts/stick-to-window-when-scrolling.js index 31bc45d48..e744201c9 100644 --- a/app/assets/javascripts/stick-to-window-when-scrolling.js +++ b/app/assets/javascripts/stick-to-window-when-scrolling.js @@ -3,6 +3,7 @@ var $ = global.jQuery; var GOVUK = global.GOVUK || {}; + var _mode = 'default'; // Constructor for objects holding data for each element to have sticky behaviour var StickyElement = function ($el, sticky) { @@ -13,6 +14,7 @@ this._appliedClass = null; this._$shim = null; this._stopped = false; + this._canBeStuck = true; }; StickyElement.prototype.stickyClass = function () { return (this._sticky._initialPositionsSet) ? this._fixedClass : this._initialFixedClass; @@ -25,6 +27,7 @@ }; StickyElement.prototype.stick = function () { this._appliedClass = this.stickyClass(); + this.$fixedEl.addClass(this._appliedClass); this._hasBeenCalled = true; }; StickyElement.prototype.release = function () { @@ -34,7 +37,7 @@ // 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.$fixedEl[position](this._$shim); }; StickyElement.prototype.removeShim = function () { @@ -45,7 +48,7 @@ StickyElement.prototype.updateShim = function () { if (this._$shim) { this._$shim.css({ - 'height': this.verticalSpace, + 'height': this.inPageVerticalSpace, 'width': this.horizontalSpace }); } @@ -59,6 +62,143 @@ StickyElement.prototype.isStopped = function () { return this._isStopped; }; + 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(); }); + }, + hasOppositeEdge: function (el, sticky) { + var els = this._elsThatCanBeStuck(sticky._els); + var idx; + + if (els.length < 2) { return true; } + + idx = els.indexOf(el); + + return (sticky.edge === 'top') ? idx === (els.length - 1) : idx === 0; + }, + 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 elsThatCanBeStuck = function () { + return $.grep(els, function (el) { return el.canBeStuck(); }); + }; + var totalStickyHeight = function () { + return self._getTotalHeight(elsThatCanBeStuck()); + }; + 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 (elsThatCanBeStuck().length && !dialogFitsHeight()) { + var currentEl = elsThatCanBeStuck()[0]; + + sticky.reset(currentEl); + currentEl.canBeStuck(false); + + if (!sticky._hasResized) { sticky._hasResized = true; } + } + + return this._getTotalHeight(els); + }, + getInPageEdgePosition: function (sticky) { + var idx = (sticky.edge === 'top') ? 0 : sticky._els.length - 1; + + return sticky._els[idx].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 // sticky elements @@ -72,6 +212,7 @@ this._els = []; this.CSS_SELECTOR = selector; + this.STOP_PADDING = 10; }; Sticky.prototype.getWindowDimensions = function () { return { @@ -94,7 +235,7 @@ 'bottom': windowTop + windowDimensions.height }; - $.each(self._els, function (i, el) { + var _setElementPosition = function (el) { if (self.viewportIsWideEnough(windowDimensions.width)) { if (self.windowNotPastScrolledFrom(windowPositions, self.getScrolledFrom(el))) { @@ -118,6 +259,12 @@ self.reset(el); } + }; + + $.each(self._els, function (i, el) { + if (el.canBeStuck()) { + _setElementPosition(el); + } }); if (self._initialPositionsSet === false) { self._initialPositionsSet = true; } @@ -127,13 +274,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(); } @@ -152,13 +296,21 @@ } }; // Recalculate stored dimensions for all sticky elements - Sticky.prototype.recalculate = function () { + Sticky.prototype.recalculate = function (opts) { var self = this; + var onDimensionsSet = function () { + if (_mode === 'dialog') { + dialog.fitToHeight(self); + dialog.adjustForResize(self); + } + self.setElementPositions(); + }; + + if ((opts !== undefined) && ('mode' in opts)) { _mode = opts.mode; } $.each(self._els, function (i, el) { - self.setElementDimensions(el); + self.setElementDimensions(el, onDimensionsSet); }); - self.setElementPositions(); }; Sticky.prototype.setElWidth = function (el) { var $el = el.$fixedEl; @@ -178,14 +330,16 @@ if ((!self._elsLoaded) && ($img.length > 0)) { var image = new global.Image(); image.onload = function () { - el.verticalSpace = $el.outerHeight(true); + el.inPageVerticalSpace = $el.outerHeight(true); el.height = $el.outerHeight(); + el.inPageEdgePosition = self.getInPageEdgePosition(el); callback(); }; image.src = $img.attr('src'); } else { - el.verticalSpace = $el.outerHeight(true); + el.inPageVerticalSpace = $el.outerHeight(true); el.height = $el.outerHeight(); + el.inPageEdgePosition = self.getInPageEdgePosition(el); callback(); } }; @@ -207,19 +361,22 @@ } }); }; - Sticky.prototype.init = function () { + Sticky.prototype.init = function (opts) { var self = this; var $els = $(self.CSS_SELECTOR); var numOfEls = $els.length; var onAllLoaded = function () { if (self._els.length === numOfEls) { self._elsLoaded = true; + self.endOfScrollArea = self.getEndOfScrollArea(); // set positions based on initial scroll position self.setElementPositions(); } }; + if ((opts !== undefined) && ('mode' in opts)) { _mode = opts.mode; } + if (numOfEls > 0) { $els.each(function (i, el) { // delay setting position until all stickys are loaded @@ -273,6 +430,10 @@ }); if (self.viewportIsWideEnough(windowWidth)) { + if (_mode === 'dialog') { + dialog.fitToHeight(self); + dialog.adjustForResize(self); + } self.setElementPositions(); } } @@ -282,6 +443,9 @@ var $el = el.$fixedEl; $el.removeClass(el.appliedClass()).css('width', ''); + if (_mode === 'dialog') { + dialog.releaseEl(el, this); + } el.removeShim(); el.release(); } @@ -289,17 +453,43 @@ // 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; + }; + // position of the bottom edge when in the page flow + stickAtTop.getInPageEdgePosition = function (el) { + return el.$fixedEl.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; + + if (_mode === 'dialog') { + height = dialog.getHeight(this._els); + } + + return this.endOfScrollArea - height; + }; + 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; @@ -310,10 +500,18 @@ 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.css({ + // element will be absolutely positioned so cannot rely on parent element for width + 'width': $el.width() + 'px', + 'top': offset + 'px' + }); el.stick(); } }; @@ -321,29 +519,58 @@ if (!el.isStopped()) { el.$fixedEl.css({ 'position': 'absolute', - 'top': el.scrolledTo + '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; + }; + // position of the bottom edge when in the page flow + stickAtBottom.getInPageEdgePosition = function (el) { + return el.$fixedEl.offset().top + el.height; + }; + stickAtBottom.getScrolledFrom = function (el) { + if (_mode === 'dialog') { + return dialog.getInPageEdgePosition(this); + } else { + return el.inPageEdgePosition; + } + }; + stickAtBottom.getScrollingTo = function (el) { + var height = el.height; + + 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; @@ -354,10 +581,18 @@ 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.css({ + // element will be absolutely positioned so cannot rely on parent element for width + 'width': $el.width() + 'px', + 'bottom': offset + 'px' + }); el.stick(); } }; @@ -365,17 +600,23 @@ if (!el.isStopped()) { el.$fixedEl.css({ 'position': 'absolute', - 'top': (el.scrolledTo - el.height), + '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 93bf26d80..74f616f8a 100644 --- a/app/assets/javascripts/templateFolderForm.js +++ b/app/assets/javascripts/templateFolderForm.js @@ -31,15 +31,6 @@ 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)); - // add all sticky elements from states - this.states.forEach(state => { - if (state.isStickyGroup) { - state.$el.find(".js-stick-at-bottom-when-scrolling").each((idx, el) => { - GOVUK.stickAtBottomWhenScrolling.add(el, false); - }); - } - }); - // first off show the new template / new folder buttons this.currentState = this.$form.data('prev-state') || 'unknown'; if (this.currentState === 'unknown') { @@ -141,7 +132,10 @@ ); // make sticky JS recalculate its cache of the element's position - if ('stickAtBottomWhenScrolling' in GOVUK) { + // use dialog mode for states which contain more than one form control + if (['move-to-existing-folder', 'add-new-template'].includes(this.currentState)) { + GOVUK.stickAtBottomWhenScrolling.recalculate({ 'mode': 'dialog' }); + } else { GOVUK.stickAtBottomWhenScrolling.recalculate(); } }; 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..9a6ced38a 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; From a6a7057d645b9480bbe85a4633a8320add32f820 Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Thu, 10 Jan 2019 17:29:41 +0000 Subject: [PATCH 05/13] Deal with elements no longer in the DOM Instead of keeping references to nodes detached from the DOM, remove them from the store. Likewise, add node appended to the DOM. This includes code to 'clean' DOM nodes when removed. This is important because nodes can retain classes and styles. If they are re-attached in future this can cause problems with how the state of the element is determined. --- .../stick-to-window-when-scrolling.js | 45 +++++++++++++++---- app/assets/javascripts/templateFolderForm.js | 2 - 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/stick-to-window-when-scrolling.js b/app/assets/javascripts/stick-to-window-when-scrolling.js index e744201c9..33aa092c8 100644 --- a/app/assets/javascripts/stick-to-window-when-scrolling.js +++ b/app/assets/javascripts/stick-to-window-when-scrolling.js @@ -22,6 +22,9 @@ StickyElement.prototype.appliedClass = function () { return this._appliedClass; }; + StickyElement.prototype.removeStickyClasses = function () { + this.$fixedEl.removeClass([this._initialFixedClass, this._fixedClass].join(' ')); + }; StickyElement.prototype.isStuck = function () { return this._appliedClass !== null; }; @@ -41,8 +44,10 @@ 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 () { @@ -54,13 +59,18 @@ } }; 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) { @@ -346,10 +356,18 @@ Sticky.prototype.allElementsLoaded = function (totalEls) { return this._els.length === totalEls; }; + 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 elObj = new StickyElement($el, self); + var elObj; + + // guard against adding elements already stored + if (this.hasEl(el)) { return; } + + elObj= new StickyElement($el, self); self.setElementDimensions(elObj, function () { self._els.push(elObj); @@ -361,6 +379,16 @@ } }); }; + 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; }); + } + }; Sticky.prototype.init = function (opts) { var self = this; var $els = $(self.CSS_SELECTOR); @@ -411,7 +439,7 @@ if (self._hasScrolled === true) { self._hasScrolled = false; - self.setElementPositions(true); + self.setElementPositions(); } }; Sticky.prototype.checkResize = function () { @@ -442,7 +470,8 @@ if (el.isStuck()) { var $el = el.$fixedEl; - $el.removeClass(el.appliedClass()).css('width', ''); + el.removeStickyClasses(); + $el.css('width', ''); if (_mode === 'dialog') { dialog.releaseEl(el, this); } diff --git a/app/assets/javascripts/templateFolderForm.js b/app/assets/javascripts/templateFolderForm.js index 74f616f8a..01cc8bab3 100644 --- a/app/assets/javascripts/templateFolderForm.js +++ b/app/assets/javascripts/templateFolderForm.js @@ -13,9 +13,7 @@ this.$stickyBottom = this.$form.find('#sticky_template_forms'); this.$stickyBottom.append(this.nothingSelectedButtons); - GOVUK.stickAtBottomWhenScrolling.add(this.nothingSelectedButtons, true); this.$stickyBottom.append(this.itemsSelectedButtons); - GOVUK.stickAtBottomWhenScrolling.add(this.itemsSelectedButtons, true); // all the diff states that we want to show or hide this.states = [ From 1e8e8908ee2ecac82e2aee7ca43ee03c4a0b1a0b Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Tue, 15 Jan 2019 11:59:38 +0000 Subject: [PATCH 06/13] Move sync'ing this._els with DOM into own method We'll need to run this whenever recalculate runs to ensure `this._els` matches the present state of the DOM. --- .../stick-to-window-when-scrolling.js | 77 ++++++++++++------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/app/assets/javascripts/stick-to-window-when-scrolling.js b/app/assets/javascripts/stick-to-window-when-scrolling.js index 33aa092c8..63e7e0b1d 100644 --- a/app/assets/javascripts/stick-to-window-when-scrolling.js +++ b/app/assets/javascripts/stick-to-window-when-scrolling.js @@ -14,6 +14,7 @@ this._appliedClass = null; this._$shim = null; this._stopped = false; + this._hasLoaded = false; this._canBeStuck = true; }; StickyElement.prototype.stickyClass = function () { @@ -308,7 +309,7 @@ // Recalculate stored dimensions for all sticky elements Sticky.prototype.recalculate = function (opts) { var self = this; - var onDimensionsSet = function () { + var onSyncComplete = function () { if (_mode === 'dialog') { dialog.fitToHeight(self); dialog.adjustForResize(self); @@ -318,9 +319,7 @@ if ((opts !== undefined) && ('mode' in opts)) { _mode = opts.mode; } - $.each(self._els, function (i, el) { - self.setElementDimensions(el, onDimensionsSet); - }); + this.syncWithDOM(onSyncComplete); }; Sticky.prototype.setElWidth = function (el) { var $el = el.$fixedEl; @@ -336,21 +335,21 @@ var self = this; var $el = el.$fixedEl; var $img = $el.find('img'); - - if ((!self._elsLoaded) && ($img.length > 0)) { - var image = new global.Image(); - image.onload = function () { - el.inPageVerticalSpace = $el.outerHeight(true); - el.height = $el.outerHeight(); - el.inPageEdgePosition = self.getInPageEdgePosition(el); - callback(); - }; - image.src = $img.attr('src'); - } else { + var onload = function () { el.inPageVerticalSpace = $el.outerHeight(true); el.height = $el.outerHeight(); el.inPageEdgePosition = self.getInPageEdgePosition(el); callback(); + }; + + if ((!el.hasLoaded()) && ($img.length > 0)) { + var image = new global.Image(); + image.onload = function () { + onload(); + }; + image.src = $img.attr('src'); + } else { + onload(); } }; Sticky.prototype.allElementsLoaded = function (totalEls) { @@ -362,14 +361,14 @@ 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; } - elObj= new StickyElement($el, self); - - self.setElementDimensions(elObj, function () { + onDimensionsSet = function () { + elObj.hasLoaded(true); self._els.push(elObj); if (setPositions) { self.setElementPositions(); @@ -377,7 +376,11 @@ if (cb !== undefined) { cb(); } - }); + }; + + elObj = new StickyElement($el, self); + + self.setElementDimensions(elObj, onDimensionsSet); }; Sticky.prototype.remove = function (el) { if ($.inArray(el, this._els) !== -1) { @@ -389,28 +392,42 @@ this._els = $.grep(this._els, function (_el) { return _el !== el; }); } }; - Sticky.prototype.init = function (opts) { + // 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 onAllLoaded = function () { - if (self._els.length === numOfEls) { - self._elsLoaded = true; - self.endOfScrollArea = self.getEndOfScrollArea(); + var onLoaded; - // set positions based on initial scroll position - self.setElementPositions(); + onLoaded = function () { + if (self._els.length === numOfEls) { + self.endOfScrollArea = self.getEndOfScrollArea(); + if (callback !== undefined) { + callback(); + } } }; - if ((opts !== undefined) && ('mode' in opts)) { _mode = opts.mode; } + // remove any els no longer in the DOM + if (this._els.length) { + $.each(this._els, function (i, el) { + if (!el.isInPage()) { + self.remove(el); + } + }); + } - if (numOfEls > 0) { + if (numOfEls) { $els.each(function (i, el) { // delay setting position until all stickys are loaded - self.add(el, false, onAllLoaded); + self.add(el, false, onLoaded); }); + } + }; + Sticky.prototype.init = function (opts) { + var self = this; + if (this._els.length) { // flag when scrolling takes place and check (and re-position) sticky elements relative to // window position if (self._scrollTimeout === false) { @@ -424,6 +441,8 @@ self._resizeTimeout = global.setInterval(function (e) { self.checkResize(); }, 50); } } + + this.recalculate(opts); }; Sticky.prototype.viewportIsWideEnough = function (windowWidth) { return windowWidth > 768; From 4e845c3125be2f5329a98c745571f8ac6c5defbb Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Thu, 17 Jan 2019 11:45:10 +0000 Subject: [PATCH 07/13] Move setting of events into separate method --- .../stick-to-window-when-scrolling.js | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/stick-to-window-when-scrolling.js b/app/assets/javascripts/stick-to-window-when-scrolling.js index 63e7e0b1d..4630569d7 100644 --- a/app/assets/javascripts/stick-to-window-when-scrolling.js +++ b/app/assets/javascripts/stick-to-window-when-scrolling.js @@ -310,6 +310,7 @@ Sticky.prototype.recalculate = function (opts) { var self = this; var onSyncComplete = function () { + self.setEvents(); if (_mode === 'dialog') { dialog.fitToHeight(self); dialog.adjustForResize(self); @@ -425,24 +426,23 @@ } }; Sticky.prototype.init = function (opts) { + this.recalculate(opts); + }; + Sticky.prototype.setEvents = function () { var self = this; - if (this._els.length) { - // 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); - } + // 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); } - this.recalculate(opts); + // 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; From a9b7a0d88780188b796649c20869208a400b35ee Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Fri, 18 Jan 2019 11:44:59 +0000 Subject: [PATCH 08/13] Add box shadow by JS When mode === 'dialog', sticky elements are stacked so we need to apply the box shadow to the top (when sticking to the bottom edge) and bottom (when sticking to the top edge) elements in the stack. From what I can see, we need the version of `nth-child` that supports targeting by selector. As of this date, support for this is only in Safari: https://caniuse.com/#feat=css-nth-child-of Until we can use this version of `nth-child`, we need to use JS to apply the styles. --- .../stick-to-window-when-scrolling.js | 97 +++++++++++++------ .../stick-at-top-when-scrolling.scss | 10 ++ 2 files changed, 80 insertions(+), 27 deletions(-) diff --git a/app/assets/javascripts/stick-to-window-when-scrolling.js b/app/assets/javascripts/stick-to-window-when-scrolling.js index 4630569d7..d18ccda41 100644 --- a/app/assets/javascripts/stick-to-window-when-scrolling.js +++ b/app/assets/javascripts/stick-to-window-when-scrolling.js @@ -5,6 +5,41 @@ 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) { this._sticky = sticky; @@ -23,19 +58,23 @@ StickyElement.prototype.appliedClass = function () { return this._appliedClass; }; - StickyElement.prototype.removeStickyClasses = function () { - this.$fixedEl.removeClass([this._initialFixedClass, this._fixedClass].join(' ')); + 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 @@ -101,16 +140,6 @@ _elsThatCanBeStuck: function (els) { return $.grep(els, function (el) { return el.canBeStuck(); }); }, - hasOppositeEdge: function (el, sticky) { - var els = this._elsThatCanBeStuck(sticky._els); - var idx; - - if (els.length < 2) { return true; } - - idx = els.indexOf(el); - - return (sticky.edge === 'top') ? idx === (els.length - 1) : idx === 0; - }, getOffsetFromEdge: function (el, sticky) { var els = this._elsThatCanBeStuck(sticky._els).slice(); var elIdx; @@ -157,11 +186,8 @@ var self = this; var els = sticky._els.slice(); var height = sticky.getWindowDimensions().height; - var elsThatCanBeStuck = function () { - return $.grep(els, function (el) { return el.canBeStuck(); }); - }; var totalStickyHeight = function () { - return self._getTotalHeight(elsThatCanBeStuck()); + return self._getTotalHeight(self._elsThatCanBeStuck(els)); }; var dialogFitsHeight = function () { return totalStickyHeight() <= height; @@ -176,8 +202,8 @@ // reset elements $.each(els, function (i, el) { el.canBeStuck(true); }); - while (elsThatCanBeStuck().length && !dialogFitsHeight()) { - var currentEl = elsThatCanBeStuck()[0]; + while (self._elsThatCanBeStuck(els).length && !dialogFitsHeight()) { + var currentEl = self._elsThatCanBeStuck(els)[0]; sticky.reset(currentEl); currentEl.canBeStuck(false); @@ -187,10 +213,21 @@ return this._getTotalHeight(els); }, - getInPageEdgePosition: function (sticky) { - var idx = (sticky.edge === 'top') ? 0 : sticky._els.length - 1; + getElementAtStickyEdge: function (sticky) { + var els = this._elsThatCanBeStuck(sticky._els); + var idx = (sticky.edge === 'top') ? 0 : els.length - 1; - return sticky._els[idx].inPageEdgePosition; + 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)); @@ -272,12 +309,18 @@ } }; + // 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 @@ -489,13 +532,13 @@ if (el.isStuck()) { var $el = el.$fixedEl; - el.removeStickyClasses(); + el.removeStickyClasses(this); $el.css('width', ''); if (_mode === 'dialog') { dialog.releaseEl(el, this); } el.removeShim(); - el.release(); + el.release(this); } }; @@ -560,7 +603,7 @@ 'width': $el.width() + 'px', 'top': offset + 'px' }); - el.stick(); + el.stick(this); } }; stickAtTop.stop = function (el) { @@ -641,7 +684,7 @@ 'width': $el.width() + 'px', 'bottom': offset + 'px' }); - el.stick(); + el.stick(this); } }; stickAtBottom.stop = function (el) { 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 9a6ced38a..9c5cea1f1 100644 --- a/app/assets/stylesheets/components/stick-at-top-when-scrolling.scss +++ b/app/assets/stylesheets/components/stick-at-top-when-scrolling.scss @@ -86,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); @@ -102,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); From 98789c98c8ed5714178404d99a4a7c57b5a519bb Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Wed, 16 Jan 2019 11:38:14 +0000 Subject: [PATCH 09/13] Reset onload flag when recalculate called This ensures sticky elements don't fade in if in a sticky position when the page loads. The fade is there to show when an element is becoming sticky. This has no use if it was already sticky the first time you see it. Because the same situation applies when recalculate is called, we also want the flag to be reset before this happens. --- app/assets/javascripts/stick-to-window-when-scrolling.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/assets/javascripts/stick-to-window-when-scrolling.js b/app/assets/javascripts/stick-to-window-when-scrolling.js index d18ccda41..16b1f5995 100644 --- a/app/assets/javascripts/stick-to-window-when-scrolling.js +++ b/app/assets/javascripts/stick-to-window-when-scrolling.js @@ -462,6 +462,9 @@ } if (numOfEls) { + // reset flag marking page load + this._initialPositionsSet = false; + $els.each(function (i, el) { // delay setting position until all stickys are loaded self.add(el, false, onLoaded); From 59b02e0fe00f6a59c1fbfc62496fd6e059642e81 Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Thu, 17 Jan 2019 10:52:13 +0000 Subject: [PATCH 10/13] Change when folder controls become sticky The controls for the template folders are all present in the page when it loads. The templateFolderForm JS filters them so you only see the one you need to for the thing you're trying to do. This changes when the controls are made sticky so it happens after the templateFolderForm JS has performed its filtering. --- app/assets/javascripts/templateFolderForm.js | 15 +++++++++++++++ app/templates/views/templates/_move_to.html | 12 ++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/templateFolderForm.js b/app/assets/javascripts/templateFolderForm.js index 01cc8bab3..b6668d19a 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', () => { diff --git a/app/templates/views/templates/_move_to.html b/app/templates/views/templates/_move_to.html index cf752d9b8..c08713bdf 100644 --- a/app/templates/views/templates/_move_to.html +++ b/app/templates/views/templates/_move_to.html @@ -6,14 +6,14 @@ {% if templates_and_folders_form.move_to.choices and template_list.templates_to_show %}
-
+
{{ radios_nested(templates_and_folders_form.move_to, move_to_children, option_hints=option_hints) }}
-
+
{{ page_footer('Move', button_name='operation', button_value='move-to-existing-folder') }}
-
+
Move to a new folder {{ textbox(templates_and_folders_form.move_to_new_folder_name) }} @@ -21,7 +21,7 @@
{% endif %} -
+
Add a new folder {{ textbox(templates_and_folders_form.add_new_folder_name) }} @@ -31,10 +31,10 @@
Add a new template -
+
{{ radios(templates_and_folders_form.add_template_by_template_type) }}
-
+
{{ page_footer('Continue', button_name='operation', button_value='add-new-template') }}
From c66caaf047d5c29335fdee68fc9781550d0b23c5 Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Mon, 21 Jan 2019 16:27:28 +0000 Subject: [PATCH 11/13] Add stop point for stick-to-bottom folder controls --- app/templates/components/live-search.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 %}
-