Optimized form is-dirty handling (references #2900)

This commit is contained in:
Bernd Bestel 2026-03-30 16:32:31 +02:00
parent c0c9a38e39
commit 00a7160d74
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
2 changed files with 65 additions and 69 deletions

View File

@ -48,7 +48,7 @@
### Userfields ### Userfields
- xxx - Fixed that Userfields of type "Select list (a single item can be selected)" changed by keyboard only were not saved
### General ### General

View File

@ -1,10 +1,10 @@
Grocy.Api = {}; Grocy.Api = {};
Grocy.Api.Get = function(apiFunction, success, error) Grocy.Api.Get = function (apiFunction, success, error)
{ {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
var url = U('/api/' + apiFunction); var url = U('/api/' + apiFunction);
xhr.onreadystatechange = function() xhr.onreadystatechange = function ()
{ {
if (xhr.readyState === XMLHttpRequest.DONE) if (xhr.readyState === XMLHttpRequest.DONE)
{ {
@ -36,12 +36,12 @@ Grocy.Api.Get = function(apiFunction, success, error)
xhr.send(); xhr.send();
}; };
Grocy.Api.Post = function(apiFunction, jsonData, success, error) Grocy.Api.Post = function (apiFunction, jsonData, success, error)
{ {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
var url = U('/api/' + apiFunction); var url = U('/api/' + apiFunction);
xhr.onreadystatechange = function() xhr.onreadystatechange = function ()
{ {
if (xhr.readyState === XMLHttpRequest.DONE) if (xhr.readyState === XMLHttpRequest.DONE)
{ {
@ -74,12 +74,12 @@ Grocy.Api.Post = function(apiFunction, jsonData, success, error)
xhr.send(JSON.stringify(jsonData)); xhr.send(JSON.stringify(jsonData));
}; };
Grocy.Api.Put = function(apiFunction, jsonData, success, error) Grocy.Api.Put = function (apiFunction, jsonData, success, error)
{ {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
var url = U('/api/' + apiFunction); var url = U('/api/' + apiFunction);
xhr.onreadystatechange = function() xhr.onreadystatechange = function ()
{ {
if (xhr.readyState === XMLHttpRequest.DONE) if (xhr.readyState === XMLHttpRequest.DONE)
{ {
@ -112,12 +112,12 @@ Grocy.Api.Put = function(apiFunction, jsonData, success, error)
xhr.send(JSON.stringify(jsonData)); xhr.send(JSON.stringify(jsonData));
}; };
Grocy.Api.Delete = function(apiFunction, jsonData, success, error) Grocy.Api.Delete = function (apiFunction, jsonData, success, error)
{ {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
var url = U('/api/' + apiFunction); var url = U('/api/' + apiFunction);
xhr.onreadystatechange = function() xhr.onreadystatechange = function ()
{ {
if (xhr.readyState === XMLHttpRequest.DONE) if (xhr.readyState === XMLHttpRequest.DONE)
{ {
@ -150,12 +150,12 @@ Grocy.Api.Delete = function(apiFunction, jsonData, success, error)
xhr.send(JSON.stringify(jsonData)); xhr.send(JSON.stringify(jsonData));
}; };
Grocy.Api.UploadFile = function(file, group, fileName, success, error) Grocy.Api.UploadFile = function (file, group, fileName, success, error)
{ {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
var url = U('/api/files/' + group + '/' + btoa(fileName)); var url = U('/api/files/' + group + '/' + btoa(fileName));
xhr.onreadystatechange = function() xhr.onreadystatechange = function ()
{ {
if (xhr.readyState === XMLHttpRequest.DONE) if (xhr.readyState === XMLHttpRequest.DONE)
{ {
@ -188,12 +188,12 @@ Grocy.Api.UploadFile = function(file, group, fileName, success, error)
xhr.send(file); xhr.send(file);
}; };
Grocy.Api.DeleteFile = function(fileName, group, success, error) Grocy.Api.DeleteFile = function (fileName, group, success, error)
{ {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
var url = U('/api/files/' + group + '/' + btoa(fileName)); var url = U('/api/files/' + group + '/' + btoa(fileName));
xhr.onreadystatechange = function() xhr.onreadystatechange = function ()
{ {
if (xhr.readyState === XMLHttpRequest.DONE) if (xhr.readyState === XMLHttpRequest.DONE)
{ {
@ -226,14 +226,14 @@ Grocy.Api.DeleteFile = function(fileName, group, success, error)
xhr.send(); xhr.send();
}; };
U = function(relativePath) U = function (relativePath)
{ {
return Grocy.BaseUrl.replace(/\/$/, '') + relativePath; return Grocy.BaseUrl.replace(/\/$/, '') + relativePath;
} }
Grocy.Translator = new window.translator.default(Grocy.LocalizationStrings); Grocy.Translator = new window.translator.default(Grocy.LocalizationStrings);
Grocy.TranslatorQu = new window.translator.default(Grocy.LocalizationStringsQu); Grocy.TranslatorQu = new window.translator.default(Grocy.LocalizationStringsQu);
__t = function(text, ...placeholderValues) __t = function (text, ...placeholderValues)
{ {
if (!text) if (!text)
{ {
@ -258,7 +258,7 @@ __t = function(text, ...placeholderValues)
return Grocy.Translator.__(text, ...placeholderValues); return Grocy.Translator.__(text, ...placeholderValues);
} }
} }
__n = function(number, singularForm, pluralForm, isQu = false) __n = function (number, singularForm, pluralForm, isQu = false)
{ {
if (!singularForm) if (!singularForm)
{ {
@ -291,9 +291,9 @@ __n = function(number, singularForm, pluralForm, isQu = false)
} }
} }
RefreshContextualTimeago = function(rootSelector = "#page-content") RefreshContextualTimeago = function (rootSelector = "#page-content")
{ {
$(rootSelector + " time.timeago").each(function() $(rootSelector + " time.timeago").each(function ()
{ {
var element = $(this); var element = $(this);
@ -357,7 +357,7 @@ toastr.options = {
}; };
Grocy.FrontendHelpers = {}; Grocy.FrontendHelpers = {};
Grocy.FrontendHelpers.ValidateForm = function(formId, reportValidity = false) Grocy.FrontendHelpers.ValidateForm = function (formId, reportValidity = false)
{ {
var form = document.getElementById(formId); var form = document.getElementById(formId);
if (form === null || form === undefined) if (form === null || form === undefined)
@ -375,7 +375,7 @@ Grocy.FrontendHelpers.ValidateForm = function(formId, reportValidity = false)
return form.checkValidity(); return form.checkValidity();
} }
Grocy.FrontendHelpers.BeginUiBusy = function(formId = null) Grocy.FrontendHelpers.BeginUiBusy = function (formId = null)
{ {
$("body").addClass("cursor-busy"); $("body").addClass("cursor-busy");
@ -385,7 +385,7 @@ Grocy.FrontendHelpers.BeginUiBusy = function(formId = null)
} }
} }
Grocy.FrontendHelpers.EndUiBusy = function(formId = null) Grocy.FrontendHelpers.EndUiBusy = function (formId = null)
{ {
$("body").removeClass("cursor-busy"); $("body").removeClass("cursor-busy");
@ -395,10 +395,10 @@ Grocy.FrontendHelpers.EndUiBusy = function(formId = null)
} }
} }
Grocy.FrontendHelpers.ShowGenericError = function(message, exception) Grocy.FrontendHelpers.ShowGenericError = function (message, exception)
{ {
toastr.error(__t(message) + '<br><br>' + __t('Click to show technical details'), '', { toastr.error(__t(message) + '<br><br>' + __t('Click to show technical details'), '', {
onclick: function() onclick: function ()
{ {
var errorDetails = JSON.stringify(exception, null, 4); var errorDetails = JSON.stringify(exception, null, 4);
if (typeof exception === "object" && exception !== null && exception.hasOwnProperty("error_message")) if (typeof exception === "object" && exception !== null && exception.hasOwnProperty("error_message"))
@ -418,7 +418,7 @@ Grocy.FrontendHelpers.ShowGenericError = function(message, exception)
console.error(exception); console.error(exception);
} }
Grocy.FrontendHelpers.SaveUserSetting = function(settingsKey, value, force = false) Grocy.FrontendHelpers.SaveUserSetting = function (settingsKey, value, force = false)
{ {
if (Grocy.UserSettings[settingsKey] == value && !force) if (Grocy.UserSettings[settingsKey] == value && !force)
{ {
@ -430,30 +430,30 @@ Grocy.FrontendHelpers.SaveUserSetting = function(settingsKey, value, force = fal
jsonData = {}; jsonData = {};
jsonData.value = value; jsonData.value = value;
Grocy.Api.Put('user/settings/' + settingsKey, jsonData, Grocy.Api.Put('user/settings/' + settingsKey, jsonData,
function(result) function (result)
{ {
// Nothing to do... // Nothing to do...
}, },
function(xhr) function (xhr)
{ {
console.error(xhr); console.error(xhr);
} }
); );
} }
Grocy.FrontendHelpers.DeleteUserSetting = function(settingsKey, reloadPageOnSuccess = false) Grocy.FrontendHelpers.DeleteUserSetting = function (settingsKey, reloadPageOnSuccess = false)
{ {
delete Grocy.UserSettings[settingsKey]; delete Grocy.UserSettings[settingsKey];
Grocy.Api.Delete('user/settings/' + settingsKey, {}, Grocy.Api.Delete('user/settings/' + settingsKey, {},
function(result) function (result)
{ {
if (reloadPageOnSuccess) if (reloadPageOnSuccess)
{ {
location.reload(); location.reload();
} }
}, },
function(xhr) function (xhr)
{ {
if (xhr.statusText) if (xhr.statusText)
{ {
@ -463,7 +463,7 @@ Grocy.FrontendHelpers.DeleteUserSetting = function(settingsKey, reloadPageOnSucc
); );
} }
Grocy.FrontendHelpers.RunWebhook = function(webhook, data, repetitions = 1) Grocy.FrontendHelpers.RunWebhook = function (webhook, data, repetitions = 1)
{ {
Object.assign(data, webhook.extra_data); Object.assign(data, webhook.extra_data);
var hasAlreadyFailed = false; var hasAlreadyFailed = false;
@ -472,7 +472,7 @@ Grocy.FrontendHelpers.RunWebhook = function(webhook, data, repetitions = 1)
{ {
if (webhook.json) if (webhook.json)
{ {
$.ajax(webhook.hook, { "data": JSON.stringify(data), "contentType": "application/json", "type": "POST" }).fail(function(req, status, errorThrown) $.ajax(webhook.hook, { "data": JSON.stringify(data), "contentType": "application/json", "type": "POST" }).fail(function (req, status, errorThrown)
{ {
if (!hasAlreadyFailed) if (!hasAlreadyFailed)
{ {
@ -483,7 +483,7 @@ Grocy.FrontendHelpers.RunWebhook = function(webhook, data, repetitions = 1)
} }
else else
{ {
$.post(webhook.hook, data).fail(function(req, status, errorThrown) $.post(webhook.hook, data).fail(function (req, status, errorThrown)
{ {
if (!hasAlreadyFailed) if (!hasAlreadyFailed)
{ {
@ -495,17 +495,13 @@ Grocy.FrontendHelpers.RunWebhook = function(webhook, data, repetitions = 1)
} }
} }
$(document).on("keyup paste change", "input, textarea", function() $(document).on("keyup paste change click", "input, select, textarea", function ()
{
$(this).addClass("is-dirty").closest("form").addClass("is-dirty");
});
$(document).on("click", "select", function()
{ {
$(this).addClass("is-dirty").closest("form").addClass("is-dirty"); $(this).addClass("is-dirty").closest("form").addClass("is-dirty");
}); });
// Auto saving user setting controls // Auto saving user setting controls
$(document).on("change", ".user-setting-control", function() $(document).on("change", ".user-setting-control", function ()
{ {
var element = $(this); var element = $(this);
var settingKey = element.attr("data-setting-key"); var settingKey = element.attr("data-setting-key");
@ -534,7 +530,7 @@ $(document).on("change", ".user-setting-control", function()
}); });
// Show file name Bootstrap custom file input // Show file name Bootstrap custom file input
$('input.custom-file-input').on('change', function() $('input.custom-file-input').on('change', function ()
{ {
$(this).next('.custom-file-label').html(GetFileNameFromPath($(this).val())); $(this).next('.custom-file-label').html(GetFileNameFromPath($(this).val()));
}); });
@ -545,9 +541,9 @@ if ($(".custom-file-label").length > 0)
$("<style>").html('.custom-file-label::after { content: "' + __t("Select file") + '"; }').appendTo("head"); $("<style>").html('.custom-file-label::after { content: "' + __t("Select file") + '"; }').appendTo("head");
} }
ResizeResponsiveEmbeds = function() ResizeResponsiveEmbeds = function ()
{ {
$("iframe.embed-responsive").each(function() $("iframe.embed-responsive").each(function ()
{ {
var iframeBody = $(this)[0].contentWindow.document.body; var iframeBody = $(this)[0].contentWindow.document.body;
if (iframeBody) if (iframeBody)
@ -568,25 +564,25 @@ ResizeResponsiveEmbeds = function()
} }
$("embed.embed-responsive:not(.resize-done)").attr("height", maxHeight.toString() + "px").addClass("resize-done"); $("embed.embed-responsive:not(.resize-done)").attr("height", maxHeight.toString() + "px").addClass("resize-done");
} }
$(window).on("resize", function() $(window).on("resize", function ()
{ {
ResizeResponsiveEmbeds(); ResizeResponsiveEmbeds();
}); });
$("iframe").on("load", function() $("iframe").on("load", function ()
{ {
ResizeResponsiveEmbeds(); ResizeResponsiveEmbeds();
}); });
$(document).on("shown.bs.modal", function(e) $(document).on("shown.bs.modal", function (e)
{ {
ResizeResponsiveEmbeds(); ResizeResponsiveEmbeds();
}); });
$(document).on("hidden.bs.modal", function(e) $(document).on("hidden.bs.modal", function (e)
{ {
$("body").removeClass("fullscreen-card"); $("body").removeClass("fullscreen-card");
}); });
$("body").children().each(function(index, child) $("body").children().each(function (index, child)
{ {
new ResizeObserver(function() new ResizeObserver(function ()
{ {
window.parent.postMessage(WindowMessageBag("ResizeResponsiveEmbeds"), Grocy.BaseUrl); window.parent.postMessage(WindowMessageBag("ResizeResponsiveEmbeds"), Grocy.BaseUrl);
}).observe(child); }).observe(child);
@ -608,7 +604,7 @@ if (window.location.hash)
function RefreshLocaleNumberDisplay(rootSelector = "#page-content") function RefreshLocaleNumberDisplay(rootSelector = "#page-content")
{ {
$(rootSelector + " .locale-number.locale-number-currency:not('.number-parsing-done')").each(function() $(rootSelector + " .locale-number.locale-number-currency:not('.number-parsing-done')").each(function ()
{ {
var element = $(this); var element = $(this);
var text = element.text(); var text = element.text();
@ -622,7 +618,7 @@ function RefreshLocaleNumberDisplay(rootSelector = "#page-content")
element.addClass("number-parsing-done"); element.addClass("number-parsing-done");
}); });
$(rootSelector + " .locale-number.locale-number-quantity-amount:not('.number-parsing-done')").each(function() $(rootSelector + " .locale-number.locale-number-quantity-amount:not('.number-parsing-done')").each(function ()
{ {
var element = $(this); var element = $(this);
var text = element.text(); var text = element.text();
@ -636,7 +632,7 @@ function RefreshLocaleNumberDisplay(rootSelector = "#page-content")
element.addClass("number-parsing-done"); element.addClass("number-parsing-done");
}); });
$(rootSelector + " .locale-number.locale-number-generic:not('.number-parsing-done')").each(function() $(rootSelector + " .locale-number.locale-number-generic:not('.number-parsing-done')").each(function ()
{ {
var element = $(this); var element = $(this);
var text = element.text(); var text = element.text();
@ -651,9 +647,9 @@ function RefreshLocaleNumberDisplay(rootSelector = "#page-content")
}); });
} }
RefreshLocaleNumberDisplay(); RefreshLocaleNumberDisplay();
$(".locale-number").each(function() $(".locale-number").each(function ()
{ {
new MutationObserver(function(mutations) new MutationObserver(function (mutations)
{ {
mutations.forEach(mutation => mutations.forEach(mutation =>
{ {
@ -671,7 +667,7 @@ $(".locale-number").each(function()
function RefreshLocaleNumberInput(rootSelector = "#page-content") function RefreshLocaleNumberInput(rootSelector = "#page-content")
{ {
$(rootSelector + " .locale-number-input.locale-number-currency").each(function() $(rootSelector + " .locale-number-input.locale-number-currency").each(function ()
{ {
var element = $(this); var element = $(this);
var value = element.val(); var value = element.val();
@ -683,7 +679,7 @@ function RefreshLocaleNumberInput(rootSelector = "#page-content")
element.val(Number.parseFloat(value).toLocaleString("en", { minimumFractionDigits: Grocy.UserSettings.stock_decimal_places_prices_input, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_prices_input, useGrouping: false })); element.val(Number.parseFloat(value).toLocaleString("en", { minimumFractionDigits: Grocy.UserSettings.stock_decimal_places_prices_input, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_prices_input, useGrouping: false }));
}); });
$(rootSelector + " .locale-number-input.locale-number-quantity-amount").each(function() $(rootSelector + " .locale-number-input.locale-number-quantity-amount").each(function ()
{ {
var element = $(this); var element = $(this);
var value = element.val(); var value = element.val();
@ -695,7 +691,7 @@ function RefreshLocaleNumberInput(rootSelector = "#page-content")
element.val(Number.parseFloat(value).toLocaleString("en", { minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts, useGrouping: false })); element.val(Number.parseFloat(value).toLocaleString("en", { minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts, useGrouping: false }));
}); });
$(rootSelector + " .locale-number-input.locale-number-generic").each(function() $(rootSelector + " .locale-number-input.locale-number-generic").each(function ()
{ {
var element = $(this); var element = $(this);
var value = element.val(); var value = element.val();
@ -709,7 +705,7 @@ function RefreshLocaleNumberInput(rootSelector = "#page-content")
} }
RefreshLocaleNumberInput(); RefreshLocaleNumberInput();
$(document).on("click", ".easy-link-copy-textbox", function() $(document).on("click", ".easy-link-copy-textbox", function ()
{ {
$(this).select(); $(this).select();
}); });
@ -733,12 +729,12 @@ if (GetUriParam("embedded"))
</div>'); </div>');
} }
$(document).on("click", ".close-last-modal-button", function() $(document).on("click", ".close-last-modal-button", function ()
{ {
window.parent.postMessage(WindowMessageBag("CloseLastModal"), Grocy.BaseUrl); window.parent.postMessage(WindowMessageBag("CloseLastModal"), Grocy.BaseUrl);
}); });
$("body").on("keydown", function(e) $("body").on("keydown", function (e)
{ {
if (e.key == "Escape") if (e.key == "Escape")
{ {
@ -747,7 +743,7 @@ $("body").on("keydown", function(e)
}); });
$(window).on("message", function(e) $(window).on("message", function (e)
{ {
var data = e.originalEvent.data; var data = e.originalEvent.data;
@ -779,7 +775,7 @@ $(window).on("message", function(e)
window.postMessage(data.Payload, Grocy.BaseUrl); window.postMessage(data.Payload, Grocy.BaseUrl);
// => Bubble the broadcast message down to all child iframes // => Bubble the broadcast message down to all child iframes
$("iframe.embed-responsive").each(function() $("iframe.embed-responsive").each(function ()
{ {
$(this)[0].contentWindow.postMessage(data, Grocy.BaseUrl); $(this)[0].contentWindow.postMessage(data, Grocy.BaseUrl);
}); });
@ -787,7 +783,7 @@ $(window).on("message", function(e)
}); });
window.IsGrocy = true; window.IsGrocy = true;
Grocy.GetTopmostWindow = function() Grocy.GetTopmostWindow = function ()
{ {
if (window.top.IsGrocy) if (window.top.IsGrocy)
{ {
@ -814,7 +810,7 @@ Grocy.GetTopmostWindow = function()
} }
} }
$(document).on("click", ".show-as-dialog-link", function(e) $(document).on("click", ".show-as-dialog-link", function (e)
{ {
e.preventDefault(); e.preventDefault();
@ -854,7 +850,7 @@ $('[data-toggle="tooltip"]').tooltip();
// serializeJSON defaults // serializeJSON defaults
$.serializeJSON.defaultOptions.checkboxUncheckedValue = "0"; $.serializeJSON.defaultOptions.checkboxUncheckedValue = "0";
$(Grocy.UserPermissions).each(function(index, item) $(Grocy.UserPermissions).each(function (index, item)
{ {
if (item.has_permission == 0) if (item.has_permission == 0)
{ {
@ -862,7 +858,7 @@ $(Grocy.UserPermissions).each(function(index, item)
} }
}); });
$('a.link-return').not(".btn").each(function() $('a.link-return').not(".btn").each(function ()
{ {
var base = $(this).data('href'); var base = $(this).data('href');
if (base.contains('?')) if (base.contains('?'))
@ -875,7 +871,7 @@ $('a.link-return').not(".btn").each(function()
} }
}); });
$(document).on("click", "a.btn.link-return", function(e) $(document).on("click", "a.btn.link-return", function (e)
{ {
e.preventDefault(); e.preventDefault();
@ -890,7 +886,7 @@ $(document).on("click", "a.btn.link-return", function(e)
} }
}); });
$('.dropdown-item').has('.form-check input[type=checkbox]').on('click', function(e) $('.dropdown-item').has('.form-check input[type=checkbox]').on('click', function (e)
{ {
if ($(e.target).is('div.form-check') || $(e.target).is('div.dropdown-item')) if ($(e.target).is('div.form-check') || $(e.target).is('div.dropdown-item'))
{ {
@ -898,12 +894,12 @@ $('.dropdown-item').has('.form-check input[type=checkbox]').on('click', function
} }
}); });
$('[data-toggle="tooltip"][data-html="true"]').on("shown.bs.tooltip", function() $('[data-toggle="tooltip"][data-html="true"]').on("shown.bs.tooltip", function ()
{ {
RefreshLocaleNumberDisplay(".tooltip"); RefreshLocaleNumberDisplay(".tooltip");
}) })
$(document).on("click", '.btn, a, button', function(e) $(document).on("click", '.btn, a, button', function (e)
{ {
// Remove focus and hide any tooltips after click // Remove focus and hide any tooltips after click
document.activeElement.blur(); document.activeElement.blur();
@ -912,7 +908,7 @@ $(document).on("click", '.btn, a, button', function(e)
// Delay only initial field focus // Delay only initial field focus
Grocy.FormFocusDelay = 500; Grocy.FormFocusDelay = 500;
setTimeout(function() setTimeout(function ()
{ {
Grocy.FormFocusDelay = 0; Grocy.FormFocusDelay = 0;
}, 1000); }, 1000);