decide.nolog.cz/app/helpers/scroll-first-invalid-element-into-view-port.js
Jeldrik Hanschke d27a6a4202
scroll to invalid form element if it's behind form navigation buttons (#307)
A form element may be in view port but hidden by form navigation buttons
with have a fixed position on mobile. In that case the form element should
still be scrolled to top of view port to make it visible.
2019-11-17 19:15:55 +01:00

55 lines
2.3 KiB
JavaScript

import { helper } from '@ember/component/helper';
import { next } from '@ember/runloop';
import { assert } from '@ember/debug';
function elementIsNotVisible(element) {
let elementPosition = element.getBoundingClientRect();
let windowHeight = window.innerHeight;
// an element is not visible if
return false ||
// it's above the current view port
elementPosition.top <= 0 ||
// it's below the current view port
elementPosition.bottom >= windowHeight ||
// it's in current view port but hidden by fixed navigation
(
getComputedStyle(document.querySelector('.cr-steps-bottom-nav')).position === 'fixed' &&
elementPosition.bottom >= windowHeight - document.querySelector('.cr-steps-bottom-nav').offsetHeight
);
}
function scrollFirstInvalidElementIntoViewPort() {
// `schedule('afterRender', function() {})` would be more approperiate but there seems to be a
// timing issue in Firefox causing the Browser not scrolling up far enough if doing so
// delaying to next runloop therefore
next(function() {
let invalidInput = document.querySelector('.form-control.is-invalid, .custom-control-input.is-invalid');
assert(
'Atleast one form control must be marked as invalid if form submission was rejected as invalid',
invalidInput
);
// focus first invalid control
invalidInput.focus({ preventScroll: true });
// scroll to label or legend of first invalid control if it's visible yet
if (elementIsNotVisible(invalidInput)) {
// Radio groups have a label and a legend. While the label is per input, the legend is for
// the whole group. Croodle should bring the legend into view in that case.
// Due to a bug in Ember Bootstrap it renders a `<label>` instead of a `<legend>`:
// https://github.com/kaliber5/ember-bootstrap/issues/931
// As a work-a-round we look the correct label up by a custom convention for the `id` of the
// inputs and the `for` of the input group `<label>` (which should be a `<legend>`).
let label =
document.querySelector(`label[for="${invalidInput.id.substr(0, invalidInput.id.indexOf('_'))}"`) ||
document.querySelector(`label[for="${invalidInput.id}"]`);
label.scrollIntoView({ behavior: 'smooth' });
}
});
}
export default helper(function() {
return scrollFirstInvalidElementIntoViewPort;
});