Centralize handling of form elements state handling.

This makes it easier to maintain state changes. Seems to handle all states well now
This commit is contained in:
Magnus Hoff 2018-09-30 22:30:52 +02:00
parent 85014d2789
commit c6dd37ed9e
3 changed files with 66 additions and 20 deletions

View file

@ -59,7 +59,13 @@ function confirmDiscard() {
return popup(instantiate("confirm-discard")); return popup(instantiate("confirm-discard"));
} }
let hasBeenOpen = false; const state = {
hasBeenOpen: false,
saving: false,
editing: function () { return document.querySelector(".container").classList.contains('edit'); },
hasCancelUrl: function () { return document.querySelector("a.button-cancel").getAttribute('href') !== ""; }
};
function openEditor() { function openEditor() {
const bodyElement = document.querySelector("body"); const bodyElement = document.querySelector("body");
const container = document.querySelector(".container"); const container = document.querySelector(".container");
@ -69,6 +75,7 @@ function openEditor() {
const shadow = editor.querySelector('textarea.shadow-control'); const shadow = editor.querySelector('textarea.shadow-control');
const form = document.getElementById('article-editor'); const form = document.getElementById('article-editor');
const cancel = form.querySelector('.cancel'); const cancel = form.querySelector('.cancel');
const cancelInteractionGroup = form.querySelector(".cancel-interaction-group");
const footer = document.querySelector("footer"); const footer = document.querySelector("footer");
const lastUpdated = footer.querySelector(".last-updated"); const lastUpdated = footer.querySelector(".last-updated");
@ -76,22 +83,40 @@ function openEditor() {
textarea.style.height = rendered.clientHeight + "px"; textarea.style.height = rendered.clientHeight + "px";
container.classList.add('edit'); container.classList.add('edit');
updateFormEnabledState();
autosizeTextarea(textarea, shadow); autosizeTextarea(textarea, shadow);
textarea.setSelectionRange(textarea.value.length, textarea.value.length); textarea.setSelectionRange(textarea.value.length, textarea.value.length);
textarea.focus(); textarea.focus();
if (hasBeenOpen) return; if (state.hasBeenOpen) return;
hasBeenOpen = true; state.hasBeenOpen = true;
textarea.addEventListener('input', () => autosizeTextarea(textarea, shadow)); textarea.addEventListener('input', () => autosizeTextarea(textarea, shadow));
window.addEventListener('resize', () => autosizeTextarea(textarea, shadow)); window.addEventListener('resize', () => autosizeTextarea(textarea, shadow));
function updateFormEnabledState() {
const baseEnabled = !state.saving && state.editing();
const enabled = {
cancel: baseEnabled && state.hasCancelUrl(),
};
cancelInteractionGroup.classList.remove(!enabled.cancel ? "interaction-group--root--enabled" : "interaction-group--root--disabled");
cancelInteractionGroup.classList.add(enabled.cancel ? "interaction-group--root--enabled" : "interaction-group--root--disabled");
for (const el of form.elements) {
el.disabled = !baseEnabled;
}
// TODO: edit-link in footer?
}
function doSave() { function doSave() {
state.saving = true;
updateFormEnabledState();
const body = queryArgsFromForm(form); const body = queryArgsFromForm(form);
textarea.disabled = true;
// TODO Disable other interaction as well: title editor, cancel and OK buttons
fetch( fetch(
form.getAttribute("action"), form.getAttribute("action"),
@ -111,7 +136,8 @@ function openEditor() {
if (probablyLoginRedirect) { if (probablyLoginRedirect) {
return loginDialog(response.url) return loginDialog(response.url)
.then(() => { .then(() => {
textarea.disabled = false; state.saving = false;
updateFormEnabledState();
}); });
} }
@ -119,9 +145,10 @@ function openEditor() {
return response.json() return response.json()
.then(result => { .then(result => {
// Update url-bar, page title and footer // Update url-bar, page title, footer and cancel link
window.history.replaceState(null, result.title, result.slug == "" ? "." : result.slug); const url = result.slug == "" ? "." : result.slug;
// TODO Cancel-link URL should be updated to new slug window.history.replaceState(null, result.title, url);
cancel.setAttribute("href", url);
document.querySelector("title").textContent = result.title; document.querySelector("title").textContent = result.title;
lastUpdated.innerHTML = result.last_updated; lastUpdated.innerHTML = result.last_updated;
lastUpdated.classList.remove("missing"); lastUpdated.classList.remove("missing");
@ -144,9 +171,11 @@ function openEditor() {
if (!result.conflict) { if (!result.conflict) {
container.classList.remove('edit'); container.classList.remove('edit');
document.activeElement && document.activeElement.blur();
} }
textarea.disabled = false; state.saving = false;
updateFormEnabledState();
autosizeTextarea(textarea, shadow); autosizeTextarea(textarea, shadow);
if (result.conflict) { if (result.conflict) {
@ -156,7 +185,8 @@ function openEditor() {
} }
}); });
}).catch(err => { }).catch(err => {
textarea.disabled = false; state.saving = false;
updateFormEnabledState();
console.error(err); console.error(err);
return alertAsync(err.toString()); return alertAsync(err.toString());
}); });
@ -167,6 +197,8 @@ function openEditor() {
.then(doReset => { .then(doReset => {
if (doReset) { if (doReset) {
container.classList.remove('edit'); container.classList.remove('edit');
document.activeElement && document.activeElement.blur();
updateFormEnabledState();
form.reset(); form.reset();
let selectedTheme = form.querySelector(`.theme-picker--option[checked]`).value; let selectedTheme = form.querySelector(`.theme-picker--option[checked]`).value;
@ -197,8 +229,7 @@ function openEditor() {
document.addEventListener("keypress", function (ev) { document.addEventListener("keypress", function (ev) {
const accel = ev.ctrlKey || ev.metaKey; // Imprecise, but works cross platform const accel = ev.ctrlKey || ev.metaKey; // Imprecise, but works cross platform
if (ev.key === "Enter" && accel) { if (ev.key === "Enter" && accel) {
const isEditing = container.classList.contains('edit'); if (!state.editing()) return;
if (!isEditing) return;
ev.stopPropagation(); ev.stopPropagation();
ev.preventDefault(); ev.preventDefault();

View file

@ -414,6 +414,14 @@ h1>input {
--button-alt: var(--theme-input); --button-alt: var(--theme-input);
} }
.interaction-group--root--enabled .interaction-group--disabled {
display: none;
}
.interaction-group--root--disabled .interaction-group--enabled {
display: none;
}
@media (min-width: 960px) { @media (min-width: 960px) {
/* min-width is calculated like this: /* min-width is calculated like this:

View file

@ -31,13 +31,20 @@
</div> </div>
<div class="editor-controls"> <div class="editor-controls">
{{#cancel_url}} {{#edit?}}
<a class="button button-cancel cancel" href="{{.}}">Cancel</a> <div class="cancel-interaction-group {{#cancel_url}}interaction-group--root--enabled{{/cancel_url}}{{^cancel_url}}interaction-group--root--disabled{{/cancel_url}}">
{{/cancel_url}} <a class="interaction-group--enabled button button-cancel cancel" href="{{#cancel_url}}{{.}}{{/cancel_url}}">Cancel</a>
{{^cancel_url}} <button class="interaction-group--disabled button button-cancel" disabled>Cancel</a>
<button class="button button-cancel cancel" disabled>Cancel</a> </div>
{{/cancel_url}} <button class="button button-default" type=submit {{^edit?}}disabled{{/edit}}>Save</button>
<button class="button button-default" type=submit>Save</button> {{/edit}}
{{^edit?}}
<div class="cancel-interaction-group interaction-group--root--disabled">
<a class="interaction-group--enabled button button-cancel cancel" href="{{#cancel_url}}{{.}}{{/cancel_url}}">Cancel</a>
<button class="interaction-group--disabled button button-cancel" disabled>Cancel</a>
</div>
<button class="button button-default" type=submit disabled>Save</button>
{{/edit}}
</div> </div>
</form> </form>