Merge branch 'form-errors' into 'master'

small ui/ux refinements

See merge request !170
This commit is contained in:
Felix Hamann 2019-04-01 22:54:12 +02:00
commit 6fbb1888c5
9 changed files with 90 additions and 51 deletions

View File

@ -66,6 +66,14 @@
input, textarea { input, textarea {
border-color: var(--color-error) !important; border-color: var(--color-error) !important;
} }
.form-error {
display: block;
}
}
.form-error {
display: none;
} }
@media (max-width: 768px) { @media (max-width: 768px) {

View File

@ -8,6 +8,9 @@
var AUTOSUBMIT_BUTTON_SELECTOR = '[type="submit"][data-autosubmit]'; var AUTOSUBMIT_BUTTON_SELECTOR = '[type="submit"][data-autosubmit]';
var AJAX_SUBMIT_FLAG = 'ajaxSubmit'; var AJAX_SUBMIT_FLAG = 'ajaxSubmit';
var FORM_GROUP_CLASS = 'form-group';
var FORM_GROUP_WITH_ERRORS_CLASS = 'form-group--has-error';
function formValidator(inputs) { function formValidator(inputs) {
var done = true; var done = true;
inputs.forEach(function(inp) { inputs.forEach(function(inp) {
@ -52,6 +55,12 @@
// inputs // inputs
utilInstances.push(window.utils.setup('inputs', form, options)); utilInstances.push(window.utils.setup('inputs', form, options));
// form group errors
var formGroups = Array.from(form.querySelectorAll('.' + FORM_GROUP_CLASS));
formGroups.forEach(function(formGroup) {
utilInstances.push(window.utils.setup('errorRemover', formGroup, options));
});
form.classList.add(JS_INITIALIZED); form.classList.add(JS_INITIALIZED);
function destroyUtils() { function destroyUtils() {
@ -165,4 +174,29 @@
destroy: function() {}, destroy: function() {},
}; };
}; };
// listens for focus events and removes any errors on an input
window.utils.errorRemover = function(formGroup, options) {
var inputElement = formGroup.querySelector('input:not([type="hidden"]), textarea, select');
if (!inputElement) {
return false;
}
inputElement.addEventListener('focus', focusListener);
function focusListener() {
var hasError = formGroup.classList.contains(FORM_GROUP_WITH_ERRORS_CLASS);
if (hasError) {
formGroup.classList.remove(FORM_GROUP_WITH_ERRORS_CLASS);
}
}
return {
scope: formGroup,
destroy: function() {
inputElement.removeEventListener('focus', focusListener);
},
};
};
})(); })();

View File

@ -3,37 +3,37 @@
window.utils = window.utils || {}; window.utils = window.utils || {};
var JS_INITIALIZED_CLASS = 'js-initialized'; var JS_INITIALIZED_CLASS = 'js-inputs-initialized';
function isNotInitialized(element) {
return !element.classList.contains(JS_INITIALIZED_CLASS);
}
window.utils.inputs = function(wrapper, options) { window.utils.inputs = function(wrapper, options) {
options = options || {};
var utilInstances = []; var utilInstances = [];
if (wrapper.classList.contains(JS_INITIALIZED_CLASS) && !options.force) {
return false;
}
// checkboxes // checkboxes
var checkboxes = Array.from(wrapper.querySelectorAll('input[type="checkbox"]')); var checkboxes = Array.from(wrapper.querySelectorAll('input[type="checkbox"]'));
checkboxes.filter(isNotInitialized).forEach(function(checkbox) { checkboxes.forEach(function(checkbox) {
utilInstances.push(window.utils.setup('checkbox', checkbox)); utilInstances.push(window.utils.setup('checkbox', checkbox));
}); });
// radios // radios
var radios = Array.from(wrapper.querySelectorAll('input[type="radio"]')); var radios = Array.from(wrapper.querySelectorAll('input[type="radio"]'));
radios.filter(isNotInitialized).forEach(function(radio) { radios.forEach(function(radio) {
utilInstances.push(window.utils.setup('radio', radio)); utilInstances.push(window.utils.setup('radio', radio));
}); });
// file-uploads // file-uploads
var fileUploads = Array.from(wrapper.querySelectorAll('input[type="file"]')); var fileUploads = Array.from(wrapper.querySelectorAll('input[type="file"]'));
fileUploads.filter(isNotInitialized).forEach(function(input) { fileUploads.forEach(function(input) {
utilInstances.push(window.utils.setup('fileUpload', input, options)); utilInstances.push(window.utils.setup('fileUpload', input, options));
}); });
// file-checkboxes // file-checkboxes
var fileCheckboxes = Array.from(wrapper.querySelectorAll('.file-checkbox')); var fileCheckboxes = Array.from(wrapper.querySelectorAll('.file-checkbox'));
fileCheckboxes.filter(isNotInitialized).forEach(function(input) { fileCheckboxes.forEach(function(input) {
utilInstances.push(window.utils.setup('fileCheckbox', input, options)); utilInstances.push(window.utils.setup('fileCheckbox', input, options));
}); });
@ -45,6 +45,8 @@
}); });
} }
wrapper.classList.add(JS_INITIALIZED_CLASS);
return { return {
scope: wrapper, scope: wrapper,
destroy: destroyUtils, destroy: destroyUtils,
@ -74,7 +76,6 @@
if (!i18n) { if (!i18n) {
throw new Error('window.utils.fileUpload(input, options) needs to be passed i18n object via options'); throw new Error('window.utils.fileUpload(input, options) needs to be passed i18n object via options');
} }
input.classList.add(JS_INITIALIZED_CLASS);
function renderFileList(files) { function renderFileList(files) {
fileList.innerHTML = ''; fileList.innerHTML = '';
@ -166,8 +167,6 @@
cont = cont.parentNode; cont = cont.parentNode;
} }
addListener(cont); addListener(cont);
input.classList.add(JS_INITIALIZED_CLASS);
cont.classList.add(JS_INITIALIZED_CLASS);
} }
setup(); setup();
@ -190,7 +189,6 @@
labelEl.setAttribute('for', input.id); labelEl.setAttribute('for', input.id);
wrapperEl.appendChild(input); wrapperEl.appendChild(input);
wrapperEl.appendChild(labelEl); wrapperEl.appendChild(labelEl);
input.classList.add(JS_INITIALIZED_CLASS);
if (siblingEl) { if (siblingEl) {
parentEl.insertBefore(wrapperEl, siblingEl); parentEl.insertBefore(wrapperEl, siblingEl);
@ -219,7 +217,6 @@
wrapperEl.appendChild(siblingEl); wrapperEl.appendChild(siblingEl);
} }
input.classList.add(JS_INITIALIZED_CLASS);
parentEl.appendChild(wrapperEl); parentEl.appendChild(wrapperEl);
} }
@ -233,8 +230,6 @@
window.utils.implicitSubmit = function(input, options) { window.utils.implicitSubmit = function(input, options) {
var submit = options.submit; var submit = options.submit;
console.log('implicitSubmit', input, submit);
if (!submit) { if (!submit) {
throw new Error('window.utils.implicitSubmit(input, options) needs to be passed a submit element via options'); throw new Error('window.utils.implicitSubmit(input, options) needs to be passed a submit element via options');
} }
@ -247,7 +242,7 @@
}; };
input.addEventListener('keypress', doSubmit); input.addEventListener('keypress', doSubmit);
return { return {
scope: input, scope: input,
destroy: function() { destroy: function() {

View File

@ -37,6 +37,9 @@
if (isAlreadySetup) { if (isAlreadySetup) {
console.warn('Trying to setup a JS utility that\'s already been set up', { utility: utilName, scope, options }); console.warn('Trying to setup a JS utility that\'s already been set up', { utility: utilName, scope, options });
if (!options.force) {
return false;
}
} }
} }

View File

@ -1,17 +1,14 @@
<div .container> <section>
<h1>Uni2work - Admin Demopage
<p data-tooltip="Solch ein Tooltip kann mit dem <em>data-tooltip</em> Attribut erzeugt werden. Funktioniert aber nur in Block-Elementen die einen sinnvollen Wrapper haben."> <p data-tooltip="Solch ein Tooltip kann mit dem <em>data-tooltip</em> Attribut erzeugt werden. Funktioniert aber nur in Block-Elementen die einen sinnvollen Wrapper haben.">
Diese interne Seite dient lediglich zum Testen diverser Funktionalitäten Diese interne Seite dient lediglich zum Testen diverser Funktionalitäten
und zur Demonstration der verschiedenen Hilfsfunktionen/Module. und zur Demonstration der verschiedenen Hilfsfunktionen/Module.
Der Handler sollte jeweils aktuelle Beispiele für alle möglichen Funktionalitäten enthalten, so dass man immer weiß, wo man nachschlagen kann. Der Handler sollte jeweils aktuelle Beispiele für alle möglichen Funktionalitäten enthalten, so dass man immer weiß, wo man nachschlagen kann.
<section>
<div .container.js-show-hide>
<h2 .js-show-hide__toggle>Teilweise funktionierende Abschnitte <h2 .js-show-hide__toggle>Teilweise funktionierende Abschnitte
<ul .js-show-hide__target> <ul>
<li .list-group-item> <li .list-group-item>
<a href=@{UsersR}>Benutzer Verwaltung <a href=@{UsersR}>Benutzer Verwaltung
@ -22,7 +19,7 @@
<li .list-group-item> <li .list-group-item>
<a href=@{CourseNewR}>Kurse anlegen <a href=@{CourseNewR}>Kurse anlegen
<div .container> <section>
<h2>Funktionen zum Testen <h2>Funktionen zum Testen
<ul> <ul>

View File

@ -474,7 +474,7 @@ ul.list--inline {
/* DEFINITION LIST */ /* DEFINITION LIST */
.deflist { .deflist {
display: grid; display: grid;
grid-template-columns: 100% ; grid-template-columns: 100%;
} }
.deflist__dt, .deflist__dt,
.deflist__dd { .deflist__dd {
@ -488,6 +488,10 @@ ul.list--inline {
.deflist__dd { .deflist__dd {
font-size: 18px; font-size: 18px;
margin-bottom: 10px; margin-bottom: 10px;
> p {
margin-top: 0;
}
} }
@media (min-width: 768px) { @media (min-width: 768px) {
@ -507,7 +511,6 @@ ul.list--inline {
.deflist__dt, .deflist__dt,
.deflist__dd { .deflist__dd {
border-bottom: 1px solid #d3d3d3;
padding: 12px 0; padding: 12px 0;
margin: 0; margin: 0;
font-size: 16px; font-size: 16px;
@ -527,16 +530,14 @@ ul.list--inline {
} }
section { section {
padding-bottom: 20px; padding-bottom: 30px;
margin-bottom: 20px;
border-bottom: 1px solid #d3d3d3; border-bottom: 1px solid #d3d3d3;
+ section { + section {
margin-top: 20px; margin-top: 20px;
padding-top: 20px;
} }
&:last-of-type { &:last-child {
border-bottom: none; border-bottom: none;
} }
} }

View File

@ -1,6 +1,7 @@
<section>
UniWorX erfahrene Veranstalter finden
hier die wichtigsten Neuerungen.
UniWorX erfahrene Veranstalter finden
hier die wichtigsten Neuerungen.
<section> <section>
<h2>Bekannte Probleme in Bearbeitung <h2>Bekannte Probleme in Bearbeitung
@ -178,4 +179,4 @@ hier die wichtigsten Neuerungen.
Planmäßige Wartungen werden ohne Ankündigung Planmäßige Wartungen werden ohne Ankündigung
immer um 2:00h nachts durchgeführt. immer um 2:00h nachts durchgeführt.
Es wird daher empfohlen, keine kritischen Abgabefristen Es wird daher empfohlen, keine kritischen Abgabefristen
um oder kurz nach dieser Zeit einzustellen. um oder kurz nach dieser Zeit einzustellen.

View File

@ -1,4 +1,3 @@
.table-filter { .table-filter {
border-bottom: 1px solid #d3d3d3;
margin-bottom: 13px; margin-bottom: 13px;
} }

View File

@ -1,18 +1,19 @@
$newline never $newline never
$# Wrapper for all kinds of forms $# Wrapper for all kinds of forms
<form ##{formId} method=#{decodeUtf8 (renderStdMethod formMethod)} action=#{fromMaybe "" formActionUrl} enctype=#{formEncoding} *{formAttrs}> <section>
$# Distinguish different falvours of submit button layouts here: <form ##{formId} method=#{decodeUtf8 (renderStdMethod formMethod)} action=#{fromMaybe "" formActionUrl} enctype=#{formEncoding} *{formAttrs}>
$case formSubmit $# Distinguish different falvours of submit button layouts here:
$of FormNoSubmit $case formSubmit
^{formWidget} $of FormNoSubmit
$of FormSubmit ^{formWidget}
^{formWidget} $of FormSubmit
^{submitButtonView} ^{formWidget}
$of FormDualSubmit ^{submitButtonView}
^{submitButtonView} $of FormDualSubmit
^{formWidget} ^{submitButtonView}
^{submitButtonView} ^{formWidget}
$of FormAutoSubmit ^{submitButtonView}
^{formWidget} $of FormAutoSubmit
<button type=submit data-autosubmit> ^{formWidget}
^{btnLabel BtnSubmit} <button type=submit data-autosubmit>
^{btnLabel BtnSubmit}