diff --git a/js/components/barcodescanner.js b/js/components/barcodescanner.js
new file mode 100644
index 00000000..0e90875d
--- /dev/null
+++ b/js/components/barcodescanner.js
@@ -0,0 +1,306 @@
+import Quagga from '@ericblade/quagga2/dist/quagga';
+
+function barcodescanner(Grocy)
+{
+ Grocy.Components.BarcodeScanner = {};
+
+ Grocy.Components.BarcodeScanner.LiveVideoSizeAdjusted = false;
+ Grocy.Components.BarcodeScanner.CheckCapabilities = async function()
+ {
+ var track = Quagga.CameraAccess.getActiveTrack();
+ var capabilities = {};
+ if (typeof track.getCapabilities === 'function')
+ {
+ capabilities = track.getCapabilities();
+ }
+
+ // If there is more than 1 camera, show the camera selection
+ var cameras = await Quagga.CameraAccess.enumerateVideoDevices();
+ var cameraSelect = document.querySelector('.cameraSelect-wrapper');
+ if (cameraSelect)
+ {
+ cameraSelect.style.display = cameras.length > 1 ? 'inline-block' : 'none';
+ }
+
+ // Check if the camera is capable to turn on a torch.
+ var canTorch = typeof capabilities.torch === 'boolean' && capabilities.torch
+ // Remove the torch button, if either the device can not torch or AutoTorchOn is set.
+ var node = document.querySelector('.torch');
+ if (node)
+ {
+ node.style.display = canTorch && !Grocy.FeatureFlags.GROCY_FEATURE_FLAG_AUTO_TORCH_ON_WITH_CAMERA ? 'inline-block' : 'none';
+ }
+ // If AutoTorchOn is set, turn on the torch.
+ if (canTorch && Grocy.FeatureFlags.GROCY_FEATURE_FLAG_AUTO_TORCH_ON_WITH_CAMERA)
+ {
+ Grocy.Components.BarcodeScanner.TorchOn(track);
+ }
+
+ // Reduce the height of the video, if it's higher than then the viewport
+ if (!Grocy.Components.BarcodeScanner.LiveVideoSizeAdjusted)
+ {
+ var bc = document.getElementById('barcodescanner-container');
+ if (bc)
+ {
+ var bcAspectRatio = bc.offsetWidth / bc.offsetHeight;
+ var settings = track.getSettings();
+ if (bcAspectRatio > settings.aspectRatio)
+ {
+ var v = document.querySelector('#barcodescanner-livestream video')
+ if (v)
+ {
+ var c = document.querySelector('#barcodescanner-livestream canvas')
+ var newWidth = v.clientWidth / bcAspectRatio * settings.aspectRatio + 'px';
+ v.style.width = newWidth;
+ c.style.width = newWidth;
+ }
+ }
+
+ Grocy.Components.BarcodeScanner.LiveVideoSizeAdjusted = true;
+ }
+ }
+ }
+
+ Grocy.Components.BarcodeScanner.StartScanning = function()
+ {
+ Grocy.Components.BarcodeScanner.DecodedCodesCount = 0;
+ Grocy.Components.BarcodeScanner.DecodedCodesErrorCount = 0;
+
+ Quagga.init({
+ inputStream: {
+ name: "Live",
+ type: "LiveStream",
+ target: document.querySelector("#barcodescanner-livestream"),
+ constraints: {
+ facingMode: "environment",
+ ...(window.localStorage.getItem('cameraId') && { deviceId: window.localStorage.getItem('cameraId') }) // If preferred cameraId is set, request to use that specific camera
+ }
+ },
+ locator: {
+ patchSize: Grocy.UserSettings.quagga2_patchsize,
+ halfSample: Grocy.UserSettings.quagga2_halfsample,
+ debug: {
+ showCanvas: Grocy.UserSettings.quagga2_debug,
+ showPatches: Grocy.UserSettings.quagga2_debug,
+ showFoundPatches: Grocy.UserSettings.quagga2_debug,
+ showSkeleton: Grocy.UserSettings.quagga2_debug,
+ showLabels: Grocy.UserSettings.quagga2_debug,
+ showPatchLabels: Grocy.UserSettings.quagga2_debug,
+ showRemainingPatchLabels: Grocy.UserSettings.quagga2_debug,
+ boxFromPatches: {
+ showTransformed: Grocy.UserSettings.quagga2_debug,
+ showTransformedBox: Grocy.UserSettings.quagga2_debug,
+ showBB: Grocy.UserSettings.quagga2_debug
+ }
+ }
+ },
+ numOfWorkers: Grocy.UserSettings.quagga2_numofworkers,
+ frequency: Grocy.UserSettings.quagga2_frequency,
+ decoder: {
+ readers: [
+ "ean_reader",
+ "ean_8_reader",
+ "code_128_reader"
+ ],
+ debug: {
+ showCanvas: Grocy.UserSettings.quagga2_debug,
+ showPatches: Grocy.UserSettings.quagga2_debug,
+ showFoundPatches: Grocy.UserSettings.quagga2_debug,
+ showSkeleton: Grocy.UserSettings.quagga2_debug,
+ showLabels: Grocy.UserSettings.quagga2_debug,
+ showPatchLabels: Grocy.UserSettings.quagga2_debug,
+ showRemainingPatchLabels: Grocy.UserSettings.quagga2_debug,
+ boxFromPatches: {
+ showTransformed: Grocy.UserSettings.quagga2_debug,
+ showTransformedBox: Grocy.UserSettings.quagga2_debug,
+ showBB: Grocy.UserSettings.quagga2_debug
+ }
+ }
+ },
+ locate: true
+ }, function(error)
+ {
+ // error *needs* to be logged here, otherwise the stack trace is lying.
+ console.error(error);
+ if (error)
+ {
+ Grocy.FrontendHelpers.ShowGenericError("Error while initializing the barcode scanning library", error.message);
+ toastr.info(Grocy.translate("Camera access is only possible when supported and allowed by your browser and when grocy is served via a secure (https://) connection"));
+ window.localStorage.removeItem("cameraId");
+ setTimeout(function()
+ {
+ bootbox.hideAll();
+ }, 500);
+ return;
+ }
+
+ Grocy.Components.BarcodeScanner.CheckCapabilities();
+
+ Quagga.start();
+ });
+ }
+
+ Grocy.Components.BarcodeScanner.StopScanning = function()
+ {
+ Quagga.stop();
+
+ Grocy.Components.BarcodeScanner.DecodedCodesCount = 0;
+ Grocy.Components.BarcodeScanner.DecodedCodesErrorCount = 0;
+
+ bootbox.hideAll();
+ }
+
+ Grocy.Components.BarcodeScanner.TorchOn = function(track)
+ {
+ if (track)
+ {
+ track.applyConstraints({
+ advanced: [
+ {
+ torch: true
+ }
+ ]
+ });
+ }
+ }
+
+ Quagga.onDetected(function(result)
+ {
+ $.each(result.codeResult.decodedCodes, function(id, error)
+ {
+ if (error.error != undefined)
+ {
+ Grocy.Components.BarcodeScanner.DecodedCodesCount++;
+ Grocy.Components.BarcodeScanner.DecodedCodesErrorCount += parseFloat(error.error);
+ }
+ });
+
+ if (Grocy.Components.BarcodeScanner.DecodedCodesErrorCount / Grocy.Components.BarcodeScanner.DecodedCodesCount < 0.15)
+ {
+ Grocy.Components.BarcodeScanner.StopScanning();
+ $(document).trigger("Grocy.BarcodeScanned", [result.codeResult.code, Grocy.Components.BarcodeScanner.CurrentTarget]);
+ }
+ });
+
+ Quagga.onProcessed(function(result)
+ {
+ var drawingCtx = Quagga.canvas.ctx.overlay;
+ var drawingCanvas = Quagga.canvas.dom.overlay;
+
+ if (result)
+ {
+ if (result.boxes)
+ {
+ drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute("width")), parseInt(drawingCanvas.getAttribute("height")));
+ result.boxes.filter(function(box)
+ {
+ return box !== result.box;
+ }).forEach(function(box)
+ {
+ Quagga.ImageDebug.drawPath(box, { x: 0, y: 1 }, drawingCtx, { color: "yellow", lineWidth: 4 });
+ });
+ }
+
+ if (result.box)
+ {
+ Quagga.ImageDebug.drawPath(result.box, { x: 0, y: 1 }, drawingCtx, { color: "green", lineWidth: 4 });
+ }
+
+ if (result.codeResult && result.codeResult.code)
+ {
+ Quagga.ImageDebug.drawPath(result.line, { x: 'x', y: 'y' }, drawingCtx, { color: "red", lineWidth: 4 });
+ }
+ }
+ });
+
+ $(document).on("click", "#barcodescanner-start-button", async function(e)
+ {
+ e.preventDefault();
+ var inputElement = $(e.currentTarget).prev();
+ if (inputElement.hasAttr("disabled"))
+ {
+ // Do nothing and disable the barcode scanner start button
+ $(e.currentTarget).addClass("disabled");
+ return;
+ }
+
+ Grocy.Components.BarcodeScanner.CurrentTarget = inputElement.attr("data-target");
+
+ var dialog = bootbox.dialog({
+ message: '
',
+ title: Grocy.translate('Scan a barcode'),
+ onEscape: function()
+ {
+ Grocy.Components.BarcodeScanner.StopScanning();
+ },
+ size: 'big',
+ backdrop: true,
+ closeButton: true,
+ buttons: {
+ torch: {
+ label: '',
+ className: 'btn-warning responsive-button torch',
+ callback: function()
+ {
+ Grocy.Components.BarcodeScanner.TorchOn(Quagga.CameraAccess.getActiveTrack());
+ return false;
+ }
+ },
+ cancel: {
+ label: Grocy.translate('Cancel'),
+ className: 'btn-secondary responsive-button',
+ callback: function()
+ {
+ Grocy.Components.BarcodeScanner.StopScanning();
+ }
+ }
+ }
+ });
+
+ // Add camera select to existing dialog
+ dialog.find('.bootbox-body').append('');
+ var cameraSelect = document.querySelector('.cameraSelect');
+
+ if (cameraSelect != null)
+ {
+ var cameras = await Quagga.CameraAccess.enumerateVideoDevices();
+ cameras.forEach(camera =>
+ {
+ var option = document.createElement("option");
+ option.text = camera.label ? camera.label : camera.deviceId; // Use camera label if it exists, else show device id
+ option.value = camera.deviceId;
+ cameraSelect.appendChild(option);
+ });
+
+ // Set initial value to preferred camera if one exists - and if not, start out empty
+ cameraSelect.value = window.localStorage.getItem('cameraId');
+
+ cameraSelect.onchange = function()
+ {
+ window.localStorage.setItem('cameraId', cameraSelect.value);
+ Quagga.stop();
+ Grocy.Components.BarcodeScanner.StartScanning();
+ };
+ }
+
+ Grocy.Components.BarcodeScanner.StartScanning();
+ });
+
+ setTimeout(function()
+ {
+ $(".barcodescanner-input:visible").each(function()
+ {
+ if ($(this).hasAttr("disabled"))
+ {
+ $(this).after('');
+ }
+ else
+ {
+ $(this).after('');
+ }
+ });
+ }, 50);
+
+}
+
+export { barcodescanner }
\ No newline at end of file
diff --git a/js/components/batterycard.js b/js/components/batterycard.js
new file mode 100644
index 00000000..f2159d19
--- /dev/null
+++ b/js/components/batterycard.js
@@ -0,0 +1,36 @@
+import { EmptyElementWhenMatches } from '../helpers/extensions'
+import { RefreshContextualTimeago } from '../configs/timeago'
+
+function batterycard(Grocy)
+{
+
+ Grocy.Components.BatteryCard = {};
+
+ Grocy.Components.BatteryCard.Refresh = function(batteryId)
+ {
+ Grocy.Api.Get('batteries/' + batteryId,
+ function(batteryDetails)
+ {
+ $('#batterycard-battery-name').text(batteryDetails.battery.name);
+ $('#batterycard-battery-used_in').text(batteryDetails.battery.used_in);
+ $('#batterycard-battery-last-charged').text((batteryDetails.last_charged || Grocy.translate('never')));
+ $('#batterycard-battery-last-charged-timeago').attr("datetime", batteryDetails.last_charged || '');
+ $('#batterycard-battery-charge-cycles-count').text((batteryDetails.charge_cycles_count || '0'));
+
+ $('#batterycard-battery-edit-button').attr("href", Grocy.FormatUrl("/battery/" + batteryDetails.battery.id.toString()));
+ $('#batterycard-battery-journal-button').attr("href", Grocy.FormatUrl("/batteriesjournal?embedded&battery=" + batteryDetails.battery.id.toString()));
+ $('#batterycard-battery-edit-button').removeClass("disabled");
+ $('#batterycard-battery-journal-button').removeClass("disabled");
+
+ EmptyElementWhenMatches('#batterycard-battery-last-charged-timeago', Grocy.translate('timeago_nan'));
+ RefreshContextualTimeago(".batterycard");
+ },
+ function(xhr)
+ {
+ console.error(xhr);
+ }
+ );
+ };
+}
+
+export { batterycard }
\ No newline at end of file
diff --git a/js/components/calendarcard.js b/js/components/calendarcard.js
new file mode 100644
index 00000000..11a16131
--- /dev/null
+++ b/js/components/calendarcard.js
@@ -0,0 +1,45 @@
+function calendarcard(Grocy)
+{
+ $('#calendar').datetimepicker(
+ {
+ format: 'L',
+ buttons: {
+ showToday: true,
+ showClose: false
+ },
+ calendarWeeks: true,
+ locale: moment.locale(),
+ icons: {
+ time: 'far fa-clock',
+ date: 'far fa-calendar',
+ up: 'fas fa-arrow-up',
+ down: 'fas fa-arrow-down',
+ previous: 'fas fa-chevron-left',
+ next: 'fas fa-chevron-right',
+ today: 'fas fa-calendar-check',
+ clear: 'far fa-trash-alt',
+ close: 'far fa-times-circle'
+ },
+ keepOpen: true,
+ inline: true,
+ keyBinds: {
+ up: function(widget) { },
+ down: function(widget) { },
+ 'control up': function(widget) { },
+ 'control down': function(widget) { },
+ left: function(widget) { },
+ right: function(widget) { },
+ pageUp: function(widget) { },
+ pageDown: function(widget) { },
+ enter: function(widget) { },
+ escape: function(widget) { },
+ 'control space': function(widget) { },
+ t: function(widget) { },
+ 'delete': function(widget) { }
+ }
+ });
+
+ $('#calendar').datetimepicker('show');
+}
+
+export { calendarcard }
\ No newline at end of file
diff --git a/js/components/chorecard.js b/js/components/chorecard.js
new file mode 100644
index 00000000..05595a12
--- /dev/null
+++ b/js/components/chorecard.js
@@ -0,0 +1,43 @@
+import { EmptyElementWhenMatches } from '../helpers/extensions'
+import { RefreshContextualTimeago } from '../configs/timeago'
+
+function chorecard(Grocy)
+{
+ Grocy.Components.ChoreCard = {};
+
+ Grocy.Components.ChoreCard.Refresh = function(choreId)
+ {
+ Grocy.Api.Get('chores/' + choreId,
+ function(choreDetails)
+ {
+ $('#chorecard-chore-name').text(choreDetails.chore.name);
+ $('#chorecard-chore-last-tracked').text((choreDetails.last_tracked || Grocy.translate('never')));
+ $('#chorecard-chore-last-tracked-timeago').attr("datetime", choreDetails.last_tracked || '');
+ $('#chorecard-chore-tracked-count').text((choreDetails.tracked_count || '0'));
+ $('#chorecard-chore-last-done-by').text((choreDetails.last_done_by.display_name || Grocy.translate('Unknown')));
+
+ $('#chorecard-chore-edit-button').attr("href", Grocy.FormatUrl("/chore/" + choreDetails.chore.id.toString()));
+ $('#chorecard-chore-journal-button').attr("href", Grocy.FormatUrl("/choresjournal?embedded&chore=" + choreDetails.chore.id.toString()));
+ $('#chorecard-chore-edit-button').removeClass("disabled");
+ $('#chorecard-chore-journal-button').removeClass("disabled");
+
+ if (choreDetails.chore.track_date_only == 1)
+ {
+ $("#chorecard-chore-last-tracked-timeago").addClass("timeago-date-only");
+ }
+ else
+ {
+ $("#chorecard-chore-last-tracked-timeago").removeClass("timeago-date-only");
+ }
+
+ EmptyElementWhenMatches('#chorecard-chore-last-tracked-timeago', Grocy.translate('timeago_nan'));
+ RefreshContextualTimeago(".chorecard");
+ },
+ function(xhr)
+ {
+ console.error(xhr);
+ }
+ );
+ };
+}
+export { chorecard }
\ No newline at end of file
diff --git a/js/components/datetimepicker.js b/js/components/datetimepicker.js
new file mode 100644
index 00000000..06b3caec
--- /dev/null
+++ b/js/components/datetimepicker.js
@@ -0,0 +1,317 @@
+import { EmptyElementWhenMatches } from '../helpers/extensions'
+import { RefreshContextualTimeago } from '../configs/timeago'
+
+function datetimepicker(Grocy)
+{
+ Grocy.Components.DateTimePicker = {};
+
+ Grocy.Components.DateTimePicker.GetInputElement = function()
+ {
+ return $('.datetimepicker').find('input').not(".form-check-input");
+ }
+
+ Grocy.Components.DateTimePicker.GetValue = function()
+ {
+ return Grocy.Components.DateTimePicker.GetInputElement().val();
+ }
+
+ Grocy.Components.DateTimePicker.SetValue = function(value)
+ {
+ // "Click" the shortcut checkbox when the desired value is
+ // not the shortcut value and it is currently set
+ var shortcutValue = $("#datetimepicker-shortcut").data("datetimepicker-shortcut-value");
+ if (value != shortcutValue && $("#datetimepicker-shortcut").is(":checked"))
+ {
+ $("#datetimepicker-shortcut").click();
+ }
+ Grocy.Components.DateTimePicker.GetInputElement().val(value);
+ Grocy.Components.DateTimePicker.GetInputElement().trigger('change');
+
+ Grocy.Components.DateTimePicker.GetInputElement().keyup();
+ }
+
+ Grocy.Components.DateTimePicker.Clear = function()
+ {
+ $(".datetimepicker").datetimepicker("destroy");
+ Grocy.Components.DateTimePicker.Init();
+
+ Grocy.Components.DateTimePicker.GetInputElement().val("");
+
+ // "Click" the shortcut checkbox when the desired value is
+ // not the shortcut value and it is currently set
+ var value = "";
+ var shortcutValue = $("#datetimepicker-shortcut").data("datetimepicker-shortcut-value");
+ if (value != shortcutValue && $("#datetimepicker-shortcut").is(":checked"))
+ {
+ $("#datetimepicker-shortcut").click();
+ }
+
+ $('#datetimepicker-timeago').text('');
+ }
+
+ Grocy.Components.DateTimePicker.ChangeFormat = function(format)
+ {
+ $(".datetimepicker").datetimepicker("destroy");
+ Grocy.Components.DateTimePicker.GetInputElement().data("format", format);
+ Grocy.Components.DateTimePicker.Init();
+
+ if (format == "YYYY-MM-DD")
+ {
+ Grocy.Components.DateTimePicker.GetInputElement().addClass("date-only-datetimepicker");
+ }
+ else
+ {
+ Grocy.Components.DateTimePicker.GetInputElement().removeClass("date-only-datetimepicker");
+ }
+ }
+
+ var startDate = null;
+ var inputElement = Grocy.Components.DateTimePicker.GetInputElement();
+ if (inputElement.data('init-with-now') === true)
+ {
+ startDate = moment().format(inputElement.data('format'));
+ }
+ if (inputElement.data('init-value').length > 0)
+ {
+ startDate = moment(inputElement.data('init-value')).format(Grocy.Components.DateTimePicker.GetInputElement().data('format'));
+ }
+
+ var limitDate = moment('2999-12-31 23:59:59');
+ if (Grocy.Components.DateTimePicker.GetInputElement().data('limit-end-to-now') === true)
+ {
+ limitDate = moment();
+ }
+
+ Grocy.Components.DateTimePicker.Init = function()
+ {
+ $('.datetimepicker').datetimepicker(
+ {
+ format: Grocy.Components.DateTimePicker.GetInputElement().data('format'),
+ buttons: {
+ showToday: true,
+ showClose: true
+ },
+ calendarWeeks: Grocy.CalendarShowWeekNumbers,
+ maxDate: limitDate,
+ locale: moment.locale(),
+ defaultDate: startDate,
+ useCurrent: false,
+ icons: {
+ time: 'far fa-clock',
+ date: 'far fa-calendar',
+ up: 'fas fa-arrow-up',
+ down: 'fas fa-arrow-down',
+ previous: 'fas fa-chevron-left',
+ next: 'fas fa-chevron-right',
+ today: 'fas fa-calendar-check',
+ clear: 'far fa-trash-alt',
+ close: 'far fa-times-circle'
+ },
+ sideBySide: true,
+ keyBinds: {
+ up: function(widget) { },
+ down: function(widget) { },
+ 'control up': function(widget) { },
+ 'control down': function(widget) { },
+ left: function(widget) { },
+ right: function(widget) { },
+ pageUp: function(widget) { },
+ pageDown: function(widget) { },
+ enter: function(widget) { },
+ escape: function(widget) { },
+ 'control space': function(widget) { },
+ t: function(widget) { },
+ 'delete': function(widget) { }
+ }
+ });
+ }
+ Grocy.Components.DateTimePicker.Init();
+
+ Grocy.Components.DateTimePicker.GetInputElement().on('keyup', function(e)
+ {
+ $('.datetimepicker').datetimepicker('hide');
+
+ var value = Grocy.Components.DateTimePicker.GetValue();
+ var now = new Date();
+ var centuryStart = Number.parseInt(now.getFullYear().toString().substring(0, 2) + '00');
+ var centuryEnd = Number.parseInt(now.getFullYear().toString().substring(0, 2) + '99');
+ var format = Grocy.Components.DateTimePicker.GetInputElement().data('format');
+ var nextInputElement = $(Grocy.Components.DateTimePicker.GetInputElement().data('next-input-selector'));
+
+ //If input is empty and any arrow key is pressed, set date to today
+ if (value.length === 0 && (e.keyCode === 38 || e.keyCode === 40 || e.keyCode === 37 || e.keyCode === 39))
+ {
+ Grocy.Components.DateTimePicker.SetValue(moment(new Date(), format, true).format(format));
+ nextInputElement.focus();
+ }
+ else if (value === 'x' || value === 'X')
+ {
+ Grocy.Components.DateTimePicker.SetValue(moment('2999-12-31 23:59:59').format(format));
+ nextInputElement.focus();
+ }
+ else if (value.length === 4 && !(Number.parseInt(value) > centuryStart && Number.parseInt(value) < centuryEnd))
+ {
+ var date = moment((new Date()).getFullYear().toString() + value);
+ if (date.isBefore(moment()))
+ {
+ date.add(1, "year");
+ }
+ Grocy.Components.DateTimePicker.SetValue(date.format(format));
+ nextInputElement.focus();
+ }
+ else if (value.length === 8 && $.isNumeric(value))
+ {
+ Grocy.Components.DateTimePicker.SetValue(value.replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3'));
+ nextInputElement.focus();
+ }
+ else if (value.length === 7 && $.isNumeric(value.substring(0, 6)) && (value.substring(6, 7).toLowerCase() === "e" || value.substring(6, 7).toLowerCase() === "+"))
+ {
+ var endOfMonth = moment(value.substring(0, 4) + "-" + value.substring(4, 6) + "-01").endOf("month");
+ Grocy.Components.DateTimePicker.SetValue(endOfMonth.format(format));
+ nextInputElement.focus();
+ }
+ else
+ {
+ var dateObj = moment(value, format, true);
+ if (dateObj.isValid())
+ {
+ if (e.shiftKey)
+ {
+ // WITH shift modifier key
+
+ if (e.keyCode === 38) // Up
+ {
+ Grocy.Components.DateTimePicker.SetValue(dateObj.add(-1, 'months').format(format));
+ }
+ else if (e.keyCode === 40) // Down
+ {
+ Grocy.Components.DateTimePicker.SetValue(dateObj.add(1, 'months').format(format));
+ }
+ else if (e.keyCode === 37) // Left
+ {
+ Grocy.Components.DateTimePicker.SetValue(dateObj.add(-1, 'years').format(format));
+ }
+ else if (e.keyCode === 39) // Right
+ {
+ Grocy.Components.DateTimePicker.SetValue(dateObj.add(1, 'years').format(format));
+ }
+ }
+ else
+ {
+ // WITHOUT shift modifier key
+
+ if (e.keyCode === 38) // Up
+ {
+ Grocy.Components.DateTimePicker.SetValue(dateObj.add(-1, 'days').format(format));
+ }
+ else if (e.keyCode === 40) // Down
+ {
+ Grocy.Components.DateTimePicker.SetValue(dateObj.add(1, 'days').format(format));
+ }
+ else if (e.keyCode === 37) // Left
+ {
+ Grocy.Components.DateTimePicker.SetValue(dateObj.add(-1, 'weeks').format(format));
+ }
+ else if (e.keyCode === 39) // Right
+ {
+ Grocy.Components.DateTimePicker.SetValue(dateObj.add(1, 'weeks').format(format));
+ }
+ }
+ }
+ }
+
+ //Custom validation
+ value = Grocy.Components.DateTimePicker.GetValue();
+ dateObj = moment(value, format, true);
+ var element = Grocy.Components.DateTimePicker.GetInputElement()[0];
+ if (!dateObj.isValid())
+ {
+ if ($(element).hasAttr("required"))
+ {
+ element.setCustomValidity("error");
+ }
+ }
+ else
+ {
+ if (Grocy.Components.DateTimePicker.GetInputElement().data('limit-end-to-now') === true && dateObj.isAfter(moment()))
+ {
+ element.setCustomValidity("error");
+ }
+ else if (Grocy.Components.DateTimePicker.GetInputElement().data('limit-start-to-now') === true && dateObj.isBefore(moment()))
+ {
+ element.setCustomValidity("error");
+ }
+ else
+ {
+ element.setCustomValidity("");
+ }
+
+ var earlierThanLimit = Grocy.Components.DateTimePicker.GetInputElement().data("earlier-than-limit");
+ if (!earlierThanLimit.isEmpty())
+ {
+ if (moment(value).isBefore(moment(earlierThanLimit)))
+ {
+ $("#datetimepicker-earlier-than-info").removeClass("d-none");
+ }
+ else
+ {
+ $("#datetimepicker-earlier-than-info").addClass("d-none");
+ }
+ }
+ }
+
+ // "Click" the shortcut checkbox when the shortcut value was
+ // entered manually and it is currently not set
+ var shortcutValue = $("#datetimepicker-shortcut").data("datetimepicker-shortcut-value");
+ if (value == shortcutValue && !$("#datetimepicker-shortcut").is(":checked"))
+ {
+ $("#datetimepicker-shortcut").click();
+ }
+ });
+
+ Grocy.Components.DateTimePicker.GetInputElement().on('input', function(e)
+ {
+ $('#datetimepicker-timeago').attr("datetime", Grocy.Components.DateTimePicker.GetValue());
+ EmptyElementWhenMatches('#datetimepicker-timeago', Grocy.translate('timeago_nan'));
+ RefreshContextualTimeago("#datetimepicker-wrapper");
+ });
+
+ $('.datetimepicker').on('update.datetimepicker', function(e)
+ {
+ Grocy.Components.DateTimePicker.GetInputElement().trigger('input');
+ Grocy.Components.DateTimePicker.GetInputElement().trigger('change');
+ Grocy.Components.DateTimePicker.GetInputElement().trigger('keypress');
+ Grocy.Components.DateTimePicker.GetInputElement().trigger('keyup');
+ });
+
+ $('.datetimepicker').on('hide.datetimepicker', function(e)
+ {
+ Grocy.Components.DateTimePicker.GetInputElement().trigger('input');
+ Grocy.Components.DateTimePicker.GetInputElement().trigger('change');
+ Grocy.Components.DateTimePicker.GetInputElement().trigger('keypress');
+ Grocy.Components.DateTimePicker.GetInputElement().trigger('keyup');
+ });
+
+ $("#datetimepicker-shortcut").on("click", function()
+ {
+ if (this.checked)
+ {
+ var value = $("#datetimepicker-shortcut").data("datetimepicker-shortcut-value");
+ Grocy.Components.DateTimePicker.SetValue(value);
+ Grocy.Components.DateTimePicker.GetInputElement().attr("readonly", "");
+ $(Grocy.Components.DateTimePicker.GetInputElement().data('next-input-selector')).focus();
+ }
+ else
+ {
+ Grocy.Components.DateTimePicker.SetValue("");
+ Grocy.Components.DateTimePicker.GetInputElement().removeAttr("readonly");
+ Grocy.Components.DateTimePicker.GetInputElement().focus();
+ }
+
+ Grocy.Components.DateTimePicker.GetInputElement().trigger('input');
+ Grocy.Components.DateTimePicker.GetInputElement().trigger('change');
+ Grocy.Components.DateTimePicker.GetInputElement().trigger('keypress');
+ });
+}
+
+export { datetimepicker }
\ No newline at end of file
diff --git a/js/components/datetimepicker2.js b/js/components/datetimepicker2.js
new file mode 100644
index 00000000..5b781354
--- /dev/null
+++ b/js/components/datetimepicker2.js
@@ -0,0 +1,317 @@
+import { EmptyElementWhenMatches } from '../helpers/extensions'
+import { RefreshContextualTimeago } from '../configs/timeago'
+
+function datetimepicker2(Grocy)
+{
+ Grocy.Components.DateTimePicker2 = {};
+
+ Grocy.Components.DateTimePicker2.GetInputElement = function()
+ {
+ return $('.datetimepicker2').find('input').not(".form-check-input");
+ }
+
+ Grocy.Components.DateTimePicker2.GetValue = function()
+ {
+ return Grocy.Components.DateTimePicker2.GetInputElement().val();
+ }
+
+ Grocy.Components.DateTimePicker2.SetValue = function(value)
+ {
+ // "Click" the shortcut checkbox when the desired value is
+ // not the shortcut value and it is currently set
+ var shortcutValue = $("#datetimepicker2-shortcut").data("datetimepicker2-shortcut-value");
+ if (value != shortcutValue && $("#datetimepicker2-shortcut").is(":checked"))
+ {
+ $("#datetimepicker2-shortcut").click();
+ }
+
+ Grocy.Components.DateTimePicker2.GetInputElement().val(value);
+ Grocy.Components.DateTimePicker2.GetInputElement().trigger('change');
+
+ Grocy.Components.DateTimePicker2.GetInputElement().keyup();
+ }
+
+ Grocy.Components.DateTimePicker2.Clear = function()
+ {
+ $(".datetimepicker2").datetimepicker("destroy");
+ Grocy.Components.DateTimePicker2.Init();
+
+ Grocy.Components.DateTimePicker2.GetInputElement().val("");
+
+ // "Click" the shortcut checkbox when the desired value is
+ // not the shortcut value and it is currently set
+ var value = "";
+ var shortcutValue = $("#datetimepicker2-shortcut").data("datetimepicker2-shortcut-value");
+ if (value != shortcutValue && $("#datetimepicker2-shortcut").is(":checked"))
+ {
+ $("#datetimepicker2-shortcut").click();
+ }
+
+ $('#datetimepicker2-timeago').text('');
+ }
+
+ Grocy.Components.DateTimePicker2.ChangeFormat = function(format)
+ {
+ $(".datetimepicker2").datetimepicker("destroy");
+ Grocy.Components.DateTimePicker2.GetInputElement().data("format", format);
+ Grocy.Components.DateTimePicker2.Init();
+
+ if (format == "YYYY-MM-DD")
+ {
+ Grocy.Components.DateTimePicker2.GetInputElement().addClass("date-only-datetimepicker");
+ }
+ else
+ {
+ Grocy.Components.DateTimePicker2.GetInputElement().removeClass("date-only-datetimepicker");
+ }
+ }
+
+ var startDate = null;
+ if (Grocy.Components.DateTimePicker2.GetInputElement().data('init-with-now') === true)
+ {
+ startDate = moment().format(Grocy.Components.DateTimePicker2.GetInputElement().data('format'));
+ }
+ if (Grocy.Components.DateTimePicker2.GetInputElement().data('init-value').length > 0)
+ {
+ startDate = moment(Grocy.Components.DateTimePicker2.GetInputElement().data('init-value')).format(Grocy.Components.DateTimePicker2.GetInputElement().data('format'));
+ }
+
+ var limitDate = moment('2999-12-31 23:59:59');
+ if (Grocy.Components.DateTimePicker2.GetInputElement().data('limit-end-to-now') === true)
+ {
+ limitDate = moment();
+ }
+
+ Grocy.Components.DateTimePicker2.Init = function()
+ {
+ $('.datetimepicker2').datetimepicker(
+ {
+ format: Grocy.Components.DateTimePicker2.GetInputElement().data('format'),
+ buttons: {
+ showToday: true,
+ showClose: true
+ },
+ calendarWeeks: Grocy.CalendarShowWeekNumbers,
+ maxDate: limitDate,
+ locale: moment.locale(),
+ defaultDate: startDate,
+ useCurrent: false,
+ icons: {
+ time: 'far fa-clock',
+ date: 'far fa-calendar',
+ up: 'fas fa-arrow-up',
+ down: 'fas fa-arrow-down',
+ previous: 'fas fa-chevron-left',
+ next: 'fas fa-chevron-right',
+ today: 'fas fa-calendar-check',
+ clear: 'far fa-trash-alt',
+ close: 'far fa-times-circle'
+ },
+ sideBySide: true,
+ keyBinds: {
+ up: function(widget) { },
+ down: function(widget) { },
+ 'control up': function(widget) { },
+ 'control down': function(widget) { },
+ left: function(widget) { },
+ right: function(widget) { },
+ pageUp: function(widget) { },
+ pageDown: function(widget) { },
+ enter: function(widget) { },
+ escape: function(widget) { },
+ 'control space': function(widget) { },
+ t: function(widget) { },
+ 'delete': function(widget) { }
+ }
+ });
+ }
+ Grocy.Components.DateTimePicker2.Init();
+
+ Grocy.Components.DateTimePicker2.GetInputElement().on('keyup', function(e)
+ {
+ $('.datetimepicker2').datetimepicker('hide');
+
+ var value = Grocy.Components.DateTimePicker2.GetValue();
+ var now = new Date();
+ var centuryStart = Number.parseInt(now.getFullYear().toString().substring(0, 2) + '00');
+ var centuryEnd = Number.parseInt(now.getFullYear().toString().substring(0, 2) + '99');
+ var format = Grocy.Components.DateTimePicker2.GetInputElement().data('format');
+ var nextInputElement = $(Grocy.Components.DateTimePicker2.GetInputElement().data('next-input-selector'));
+
+ //If input is empty and any arrow key is pressed, set date to today
+ if (value.length === 0 && (e.keyCode === 38 || e.keyCode === 40 || e.keyCode === 37 || e.keyCode === 39))
+ {
+ Grocy.Components.DateTimePicker2.SetValue(moment(new Date(), format, true).format(format));
+ nextInputElement.focus();
+ }
+ else if (value === 'x' || value === 'X')
+ {
+ Grocy.Components.DateTimePicker2.SetValue(moment('2999-12-31 23:59:59').format(format));
+ nextInputElement.focus();
+ }
+ else if (value.length === 4 && !(Number.parseInt(value) > centuryStart && Number.parseInt(value) < centuryEnd))
+ {
+ var date = moment((new Date()).getFullYear().toString() + value);
+ if (date.isBefore(moment()))
+ {
+ date.add(1, "year");
+ }
+ Grocy.Components.DateTimePicker2.SetValue(date.format(format));
+ nextInputElement.focus();
+ }
+ else if (value.length === 8 && $.isNumeric(value))
+ {
+ Grocy.Components.DateTimePicker2.SetValue(value.replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3'));
+ nextInputElement.focus();
+ }
+ else if (value.length === 7 && $.isNumeric(value.substring(0, 6)) && (value.substring(6, 7).toLowerCase() === "e" || value.substring(6, 7).toLowerCase() === "+"))
+ {
+ var endOfMonth = moment(value.substring(0, 4) + "-" + value.substring(4, 6) + "-01").endOf("month");
+ Grocy.Components.DateTimePicker2.SetValue(endOfMonth.format(format));
+ nextInputElement.focus();
+ }
+ else
+ {
+ var dateObj = moment(value, format, true);
+ if (dateObj.isValid())
+ {
+ if (e.shiftKey)
+ {
+ // WITH shift modifier key
+
+ if (e.keyCode === 38) // Up
+ {
+ Grocy.Components.DateTimePicker2.SetValue(dateObj.add(-1, 'months').format(format));
+ }
+ else if (e.keyCode === 40) // Down
+ {
+ Grocy.Components.DateTimePicker2.SetValue(dateObj.add(1, 'months').format(format));
+ }
+ else if (e.keyCode === 37) // Left
+ {
+ Grocy.Components.DateTimePicker2.SetValue(dateObj.add(-1, 'years').format(format));
+ }
+ else if (e.keyCode === 39) // Right
+ {
+ Grocy.Components.DateTimePicker2.SetValue(dateObj.add(1, 'years').format(format));
+ }
+ }
+ else
+ {
+ // WITHOUT shift modifier key
+
+ if (e.keyCode === 38) // Up
+ {
+ Grocy.Components.DateTimePicker2.SetValue(dateObj.add(-1, 'days').format(format));
+ }
+ else if (e.keyCode === 40) // Down
+ {
+ Grocy.Components.DateTimePicker2.SetValue(dateObj.add(1, 'days').format(format));
+ }
+ else if (e.keyCode === 37) // Left
+ {
+ Grocy.Components.DateTimePicker2.SetValue(dateObj.add(-1, 'weeks').format(format));
+ }
+ else if (e.keyCode === 39) // Right
+ {
+ Grocy.Components.DateTimePicker2.SetValue(dateObj.add(1, 'weeks').format(format));
+ }
+ }
+ }
+ }
+
+ //Custom validation
+ value = Grocy.Components.DateTimePicker2.GetValue();
+ dateObj = moment(value, format, true);
+ var element = Grocy.Components.DateTimePicker2.GetInputElement()[0];
+ if (!dateObj.isValid())
+ {
+ if ($(element).hasAttr("required"))
+ {
+ element.setCustomValidity("error");
+ }
+ }
+ else
+ {
+ if (Grocy.Components.DateTimePicker2.GetInputElement().data('limit-end-to-now') === true && dateObj.isAfter(moment()))
+ {
+ element.setCustomValidity("error");
+ }
+ else if (Grocy.Components.DateTimePicker2.GetInputElement().data('limit-start-to-now') === true && dateObj.isBefore(moment()))
+ {
+ element.setCustomValidity("error");
+ }
+ else
+ {
+ element.setCustomValidity("");
+ }
+
+ var earlierThanLimit = Grocy.Components.DateTimePicker2.GetInputElement().data("earlier-than-limit");
+ if (!earlierThanLimit.isEmpty())
+ {
+ if (moment(value).isBefore(moment(earlierThanLimit)))
+ {
+ $("#datetimepicker-earlier-than-info").removeClass("d-none");
+ }
+ else
+ {
+ $("#datetimepicker-earlier-than-info").addClass("d-none");
+ }
+ }
+ }
+
+ // "Click" the shortcut checkbox when the shortcut value was
+ // entered manually and it is currently not set
+ var shortcutValue = $("#datetimepicker2-shortcut").data("datetimepicker2-shortcut-value");
+ if (value == shortcutValue && !$("#datetimepicker2-shortcut").is(":checked"))
+ {
+ $("#datetimepicker2-shortcut").click();
+ }
+ });
+
+ Grocy.Components.DateTimePicker2.GetInputElement().on('input', function(e)
+ {
+ $('#datetimepicker2-timeago').attr("datetime", Grocy.Components.DateTimePicker2.GetValue());
+ EmptyElementWhenMatches('#datetimepicker2-timeago', Grocy.translate('timeago_nan'));
+ RefreshContextualTimeago("#datetimepicker2-wrapper");
+ });
+
+ $('.datetimepicker2').on('update.datetimepicker', function(e)
+ {
+ Grocy.Components.DateTimePicker2.GetInputElement().trigger('input');
+ Grocy.Components.DateTimePicker2.GetInputElement().trigger('change');
+ Grocy.Components.DateTimePicker2.GetInputElement().trigger('keypress');
+ Grocy.Components.DateTimePicker2.GetInputElement().trigger('keyup');
+ });
+
+ $('.datetimepicker2').on('hide.datetimepicker', function(e)
+ {
+ Grocy.Components.DateTimePicker2.GetInputElement().trigger('input');
+ Grocy.Components.DateTimePicker2.GetInputElement().trigger('change');
+ Grocy.Components.DateTimePicker2.GetInputElement().trigger('keypress');
+ Grocy.Components.DateTimePicker2.GetInputElement().trigger('keyup');
+ });
+
+ $("#datetimepicker2-shortcut").on("click", function()
+ {
+ if (this.checked)
+ {
+ var value = $("#datetimepicker2-shortcut").data("datetimepicker2-shortcut-value");
+ Grocy.Components.DateTimePicker2.SetValue(value);
+ Grocy.Components.DateTimePicker2.GetInputElement().attr("readonly", "");
+ $(Grocy.Components.DateTimePicker2.GetInputElement().data('next-input-selector')).focus();
+ }
+ else
+ {
+ Grocy.Components.DateTimePicker2.SetValue("");
+ Grocy.Components.DateTimePicker2.GetInputElement().removeAttr("readonly");
+ Grocy.Components.DateTimePicker2.GetInputElement().focus();
+ }
+
+ Grocy.Components.DateTimePicker2.GetInputElement().trigger('input');
+ Grocy.Components.DateTimePicker2.GetInputElement().trigger('change');
+ Grocy.Components.DateTimePicker2.GetInputElement().trigger('keypress');
+ });
+}
+
+export { datetimepicker2 }
\ No newline at end of file
diff --git a/js/components/index.js b/js/components/index.js
new file mode 100644
index 00000000..ce706056
--- /dev/null
+++ b/js/components/index.js
@@ -0,0 +1,15 @@
+export * from './barcodescanner';
+export * from './batterycard';
+export * from './calendarcard';
+export * from './chorecard';
+export * from './datetimepicker';
+export * from './datetimepicker2';
+export * from './locationpicker';
+export * from './numberpicker';
+export * from './productamountpicker';
+export * from './productcard';
+export * from './productpicker';
+export * from './recipepicker';
+export * from './shoppinglocationpicker';
+export * from './userfieldsform';
+export * from './userpicker';
\ No newline at end of file
diff --git a/js/components/locationpicker.js b/js/components/locationpicker.js
new file mode 100644
index 00000000..66e0b912
--- /dev/null
+++ b/js/components/locationpicker.js
@@ -0,0 +1,80 @@
+function locationpicker(Grocy)
+{
+
+ Grocy.Components.LocationPicker = {};
+
+ Grocy.Components.LocationPicker.GetPicker = function()
+ {
+ return $('#location_id');
+ }
+
+ Grocy.Components.LocationPicker.GetInputElement = function()
+ {
+ return $('#location_id_text_input');
+ }
+
+ Grocy.Components.LocationPicker.GetValue = function()
+ {
+ return $('#location_id').val();
+ }
+
+ Grocy.Components.LocationPicker.SetValue = function(value)
+ {
+ Grocy.Components.LocationPicker.GetInputElement().val(value);
+ Grocy.Components.LocationPicker.GetInputElement().trigger('change');
+ }
+
+ Grocy.Components.LocationPicker.SetId = function(value)
+ {
+ Grocy.Components.LocationPicker.GetPicker().val(value);
+ Grocy.Components.LocationPicker.GetPicker().data('combobox').refresh();
+ Grocy.Components.LocationPicker.GetInputElement().trigger('change');
+ }
+
+ Grocy.Components.LocationPicker.Clear = function()
+ {
+ Grocy.Components.LocationPicker.SetValue('');
+ Grocy.Components.LocationPicker.SetId(null);
+ }
+
+ $('.location-combobox').combobox({
+ appendId: '_text_input',
+ bsVersion: '4',
+ clearIfNoMatch: true
+ });
+
+ // these names seem a bit long, but as they live in global space
+ // and this is a component, they need to be unique.
+ var locationpicker_doFocus = false;
+ var this_location_picker = Grocy.Components.LocationPicker.GetPicker();
+
+ var prefillByName = this_location_picker.parent().data('prefill-by-name').toString();
+ if (typeof prefillByName !== "undefined")
+ {
+ var possibleOptionElement = $("#location_id option:contains(\"" + prefillByName + "\")").first();
+
+ if (possibleOptionElement.length > 0)
+ {
+ locationpicker_doFocus = true;
+ this_location_picker.val(possibleOptionElement.val());
+ }
+ }
+
+ var prefillById = this_location_picker.parent().data('prefill-by-id').toString();
+ if (typeof prefillById !== "undefined")
+ {
+ locationpicker_doFocus = true;
+ this_location_picker.val(prefillById);
+ }
+
+ if (locationpicker_doFocus)
+ {
+ this_location_picker.data('combobox').refresh();
+ this_location_picker.trigger('change');
+
+ $(this_location_picker.parent().data('next-input-selector').toString())
+ .focus();
+ }
+}
+
+export { locationpicker }
\ No newline at end of file
diff --git a/js/components/numberpicker.js b/js/components/numberpicker.js
new file mode 100644
index 00000000..f60d2cfd
--- /dev/null
+++ b/js/components/numberpicker.js
@@ -0,0 +1,99 @@
+function numberpicker(Grocy)
+{
+ $(".numberpicker-down-button").unbind('click').on("click", function()
+ {
+ var inputElement = $(this).parent().parent().find('input[type="number"]');
+ inputElement.val(parseFloat(inputElement.val() || 1) - 1);
+ inputElement.trigger('keyup');
+ inputElement.trigger('change');
+ });
+
+ $(".numberpicker-up-button").unbind('click').on("click", function()
+ {
+ var inputElement = $(this).parent().parent().find('input[type="number"]');
+ inputElement.val(parseFloat(inputElement.val() || 0) + 1);
+ inputElement.trigger('keyup');
+ inputElement.trigger('change');
+ });
+
+ $(".numberpicker").on("keyup", function()
+ {
+ if ($(this).attr("data-not-equal") && !$(this).attr("data-not-equal").toString().isEmpty() && $(this).attr("data-not-equal") == $(this).val())
+ {
+ $(this)[0].setCustomValidity("error");
+ }
+ else
+ {
+ $(this)[0].setCustomValidity("");
+ }
+ });
+
+ $(".numberpicker").each(function()
+ {
+ new MutationObserver(function(mutations)
+ {
+ mutations.forEach(function(mutation)
+ {
+ if (mutation.type == "attributes" && (mutation.attributeName == "min" || mutation.attributeName == "max" || mutation.attributeName == "data-not-equal" || mutation.attributeName == "data-initialised"))
+ {
+ var element = $(mutation.target);
+ var min = element.attr("min");
+ var decimals = element.attr("data-decimals");
+
+ var max = "";
+ if (element.hasAttr("max"))
+ {
+ max = element.attr("max");
+ }
+
+ if (element.hasAttr("data-not-equal"))
+ {
+ var notEqual = element.attr("data-not-equal");
+
+ if (notEqual != "NaN")
+ {
+ if (max.isEmpty())
+ {
+ element.parent().find(".invalid-feedback").text(Grocy.translate("This cannot be lower than %1$s or equal %2$s and needs to be a valid number with max. %3$s decimal places", parseFloat(min).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), parseFloat(notEqual).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), decimals));
+ }
+ else
+ {
+ element.parent().find(".invalid-feedback").text(Grocy.translate("This must be between %1$s and %2$s, cannot equal %3$s and needs to be a valid number with max. %4$s decimal places", parseFloat(min).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), parseFloat(max).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), parseFloat(notEqual).toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals }), decimals));
+ }
+
+ return;
+ }
+ }
+
+ if (max.isEmpty())
+ {
+ element.parent().find(".invalid-feedback").text(Grocy.translate("This cannot be lower than %1$s and needs to be a valid number with max. %2$s decimal places", parseFloat(min).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), decimals));
+ }
+ else
+ {
+ element.parent().find(".invalid-feedback").text(Grocy.translate("This must between %1$s and %2$s and needs to be a valid number with max. %3$s decimal places", parseFloat(min).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), parseFloat(max).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), decimals));
+ }
+ }
+ });
+ }).observe(this, {
+ attributes: true
+ });
+ });
+ $(".numberpicker").attr("data-initialised", "true"); // Dummy change to trigger MutationObserver above once
+
+ $(".numberpicker").on("keydown", function(e)
+ {
+ if (e.key == "ArrowUp")
+ {
+ e.preventDefault();
+ $(this).parent().find(".numberpicker-up-button").click();
+ }
+ else if (e.key == "ArrowDown")
+ {
+ e.preventDefault();
+ $(this).parent().find(".numberpicker-down-button").click();
+ }
+ });
+}
+
+export { numberpicker }
\ No newline at end of file
diff --git a/js/components/productamountpicker.js b/js/components/productamountpicker.js
new file mode 100644
index 00000000..d0131939
--- /dev/null
+++ b/js/components/productamountpicker.js
@@ -0,0 +1,123 @@
+function productamountpicker(Grocy)
+{
+ Grocy.Use("numberpicker");
+
+ Grocy.Components.ProductAmountPicker = {};
+ Grocy.Components.ProductAmountPicker.AllowAnyQuEnabled = false;
+
+ Grocy.Components.ProductAmountPicker.Reload = function(productId, destinationQuId, forceInitialDisplayQu = false)
+ {
+ var conversionsForProduct = Grocy.QuantityUnitConversionsResolved.filter(elem => elem.product_id == productId);
+
+ if (!Grocy.Components.ProductAmountPicker.AllowAnyQuEnabled)
+ {
+ var qu = Grocy.QuantityUnits.find(elem => elem.id == destinationQuId);
+ $("#qu_id").find("option").remove().end();
+ $("#qu_id").attr("data-destination-qu-name", qu.name);
+ $("#qu_id").attr("data-destination-qu-name-plural", qu.name_plural);
+
+ conversionsForProduct.forEach(conversion =>
+ {
+ var factor = parseFloat(conversion.factor);
+ if (conversion.to_qu_id == destinationQuId)
+ {
+ factor = 1;
+ }
+
+ if (!$('#qu_id option[value="' + conversion.to_qu_id + '"]').length) // Don't add the destination QU multiple times
+ {
+ $("#qu_id").append('');
+ }
+ });
+ }
+
+ if (!Grocy.Components.ProductAmountPicker.InitialValueSet || forceInitialDisplayQu)
+ {
+ $("#qu_id").val($("#qu_id").attr("data-initial-qu-id"));
+ }
+
+ if (!Grocy.Components.ProductAmountPicker.InitialValueSet)
+ {
+ var convertedAmount = $("#display_amount").val() * $("#qu_id option:selected").attr("data-qu-factor");
+ $("#display_amount").val(convertedAmount);
+
+ Grocy.Components.ProductAmountPicker.InitialValueSet = true;
+ }
+
+ if (conversionsForProduct.length === 1 && !forceInitialDisplayQu)
+ {
+ $("#qu_id").val($("#qu_id option:first").val());
+ }
+
+ if ($('#qu_id option').length == 1)
+ {
+ $("#qu_id").attr("disabled", "");
+ }
+ else
+ {
+ $("#qu_id").removeAttr("disabled");
+ }
+
+ $(".input-group-productamountpicker").trigger("change");
+ }
+
+ Grocy.Components.ProductAmountPicker.SetQuantityUnit = function(quId)
+ {
+ $("#qu_id").val(quId);
+ }
+
+ Grocy.Components.ProductAmountPicker.AllowAnyQu = function(keepInitialQu = false)
+ {
+ Grocy.Components.ProductAmountPicker.AllowAnyQuEnabled = true;
+
+ $("#qu_id").find("option").remove().end();
+ Grocy.QuantityUnits.forEach(qu =>
+ {
+ $("#qu_id").append('');
+ });
+
+ if (keepInitialQu)
+ {
+ Grocy.Components.ProductAmountPicker.SetQuantityUnit($("#qu_id").attr("data-initial-qu-id"));
+ }
+
+ $("#qu_id").removeAttr("disabled");
+
+ $(".input-group-productamountpicker").trigger("change");
+ }
+
+ Grocy.Components.ProductAmountPicker.Reset = function()
+ {
+ $("#qu_id").find("option").remove();
+ $("#qu-conversion-info").addClass("d-none");
+ $("#qu-display_amount-info").val("");
+ }
+
+ $(".input-group-productamountpicker").on("change", function()
+ {
+ var selectedQuName = $("#qu_id option:selected").text();
+ var quFactor = $("#qu_id option:selected").attr("data-qu-factor");
+ var amount = $("#display_amount").val();
+ var destinationAmount = amount / quFactor;
+ var destinationQuName = Grocy.translaten(destinationAmount, $("#qu_id").attr("data-destination-qu-name"), $("#qu_id").attr("data-destination-qu-name-plural"))
+
+ if ($("#qu_id").attr("data-destination-qu-name") == selectedQuName || Grocy.Components.ProductAmountPicker.AllowAnyQuEnabled || amount.toString().isEmpty() || selectedQuName.toString().isEmpty())
+ {
+ $("#qu-conversion-info").addClass("d-none");
+ }
+ else
+ {
+ $("#qu-conversion-info").removeClass("d-none");
+ $("#qu-conversion-info").text(Grocy.translate("This equals %1$s %2$s", destinationAmount.toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts }), destinationQuName));
+ }
+
+ $("#amount").val(destinationAmount.toFixed(Grocy.UserSettings.stock_decimal_places_amounts).replace(/0*$/g, ''));
+ });
+
+ $("#display_amount").on("keyup", function()
+ {
+ $(".input-group-productamountpicker").trigger("change");
+ });
+}
+
+export { productamountpicker }
\ No newline at end of file
diff --git a/js/components/productcard.js b/js/components/productcard.js
new file mode 100644
index 00000000..0d1e6711
--- /dev/null
+++ b/js/components/productcard.js
@@ -0,0 +1,245 @@
+import Chart from 'chart.js';
+import { EmptyElementWhenMatches } from '../helpers/extensions'
+import { RefreshContextualTimeago } from '../configs/timeago'
+
+function productcard(Grocy)
+{
+
+ Grocy.Components.ProductCard = {};
+
+ Grocy.Components.ProductCard.Refresh = function(productId)
+ {
+ Grocy.Api.Get('stock/products/' + productId,
+ function(productDetails)
+ {
+ var stockAmount = productDetails.stock_amount || '0';
+ var stockValue = productDetails.stock_value || '0';
+ var stockAmountOpened = productDetails.stock_amount_opened || '0';
+ $('#productcard-product-name').text(productDetails.product.name);
+ $('#productcard-product-description').html(productDetails.product.description);
+ $('#productcard-product-stock-amount').text(stockAmount);
+ $('#productcard-product-stock-qu-name').text(Grocy.translaten(stockAmount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural));
+ $('#productcard-product-stock-value').text(stockValue + ' ' + Grocy.Currency);
+ $('#productcard-product-last-purchased').text((productDetails.last_purchased || '2999-12-31').substring(0, 10));
+ $('#productcard-product-last-purchased-timeago').attr("datetime", productDetails.last_purchased || '2999-12-31');
+ $('#productcard-product-last-used').text((productDetails.last_used || '2999-12-31').substring(0, 10));
+ $('#productcard-product-last-used-timeago').attr("datetime", productDetails.last_used || '2999-12-31');
+ if (productDetails.location != null)
+ {
+ $('#productcard-product-location').text(productDetails.location.name);
+ }
+ $('#productcard-product-spoil-rate').text((parseFloat(productDetails.spoil_rate_percent) / 100).toLocaleString(undefined, { style: "percent" }));
+
+ if (productDetails.is_aggregated_amount == 1)
+ {
+ $('#productcard-product-stock-amount-aggregated').text(productDetails.stock_amount_aggregated);
+ $('#productcard-product-stock-qu-name-aggregated').text(Grocy.translaten(productDetails.stock_amount_aggregated, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural));
+
+ if (productDetails.stock_amount_opened_aggregated > 0)
+ {
+ $('#productcard-product-stock-opened-amount-aggregated').text(Grocy.translate('%s opened', productDetails.stock_amount_opened_aggregated));
+ }
+ else
+ {
+ $('#productcard-product-stock-opened-amount-aggregated').text("");
+ }
+
+ $("#productcard-aggregated-amounts").removeClass("d-none");
+ }
+ else
+ {
+ $("#productcard-aggregated-amounts").addClass("d-none");
+ }
+
+ if (productDetails.product.description != null && !productDetails.product.description.isEmpty())
+ {
+ $("#productcard-product-description-wrapper").removeClass("d-none");
+ }
+ else
+ {
+ $("#productcard-product-description-wrapper").addClass("d-none");
+ }
+
+ if (productDetails.average_shelf_life_days == -1)
+ {
+ $('#productcard-product-average-shelf-life').text(Grocy.translate("Unknown"));
+ }
+ else if (parseInt(productDetails.average_shelf_life_days) > 73000) // > 200 years aka forever
+ {
+ $('#productcard-product-average-shelf-life').text(Grocy.translate("Unlimited"));
+ }
+ else
+ {
+ $('#productcard-product-average-shelf-life').text(moment.duration(productDetails.average_shelf_life_days, "days").humanize());
+ }
+
+ if (stockAmountOpened > 0)
+ {
+ $('#productcard-product-stock-opened-amount').text(Grocy.translate('%s opened', stockAmountOpened));
+ }
+ else
+ {
+ $('#productcard-product-stock-opened-amount').text("");
+ }
+
+ $('#productcard-product-edit-button').attr("href", Grocy.FormatUrl("/product/" + productDetails.product.id.toString() + '?' + 'returnto=' + encodeURIComponent(Grocy.CurrentUrlRelative)));
+ $('#productcard-product-journal-button').attr("href", Grocy.FormatUrl("/stockjournal?embedded&product=" + productDetails.product.id.toString()));
+ $('#productcard-product-stock-button').attr("href", Grocy.FormatUrl("/stockentries?embedded&product=" + productDetails.product.id.toString()));
+ $('#productcard-product-stock-button').removeClass("disabled");
+ $('#productcard-product-edit-button').removeClass("disabled");
+ $('#productcard-product-journal-button').removeClass("disabled");
+
+ if (productDetails.last_price !== null)
+ {
+ $('#productcard-product-last-price').text(Number.parseFloat(productDetails.last_price).toLocaleString() + ' ' + Grocy.Currency + ' per ' + productDetails.quantity_unit_stock.name);
+ }
+ else
+ {
+ $('#productcard-product-last-price').text(Grocy.translate('Unknown'));
+ }
+
+ if (productDetails.avg_price !== null)
+ {
+ $('#productcard-product-average-price').text(Number.parseFloat(productDetails.avg_price).toLocaleString() + ' ' + Grocy.Currency + ' per ' + productDetails.quantity_unit_stock.name);
+ }
+ else
+ {
+ $('#productcard-product-average-price').text(Grocy.translate('Unknown'));
+ }
+
+ if (productDetails.product.picture_file_name !== null && !productDetails.product.picture_file_name.isEmpty())
+ {
+ $("#productcard-product-picture").removeClass("d-none");
+ $("#productcard-product-picture").attr("src", Grocy.FormatUrl('/api/files/productpictures/' + btoa(productDetails.product.picture_file_name) + '?force_serve_as=picture&best_fit_width=400'));
+ }
+ else
+ {
+ $("#productcard-product-picture").addClass("d-none");
+ }
+
+ EmptyElementWhenMatches('#productcard-product-last-purchased-timeago', Grocy.translate('timeago_nan'));
+ EmptyElementWhenMatches('#productcard-product-last-used-timeago', Grocy.translate('timeago_nan'));
+ RefreshContextualTimeago(".productcard");
+ },
+ function(xhr)
+ {
+ console.error(xhr);
+ }
+ );
+
+ if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
+ {
+ Grocy.Api.Get('stock/products/' + productId + '/price-history',
+ function(priceHistoryDataPoints)
+ {
+ if (priceHistoryDataPoints.length > 0)
+ {
+ $("#productcard-product-price-history-chart").removeClass("d-none");
+ $("#productcard-no-price-data-hint").addClass("d-none");
+
+ Grocy.Components.ProductCard.ReInitPriceHistoryChart();
+ var datasets = {};
+ var chart = Grocy.Components.ProductCard.PriceHistoryChart.data;
+ priceHistoryDataPoints.forEach((dataPoint) =>
+ {
+ var key = Grocy.translate("Unknown store");
+ if (dataPoint.shopping_location)
+ {
+ key = dataPoint.shopping_location.name
+ }
+
+ if (!datasets[key])
+ {
+ datasets[key] = []
+ }
+ chart.labels.push(moment(dataPoint.date).toDate());
+ datasets[key].push(dataPoint.price);
+
+ });
+ Object.keys(datasets).forEach((key) =>
+ {
+ chart.datasets.push({
+ data: datasets[key],
+ fill: false,
+ borderColor: "HSL(" + (129 * chart.datasets.length) + ",100%,50%)",
+ label: key,
+ });
+ });
+ Grocy.Components.ProductCard.PriceHistoryChart.update();
+ }
+ else
+ {
+ $("#productcard-product-price-history-chart").addClass("d-none");
+ $("#productcard-no-price-data-hint").removeClass("d-none");
+ }
+ },
+ function(xhr)
+ {
+ console.error(xhr);
+ }
+ );
+ }
+ };
+
+ Grocy.Components.ProductCard.ReInitPriceHistoryChart = function()
+ {
+ if (typeof Grocy.Components.ProductCard.PriceHistoryChart !== "undefined")
+ {
+ Grocy.Components.ProductCard.PriceHistoryChart.destroy();
+ }
+
+ var format = 'YYYY-MM-DD';
+ Grocy.Components.ProductCard.PriceHistoryChart = new Chart(document.getElementById("productcard-product-price-history-chart"), {
+ type: "line",
+ data: {
+ labels: [ //Date objects
+ // Will be populated in Grocy.Components.ProductCard.Refresh
+ ],
+ datasets: [ //Datasets
+ // Will be populated in Grocy.Components.ProductCard.Refresh
+ ]
+ },
+ options: {
+ scales: {
+ xAxes: [{
+ type: 'time',
+ time: {
+ parser: format,
+ round: 'day',
+ tooltipFormat: format,
+ unit: 'day',
+ unitStepSize: 10,
+ displayFormats: {
+ 'day': format
+ }
+ },
+ ticks: {
+ autoSkip: true,
+ maxRotation: 0
+ }
+ }],
+ yAxes: [{
+ ticks: {
+ beginAtZero: true
+ }
+ }]
+ },
+ legend: {
+ display: true
+ }
+ }
+ });
+ }
+
+ $("#productcard-product-description").on("shown.bs.collapse", function()
+ {
+ $(".expandable-text").find("a[data-toggle='collapse']").text(Grocy.translate("Show less"));
+ })
+
+ $("#productcard-product-description").on("hidden.bs.collapse", function()
+ {
+ $(".expandable-text").find("a[data-toggle='collapse']").text(Grocy.translate("Show more"));
+ })
+}
+
+export { productcard }
\ No newline at end of file
diff --git a/js/components/productpicker.js b/js/components/productpicker.js
new file mode 100644
index 00000000..1c24e775
--- /dev/null
+++ b/js/components/productpicker.js
@@ -0,0 +1,339 @@
+import { GetUriParam, RemoveUriParam } from '../helpers/extensions';
+
+function productpicker(Grocy)
+{
+ Grocy.Use('barcodescanner');
+ Grocy.Components.ProductPicker = {};
+
+ Grocy.Components.ProductPicker.GetPicker = function()
+ {
+ return $('#product_id');
+ }
+
+ Grocy.Components.ProductPicker.GetInputElement = function()
+ {
+ return $('#product_id_text_input');
+ }
+
+ Grocy.Components.ProductPicker.GetValue = function()
+ {
+ return $('#product_id').val();
+ }
+
+ Grocy.Components.ProductPicker.SetValue = function(value)
+ {
+ Grocy.Components.ProductPicker.GetInputElement().val(value);
+ Grocy.Components.ProductPicker.GetInputElement().trigger('change');
+ }
+
+ Grocy.Components.ProductPicker.SetId = function(value)
+ {
+ Grocy.Components.ProductPicker.GetPicker().val(value);
+ Grocy.Components.ProductPicker.GetPicker().data('combobox').refresh();
+ Grocy.Components.ProductPicker.GetInputElement().trigger('change');
+ }
+
+ Grocy.Components.ProductPicker.Clear = function()
+ {
+ Grocy.Components.ProductPicker.SetValue('');
+ Grocy.Components.ProductPicker.SetId(null);
+ }
+
+ Grocy.Components.ProductPicker.InProductAddWorkflow = function()
+ {
+ return GetUriParam('flow') == "InplaceNewProductWithName";
+ }
+
+ Grocy.Components.ProductPicker.InProductModifyWorkflow = function()
+ {
+ return GetUriParam('flow') == "InplaceAddBarcodeToExistingProduct";
+ }
+
+ Grocy.Components.ProductPicker.InAnyFlow = function()
+ {
+ return Grocy.Components.ProductPicker.InProductAddWorkflow() || Grocy.Components.ProductPicker.InProductModifyWorkflow();
+ }
+
+ Grocy.Components.ProductPicker.FinishFlow = function()
+ {
+ RemoveUriParam("flow");
+ RemoveUriParam("barcode");
+ RemoveUriParam("product-name");
+ }
+
+ Grocy.Components.ProductPicker.ShowCustomError = function(text)
+ {
+ var element = $("#custom-productpicker-error");
+ element.text(text);
+ element.removeClass("d-none");
+ }
+
+ Grocy.Components.ProductPicker.HideCustomError = function()
+ {
+ $("#custom-productpicker-error").addClass("d-none");
+ }
+
+ Grocy.Components.ProductPicker.Disable = function()
+ {
+ Grocy.Components.ProductPicker.GetInputElement().attr("disabled", "");
+ $("#barcodescanner-start-button").attr("disabled", "");
+ $("#barcodescanner-start-button").addClass("disabled");
+ }
+
+ Grocy.Components.ProductPicker.Enable = function()
+ {
+ Grocy.Components.ProductPicker.GetInputElement().removeAttr("disabled");
+ $("#barcodescanner-start-button").removeAttr("disabled");
+ $("#barcodescanner-start-button").removeClass("disabled");
+ }
+
+ $('.product-combobox').combobox({
+ appendId: '_text_input',
+ bsVersion: '4',
+ clearIfNoMatch: false
+ });
+
+ var this_product_picker = Grocy.Components.ProductPicker.GetPicker();
+ var productpicker_doFocus = false;
+
+ var prefillProduct = GetUriParam('product-name');
+ var prefillProduct2 = this_product_picker.parent().data('prefill-by-name').toString();
+ if (!prefillProduct2.isEmpty())
+ {
+ prefillProduct = prefillProduct2;
+ }
+ if (typeof prefillProduct !== "undefined")
+ {
+ var possibleOptionElement = $("#product_id option[data-additional-searchdata*=\"" + prefillProduct + "\"]").first();
+ if (possibleOptionElement.length === 0)
+ {
+ possibleOptionElement = $("#product_id option:contains(\"" + prefillProduct + "\")").first();
+ }
+
+ if (possibleOptionElement.length > 0)
+ {
+ productpicker_doFocus = true;
+ this_product_picker.val(possibleOptionElement.val());
+ }
+ }
+
+ var prefillProductId = GetUriParam("product");
+ var prefillProductId2 = this_product_picker.parent().data('prefill-by-id').toString();
+ if (!prefillProductId2.isEmpty())
+ {
+ prefillProductId = prefillProductId2;
+ }
+ if (typeof prefillProductId !== "undefined")
+ {
+ this_product_picker.val(prefillProductId);
+ productpicker_doFocus = true;
+ }
+
+ if (productpicker_doFocus)
+ {
+ this_product_picker.data('combobox').refresh();
+ this_product_picker.trigger('change');
+
+ $(this_product_picker.parent().data('next-input-selector').toString())
+ .focus();
+ }
+
+ if (GetUriParam("flow") === "InplaceAddBarcodeToExistingProduct")
+ {
+ $('#InplaceAddBarcodeToExistingProduct').text(GetUriParam("barcode"));
+ $('#flow-info-InplaceAddBarcodeToExistingProduct').removeClass('d-none');
+ $('#barcode-lookup-disabled-hint').removeClass('d-none');
+ $('#barcode-lookup-hint').addClass('d-none');
+ }
+
+ Grocy.Components.ProductPicker.PopupOpen = false;
+ $('#product_id_text_input').on('blur', function(e)
+ {
+ if (Grocy.Components.ProductPicker.GetPicker().hasClass("combobox-menu-visible"))
+ {
+ return;
+ }
+ $('#product_id').attr("barcode", "null");
+
+ var input = $('#product_id_text_input').val().toString();
+ var possibleOptionElement = [];
+
+ // did we enter a grocycode?
+ if (input.startsWith("grcy"))
+ {
+ var gc = input.split(":");
+ if (gc[1] == "p")
+ {
+ // find product id
+ possibleOptionElement = $("#product_id option[value=\"" + gc[2] + "\"]").first();
+ $("#product_id").data("grocycode", true);
+ }
+ }
+ else // process barcode as usual
+ {
+ possibleOptionElement = $("#product_id option[data-additional-searchdata*=\"" + input + ",\"]").first();
+ }
+
+ if (GetUriParam('flow') === undefined && input.length > 0 && possibleOptionElement.length > 0)
+ {
+ $('#product_id').val(possibleOptionElement.val());
+ $('#product_id').attr("barcode", input);
+ $('#product_id').data('combobox').refresh();
+ $('#product_id').trigger('change');
+ }
+ else
+ {
+ if (Grocy.Components.ProductPicker.PopupOpen === true)
+ {
+ return;
+ }
+
+ var optionElement = $("#product_id option:contains(\"" + input + "\")").first();
+ if (input.length > 0 && optionElement.length === 0 && GetUriParam('flow') === undefined && Grocy.Components.ProductPicker.GetPicker().parent().data('disallow-all-product-workflows').toString() === "false")
+ {
+ var addProductWorkflowsAdditionalCssClasses = "";
+ if (Grocy.Components.ProductPicker.GetPicker().parent().data('disallow-add-product-workflows').toString() === "true")
+ {
+ addProductWorkflowsAdditionalCssClasses = "d-none";
+ }
+
+ var embedded = "";
+ if (GetUriParam("embedded") !== undefined)
+ {
+ embedded = "embedded";
+ }
+
+ var buttons = {
+ cancel: {
+ label: Grocy.translate('Cancel'),
+ className: 'btn-secondary responsive-button',
+ callback: function()
+ {
+ Grocy.Components.ProductPicker.PopupOpen = false;
+ Grocy.Components.ProductPicker.SetValue('');
+ }
+ },
+ addnewproduct: {
+ label: 'P ' + Grocy.translate('Add as new product'),
+ className: 'btn-success add-new-product-dialog-button responsive-button ' + addProductWorkflowsAdditionalCssClasses,
+ callback: function()
+ {
+
+ Grocy.Components.ProductPicker.PopupOpen = false;
+ window.location.href = Grocy.FormatUrl('/product/new?flow=InplaceNewProductWithName&name=' + encodeURIComponent(input) + '&returnto=' + encodeURIComponent(Grocy.CurrentUrlRelative + "?flow=InplaceNewProductWithName&" + embedded) + "&" + embedded);
+ }
+ },
+ addbarcode: {
+ label: 'B ' + Grocy.translate('Add as barcode to existing product'),
+ className: 'btn-info add-new-barcode-dialog-button responsive-button',
+ callback: function()
+ {
+ Grocy.Components.ProductPicker.PopupOpen = false;
+ window.location.href = Grocy.FormatUrl(Grocy.CurrentUrlRelative + '?flow=InplaceAddBarcodeToExistingProduct&barcode=' + encodeURIComponent(input));
+ }
+ },
+ addnewproductwithbarcode: {
+ label: 'A ' + Grocy.translate('Add as new product and prefill barcode'),
+ className: 'btn-warning add-new-product-with-barcode-dialog-button responsive-button ' + addProductWorkflowsAdditionalCssClasses,
+ callback: function()
+ {
+ Grocy.Components.ProductPicker.PopupOpen = false;
+ window.location.href = Grocy.FormatUrl('/product/new?flow=InplaceNewProductWithBarcode&barcode=' + encodeURIComponent(input) + '&returnto=' + encodeURIComponent(Grocy.CurrentUrlRelative + "?flow=InplaceAddBarcodeToExistingProduct&barcode=" + input + "&" + embedded) + "&" + embedded);
+ }
+ }
+ };
+
+ if (!Grocy.FeatureFlags.DISABLE_BROWSER_BARCODE_CAMERA_SCANNING)
+ {
+ buttons.retrycamerascanning = {
+ label: 'C ',
+ className: 'btn-primary responsive-button retry-camera-scanning-button',
+ callback: function()
+ {
+ Grocy.Components.ProductPicker.PopupOpen = false;
+ Grocy.Components.ProductPicker.SetValue('');
+ $("#barcodescanner-start-button").click();
+ }
+ };
+ }
+
+ Grocy.Components.ProductPicker.PopupOpen = true;
+ bootbox.dialog({
+ message: Grocy.translate('"%s" could not be resolved to a product, how do you want to proceed?', input),
+ title: Grocy.translate('Create or assign product'),
+ onEscape: function()
+ {
+ Grocy.Components.ProductPicker.PopupOpen = false;
+ Grocy.Components.ProductPicker.SetValue('');
+ },
+ size: 'large',
+ backdrop: true,
+ closeButton: false,
+ buttons: buttons
+ }).on('keypress', function(e)
+ {
+ if (e.key === 'B' || e.key === 'b')
+ {
+ $('.add-new-barcode-dialog-button').not(".d-none").click();
+ }
+ if (e.key === 'p' || e.key === 'P')
+ {
+ $('.add-new-product-dialog-button').not(".d-none").click();
+ }
+ if (e.key === 'a' || e.key === 'A')
+ {
+ $('.add-new-product-with-barcode-dialog-button').not(".d-none").click();
+ }
+ if (e.key === 'c' || e.key === 'C')
+ {
+ $('.retry-camera-scanning-button').not(".d-none").click();
+ }
+ });
+ }
+ }
+ });
+
+ $(document).on("Grocy.BarcodeScanned", function(e, barcode, target)
+ {
+ if (!(target == "@productpicker" || target == "undefined" || target == undefined)) // Default target
+ {
+ return;
+ }
+
+ // Don't know why the blur event does not fire immediately ... this works...
+ Grocy.Components.ProductPicker.GetInputElement().focusout();
+ Grocy.Components.ProductPicker.GetInputElement().focus();
+ Grocy.Components.ProductPicker.GetInputElement().blur();
+
+ Grocy.Components.ProductPicker.GetInputElement().val(barcode);
+
+ setTimeout(function()
+ {
+ Grocy.Components.ProductPicker.GetInputElement().focusout();
+ Grocy.Components.ProductPicker.GetInputElement().focus();
+ Grocy.Components.ProductPicker.GetInputElement().blur();
+ }, 200);
+ });
+
+ $(document).on("shown.bs.modal", function(e)
+ {
+ $(".modal-footer").addClass("d-block").addClass("d-sm-flex");
+ $(".modal-footer").find("button").addClass("mt-2").addClass("mt-sm-0");
+ })
+
+ // Make that ENTER behaves the same like TAB (trigger blur to start workflows, but only when the dropdown is not opened)
+ $('#product_id_text_input').keydown(function(event)
+ {
+ if (event.keyCode === 13) // Enter
+ {
+ if (Grocy.Components.ProductPicker.GetPicker().hasClass("combobox-menu-visible"))
+ {
+ return;
+ }
+
+ $("#product_id_text_input").trigger("blur");
+ }
+ });
+}
+
+export { productpicker }
\ No newline at end of file
diff --git a/js/components/recipepicker.js b/js/components/recipepicker.js
new file mode 100644
index 00000000..082559f2
--- /dev/null
+++ b/js/components/recipepicker.js
@@ -0,0 +1,77 @@
+function recipepicker(Grocy)
+{
+
+ Grocy.Components.RecipePicker = {};
+
+ Grocy.Components.RecipePicker.GetPicker = function()
+ {
+ return $('#recipe_id');
+ }
+
+ Grocy.Components.RecipePicker.GetInputElement = function()
+ {
+ return $('#recipe_id_text_input');
+ }
+
+ Grocy.Components.RecipePicker.GetValue = function()
+ {
+ return $('#recipe_id').val();
+ }
+
+ Grocy.Components.RecipePicker.SetValue = function(value)
+ {
+ Grocy.Components.RecipePicker.GetInputElement().val(value);
+ Grocy.Components.RecipePicker.GetInputElement().trigger('change');
+ }
+
+ Grocy.Components.RecipePicker.SetId = function(value)
+ {
+ Grocy.Components.RecipePicker.GetPicker().val(value);
+ Grocy.Components.RecipePicker.GetPicker().data('combobox').refresh();
+ Grocy.Components.RecipePicker.GetInputElement().trigger('change');
+ }
+
+ Grocy.Components.RecipePicker.Clear = function()
+ {
+ Grocy.Components.RecipePicker.SetValue('');
+ Grocy.Components.RecipePicker.SetId(null);
+ }
+
+ $('.recipe-combobox').combobox({
+ appendId: '_text_input',
+ bsVersion: '4',
+ clearIfNoMatch: true
+ });
+
+ var this_recipe_picker = Grocy.Components.RecipePicker.GetPicker();
+ var recipe_picker_doFocus = false;
+
+ var prefillByName = this_recipe_picker.parent().data('prefill-by-name').toString();
+ if (typeof prefillByName !== "undefined")
+ {
+ var possibleOptionElement = $("#recipe_id option:contains(\"" + prefillByName + "\")").first();
+
+ if (possibleOptionElement.length > 0)
+ {
+ recipe_picker_doFocus = true;
+ this_recipe_picker.val(possibleOptionElement.val());
+ }
+ }
+
+ var prefillById = this_recipe_picker.parent().data('prefill-by-id').toString();
+ if (typeof prefillById !== "undefined")
+ {
+ recipe_picker_doFocus = true;
+ this_recipe_picker.val(prefillById);
+ }
+
+ if (recipe_picker_doFocus)
+ {
+ this_recipe_picker.data('combobox').refresh();
+ this_recipe_picker.trigger('change');
+
+ $(this_recipe_picker.parent().data('next-input-selector').toString()).focus();
+ }
+}
+
+export { recipepicker }
\ No newline at end of file
diff --git a/js/components/shoppinglocationpicker.js b/js/components/shoppinglocationpicker.js
new file mode 100644
index 00000000..0e4768c2
--- /dev/null
+++ b/js/components/shoppinglocationpicker.js
@@ -0,0 +1,77 @@
+function shoppinglocationpicker(Grocy)
+{
+ Grocy.Components.ShoppingLocationPicker = {};
+
+ Grocy.Components.ShoppingLocationPicker.GetPicker = function()
+ {
+ return $('#shopping_location_id');
+ }
+
+ Grocy.Components.ShoppingLocationPicker.GetInputElement = function()
+ {
+ return $('#shopping_location_id_text_input');
+ }
+
+ Grocy.Components.ShoppingLocationPicker.GetValue = function()
+ {
+ return $('#shopping_location_id').val();
+ }
+
+ Grocy.Components.ShoppingLocationPicker.SetValue = function(value)
+ {
+ Grocy.Components.ShoppingLocationPicker.GetInputElement().val(value);
+ Grocy.Components.ShoppingLocationPicker.GetInputElement().trigger('change');
+ }
+
+ Grocy.Components.ShoppingLocationPicker.SetId = function(value)
+ {
+ Grocy.Components.ShoppingLocationPicker.GetPicker().val(value);
+ Grocy.Components.ShoppingLocationPicker.GetPicker().data('combobox').refresh();
+ Grocy.Components.ShoppingLocationPicker.GetInputElement().trigger('change');
+ }
+
+ Grocy.Components.ShoppingLocationPicker.Clear = function()
+ {
+ Grocy.Components.ShoppingLocationPicker.SetValue('');
+ Grocy.Components.ShoppingLocationPicker.SetId(null);
+ }
+
+ $('.shopping-location-combobox').combobox({
+ appendId: '_text_input',
+ bsVersion: '4',
+ clearIfNoMatch: true
+ });
+
+ var shoppinglocationpicker_doFocus = false;
+ var this_shoppinglocation_picker = $('#shopping_location_id');
+
+ var prefillByName = this_shoppinglocation_picker.parent().data('prefill-by-name').toString();
+ if (typeof prefillByName !== "undefined")
+ {
+ var possibleOptionElement = $("#shopping_location_id option:contains(\"" + prefillByName + "\")").first();
+
+ if (possibleOptionElement.length > 0)
+ {
+ this_shoppinglocation_picker.val(possibleOptionElement.val());
+ shoppinglocationpicker_doFocus = true;
+ }
+ }
+
+ var prefillById = this_shoppinglocation_picker.parent().data('prefill-by-id').toString();
+ if (typeof prefillById !== "undefined")
+ {
+ this_shoppinglocation_picker.val(prefillById);
+ shoppinglocationpicker_doFocus = true;
+ }
+
+ if (shoppinglocationpicker_doFocus)
+ {
+ this_shoppinglocation_picker.data('combobox').refresh();
+ this_shoppinglocation_picker.trigger('change');
+
+ $(this_shoppinglocation_picker.parent().data('next-input-selector').toString())
+ .focus();
+ }
+}
+
+export { shoppinglocationpicker }
\ No newline at end of file
diff --git a/js/components/userfieldsform.js b/js/components/userfieldsform.js
new file mode 100644
index 00000000..ad18fb98
--- /dev/null
+++ b/js/components/userfieldsform.js
@@ -0,0 +1,191 @@
+import { RandomString } from '../helpers/extensions';
+import { LoadImagesLazy } from '../configs/lazy'
+
+function userfieldsform(Grocy)
+{
+ Grocy.Use("datetimepicker");
+
+ Grocy.Components.UserfieldsForm = {};
+
+ Grocy.Components.UserfieldsForm.Save = function(success, error)
+ {
+ if (!$("#userfields-form").length)
+ {
+ if (success)
+ {
+ success();
+ }
+
+ return;
+ }
+
+ var jsonData = {};
+
+ $("#userfields-form .userfield-input").not("div").each(function()
+ {
+ var input = $(this);
+ var fieldName = input.attr("data-userfield-name");
+ var fieldValue = input.val();
+
+ if (input.attr("type") == "checkbox")
+ {
+ jsonData[fieldName] = "0";
+ if (input.is(":checked"))
+ {
+ jsonData[fieldName] = "1";
+ }
+ }
+ else if (input.attr("type") == "file")
+ {
+ var oldFile = input.data('old-file')
+ if (oldFile)
+ {
+ Grocy.Api.Delete('files/userfiles/' + oldFile, null, null,
+ function(xhr)
+ {
+ Grocy.FrontendHelpers.ShowGenericError('Could not delete file', xhr);
+ });
+ jsonData[fieldName] = "";
+ }
+
+ if (input[0].files.length > 0)
+ {
+ // Files service requires an extension
+ var fileName = RandomString() + '.' + input[0].files[0].name.split('.').reverse()[0];
+
+ jsonData[fieldName] = btoa(fileName) + '_' + btoa(input[0].files[0].name);
+ Grocy.Api.UploadFile(input[0].files[0], 'userfiles', fileName,
+ function(result)
+ {
+ },
+ function(xhr)
+ {
+ // When navigating away immediately from the current page, this is maybe a false positive - so ignore this for now
+ // Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
+ }
+ );
+ }
+ }
+ else if ($(this).hasAttr("multiple"))
+ {
+ jsonData[fieldName] = $(this).val().join(",");
+ }
+ else
+ {
+ jsonData[fieldName] = fieldValue;
+ }
+ });
+
+ Grocy.Api.Put('userfields/' + $("#userfields-form").data("entity") + '/' + Grocy.EditObjectId, jsonData,
+ function(result)
+ {
+ if (success)
+ {
+ success();
+ }
+ },
+ function(xhr)
+ {
+ if (error)
+ {
+ error();
+ }
+ }
+ );
+ }
+
+ Grocy.Components.UserfieldsForm.Load = function()
+ {
+ if (!$("#userfields-form").length)
+ {
+ return;
+ }
+
+ Grocy.Api.Get('userfields/' + $("#userfields-form").data("entity") + '/' + Grocy.EditObjectId,
+ function(result)
+ {
+ $.each(result, function(key, value)
+ {
+ var input = $(".userfield-input[data-userfield-name='" + key + "']");
+
+ if (input.attr("type") == "checkbox" && value == 1)
+ {
+ input.prop("checked", true);
+ }
+ else if (input.hasAttr("multiple"))
+ {
+ input.val(value.split(","));
+ $(".selectpicker").selectpicker("render");
+ }
+ else if (input.attr('type') == "file")
+ {
+ if (value != null && !value.isEmpty())
+ {
+ var fileName = atob(value.split('_')[1]);
+ var fileSrc = value.split('_')[0];
+ var formGroup = input.parent().parent().parent();
+
+ formGroup.find("label.custom-file-label").text(fileName);
+ formGroup.find(".userfield-file-show").attr('href', Grocy.FormatUrl('/files/userfiles/' + value));
+ formGroup.find('.userfield-file-show').removeClass('d-none');
+ formGroup.find('img.userfield-current-file')
+ .attr('src', Grocy.FormatUrl('/files/userfiles/' + value + '?force_serve_as=picture&best_fit_width=250&best_fit_height=250'));
+ LoadImagesLazy();
+
+ formGroup.find('.userfield-file-delete').click(
+ function()
+ {
+ formGroup.find("label.custom-file-label").text(Grocy.translate("No file selected"));
+ formGroup.find(".userfield-file-show").addClass('d-none');
+ input.attr('data-old-file', fileSrc);
+ }
+ );
+
+ input.on("change", function(e)
+ {
+ formGroup.find(".userfield-file-show").addClass('d-none');
+ });
+ }
+ }
+ else if (input.attr("data-userfield-type") == "link")
+ {
+ if (!value.isEmpty())
+ {
+ var data = JSON.parse(value);
+
+ var formRow = input.parent().parent();
+ formRow.find(".userfield-link-title").val(data.title);
+ formRow.find(".userfield-link-link").val(data.link);
+
+ input.val(value);
+ }
+ }
+ else
+ {
+ input.val(value);
+ }
+ });
+ },
+ function(xhr)
+ {
+ console.error(xhr);
+ }
+ );
+ }
+
+ $(".userfield-link").keyup(function(e)
+ {
+ var formRow = $(this).parent().parent();
+ var title = formRow.find(".userfield-link-title").val();
+ var link = formRow.find(".userfield-link-link").val();
+
+ var value = {
+ "title": title,
+ "link": link
+ };
+
+ formRow.find(".userfield-input").val(JSON.stringify(value));
+ });
+}
+
+export { userfieldsform }
\ No newline at end of file
diff --git a/js/components/userpicker.js b/js/components/userpicker.js
new file mode 100644
index 00000000..8f96fe84
--- /dev/null
+++ b/js/components/userpicker.js
@@ -0,0 +1,86 @@
+function userpicker(Grocy)
+{
+
+ Grocy.Components.UserPicker = {};
+
+ Grocy.Components.UserPicker.GetPicker = function()
+ {
+ return $('#user_id');
+ }
+
+ Grocy.Components.UserPicker.GetInputElement = function()
+ {
+ return $('#user_id_text_input');
+ }
+
+ Grocy.Components.UserPicker.GetValue = function()
+ {
+ return $('#user_id').val();
+ }
+
+ Grocy.Components.UserPicker.SetValue = function(value)
+ {
+ Grocy.Components.UserPicker.GetInputElement().val(value);
+ Grocy.Components.UserPicker.GetInputElement().trigger('change');
+ }
+
+ Grocy.Components.UserPicker.SetId = function(value)
+ {
+ Grocy.Components.UserPicker.GetPicker().val(value);
+ Grocy.Components.UserPicker.GetPicker().data('combobox').refresh();
+ Grocy.Components.UserPicker.GetInputElement().trigger('change');
+ }
+
+ Grocy.Components.UserPicker.Clear = function()
+ {
+ Grocy.Components.UserPicker.SetValue('');
+ Grocy.Components.UserPicker.SetId(null);
+ }
+
+ $('.user-combobox').combobox({
+ appendId: '_text_input',
+ bsVersion: '4'
+ });
+
+ var this_user_picker = Grocy.Components.UserPicker.GetPicker();
+ var user_picker_doFocus = false;
+ var possibleOptionElement = null;
+
+ var prefillUser = this_user_picker.parent().data('prefill-by-username').toString();
+ if (typeof prefillUser !== "undefined")
+ {
+ possibleOptionElement = $("#user_id option[data-additional-searchdata*=\"" + prefillUser + "\"]").first();
+ if (possibleOptionElement.length === 0)
+ {
+ possibleOptionElement = $("#user_id option:contains(\"" + prefillUser + "\")").first();
+ }
+
+ if (possibleOptionElement.length > 0)
+ {
+ user_picker_doFocus = true;
+ this_user_picker.val(possibleOptionElement.val());
+
+ }
+ }
+
+ var prefillUserId = this_user_picker.parent().data('prefill-by-user-id').toString();
+ if (typeof prefillUserId !== "undefined")
+ {
+ possibleOptionElement = $("#user_id option[value='" + prefillUserId + "']").first();
+ if (possibleOptionElement.length > 0)
+ {
+ user_picker_doFocus = true;
+ this_user_picker.val(possibleOptionElement.val());
+ }
+ }
+
+ if (user_picker_doFocus)
+ {
+ this_user_picker.data('combobox').refresh();
+ this_user_picker.trigger('change');
+
+ $(this_user_picker.parent().data('next-input-selector').toString())
+ .focus();
+ }
+}
+export { userpicker }
\ No newline at end of file
diff --git a/js/configs/globalstate.js b/js/configs/globalstate.js
index 9ac9f9e5..68177374 100644
--- a/js/configs/globalstate.js
+++ b/js/configs/globalstate.js
@@ -18,10 +18,11 @@ function setInitialGlobalState(Grocy)
var parentMenuSelector = menuItem.data("sub-menu-of");
if (typeof parentMenuSelector !== "undefined")
{
- $(parentMenuSelector).collapse("show");
- $(parentMenuSelector).prev(".nav-link-collapse").addClass("active-page");
+ var pMenu = $(parentMenuSelector);
+ pMenu.collapse("show");
+ pMenu.prev(".nav-link-collapse").addClass("active-page");
- $(parentMenuSelector).on("shown.bs.collapse", function()
+ pMenu.on("shown.bs.collapse", function()
{
if (!menuItem.isVisibleInViewport(75))
{
diff --git a/js/grocy.js b/js/grocy.js
index d4c0f345..d86406ad 100644
--- a/js/grocy.js
+++ b/js/grocy.js
@@ -12,6 +12,7 @@ import { HeaderClock } from "./helpers/clock";
import { animateCSS, BoolVal, EmptyElementWhenMatches, GetUriParam, RemoveUriParam, UpdateUriParam } from "./helpers/extensions";
import Translator from "gettext-translator";
import { WindowMessageBag } from './helpers/messagebag';
+import * as components from './components';
import "./helpers/string";
@@ -47,6 +48,7 @@ class GrocyClass
this.Locale = config.Locale;
this.Components = {};
+ this.initComponents = [];
// Init some classes
this.Api = new GrocyApi(this);
@@ -150,21 +152,21 @@ class GrocyClass
translate(text, ...placeholderValues)
{
- if (this.Mode === "dev")
+ /*if (this.Mode === "dev")
{
var text2 = text;
this.Api.Post('system/log-missing-localization', { "text": text2 });
- }
+ }*/
return this.Translator.__(text, ...placeholderValues)
}
translaten(number, singularForm, pluralForm)
{
- if (this.Mode === "dev")
+ /*if (this.Mode === "dev")
{
var singularForm2 = singularForm;
this.Api.Post('system/log-missing-localization', { "text": singularForm2 });
- }
+ }*/
return this.Translator.n__(singularForm, pluralForm, number, number)
}
@@ -212,6 +214,24 @@ class GrocyClass
this.IdleTime += 1;
}
+ Use(componentName)
+ {
+ // initialize Components only once
+ if (this.initComponents.find(elem => elem == componentName))
+ return;
+
+ if (Object.prototype.hasOwnProperty.call(components, componentName))
+ {
+ // add-then-init to resolve circular dependencies
+ this.initComponents.push(componentName);
+ components[componentName](this);
+ }
+ else
+ {
+ console.error("Unable to find component " + componentName);
+ }
+ }
+
UndoStockBooking(bookingId)
{
var self = this;
diff --git a/js/viewjs/barcodescannertesting.js b/js/viewjs/barcodescannertesting.js
index bf630cbf..d208867e 100644
--- a/js/viewjs/barcodescannertesting.js
+++ b/js/viewjs/barcodescannertesting.js
@@ -1,4 +1,5 @@
-Grocy.BarCodeScannerTestingHitCount = 0;
+Grocy.Use("barcodescanner");
+Grocy.BarCodeScannerTestingHitCount = 0;
Grocy.BarCodeScannerTestingMissCount = 0;
$("#scanned_barcode").on("blur", function(e)
diff --git a/js/viewjs/batteriesoverview.js b/js/viewjs/batteriesoverview.js
index ec8f5321..b891ca41 100644
--- a/js/viewjs/batteriesoverview.js
+++ b/js/viewjs/batteriesoverview.js
@@ -1,4 +1,6 @@
-var batteriesOverviewTable = $('#batteries-overview-table').DataTable({
+Grocy.Use("batterycard");
+
+var batteriesOverviewTable = $('#batteries-overview-table').DataTable({
'order': [[4, 'asc']],
'columnDefs': [
{ 'orderable': false, 'targets': 0 },
diff --git a/js/viewjs/batteriessettings.js b/js/viewjs/batteriessettings.js
index 792c3d93..b0b44a17 100644
--- a/js/viewjs/batteriessettings.js
+++ b/js/viewjs/batteriessettings.js
@@ -1,3 +1,5 @@
-$("#batteries_due_soon_days").val(Grocy.UserSettings.batteries_due_soon_days);
+Grocy.Use("numberpicker");
+
+$("#batteries_due_soon_days").val(Grocy.UserSettings.batteries_due_soon_days);
RefreshLocaleNumberInput();
diff --git a/js/viewjs/batteryform.js b/js/viewjs/batteryform.js
index 074ddea9..ebbfe45e 100644
--- a/js/viewjs/batteryform.js
+++ b/js/viewjs/batteryform.js
@@ -1,5 +1,8 @@
import { WindowMessageBag } from '../helpers/messagebag';
+Grocy.Use("numberpicker");
+Grocy.Use("userfieldsform");
+
$('#save-battery-button').on('click', function(e)
{
e.preventDefault();
diff --git a/js/viewjs/batterytracking.js b/js/viewjs/batterytracking.js
index c2a249bb..901ee912 100644
--- a/js/viewjs/batterytracking.js
+++ b/js/viewjs/batterytracking.js
@@ -1,4 +1,7 @@
-$('#save-batterytracking-button').on('click', function(e)
+Grocy.Use("batterycard");
+Grocy.Use("datetimepicker");
+
+$('#save-batterytracking-button').on('click', function(e)
{
e.preventDefault();
diff --git a/js/viewjs/choreform.js b/js/viewjs/choreform.js
index 538e6d28..c562dc9d 100644
--- a/js/viewjs/choreform.js
+++ b/js/viewjs/choreform.js
@@ -1,4 +1,7 @@
-$('#save-chore-button').on('click', function(e)
+Grocy.Use("numberpicker");
+Grocy.Use("userfieldsform");
+
+$('#save-chore-button').on('click', function(e)
{
e.preventDefault();
diff --git a/js/viewjs/choresoverview.js b/js/viewjs/choresoverview.js
index 99f124ea..82adff67 100644
--- a/js/viewjs/choresoverview.js
+++ b/js/viewjs/choresoverview.js
@@ -1,4 +1,6 @@
-var choresOverviewTable = $('#chores-overview-table').DataTable({
+Grocy.Use("chorecard");
+
+var choresOverviewTable = $('#chores-overview-table').DataTable({
'order': [[2, 'asc']],
'columnDefs': [
{ 'orderable': false, 'targets': 0 },
diff --git a/js/viewjs/choressettings.js b/js/viewjs/choressettings.js
index 52967a6a..2c0360d8 100644
--- a/js/viewjs/choressettings.js
+++ b/js/viewjs/choressettings.js
@@ -1,3 +1,5 @@
-$("#chores_due_soon_days").val(Grocy.UserSettings.chores_due_soon_days);
+Grocy.Use("numberpicker");
+
+$("#chores_due_soon_days").val(Grocy.UserSettings.chores_due_soon_days);
RefreshLocaleNumberInput();
diff --git a/js/viewjs/choretracking.js b/js/viewjs/choretracking.js
index b58c1eda..e5c7ecb6 100644
--- a/js/viewjs/choretracking.js
+++ b/js/viewjs/choretracking.js
@@ -1,4 +1,8 @@
-$('#save-choretracking-button').on('click', function(e)
+Grocy.Use("chorecard");
+Grocy.Use("datetimepicker");
+Grocy.Use("userpicker");
+
+$('#save-choretracking-button').on('click', function(e)
{
e.preventDefault();
diff --git a/js/viewjs/components/barcodescanner.js b/js/viewjs/components/barcodescanner.js
deleted file mode 100644
index 9477e3e7..00000000
--- a/js/viewjs/components/barcodescanner.js
+++ /dev/null
@@ -1,304 +0,0 @@
-/* global Quagga2DatamatrixReader */
-import Quagga from '@ericblade/quagga2/dist/quagga';
-
-Grocy.Components.BarcodeScanner = {};
-
-Quagga.registerReader("datamatrix", Quagga2DatamatrixReader);
-
-Grocy.Components.BarcodeScanner.LiveVideoSizeAdjusted = false;
-Grocy.Components.BarcodeScanner.CheckCapabilities = async function()
-{
- var track = Quagga.CameraAccess.getActiveTrack();
- var capabilities = {};
- if (typeof track.getCapabilities === 'function')
- {
- capabilities = track.getCapabilities();
- }
-
- // If there is more than 1 camera, show the camera selection
- var cameras = await Quagga.CameraAccess.enumerateVideoDevices();
- var cameraSelect = document.querySelector('.cameraSelect-wrapper');
- if (cameraSelect)
- {
- cameraSelect.style.display = cameras.length > 1 ? 'inline-block' : 'none';
- }
-
- // Check if the camera is capable to turn on a torch.
- var canTorch = typeof capabilities.torch === 'boolean' && capabilities.torch
- // Remove the torch button, if either the device can not torch or AutoTorchOn is set.
- var node = document.querySelector('.torch');
- if (node)
- {
- node.style.display = canTorch && !Grocy.FeatureFlags.GROCY_FEATURE_FLAG_AUTO_TORCH_ON_WITH_CAMERA ? 'inline-block' : 'none';
- }
- // If AutoTorchOn is set, turn on the torch.
- if (canTorch && Grocy.FeatureFlags.GROCY_FEATURE_FLAG_AUTO_TORCH_ON_WITH_CAMERA)
- {
- Grocy.Components.BarcodeScanner.TorchOn(track);
- }
-
- // Reduce the height of the video, if it's higher than then the viewport
- if (!Grocy.Components.BarcodeScanner.LiveVideoSizeAdjusted)
- {
- var bc = document.getElementById('barcodescanner-container');
- if (bc)
- {
- var bcAspectRatio = bc.offsetWidth / bc.offsetHeight;
- var settings = track.getSettings();
- if (bcAspectRatio > settings.aspectRatio)
- {
- var v = document.querySelector('#barcodescanner-livestream video')
- if (v)
- {
- var c = document.querySelector('#barcodescanner-livestream canvas')
- var newWidth = v.clientWidth / bcAspectRatio * settings.aspectRatio + 'px';
- v.style.width = newWidth;
- c.style.width = newWidth;
- }
- }
-
- Grocy.Components.BarcodeScanner.LiveVideoSizeAdjusted = true;
- }
- }
-}
-
-Grocy.Components.BarcodeScanner.StartScanning = function()
-{
- Grocy.Components.BarcodeScanner.DecodedCodesCount = 0;
- Grocy.Components.BarcodeScanner.DecodedCodesErrorCount = 0;
-
- Quagga.init({
- inputStream: {
- name: "Live",
- type: "LiveStream",
- target: document.querySelector("#barcodescanner-livestream"),
- constraints: {
- facingMode: "environment",
- ...(window.localStorage.getItem('cameraId') && { deviceId: window.localStorage.getItem('cameraId') }) // If preferred cameraId is set, request to use that specific camera
- }
- },
- locator: {
- patchSize: Grocy.UserSettings.quagga2_patchsize,
- halfSample: Grocy.UserSettings.quagga2_halfsample,
- debug: {
- showCanvas: Grocy.UserSettings.quagga2_debug,
- showPatches: Grocy.UserSettings.quagga2_debug,
- showFoundPatches: Grocy.UserSettings.quagga2_debug,
- showSkeleton: Grocy.UserSettings.quagga2_debug,
- showLabels: Grocy.UserSettings.quagga2_debug,
- showPatchLabels: Grocy.UserSettings.quagga2_debug,
- showRemainingPatchLabels: Grocy.UserSettings.quagga2_debug,
- boxFromPatches: {
- showTransformed: Grocy.UserSettings.quagga2_debug,
- showTransformedBox: Grocy.UserSettings.quagga2_debug,
- showBB: Grocy.UserSettings.quagga2_debug
- }
- }
- },
- numOfWorkers: Grocy.UserSettings.quagga2_numofworkers,
- frequency: Grocy.UserSettings.quagga2_frequency,
- decoder: {
- readers: [
- "ean_reader",
- "ean_8_reader",
- "code_128_reader",
- "datamatrix"
- ],
- debug: {
- showCanvas: Grocy.UserSettings.quagga2_debug,
- showPatches: Grocy.UserSettings.quagga2_debug,
- showFoundPatches: Grocy.UserSettings.quagga2_debug,
- showSkeleton: Grocy.UserSettings.quagga2_debug,
- showLabels: Grocy.UserSettings.quagga2_debug,
- showPatchLabels: Grocy.UserSettings.quagga2_debug,
- showRemainingPatchLabels: Grocy.UserSettings.quagga2_debug,
- boxFromPatches: {
- showTransformed: Grocy.UserSettings.quagga2_debug,
- showTransformedBox: Grocy.UserSettings.quagga2_debug,
- showBB: Grocy.UserSettings.quagga2_debug
- }
- }
- },
- locate: true
- }, function(error)
- {
- // error *needs* to be logged here, otherwise the stack trace is lying.
- console.error(error);
- if (error)
- {
- Grocy.FrontendHelpers.ShowGenericError("Error while initializing the barcode scanning library", error.message);
- toastr.info(__t("Camera access is only possible when supported and allowed by your browser and when grocy is served via a secure (https://) connection"));
- window.localStorage.removeItem("cameraId");
- setTimeout(function()
- {
- bootbox.hideAll();
- }, 500);
- return;
- }
-
- Grocy.Components.BarcodeScanner.CheckCapabilities();
-
- Quagga.start();
- });
-}
-
-Grocy.Components.BarcodeScanner.StopScanning = function()
-{
- Quagga.stop();
-
- Grocy.Components.BarcodeScanner.DecodedCodesCount = 0;
- Grocy.Components.BarcodeScanner.DecodedCodesErrorCount = 0;
-
- bootbox.hideAll();
-}
-
-Grocy.Components.BarcodeScanner.TorchOn = function(track)
-{
- if (track)
- {
- track.applyConstraints({
- advanced: [
- {
- torch: true
- }
- ]
- });
- }
-}
-
-Quagga.onDetected(function(result)
-{
- $.each(result.codeResult.decodedCodes, function(id, error)
- {
- if (error.error != undefined)
- {
- Grocy.Components.BarcodeScanner.DecodedCodesCount++;
- Grocy.Components.BarcodeScanner.DecodedCodesErrorCount += parseFloat(error.error);
- }
- });
-
- if (Grocy.Components.BarcodeScanner.DecodedCodesErrorCount / Grocy.Components.BarcodeScanner.DecodedCodesCount < 0.15)
- {
- Grocy.Components.BarcodeScanner.StopScanning();
- $(document).trigger("Grocy.BarcodeScanned", [result.codeResult.code, Grocy.Components.BarcodeScanner.CurrentTarget]);
- }
-});
-
-Quagga.onProcessed(function(result)
-{
- var drawingCtx = Quagga.canvas.ctx.overlay;
- var drawingCanvas = Quagga.canvas.dom.overlay;
-
- if (result)
- {
- if (result.boxes)
- {
- drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute("width")), parseInt(drawingCanvas.getAttribute("height")));
- result.boxes.filter(function(box)
- {
- return box !== result.box;
- }).forEach(function(box)
- {
- Quagga.ImageDebug.drawPath(box, { x: 0, y: 1 }, drawingCtx, { color: "yellow", lineWidth: 4 });
- });
- }
-
- if (result.box)
- {
- Quagga.ImageDebug.drawPath(result.box, { x: 0, y: 1 }, drawingCtx, { color: "green", lineWidth: 4 });
- }
-
- if (result.codeResult && result.codeResult.code)
- {
- Quagga.ImageDebug.drawPath(result.line, { x: 'x', y: 'y' }, drawingCtx, { color: "red", lineWidth: 4 });
- }
- }
-});
-
-$(document).on("click", "#barcodescanner-start-button", async function(e)
-{
- e.preventDefault();
- var inputElement = $(e.currentTarget).prev();
- if (inputElement.hasAttr("disabled"))
- {
- // Do nothing and disable the barcode scanner start button
- $(e.currentTarget).addClass("disabled");
- return;
- }
-
- Grocy.Components.BarcodeScanner.CurrentTarget = inputElement.attr("data-target");
-
- var dialog = bootbox.dialog({
- message: '',
- title: __t('Scan a barcode'),
- onEscape: function()
- {
- Grocy.Components.BarcodeScanner.StopScanning();
- },
- size: 'big',
- backdrop: true,
- closeButton: true,
- buttons: {
- torch: {
- label: '',
- className: 'btn-warning responsive-button torch',
- callback: function()
- {
- Grocy.Components.BarcodeScanner.TorchOn(Quagga.CameraAccess.getActiveTrack());
- return false;
- }
- },
- cancel: {
- label: __t('Cancel'),
- className: 'btn-secondary responsive-button',
- callback: function()
- {
- Grocy.Components.BarcodeScanner.StopScanning();
- }
- }
- }
- });
-
- // Add camera select to existing dialog
- dialog.find('.bootbox-body').append('
');
- var cameraSelect = document.querySelector('.cameraSelect');
-
- if (cameraSelect != null)
- {
- var cameras = await Quagga.CameraAccess.enumerateVideoDevices();
- cameras.forEach(camera =>
- {
- var option = document.createElement("option");
- option.text = camera.label ? camera.label : camera.deviceId; // Use camera label if it exists, else show device id
- option.value = camera.deviceId;
- cameraSelect.appendChild(option);
- });
-
- // Set initial value to preferred camera if one exists - and if not, start out empty
- cameraSelect.value = window.localStorage.getItem('cameraId');
-
- cameraSelect.onchange = function()
- {
- window.localStorage.setItem('cameraId', cameraSelect.value);
- Quagga.stop();
- Grocy.Components.BarcodeScanner.StartScanning();
- };
- }
-
- Grocy.Components.BarcodeScanner.StartScanning();
-});
-
-setTimeout(function()
-{
- $(".barcodescanner-input:visible").each(function()
- {
- if ($(this).hasAttr("disabled"))
- {
- $(this).after('');
- }
- else
- {
- $(this).after('');
- }
- });
-}, 50);
diff --git a/js/viewjs/components/batterycard.js b/js/viewjs/components/batterycard.js
deleted file mode 100644
index e4ad85ab..00000000
--- a/js/viewjs/components/batterycard.js
+++ /dev/null
@@ -1,27 +0,0 @@
-Grocy.Components.BatteryCard = {};
-
-Grocy.Components.BatteryCard.Refresh = function(batteryId)
-{
- Grocy.Api.Get('batteries/' + batteryId,
- function(batteryDetails)
- {
- $('#batterycard-battery-name').text(batteryDetails.battery.name);
- $('#batterycard-battery-used_in').text(batteryDetails.battery.used_in);
- $('#batterycard-battery-last-charged').text((batteryDetails.last_charged || __t('never')));
- $('#batterycard-battery-last-charged-timeago').attr("datetime", batteryDetails.last_charged || '');
- $('#batterycard-battery-charge-cycles-count').text((batteryDetails.charge_cycles_count || '0'));
-
- $('#batterycard-battery-edit-button').attr("href", U("/battery/" + batteryDetails.battery.id.toString()));
- $('#batterycard-battery-journal-button').attr("href", U("/batteriesjournal?embedded&battery=" + batteryDetails.battery.id.toString()));
- $('#batterycard-battery-edit-button').removeClass("disabled");
- $('#batterycard-battery-journal-button').removeClass("disabled");
-
- EmptyElementWhenMatches('#batterycard-battery-last-charged-timeago', __t('timeago_nan'));
- RefreshContextualTimeago(".batterycard");
- },
- function(xhr)
- {
- console.error(xhr);
- }
- );
-};
diff --git a/js/viewjs/components/calendarcard.js b/js/viewjs/components/calendarcard.js
deleted file mode 100644
index 36cc3425..00000000
--- a/js/viewjs/components/calendarcard.js
+++ /dev/null
@@ -1,40 +0,0 @@
-$('#calendar').datetimepicker(
- {
- format: 'L',
- buttons: {
- showToday: true,
- showClose: false
- },
- calendarWeeks: true,
- locale: moment.locale(),
- icons: {
- time: 'far fa-clock',
- date: 'far fa-calendar',
- up: 'fas fa-arrow-up',
- down: 'fas fa-arrow-down',
- previous: 'fas fa-chevron-left',
- next: 'fas fa-chevron-right',
- today: 'fas fa-calendar-check',
- clear: 'far fa-trash-alt',
- close: 'far fa-times-circle'
- },
- keepOpen: true,
- inline: true,
- keyBinds: {
- up: function(widget) { },
- down: function(widget) { },
- 'control up': function(widget) { },
- 'control down': function(widget) { },
- left: function(widget) { },
- right: function(widget) { },
- pageUp: function(widget) { },
- pageDown: function(widget) { },
- enter: function(widget) { },
- escape: function(widget) { },
- 'control space': function(widget) { },
- t: function(widget) { },
- 'delete': function(widget) { }
- }
- });
-
-$('#calendar').datetimepicker('show');
diff --git a/js/viewjs/components/chorecard.js b/js/viewjs/components/chorecard.js
deleted file mode 100644
index 861eaa51..00000000
--- a/js/viewjs/components/chorecard.js
+++ /dev/null
@@ -1,36 +0,0 @@
-Grocy.Components.ChoreCard = {};
-
-Grocy.Components.ChoreCard.Refresh = function(choreId)
-{
- Grocy.Api.Get('chores/' + choreId,
- function(choreDetails)
- {
- $('#chorecard-chore-name').text(choreDetails.chore.name);
- $('#chorecard-chore-last-tracked').text((choreDetails.last_tracked || __t('never')));
- $('#chorecard-chore-last-tracked-timeago').attr("datetime", choreDetails.last_tracked || '');
- $('#chorecard-chore-tracked-count').text((choreDetails.tracked_count || '0'));
- $('#chorecard-chore-last-done-by').text((choreDetails.last_done_by.display_name || __t('Unknown')));
-
- $('#chorecard-chore-edit-button').attr("href", U("/chore/" + choreDetails.chore.id.toString()));
- $('#chorecard-chore-journal-button').attr("href", U("/choresjournal?embedded&chore=" + choreDetails.chore.id.toString()));
- $('#chorecard-chore-edit-button').removeClass("disabled");
- $('#chorecard-chore-journal-button').removeClass("disabled");
-
- if (choreDetails.chore.track_date_only == 1)
- {
- $("#chorecard-chore-last-tracked-timeago").addClass("timeago-date-only");
- }
- else
- {
- $("#chorecard-chore-last-tracked-timeago").removeClass("timeago-date-only");
- }
-
- EmptyElementWhenMatches('#chorecard-chore-last-tracked-timeago', __t('timeago_nan'));
- RefreshContextualTimeago(".chorecard");
- },
- function(xhr)
- {
- console.error(xhr);
- }
- );
-};
diff --git a/js/viewjs/components/datetimepicker.js b/js/viewjs/components/datetimepicker.js
deleted file mode 100644
index d6dc97a5..00000000
--- a/js/viewjs/components/datetimepicker.js
+++ /dev/null
@@ -1,308 +0,0 @@
-Grocy.Components.DateTimePicker = {};
-
-Grocy.Components.DateTimePicker.GetInputElement = function()
-{
- return $('.datetimepicker').find('input').not(".form-check-input");
-}
-
-Grocy.Components.DateTimePicker.GetValue = function()
-{
- return Grocy.Components.DateTimePicker.GetInputElement().val();
-}
-
-Grocy.Components.DateTimePicker.SetValue = function(value)
-{
- // "Click" the shortcut checkbox when the desired value is
- // not the shortcut value and it is currently set
- var shortcutValue = $("#datetimepicker-shortcut").data("datetimepicker-shortcut-value");
- if (value != shortcutValue && $("#datetimepicker-shortcut").is(":checked"))
- {
- $("#datetimepicker-shortcut").click();
- }
- Grocy.Components.DateTimePicker.GetInputElement().val(value);
- Grocy.Components.DateTimePicker.GetInputElement().trigger('change');
-
- Grocy.Components.DateTimePicker.GetInputElement().keyup();
-}
-
-Grocy.Components.DateTimePicker.Clear = function()
-{
- $(".datetimepicker").datetimepicker("destroy");
- Grocy.Components.DateTimePicker.Init();
-
- Grocy.Components.DateTimePicker.GetInputElement().val("");
-
- // "Click" the shortcut checkbox when the desired value is
- // not the shortcut value and it is currently set
- var value = "";
- var shortcutValue = $("#datetimepicker-shortcut").data("datetimepicker-shortcut-value");
- if (value != shortcutValue && $("#datetimepicker-shortcut").is(":checked"))
- {
- $("#datetimepicker-shortcut").click();
- }
-
- $('#datetimepicker-timeago').text('');
-}
-
-Grocy.Components.DateTimePicker.ChangeFormat = function(format)
-{
- $(".datetimepicker").datetimepicker("destroy");
- Grocy.Components.DateTimePicker.GetInputElement().data("format", format);
- Grocy.Components.DateTimePicker.Init();
-
- if (format == "YYYY-MM-DD")
- {
- Grocy.Components.DateTimePicker.GetInputElement().addClass("date-only-datetimepicker");
- }
- else
- {
- Grocy.Components.DateTimePicker.GetInputElement().removeClass("date-only-datetimepicker");
- }
-}
-
-var startDate = null;
-if (Grocy.Components.DateTimePicker.GetInputElement().data('init-with-now') === true)
-{
- startDate = moment().format(Grocy.Components.DateTimePicker.GetInputElement().data('format'));
-}
-if (Grocy.Components.DateTimePicker.GetInputElement().data('init-value').length > 0)
-{
- startDate = moment(Grocy.Components.DateTimePicker.GetInputElement().data('init-value')).format(Grocy.Components.DateTimePicker.GetInputElement().data('format'));
-}
-
-var limitDate = moment('2999-12-31 23:59:59');
-if (Grocy.Components.DateTimePicker.GetInputElement().data('limit-end-to-now') === true)
-{
- limitDate = moment();
-}
-
-Grocy.Components.DateTimePicker.Init = function()
-{
- $('.datetimepicker').datetimepicker(
- {
- format: Grocy.Components.DateTimePicker.GetInputElement().data('format'),
- buttons: {
- showToday: true,
- showClose: true
- },
- calendarWeeks: Grocy.CalendarShowWeekNumbers,
- maxDate: limitDate,
- locale: moment.locale(),
- defaultDate: startDate,
- useCurrent: false,
- icons: {
- time: 'far fa-clock',
- date: 'far fa-calendar',
- up: 'fas fa-arrow-up',
- down: 'fas fa-arrow-down',
- previous: 'fas fa-chevron-left',
- next: 'fas fa-chevron-right',
- today: 'fas fa-calendar-check',
- clear: 'far fa-trash-alt',
- close: 'far fa-times-circle'
- },
- sideBySide: true,
- keyBinds: {
- up: function(widget) { },
- down: function(widget) { },
- 'control up': function(widget) { },
- 'control down': function(widget) { },
- left: function(widget) { },
- right: function(widget) { },
- pageUp: function(widget) { },
- pageDown: function(widget) { },
- enter: function(widget) { },
- escape: function(widget) { },
- 'control space': function(widget) { },
- t: function(widget) { },
- 'delete': function(widget) { }
- }
- });
-}
-Grocy.Components.DateTimePicker.Init();
-
-Grocy.Components.DateTimePicker.GetInputElement().on('keyup', function(e)
-{
- $('.datetimepicker').datetimepicker('hide');
-
- var value = Grocy.Components.DateTimePicker.GetValue();
- var now = new Date();
- var centuryStart = Number.parseInt(now.getFullYear().toString().substring(0, 2) + '00');
- var centuryEnd = Number.parseInt(now.getFullYear().toString().substring(0, 2) + '99');
- var format = Grocy.Components.DateTimePicker.GetInputElement().data('format');
- var nextInputElement = $(Grocy.Components.DateTimePicker.GetInputElement().data('next-input-selector'));
-
- //If input is empty and any arrow key is pressed, set date to today
- if (value.length === 0 && (e.keyCode === 38 || e.keyCode === 40 || e.keyCode === 37 || e.keyCode === 39))
- {
- Grocy.Components.DateTimePicker.SetValue(moment(new Date(), format, true).format(format));
- nextInputElement.focus();
- }
- else if (value === 'x' || value === 'X')
- {
- Grocy.Components.DateTimePicker.SetValue(moment('2999-12-31 23:59:59').format(format));
- nextInputElement.focus();
- }
- else if (value.length === 4 && !(Number.parseInt(value) > centuryStart && Number.parseInt(value) < centuryEnd))
- {
- var date = moment((new Date()).getFullYear().toString() + value);
- if (date.isBefore(moment()))
- {
- date.add(1, "year");
- }
- Grocy.Components.DateTimePicker.SetValue(date.format(format));
- nextInputElement.focus();
- }
- else if (value.length === 8 && $.isNumeric(value))
- {
- Grocy.Components.DateTimePicker.SetValue(value.replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3'));
- nextInputElement.focus();
- }
- else if (value.length === 7 && $.isNumeric(value.substring(0, 6)) && (value.substring(6, 7).toLowerCase() === "e" || value.substring(6, 7).toLowerCase() === "+"))
- {
- var endOfMonth = moment(value.substring(0, 4) + "-" + value.substring(4, 6) + "-01").endOf("month");
- Grocy.Components.DateTimePicker.SetValue(endOfMonth.format(format));
- nextInputElement.focus();
- }
- else
- {
- var dateObj = moment(value, format, true);
- if (dateObj.isValid())
- {
- if (e.shiftKey)
- {
- // WITH shift modifier key
-
- if (e.keyCode === 38) // Up
- {
- Grocy.Components.DateTimePicker.SetValue(dateObj.add(-1, 'months').format(format));
- }
- else if (e.keyCode === 40) // Down
- {
- Grocy.Components.DateTimePicker.SetValue(dateObj.add(1, 'months').format(format));
- }
- else if (e.keyCode === 37) // Left
- {
- Grocy.Components.DateTimePicker.SetValue(dateObj.add(-1, 'years').format(format));
- }
- else if (e.keyCode === 39) // Right
- {
- Grocy.Components.DateTimePicker.SetValue(dateObj.add(1, 'years').format(format));
- }
- }
- else
- {
- // WITHOUT shift modifier key
-
- if (e.keyCode === 38) // Up
- {
- Grocy.Components.DateTimePicker.SetValue(dateObj.add(-1, 'days').format(format));
- }
- else if (e.keyCode === 40) // Down
- {
- Grocy.Components.DateTimePicker.SetValue(dateObj.add(1, 'days').format(format));
- }
- else if (e.keyCode === 37) // Left
- {
- Grocy.Components.DateTimePicker.SetValue(dateObj.add(-1, 'weeks').format(format));
- }
- else if (e.keyCode === 39) // Right
- {
- Grocy.Components.DateTimePicker.SetValue(dateObj.add(1, 'weeks').format(format));
- }
- }
- }
- }
-
- //Custom validation
- value = Grocy.Components.DateTimePicker.GetValue();
- dateObj = moment(value, format, true);
- var element = Grocy.Components.DateTimePicker.GetInputElement()[0];
- if (!dateObj.isValid())
- {
- if ($(element).hasAttr("required"))
- {
- element.setCustomValidity("error");
- }
- }
- else
- {
- if (Grocy.Components.DateTimePicker.GetInputElement().data('limit-end-to-now') === true && dateObj.isAfter(moment()))
- {
- element.setCustomValidity("error");
- }
- else if (Grocy.Components.DateTimePicker.GetInputElement().data('limit-start-to-now') === true && dateObj.isBefore(moment()))
- {
- element.setCustomValidity("error");
- }
- else
- {
- element.setCustomValidity("");
- }
-
- var earlierThanLimit = Grocy.Components.DateTimePicker.GetInputElement().data("earlier-than-limit");
- if (!earlierThanLimit.isEmpty())
- {
- if (moment(value).isBefore(moment(earlierThanLimit)))
- {
- $("#datetimepicker-earlier-than-info").removeClass("d-none");
- }
- else
- {
- $("#datetimepicker-earlier-than-info").addClass("d-none");
- }
- }
- }
-
- // "Click" the shortcut checkbox when the shortcut value was
- // entered manually and it is currently not set
- var shortcutValue = $("#datetimepicker-shortcut").data("datetimepicker-shortcut-value");
- if (value == shortcutValue && !$("#datetimepicker-shortcut").is(":checked"))
- {
- $("#datetimepicker-shortcut").click();
- }
-});
-
-Grocy.Components.DateTimePicker.GetInputElement().on('input', function(e)
-{
- $('#datetimepicker-timeago').attr("datetime", Grocy.Components.DateTimePicker.GetValue());
- EmptyElementWhenMatches('#datetimepicker-timeago', __t('timeago_nan'));
- RefreshContextualTimeago("#datetimepicker-wrapper");
-});
-
-$('.datetimepicker').on('update.datetimepicker', function(e)
-{
- Grocy.Components.DateTimePicker.GetInputElement().trigger('input');
- Grocy.Components.DateTimePicker.GetInputElement().trigger('change');
- Grocy.Components.DateTimePicker.GetInputElement().trigger('keypress');
- Grocy.Components.DateTimePicker.GetInputElement().trigger('keyup');
-});
-
-$('.datetimepicker').on('hide.datetimepicker', function(e)
-{
- Grocy.Components.DateTimePicker.GetInputElement().trigger('input');
- Grocy.Components.DateTimePicker.GetInputElement().trigger('change');
- Grocy.Components.DateTimePicker.GetInputElement().trigger('keypress');
- Grocy.Components.DateTimePicker.GetInputElement().trigger('keyup');
-});
-
-$("#datetimepicker-shortcut").on("click", function()
-{
- if (this.checked)
- {
- var value = $("#datetimepicker-shortcut").data("datetimepicker-shortcut-value");
- Grocy.Components.DateTimePicker.SetValue(value);
- Grocy.Components.DateTimePicker.GetInputElement().attr("readonly", "");
- $(Grocy.Components.DateTimePicker.GetInputElement().data('next-input-selector')).focus();
- }
- else
- {
- Grocy.Components.DateTimePicker.SetValue("");
- Grocy.Components.DateTimePicker.GetInputElement().removeAttr("readonly");
- Grocy.Components.DateTimePicker.GetInputElement().focus();
- }
-
- Grocy.Components.DateTimePicker.GetInputElement().trigger('input');
- Grocy.Components.DateTimePicker.GetInputElement().trigger('change');
- Grocy.Components.DateTimePicker.GetInputElement().trigger('keypress');
-});
diff --git a/js/viewjs/components/datetimepicker2.js b/js/viewjs/components/datetimepicker2.js
deleted file mode 100644
index 5fcc4209..00000000
--- a/js/viewjs/components/datetimepicker2.js
+++ /dev/null
@@ -1,309 +0,0 @@
-Grocy.Components.DateTimePicker2 = {};
-
-Grocy.Components.DateTimePicker2.GetInputElement = function()
-{
- return $('.datetimepicker2').find('input').not(".form-check-input");
-}
-
-Grocy.Components.DateTimePicker2.GetValue = function()
-{
- return Grocy.Components.DateTimePicker2.GetInputElement().val();
-}
-
-Grocy.Components.DateTimePicker2.SetValue = function(value)
-{
- // "Click" the shortcut checkbox when the desired value is
- // not the shortcut value and it is currently set
- var shortcutValue = $("#datetimepicker2-shortcut").data("datetimepicker2-shortcut-value");
- if (value != shortcutValue && $("#datetimepicker2-shortcut").is(":checked"))
- {
- $("#datetimepicker2-shortcut").click();
- }
-
- Grocy.Components.DateTimePicker2.GetInputElement().val(value);
- Grocy.Components.DateTimePicker2.GetInputElement().trigger('change');
-
- Grocy.Components.DateTimePicker2.GetInputElement().keyup();
-}
-
-Grocy.Components.DateTimePicker2.Clear = function()
-{
- $(".datetimepicker2").datetimepicker("destroy");
- Grocy.Components.DateTimePicker2.Init();
-
- Grocy.Components.DateTimePicker2.GetInputElement().val("");
-
- // "Click" the shortcut checkbox when the desired value is
- // not the shortcut value and it is currently set
- var value = "";
- var shortcutValue = $("#datetimepicker2-shortcut").data("datetimepicker2-shortcut-value");
- if (value != shortcutValue && $("#datetimepicker2-shortcut").is(":checked"))
- {
- $("#datetimepicker2-shortcut").click();
- }
-
- $('#datetimepicker2-timeago').text('');
-}
-
-Grocy.Components.DateTimePicker2.ChangeFormat = function(format)
-{
- $(".datetimepicker2").datetimepicker("destroy");
- Grocy.Components.DateTimePicker2.GetInputElement().data("format", format);
- Grocy.Components.DateTimePicker2.Init();
-
- if (format == "YYYY-MM-DD")
- {
- Grocy.Components.DateTimePicker2.GetInputElement().addClass("date-only-datetimepicker");
- }
- else
- {
- Grocy.Components.DateTimePicker2.GetInputElement().removeClass("date-only-datetimepicker");
- }
-}
-
-var startDate = null;
-if (Grocy.Components.DateTimePicker2.GetInputElement().data('init-with-now') === true)
-{
- startDate = moment().format(Grocy.Components.DateTimePicker2.GetInputElement().data('format'));
-}
-if (Grocy.Components.DateTimePicker2.GetInputElement().data('init-value').length > 0)
-{
- startDate = moment(Grocy.Components.DateTimePicker2.GetInputElement().data('init-value')).format(Grocy.Components.DateTimePicker2.GetInputElement().data('format'));
-}
-
-var limitDate = moment('2999-12-31 23:59:59');
-if (Grocy.Components.DateTimePicker2.GetInputElement().data('limit-end-to-now') === true)
-{
- limitDate = moment();
-}
-
-Grocy.Components.DateTimePicker2.Init = function()
-{
- $('.datetimepicker2').datetimepicker(
- {
- format: Grocy.Components.DateTimePicker2.GetInputElement().data('format'),
- buttons: {
- showToday: true,
- showClose: true
- },
- calendarWeeks: Grocy.CalendarShowWeekNumbers,
- maxDate: limitDate,
- locale: moment.locale(),
- defaultDate: startDate,
- useCurrent: false,
- icons: {
- time: 'far fa-clock',
- date: 'far fa-calendar',
- up: 'fas fa-arrow-up',
- down: 'fas fa-arrow-down',
- previous: 'fas fa-chevron-left',
- next: 'fas fa-chevron-right',
- today: 'fas fa-calendar-check',
- clear: 'far fa-trash-alt',
- close: 'far fa-times-circle'
- },
- sideBySide: true,
- keyBinds: {
- up: function(widget) { },
- down: function(widget) { },
- 'control up': function(widget) { },
- 'control down': function(widget) { },
- left: function(widget) { },
- right: function(widget) { },
- pageUp: function(widget) { },
- pageDown: function(widget) { },
- enter: function(widget) { },
- escape: function(widget) { },
- 'control space': function(widget) { },
- t: function(widget) { },
- 'delete': function(widget) { }
- }
- });
-}
-Grocy.Components.DateTimePicker2.Init();
-
-Grocy.Components.DateTimePicker2.GetInputElement().on('keyup', function(e)
-{
- $('.datetimepicker2').datetimepicker('hide');
-
- var value = Grocy.Components.DateTimePicker2.GetValue();
- var now = new Date();
- var centuryStart = Number.parseInt(now.getFullYear().toString().substring(0, 2) + '00');
- var centuryEnd = Number.parseInt(now.getFullYear().toString().substring(0, 2) + '99');
- var format = Grocy.Components.DateTimePicker2.GetInputElement().data('format');
- var nextInputElement = $(Grocy.Components.DateTimePicker2.GetInputElement().data('next-input-selector'));
-
- //If input is empty and any arrow key is pressed, set date to today
- if (value.length === 0 && (e.keyCode === 38 || e.keyCode === 40 || e.keyCode === 37 || e.keyCode === 39))
- {
- Grocy.Components.DateTimePicker2.SetValue(moment(new Date(), format, true).format(format));
- nextInputElement.focus();
- }
- else if (value === 'x' || value === 'X')
- {
- Grocy.Components.DateTimePicker2.SetValue(moment('2999-12-31 23:59:59').format(format));
- nextInputElement.focus();
- }
- else if (value.length === 4 && !(Number.parseInt(value) > centuryStart && Number.parseInt(value) < centuryEnd))
- {
- var date = moment((new Date()).getFullYear().toString() + value);
- if (date.isBefore(moment()))
- {
- date.add(1, "year");
- }
- Grocy.Components.DateTimePicker2.SetValue(date.format(format));
- nextInputElement.focus();
- }
- else if (value.length === 8 && $.isNumeric(value))
- {
- Grocy.Components.DateTimePicker2.SetValue(value.replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3'));
- nextInputElement.focus();
- }
- else if (value.length === 7 && $.isNumeric(value.substring(0, 6)) && (value.substring(6, 7).toLowerCase() === "e" || value.substring(6, 7).toLowerCase() === "+"))
- {
- var endOfMonth = moment(value.substring(0, 4) + "-" + value.substring(4, 6) + "-01").endOf("month");
- Grocy.Components.DateTimePicker2.SetValue(endOfMonth.format(format));
- nextInputElement.focus();
- }
- else
- {
- var dateObj = moment(value, format, true);
- if (dateObj.isValid())
- {
- if (e.shiftKey)
- {
- // WITH shift modifier key
-
- if (e.keyCode === 38) // Up
- {
- Grocy.Components.DateTimePicker2.SetValue(dateObj.add(-1, 'months').format(format));
- }
- else if (e.keyCode === 40) // Down
- {
- Grocy.Components.DateTimePicker2.SetValue(dateObj.add(1, 'months').format(format));
- }
- else if (e.keyCode === 37) // Left
- {
- Grocy.Components.DateTimePicker2.SetValue(dateObj.add(-1, 'years').format(format));
- }
- else if (e.keyCode === 39) // Right
- {
- Grocy.Components.DateTimePicker2.SetValue(dateObj.add(1, 'years').format(format));
- }
- }
- else
- {
- // WITHOUT shift modifier key
-
- if (e.keyCode === 38) // Up
- {
- Grocy.Components.DateTimePicker2.SetValue(dateObj.add(-1, 'days').format(format));
- }
- else if (e.keyCode === 40) // Down
- {
- Grocy.Components.DateTimePicker2.SetValue(dateObj.add(1, 'days').format(format));
- }
- else if (e.keyCode === 37) // Left
- {
- Grocy.Components.DateTimePicker2.SetValue(dateObj.add(-1, 'weeks').format(format));
- }
- else if (e.keyCode === 39) // Right
- {
- Grocy.Components.DateTimePicker2.SetValue(dateObj.add(1, 'weeks').format(format));
- }
- }
- }
- }
-
- //Custom validation
- value = Grocy.Components.DateTimePicker2.GetValue();
- dateObj = moment(value, format, true);
- var element = Grocy.Components.DateTimePicker2.GetInputElement()[0];
- if (!dateObj.isValid())
- {
- if ($(element).hasAttr("required"))
- {
- element.setCustomValidity("error");
- }
- }
- else
- {
- if (Grocy.Components.DateTimePicker2.GetInputElement().data('limit-end-to-now') === true && dateObj.isAfter(moment()))
- {
- element.setCustomValidity("error");
- }
- else if (Grocy.Components.DateTimePicker2.GetInputElement().data('limit-start-to-now') === true && dateObj.isBefore(moment()))
- {
- element.setCustomValidity("error");
- }
- else
- {
- element.setCustomValidity("");
- }
-
- var earlierThanLimit = Grocy.Components.DateTimePicker2.GetInputElement().data("earlier-than-limit");
- if (!earlierThanLimit.isEmpty())
- {
- if (moment(value).isBefore(moment(earlierThanLimit)))
- {
- $("#datetimepicker-earlier-than-info").removeClass("d-none");
- }
- else
- {
- $("#datetimepicker-earlier-than-info").addClass("d-none");
- }
- }
- }
-
- // "Click" the shortcut checkbox when the shortcut value was
- // entered manually and it is currently not set
- var shortcutValue = $("#datetimepicker2-shortcut").data("datetimepicker2-shortcut-value");
- if (value == shortcutValue && !$("#datetimepicker2-shortcut").is(":checked"))
- {
- $("#datetimepicker2-shortcut").click();
- }
-});
-
-Grocy.Components.DateTimePicker2.GetInputElement().on('input', function(e)
-{
- $('#datetimepicker2-timeago').attr("datetime", Grocy.Components.DateTimePicker2.GetValue());
- EmptyElementWhenMatches('#datetimepicker2-timeago', __t('timeago_nan'));
- RefreshContextualTimeago("#datetimepicker2-wrapper");
-});
-
-$('.datetimepicker2').on('update.datetimepicker', function(e)
-{
- Grocy.Components.DateTimePicker2.GetInputElement().trigger('input');
- Grocy.Components.DateTimePicker2.GetInputElement().trigger('change');
- Grocy.Components.DateTimePicker2.GetInputElement().trigger('keypress');
- Grocy.Components.DateTimePicker2.GetInputElement().trigger('keyup');
-});
-
-$('.datetimepicker2').on('hide.datetimepicker', function(e)
-{
- Grocy.Components.DateTimePicker2.GetInputElement().trigger('input');
- Grocy.Components.DateTimePicker2.GetInputElement().trigger('change');
- Grocy.Components.DateTimePicker2.GetInputElement().trigger('keypress');
- Grocy.Components.DateTimePicker2.GetInputElement().trigger('keyup');
-});
-
-$("#datetimepicker2-shortcut").on("click", function()
-{
- if (this.checked)
- {
- var value = $("#datetimepicker2-shortcut").data("datetimepicker2-shortcut-value");
- Grocy.Components.DateTimePicker2.SetValue(value);
- Grocy.Components.DateTimePicker2.GetInputElement().attr("readonly", "");
- $(Grocy.Components.DateTimePicker2.GetInputElement().data('next-input-selector')).focus();
- }
- else
- {
- Grocy.Components.DateTimePicker2.SetValue("");
- Grocy.Components.DateTimePicker2.GetInputElement().removeAttr("readonly");
- Grocy.Components.DateTimePicker2.GetInputElement().focus();
- }
-
- Grocy.Components.DateTimePicker2.GetInputElement().trigger('input');
- Grocy.Components.DateTimePicker2.GetInputElement().trigger('change');
- Grocy.Components.DateTimePicker2.GetInputElement().trigger('keypress');
-});
diff --git a/js/viewjs/components/locationpicker.js b/js/viewjs/components/locationpicker.js
deleted file mode 100644
index 7ed5f7fd..00000000
--- a/js/viewjs/components/locationpicker.js
+++ /dev/null
@@ -1,74 +0,0 @@
-Grocy.Components.LocationPicker = {};
-
-Grocy.Components.LocationPicker.GetPicker = function()
-{
- return $('#location_id');
-}
-
-Grocy.Components.LocationPicker.GetInputElement = function()
-{
- return $('#location_id_text_input');
-}
-
-Grocy.Components.LocationPicker.GetValue = function()
-{
- return $('#location_id').val();
-}
-
-Grocy.Components.LocationPicker.SetValue = function(value)
-{
- Grocy.Components.LocationPicker.GetInputElement().val(value);
- Grocy.Components.LocationPicker.GetInputElement().trigger('change');
-}
-
-Grocy.Components.LocationPicker.SetId = function(value)
-{
- Grocy.Components.LocationPicker.GetPicker().val(value);
- Grocy.Components.LocationPicker.GetPicker().data('combobox').refresh();
- Grocy.Components.LocationPicker.GetInputElement().trigger('change');
-}
-
-Grocy.Components.LocationPicker.Clear = function()
-{
- Grocy.Components.LocationPicker.SetValue('');
- Grocy.Components.LocationPicker.SetId(null);
-}
-
-$('.location-combobox').combobox({
- appendId: '_text_input',
- bsVersion: '4',
- clearIfNoMatch: true
-});
-
-// these names seem a bit long, but as they live in global space
-// and this is a component, they need to be unique.
-var locationpicker_doFocus = false;
-var this_location_picker = Grocy.Components.LocationPicker.GetPicker();
-
-var prefillByName = this_location_picker.parent().data('prefill-by-name').toString();
-if (typeof prefillByName !== "undefined")
-{
- var possibleOptionElement = $("#location_id option:contains(\"" + prefillByName + "\")").first();
-
- if (possibleOptionElement.length > 0)
- {
- locationpicker_doFocus = true;
- this_location_picker.val(possibleOptionElement.val());
- }
-}
-
-var prefillById = this_location_picker.parent().data('prefill-by-id').toString();
-if (typeof prefillById !== "undefined")
-{
- locationpicker_doFocus = true;
- this_location_picker.val(prefillById);
-}
-
-if (locationpicker_doFocus)
-{
- this_location_picker.data('combobox').refresh();
- this_location_picker.trigger('change');
-
- $(this_location_picker.parent().data('next-input-selector').toString())
- .focus();
-}
diff --git a/js/viewjs/components/numberpicker.js b/js/viewjs/components/numberpicker.js
deleted file mode 100644
index 249e2b85..00000000
--- a/js/viewjs/components/numberpicker.js
+++ /dev/null
@@ -1,94 +0,0 @@
-$(".numberpicker-down-button").unbind('click').on("click", function()
-{
- var inputElement = $(this).parent().parent().find('input[type="number"]');
- inputElement.val(parseFloat(inputElement.val() || 1) - 1);
- inputElement.trigger('keyup');
- inputElement.trigger('change');
-});
-
-$(".numberpicker-up-button").unbind('click').on("click", function()
-{
- var inputElement = $(this).parent().parent().find('input[type="number"]');
- inputElement.val(parseFloat(inputElement.val() || 0) + 1);
- inputElement.trigger('keyup');
- inputElement.trigger('change');
-});
-
-$(".numberpicker").on("keyup", function()
-{
- if ($(this).attr("data-not-equal") && !$(this).attr("data-not-equal").toString().isEmpty() && $(this).attr("data-not-equal") == $(this).val())
- {
- $(this)[0].setCustomValidity("error");
- }
- else
- {
- $(this)[0].setCustomValidity("");
- }
-});
-
-$(".numberpicker").each(function()
-{
- new MutationObserver(function(mutations)
- {
- mutations.forEach(function(mutation)
- {
- if (mutation.type == "attributes" && (mutation.attributeName == "min" || mutation.attributeName == "max" || mutation.attributeName == "data-not-equal" || mutation.attributeName == "data-initialised"))
- {
- var element = $(mutation.target);
- var min = element.attr("min");
- var decimals = element.attr("data-decimals");
-
- var max = "";
- if (element.hasAttr("max"))
- {
- max = element.attr("max");
- }
-
- if (element.hasAttr("data-not-equal"))
- {
- var notEqual = element.attr("data-not-equal");
-
- if (notEqual != "NaN")
- {
- if (max.isEmpty())
- {
- element.parent().find(".invalid-feedback").text(__t("This cannot be lower than %1$s or equal %2$s and needs to be a valid number with max. %3$s decimal places", parseFloat(min).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), parseFloat(notEqual).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), decimals));
- }
- else
- {
- element.parent().find(".invalid-feedback").text(__t("This must be between %1$s and %2$s, cannot equal %3$s and needs to be a valid number with max. %4$s decimal places", parseFloat(min).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), parseFloat(max).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), parseFloat(notEqual).toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals }), decimals));
- }
-
- return;
- }
- }
-
- if (max.isEmpty())
- {
- element.parent().find(".invalid-feedback").text(__t("This cannot be lower than %1$s and needs to be a valid number with max. %2$s decimal places", parseFloat(min).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), decimals));
- }
- else
- {
- element.parent().find(".invalid-feedback").text(__t("This must between %1$s and %2$s and needs to be a valid number with max. %3$s decimal places", parseFloat(min).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), parseFloat(max).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: decimals }), decimals));
- }
- }
- });
- }).observe(this, {
- attributes: true
- });
-});
-$(".numberpicker").attr("data-initialised", "true"); // Dummy change to trigger MutationObserver above once
-
-$(".numberpicker").on("keydown", function(e)
-{
- if (e.key == "ArrowUp")
- {
- e.preventDefault();
- $(this).parent().find(".numberpicker-up-button").click();
- }
- else if (e.key == "ArrowDown")
- {
- e.preventDefault();
- $(this).parent().find(".numberpicker-down-button").click();
- }
-});
diff --git a/js/viewjs/components/productamountpicker.js b/js/viewjs/components/productamountpicker.js
deleted file mode 100644
index 1008267f..00000000
--- a/js/viewjs/components/productamountpicker.js
+++ /dev/null
@@ -1,116 +0,0 @@
-Grocy.Components.ProductAmountPicker = {};
-Grocy.Components.ProductAmountPicker.AllowAnyQuEnabled = false;
-
-Grocy.Components.ProductAmountPicker.Reload = function(productId, destinationQuId, forceInitialDisplayQu = false)
-{
- var conversionsForProduct = Grocy.QuantityUnitConversionsResolved.filter(elem => elem.product_id == productId);
-
- if (!Grocy.Components.ProductAmountPicker.AllowAnyQuEnabled)
- {
- var qu = Grocy.QuantityUnits.find(elem => elem.id == destinationQuId);
- $("#qu_id").find("option").remove().end();
- $("#qu_id").attr("data-destination-qu-name", qu.name);
- $("#qu_id").attr("data-destination-qu-name-plural", qu.name_plural);
-
- conversionsForProduct.forEach(conversion =>
- {
- var factor = parseFloat(conversion.factor);
- if (conversion.to_qu_id == destinationQuId)
- {
- factor = 1;
- }
-
- if (!$('#qu_id option[value="' + conversion.to_qu_id + '"]').length) // Don't add the destination QU multiple times
- {
- $("#qu_id").append('');
- }
- });
- }
-
- if (!Grocy.Components.ProductAmountPicker.InitialValueSet || forceInitialDisplayQu)
- {
- $("#qu_id").val($("#qu_id").attr("data-initial-qu-id"));
- }
-
- if (!Grocy.Components.ProductAmountPicker.InitialValueSet)
- {
- var convertedAmount = $("#display_amount").val() * $("#qu_id option:selected").attr("data-qu-factor");
- $("#display_amount").val(convertedAmount);
-
- Grocy.Components.ProductAmountPicker.InitialValueSet = true;
- }
-
- if (conversionsForProduct.length === 1 && !forceInitialDisplayQu)
- {
- $("#qu_id").val($("#qu_id option:first").val());
- }
-
- if ($('#qu_id option').length == 1)
- {
- $("#qu_id").attr("disabled", "");
- }
- else
- {
- $("#qu_id").removeAttr("disabled");
- }
-
- $(".input-group-productamountpicker").trigger("change");
-}
-
-Grocy.Components.ProductAmountPicker.SetQuantityUnit = function(quId)
-{
- $("#qu_id").val(quId);
-}
-
-Grocy.Components.ProductAmountPicker.AllowAnyQu = function(keepInitialQu = false)
-{
- Grocy.Components.ProductAmountPicker.AllowAnyQuEnabled = true;
-
- $("#qu_id").find("option").remove().end();
- Grocy.QuantityUnits.forEach(qu =>
- {
- $("#qu_id").append('');
- });
-
- if (keepInitialQu)
- {
- Grocy.Components.ProductAmountPicker.SetQuantityUnit($("#qu_id").attr("data-initial-qu-id"));
- }
-
- $("#qu_id").removeAttr("disabled");
-
- $(".input-group-productamountpicker").trigger("change");
-}
-
-Grocy.Components.ProductAmountPicker.Reset = function()
-{
- $("#qu_id").find("option").remove();
- $("#qu-conversion-info").addClass("d-none");
- $("#qu-display_amount-info").val("");
-}
-
-$(".input-group-productamountpicker").on("change", function()
-{
- var selectedQuName = $("#qu_id option:selected").text();
- var quFactor = $("#qu_id option:selected").attr("data-qu-factor");
- var amount = $("#display_amount").val();
- var destinationAmount = amount / quFactor;
- var destinationQuName = __n(destinationAmount, $("#qu_id").attr("data-destination-qu-name"), $("#qu_id").attr("data-destination-qu-name-plural"))
-
- if ($("#qu_id").attr("data-destination-qu-name") == selectedQuName || Grocy.Components.ProductAmountPicker.AllowAnyQuEnabled || amount.toString().isEmpty() || selectedQuName.toString().isEmpty())
- {
- $("#qu-conversion-info").addClass("d-none");
- }
- else
- {
- $("#qu-conversion-info").removeClass("d-none");
- $("#qu-conversion-info").text(__t("This equals %1$s %2$s", destinationAmount.toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts }), destinationQuName));
- }
-
- $("#amount").val(destinationAmount.toFixed(Grocy.UserSettings.stock_decimal_places_amounts).replace(/0*$/g, ''));
-});
-
-$("#display_amount").on("keyup", function()
-{
- $(".input-group-productamountpicker").trigger("change");
-});
diff --git a/js/viewjs/components/productcard.js b/js/viewjs/components/productcard.js
deleted file mode 100644
index 4f1cdc20..00000000
--- a/js/viewjs/components/productcard.js
+++ /dev/null
@@ -1,237 +0,0 @@
-import Chart from 'chart.js';
-
-Grocy.Components.ProductCard = {};
-
-Grocy.Components.ProductCard.Refresh = function(productId)
-{
- Grocy.Api.Get('stock/products/' + productId,
- function(productDetails)
- {
- var stockAmount = productDetails.stock_amount || '0';
- var stockValue = productDetails.stock_value || '0';
- var stockAmountOpened = productDetails.stock_amount_opened || '0';
- $('#productcard-product-name').text(productDetails.product.name);
- $('#productcard-product-description').html(productDetails.product.description);
- $('#productcard-product-stock-amount').text(stockAmount);
- $('#productcard-product-stock-qu-name').text(__n(stockAmount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural));
- $('#productcard-product-stock-value').text(stockValue + ' ' + Grocy.Currency);
- $('#productcard-product-last-purchased').text((productDetails.last_purchased || '2999-12-31').substring(0, 10));
- $('#productcard-product-last-purchased-timeago').attr("datetime", productDetails.last_purchased || '2999-12-31');
- $('#productcard-product-last-used').text((productDetails.last_used || '2999-12-31').substring(0, 10));
- $('#productcard-product-last-used-timeago').attr("datetime", productDetails.last_used || '2999-12-31');
- if (productDetails.location != null)
- {
- $('#productcard-product-location').text(productDetails.location.name);
- }
- $('#productcard-product-spoil-rate').text((parseFloat(productDetails.spoil_rate_percent) / 100).toLocaleString(undefined, { style: "percent" }));
-
- if (productDetails.is_aggregated_amount == 1)
- {
- $('#productcard-product-stock-amount-aggregated').text(productDetails.stock_amount_aggregated);
- $('#productcard-product-stock-qu-name-aggregated').text(__n(productDetails.stock_amount_aggregated, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural));
-
- if (productDetails.stock_amount_opened_aggregated > 0)
- {
- $('#productcard-product-stock-opened-amount-aggregated').text(__t('%s opened', productDetails.stock_amount_opened_aggregated));
- }
- else
- {
- $('#productcard-product-stock-opened-amount-aggregated').text("");
- }
-
- $("#productcard-aggregated-amounts").removeClass("d-none");
- }
- else
- {
- $("#productcard-aggregated-amounts").addClass("d-none");
- }
-
- if (productDetails.product.description != null && !productDetails.product.description.isEmpty())
- {
- $("#productcard-product-description-wrapper").removeClass("d-none");
- }
- else
- {
- $("#productcard-product-description-wrapper").addClass("d-none");
- }
-
- if (productDetails.average_shelf_life_days == -1)
- {
- $('#productcard-product-average-shelf-life').text(__t("Unknown"));
- }
- else if (parseInt(productDetails.average_shelf_life_days) > 73000) // > 200 years aka forever
- {
- $('#productcard-product-average-shelf-life').text(__t("Unlimited"));
- }
- else
- {
- $('#productcard-product-average-shelf-life').text(moment.duration(productDetails.average_shelf_life_days, "days").humanize());
- }
-
- if (stockAmountOpened > 0)
- {
- $('#productcard-product-stock-opened-amount').text(__t('%s opened', stockAmountOpened));
- }
- else
- {
- $('#productcard-product-stock-opened-amount').text("");
- }
-
- $('#productcard-product-edit-button').attr("href", U("/product/" + productDetails.product.id.toString() + '?' + 'returnto=' + encodeURIComponent(Grocy.CurrentUrlRelative)));
- $('#productcard-product-journal-button').attr("href", U("/stockjournal?embedded&product=" + productDetails.product.id.toString()));
- $('#productcard-product-stock-button').attr("href", U("/stockentries?embedded&product=" + productDetails.product.id.toString()));
- $('#productcard-product-stock-button').removeClass("disabled");
- $('#productcard-product-edit-button').removeClass("disabled");
- $('#productcard-product-journal-button').removeClass("disabled");
-
- if (productDetails.last_price !== null)
- {
- $('#productcard-product-last-price').text(Number.parseFloat(productDetails.last_price).toLocaleString() + ' ' + Grocy.Currency + ' per ' + productDetails.quantity_unit_stock.name);
- }
- else
- {
- $('#productcard-product-last-price').text(__t('Unknown'));
- }
-
- if (productDetails.avg_price !== null)
- {
- $('#productcard-product-average-price').text(Number.parseFloat(productDetails.avg_price).toLocaleString() + ' ' + Grocy.Currency + ' per ' + productDetails.quantity_unit_stock.name);
- }
- else
- {
- $('#productcard-product-average-price').text(__t('Unknown'));
- }
-
- if (productDetails.product.picture_file_name !== null && !productDetails.product.picture_file_name.isEmpty())
- {
- $("#productcard-product-picture").removeClass("d-none");
- $("#productcard-product-picture").attr("src", U('/api/files/productpictures/' + btoa(productDetails.product.picture_file_name) + '?force_serve_as=picture&best_fit_width=400'));
- }
- else
- {
- $("#productcard-product-picture").addClass("d-none");
- }
-
- EmptyElementWhenMatches('#productcard-product-last-purchased-timeago', __t('timeago_nan'));
- EmptyElementWhenMatches('#productcard-product-last-used-timeago', __t('timeago_nan'));
- RefreshContextualTimeago(".productcard");
- },
- function(xhr)
- {
- console.error(xhr);
- }
- );
-
- if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
- {
- Grocy.Api.Get('stock/products/' + productId + '/price-history',
- function(priceHistoryDataPoints)
- {
- if (priceHistoryDataPoints.length > 0)
- {
- $("#productcard-product-price-history-chart").removeClass("d-none");
- $("#productcard-no-price-data-hint").addClass("d-none");
-
- Grocy.Components.ProductCard.ReInitPriceHistoryChart();
- var datasets = {};
- var chart = Grocy.Components.ProductCard.PriceHistoryChart.data;
- priceHistoryDataPoints.forEach((dataPoint) =>
- {
- var key = __t("Unknown store");
- if (dataPoint.shopping_location)
- {
- key = dataPoint.shopping_location.name
- }
-
- if (!datasets[key])
- {
- datasets[key] = []
- }
- chart.labels.push(moment(dataPoint.date).toDate());
- datasets[key].push(dataPoint.price);
-
- });
- Object.keys(datasets).forEach((key) =>
- {
- chart.datasets.push({
- data: datasets[key],
- fill: false,
- borderColor: "HSL(" + (129 * chart.datasets.length) + ",100%,50%)",
- label: key,
- });
- });
- Grocy.Components.ProductCard.PriceHistoryChart.update();
- }
- else
- {
- $("#productcard-product-price-history-chart").addClass("d-none");
- $("#productcard-no-price-data-hint").removeClass("d-none");
- }
- },
- function(xhr)
- {
- console.error(xhr);
- }
- );
- }
-};
-
-Grocy.Components.ProductCard.ReInitPriceHistoryChart = function()
-{
- if (typeof Grocy.Components.ProductCard.PriceHistoryChart !== "undefined")
- {
- Grocy.Components.ProductCard.PriceHistoryChart.destroy();
- }
-
- var format = 'YYYY-MM-DD';
- Grocy.Components.ProductCard.PriceHistoryChart = new Chart(document.getElementById("productcard-product-price-history-chart"), {
- type: "line",
- data: {
- labels: [ //Date objects
- // Will be populated in Grocy.Components.ProductCard.Refresh
- ],
- datasets: [ //Datasets
- // Will be populated in Grocy.Components.ProductCard.Refresh
- ]
- },
- options: {
- scales: {
- xAxes: [{
- type: 'time',
- time: {
- parser: format,
- round: 'day',
- tooltipFormat: format,
- unit: 'day',
- unitStepSize: 10,
- displayFormats: {
- 'day': format
- }
- },
- ticks: {
- autoSkip: true,
- maxRotation: 0
- }
- }],
- yAxes: [{
- ticks: {
- beginAtZero: true
- }
- }]
- },
- legend: {
- display: true
- }
- }
- });
-}
-
-$("#productcard-product-description").on("shown.bs.collapse", function()
-{
- $(".expandable-text").find("a[data-toggle='collapse']").text(__t("Show less"));
-})
-
-$("#productcard-product-description").on("hidden.bs.collapse", function()
-{
- $(".expandable-text").find("a[data-toggle='collapse']").text(__t("Show more"));
-})
diff --git a/js/viewjs/components/productpicker.js b/js/viewjs/components/productpicker.js
deleted file mode 100644
index 6081e392..00000000
--- a/js/viewjs/components/productpicker.js
+++ /dev/null
@@ -1,331 +0,0 @@
-Grocy.Components.ProductPicker = {};
-
-Grocy.Components.ProductPicker.GetPicker = function()
-{
- return $('#product_id');
-}
-
-Grocy.Components.ProductPicker.GetInputElement = function()
-{
- return $('#product_id_text_input');
-}
-
-Grocy.Components.ProductPicker.GetValue = function()
-{
- return $('#product_id').val();
-}
-
-Grocy.Components.ProductPicker.SetValue = function(value)
-{
- Grocy.Components.ProductPicker.GetInputElement().val(value);
- Grocy.Components.ProductPicker.GetInputElement().trigger('change');
-}
-
-Grocy.Components.ProductPicker.SetId = function(value)
-{
- Grocy.Components.ProductPicker.GetPicker().val(value);
- Grocy.Components.ProductPicker.GetPicker().data('combobox').refresh();
- Grocy.Components.ProductPicker.GetInputElement().trigger('change');
-}
-
-Grocy.Components.ProductPicker.Clear = function()
-{
- Grocy.Components.ProductPicker.SetValue('');
- Grocy.Components.ProductPicker.SetId(null);
-}
-
-Grocy.Components.ProductPicker.InProductAddWorkflow = function()
-{
- return GetUriParam('flow') == "InplaceNewProductWithName";
-}
-
-Grocy.Components.ProductPicker.InProductModifyWorkflow = function()
-{
- return GetUriParam('flow') == "InplaceAddBarcodeToExistingProduct";
-}
-
-Grocy.Components.ProductPicker.InAnyFlow = function()
-{
- return Grocy.Components.ProductPicker.InProductAddWorkflow() || Grocy.Components.ProductPicker.InProductModifyWorkflow();
-}
-
-Grocy.Components.ProductPicker.FinishFlow = function()
-{
- RemoveUriParam("flow");
- RemoveUriParam("barcode");
- RemoveUriParam("product-name");
-}
-
-Grocy.Components.ProductPicker.ShowCustomError = function(text)
-{
- var element = $("#custom-productpicker-error");
- element.text(text);
- element.removeClass("d-none");
-}
-
-Grocy.Components.ProductPicker.HideCustomError = function()
-{
- $("#custom-productpicker-error").addClass("d-none");
-}
-
-Grocy.Components.ProductPicker.Disable = function()
-{
- Grocy.Components.ProductPicker.GetInputElement().attr("disabled", "");
- $("#barcodescanner-start-button").attr("disabled", "");
- $("#barcodescanner-start-button").addClass("disabled");
-}
-
-Grocy.Components.ProductPicker.Enable = function()
-{
- Grocy.Components.ProductPicker.GetInputElement().removeAttr("disabled");
- $("#barcodescanner-start-button").removeAttr("disabled");
- $("#barcodescanner-start-button").removeClass("disabled");
-}
-
-$('.product-combobox').combobox({
- appendId: '_text_input',
- bsVersion: '4',
- clearIfNoMatch: false
-});
-
-var this_product_picker = Grocy.Components.ProductPicker.GetPicker();
-var productpicker_doFocus = false;
-
-var prefillProduct = GetUriParam('product-name');
-var prefillProduct2 = this_product_picker.parent().data('prefill-by-name').toString();
-if (!prefillProduct2.isEmpty())
-{
- prefillProduct = prefillProduct2;
-}
-if (typeof prefillProduct !== "undefined")
-{
- var possibleOptionElement = $("#product_id option[data-additional-searchdata*=\"" + prefillProduct + "\"]").first();
- if (possibleOptionElement.length === 0)
- {
- possibleOptionElement = $("#product_id option:contains(\"" + prefillProduct + "\")").first();
- }
-
- if (possibleOptionElement.length > 0)
- {
- productpicker_doFocus = true;
- this_product_picker.val(possibleOptionElement.val());
- }
-}
-
-var prefillProductId = GetUriParam("product");
-var prefillProductId2 = this_product_picker.parent().data('prefill-by-id').toString();
-if (!prefillProductId2.isEmpty())
-{
- prefillProductId = prefillProductId2;
-}
-if (typeof prefillProductId !== "undefined")
-{
- this_product_picker.val(prefillProductId);
- productpicker_doFocus = true;
-}
-
-if (productpicker_doFocus)
-{
- this_product_picker.data('combobox').refresh();
- this_product_picker.trigger('change');
-
- $(this_product_picker.parent().data('next-input-selector').toString())
- .focus();
-}
-
-if (GetUriParam("flow") === "InplaceAddBarcodeToExistingProduct")
-{
- $('#InplaceAddBarcodeToExistingProduct').text(GetUriParam("barcode"));
- $('#flow-info-InplaceAddBarcodeToExistingProduct').removeClass('d-none');
- $('#barcode-lookup-disabled-hint').removeClass('d-none');
- $('#barcode-lookup-hint').addClass('d-none');
-}
-
-Grocy.Components.ProductPicker.PopupOpen = false;
-$('#product_id_text_input').on('blur', function(e)
-{
- if (Grocy.Components.ProductPicker.GetPicker().hasClass("combobox-menu-visible"))
- {
- return;
- }
- $('#product_id').attr("barcode", "null");
-
- var input = $('#product_id_text_input').val().toString();
- var possibleOptionElement = [];
-
- // did we enter a grocycode?
- if (input.startsWith("grcy"))
- {
- var gc = input.split(":");
- if (gc[1] == "p")
- {
- // find product id
- possibleOptionElement = $("#product_id option[value=\"" + gc[2] + "\"]").first();
- $("#product_id").data("grocycode", true);
- }
- }
- else // process barcode as usual
- {
- possibleOptionElement = $("#product_id option[data-additional-searchdata*=\"" + input + ",\"]").first();
- }
-
- if (GetUriParam('flow') === undefined && input.length > 0 && possibleOptionElement.length > 0)
- {
- $('#product_id').val(possibleOptionElement.val());
- $('#product_id').attr("barcode", input);
- $('#product_id').data('combobox').refresh();
- $('#product_id').trigger('change');
- }
- else
- {
- if (Grocy.Components.ProductPicker.PopupOpen === true)
- {
- return;
- }
-
- var optionElement = $("#product_id option:contains(\"" + input + "\")").first();
- if (input.length > 0 && optionElement.length === 0 && GetUriParam('flow') === undefined && Grocy.Components.ProductPicker.GetPicker().parent().data('disallow-all-product-workflows').toString() === "false")
- {
- var addProductWorkflowsAdditionalCssClasses = "";
- if (Grocy.Components.ProductPicker.GetPicker().parent().data('disallow-add-product-workflows').toString() === "true")
- {
- addProductWorkflowsAdditionalCssClasses = "d-none";
- }
-
- var embedded = "";
- if (GetUriParam("embedded") !== undefined)
- {
- embedded = "embedded";
- }
-
- var buttons = {
- cancel: {
- label: __t('Cancel'),
- className: 'btn-secondary responsive-button',
- callback: function()
- {
- Grocy.Components.ProductPicker.PopupOpen = false;
- Grocy.Components.ProductPicker.SetValue('');
- }
- },
- addnewproduct: {
- label: 'P ' + __t('Add as new product'),
- className: 'btn-success add-new-product-dialog-button responsive-button ' + addProductWorkflowsAdditionalCssClasses,
- callback: function()
- {
-
- Grocy.Components.ProductPicker.PopupOpen = false;
- window.location.href = U('/product/new?flow=InplaceNewProductWithName&name=' + encodeURIComponent(input) + '&returnto=' + encodeURIComponent(Grocy.CurrentUrlRelative + "?flow=InplaceNewProductWithName&" + embedded) + "&" + embedded);
- }
- },
- addbarcode: {
- label: 'B ' + __t('Add as barcode to existing product'),
- className: 'btn-info add-new-barcode-dialog-button responsive-button',
- callback: function()
- {
- Grocy.Components.ProductPicker.PopupOpen = false;
- window.location.href = U(Grocy.CurrentUrlRelative + '?flow=InplaceAddBarcodeToExistingProduct&barcode=' + encodeURIComponent(input));
- }
- },
- addnewproductwithbarcode: {
- label: 'A ' + __t('Add as new product and prefill barcode'),
- className: 'btn-warning add-new-product-with-barcode-dialog-button responsive-button ' + addProductWorkflowsAdditionalCssClasses,
- callback: function()
- {
- Grocy.Components.ProductPicker.PopupOpen = false;
- window.location.href = U('/product/new?flow=InplaceNewProductWithBarcode&barcode=' + encodeURIComponent(input) + '&returnto=' + encodeURIComponent(Grocy.CurrentUrlRelative + "?flow=InplaceAddBarcodeToExistingProduct&barcode=" + input + "&" + embedded) + "&" + embedded);
- }
- }
- };
-
- if (!Grocy.FeatureFlags.DISABLE_BROWSER_BARCODE_CAMERA_SCANNING)
- {
- buttons.retrycamerascanning = {
- label: 'C ',
- className: 'btn-primary responsive-button retry-camera-scanning-button',
- callback: function()
- {
- Grocy.Components.ProductPicker.PopupOpen = false;
- Grocy.Components.ProductPicker.SetValue('');
- $("#barcodescanner-start-button").click();
- }
- };
- }
-
- Grocy.Components.ProductPicker.PopupOpen = true;
- bootbox.dialog({
- message: __t('"%s" could not be resolved to a product, how do you want to proceed?', input),
- title: __t('Create or assign product'),
- onEscape: function()
- {
- Grocy.Components.ProductPicker.PopupOpen = false;
- Grocy.Components.ProductPicker.SetValue('');
- },
- size: 'large',
- backdrop: true,
- closeButton: false,
- buttons: buttons
- }).on('keypress', function(e)
- {
- if (e.key === 'B' || e.key === 'b')
- {
- $('.add-new-barcode-dialog-button').not(".d-none").click();
- }
- if (e.key === 'p' || e.key === 'P')
- {
- $('.add-new-product-dialog-button').not(".d-none").click();
- }
- if (e.key === 'a' || e.key === 'A')
- {
- $('.add-new-product-with-barcode-dialog-button').not(".d-none").click();
- }
- if (e.key === 'c' || e.key === 'C')
- {
- $('.retry-camera-scanning-button').not(".d-none").click();
- }
- });
- }
- }
-});
-
-$(document).on("Grocy.BarcodeScanned", function(e, barcode, target)
-{
- if (!(target == "@productpicker" || target == "undefined" || target == undefined)) // Default target
- {
- return;
- }
-
- // Don't know why the blur event does not fire immediately ... this works...
- Grocy.Components.ProductPicker.GetInputElement().focusout();
- Grocy.Components.ProductPicker.GetInputElement().focus();
- Grocy.Components.ProductPicker.GetInputElement().blur();
-
- Grocy.Components.ProductPicker.GetInputElement().val(barcode);
-
- setTimeout(function()
- {
- Grocy.Components.ProductPicker.GetInputElement().focusout();
- Grocy.Components.ProductPicker.GetInputElement().focus();
- Grocy.Components.ProductPicker.GetInputElement().blur();
- }, 200);
-});
-
-$(document).on("shown.bs.modal", function(e)
-{
- $(".modal-footer").addClass("d-block").addClass("d-sm-flex");
- $(".modal-footer").find("button").addClass("mt-2").addClass("mt-sm-0");
-})
-
-// Make that ENTER behaves the same like TAB (trigger blur to start workflows, but only when the dropdown is not opened)
-$('#product_id_text_input').keydown(function(event)
-{
- if (event.keyCode === 13) // Enter
- {
- if (Grocy.Components.ProductPicker.GetPicker().hasClass("combobox-menu-visible"))
- {
- return;
- }
-
- $("#product_id_text_input").trigger("blur");
- }
-});
diff --git a/js/viewjs/components/recipepicker.js b/js/viewjs/components/recipepicker.js
deleted file mode 100644
index 3e398eca..00000000
--- a/js/viewjs/components/recipepicker.js
+++ /dev/null
@@ -1,71 +0,0 @@
-Grocy.Components.RecipePicker = {};
-
-Grocy.Components.RecipePicker.GetPicker = function()
-{
- return $('#recipe_id');
-}
-
-Grocy.Components.RecipePicker.GetInputElement = function()
-{
- return $('#recipe_id_text_input');
-}
-
-Grocy.Components.RecipePicker.GetValue = function()
-{
- return $('#recipe_id').val();
-}
-
-Grocy.Components.RecipePicker.SetValue = function(value)
-{
- Grocy.Components.RecipePicker.GetInputElement().val(value);
- Grocy.Components.RecipePicker.GetInputElement().trigger('change');
-}
-
-Grocy.Components.RecipePicker.SetId = function(value)
-{
- Grocy.Components.RecipePicker.GetPicker().val(value);
- Grocy.Components.RecipePicker.GetPicker().data('combobox').refresh();
- Grocy.Components.RecipePicker.GetInputElement().trigger('change');
-}
-
-Grocy.Components.RecipePicker.Clear = function()
-{
- Grocy.Components.RecipePicker.SetValue('');
- Grocy.Components.RecipePicker.SetId(null);
-}
-
-$('.recipe-combobox').combobox({
- appendId: '_text_input',
- bsVersion: '4',
- clearIfNoMatch: true
-});
-
-var this_recipe_picker = Grocy.Components.RecipePicker.GetPicker();
-var recipe_picker_doFocus = false;
-
-var prefillByName = this_recipe_picker.parent().data('prefill-by-name').toString();
-if (typeof prefillByName !== "undefined")
-{
- var possibleOptionElement = $("#recipe_id option:contains(\"" + prefillByName + "\")").first();
-
- if (possibleOptionElement.length > 0)
- {
- recipe_picker_doFocus = true;
- this_recipe_picker.val(possibleOptionElement.val());
- }
-}
-
-var prefillById = this_recipe_picker.parent().data('prefill-by-id').toString();
-if (typeof prefillById !== "undefined")
-{
- recipe_picker_doFocus = true;
- this_recipe_picker.val(prefillById);
-}
-
-if (recipe_picker_doFocus)
-{
- this_recipe_picker.data('combobox').refresh();
- this_recipe_picker.trigger('change');
-
- $(this_recipe_picker.parent().data('next-input-selector').toString()).focus();
-}
\ No newline at end of file
diff --git a/js/viewjs/components/shoppinglocationpicker.js b/js/viewjs/components/shoppinglocationpicker.js
deleted file mode 100644
index 7f1de6d7..00000000
--- a/js/viewjs/components/shoppinglocationpicker.js
+++ /dev/null
@@ -1,72 +0,0 @@
-Grocy.Components.ShoppingLocationPicker = {};
-
-Grocy.Components.ShoppingLocationPicker.GetPicker = function()
-{
- return $('#shopping_location_id');
-}
-
-Grocy.Components.ShoppingLocationPicker.GetInputElement = function()
-{
- return $('#shopping_location_id_text_input');
-}
-
-Grocy.Components.ShoppingLocationPicker.GetValue = function()
-{
- return $('#shopping_location_id').val();
-}
-
-Grocy.Components.ShoppingLocationPicker.SetValue = function(value)
-{
- Grocy.Components.ShoppingLocationPicker.GetInputElement().val(value);
- Grocy.Components.ShoppingLocationPicker.GetInputElement().trigger('change');
-}
-
-Grocy.Components.ShoppingLocationPicker.SetId = function(value)
-{
- Grocy.Components.ShoppingLocationPicker.GetPicker().val(value);
- Grocy.Components.ShoppingLocationPicker.GetPicker().data('combobox').refresh();
- Grocy.Components.ShoppingLocationPicker.GetInputElement().trigger('change');
-}
-
-Grocy.Components.ShoppingLocationPicker.Clear = function()
-{
- Grocy.Components.ShoppingLocationPicker.SetValue('');
- Grocy.Components.ShoppingLocationPicker.SetId(null);
-}
-
-$('.shopping-location-combobox').combobox({
- appendId: '_text_input',
- bsVersion: '4',
- clearIfNoMatch: true
-});
-
-var shoppinglocationpicker_doFocus = false;
-var this_shoppinglocation_picker = $('#shopping_location_id');
-
-var prefillByName = this_shoppinglocation_picker.parent().data('prefill-by-name').toString();
-if (typeof prefillByName !== "undefined")
-{
- var possibleOptionElement = $("#shopping_location_id option:contains(\"" + prefillByName + "\")").first();
-
- if (possibleOptionElement.length > 0)
- {
- this_shoppinglocation_picker.val(possibleOptionElement.val());
- shoppinglocationpicker_doFocus = true;
- }
-}
-
-var prefillById = this_shoppinglocation_picker.parent().data('prefill-by-id').toString();
-if (typeof prefillById !== "undefined")
-{
- this_shoppinglocation_picker.val(prefillById);
- shoppinglocationpicker_doFocus = true;
-}
-
-if (shoppinglocationpicker_doFocus)
-{
- this_shoppinglocation_picker.data('combobox').refresh();
- this_shoppinglocation_picker.trigger('change');
-
- $(this_shoppinglocation_picker.parent().data('next-input-selector').toString())
- .focus();
-}
\ No newline at end of file
diff --git a/js/viewjs/components/userfieldsform.js b/js/viewjs/components/userfieldsform.js
deleted file mode 100644
index 43409bd6..00000000
--- a/js/viewjs/components/userfieldsform.js
+++ /dev/null
@@ -1,183 +0,0 @@
-import { RandomString } from '../../helpers/extensions';
-
-Grocy.Components.UserfieldsForm = {};
-
-Grocy.Components.UserfieldsForm.Save = function(success, error)
-{
- if (!$("#userfields-form").length)
- {
- if (success)
- {
- success();
- }
-
- return;
- }
-
- var jsonData = {};
-
- $("#userfields-form .userfield-input").not("div").each(function()
- {
- var input = $(this);
- var fieldName = input.attr("data-userfield-name");
- var fieldValue = input.val();
-
- if (input.attr("type") == "checkbox")
- {
- jsonData[fieldName] = "0";
- if (input.is(":checked"))
- {
- jsonData[fieldName] = "1";
- }
- }
- else if (input.attr("type") == "file")
- {
- var oldFile = input.data('old-file')
- if (oldFile)
- {
- Grocy.Api.Delete('files/userfiles/' + oldFile, null, null,
- function(xhr)
- {
- Grocy.FrontendHelpers.ShowGenericError('Could not delete file', xhr);
- });
- jsonData[fieldName] = "";
- }
-
- if (input[0].files.length > 0)
- {
- // Files service requires an extension
- var fileName = RandomString() + '.' + input[0].files[0].name.split('.').reverse()[0];
-
- jsonData[fieldName] = btoa(fileName) + '_' + btoa(input[0].files[0].name);
- Grocy.Api.UploadFile(input[0].files[0], 'userfiles', fileName,
- function(result)
- {
- },
- function(xhr)
- {
- // When navigating away immediately from the current page, this is maybe a false positive - so ignore this for now
- // Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
- }
- );
- }
- }
- else if ($(this).hasAttr("multiple"))
- {
- jsonData[fieldName] = $(this).val().join(",");
- }
- else
- {
- jsonData[fieldName] = fieldValue;
- }
- });
-
- Grocy.Api.Put('userfields/' + $("#userfields-form").data("entity") + '/' + Grocy.EditObjectId, jsonData,
- function(result)
- {
- if (success)
- {
- success();
- }
- },
- function(xhr)
- {
- if (error)
- {
- error();
- }
- }
- );
-}
-
-Grocy.Components.UserfieldsForm.Load = function()
-{
- if (!$("#userfields-form").length)
- {
- return;
- }
-
- Grocy.Api.Get('userfields/' + $("#userfields-form").data("entity") + '/' + Grocy.EditObjectId,
- function(result)
- {
- $.each(result, function(key, value)
- {
- var input = $(".userfield-input[data-userfield-name='" + key + "']");
-
- if (input.attr("type") == "checkbox" && value == 1)
- {
- input.prop("checked", true);
- }
- else if (input.hasAttr("multiple"))
- {
- input.val(value.split(","));
- $(".selectpicker").selectpicker("render");
- }
- else if (input.attr('type') == "file")
- {
- if (value != null && !value.isEmpty())
- {
- var fileName = atob(value.split('_')[1]);
- var fileSrc = value.split('_')[0];
- var formGroup = input.parent().parent().parent();
-
- formGroup.find("label.custom-file-label").text(fileName);
- formGroup.find(".userfield-file-show").attr('href', U('/files/userfiles/' + value));
- formGroup.find('.userfield-file-show').removeClass('d-none');
- formGroup.find('img.userfield-current-file')
- .attr('src', U('/files/userfiles/' + value + '?force_serve_as=picture&best_fit_width=250&best_fit_height=250'));
- LoadImagesLazy();
-
- formGroup.find('.userfield-file-delete').click(
- function()
- {
- formGroup.find("label.custom-file-label").text(__t("No file selected"));
- formGroup.find(".userfield-file-show").addClass('d-none');
- input.attr('data-old-file', fileSrc);
- }
- );
-
- input.on("change", function(e)
- {
- formGroup.find(".userfield-file-show").addClass('d-none');
- });
- }
- }
- else if (input.attr("data-userfield-type") == "link")
- {
- if (!value.isEmpty())
- {
- var data = JSON.parse(value);
-
- var formRow = input.parent().parent();
- formRow.find(".userfield-link-title").val(data.title);
- formRow.find(".userfield-link-link").val(data.link);
-
- input.val(value);
- }
- }
- else
- {
- input.val(value);
- }
- });
- },
- function(xhr)
- {
- console.error(xhr);
- }
- );
-}
-
-$(".userfield-link").keyup(function(e)
-{
- var formRow = $(this).parent().parent();
- var title = formRow.find(".userfield-link-title").val();
- var link = formRow.find(".userfield-link-link").val();
-
- var value = {
- "title": title,
- "link": link
- };
-
- formRow.find(".userfield-input").val(JSON.stringify(value));
-});
diff --git a/js/viewjs/components/userpicker.js b/js/viewjs/components/userpicker.js
deleted file mode 100644
index abfc3a50..00000000
--- a/js/viewjs/components/userpicker.js
+++ /dev/null
@@ -1,81 +0,0 @@
-Grocy.Components.UserPicker = {};
-
-Grocy.Components.UserPicker.GetPicker = function()
-{
- return $('#user_id');
-}
-
-Grocy.Components.UserPicker.GetInputElement = function()
-{
- return $('#user_id_text_input');
-}
-
-Grocy.Components.UserPicker.GetValue = function()
-{
- return $('#user_id').val();
-}
-
-Grocy.Components.UserPicker.SetValue = function(value)
-{
- Grocy.Components.UserPicker.GetInputElement().val(value);
- Grocy.Components.UserPicker.GetInputElement().trigger('change');
-}
-
-Grocy.Components.UserPicker.SetId = function(value)
-{
- Grocy.Components.UserPicker.GetPicker().val(value);
- Grocy.Components.UserPicker.GetPicker().data('combobox').refresh();
- Grocy.Components.UserPicker.GetInputElement().trigger('change');
-}
-
-Grocy.Components.UserPicker.Clear = function()
-{
- Grocy.Components.UserPicker.SetValue('');
- Grocy.Components.UserPicker.SetId(null);
-}
-
-$('.user-combobox').combobox({
- appendId: '_text_input',
- bsVersion: '4'
-});
-
-var this_user_picker = Grocy.Components.UserPicker.GetPicker();
-var user_picker_doFocus = false;
-var possibleOptionElement = null;
-
-var prefillUser = this_user_picker.parent().data('prefill-by-username').toString();
-if (typeof prefillUser !== "undefined")
-{
- possibleOptionElement = $("#user_id option[data-additional-searchdata*=\"" + prefillUser + "\"]").first();
- if (possibleOptionElement.length === 0)
- {
- possibleOptionElement = $("#user_id option:contains(\"" + prefillUser + "\")").first();
- }
-
- if (possibleOptionElement.length > 0)
- {
- user_picker_doFocus = true;
- this_user_picker.val(possibleOptionElement.val());
-
- }
-}
-
-var prefillUserId = this_user_picker.parent().data('prefill-by-user-id').toString();
-if (typeof prefillUserId !== "undefined")
-{
- possibleOptionElement = $("#user_id option[value='" + prefillUserId + "']").first();
- if (possibleOptionElement.length > 0)
- {
- user_picker_doFocus = true;
- this_user_picker.val(possibleOptionElement.val());
- }
-}
-
-if (user_picker_doFocus)
-{
- this_user_picker.data('combobox').refresh();
- this_user_picker.trigger('change');
-
- $(this_user_picker.parent().data('next-input-selector').toString())
- .focus();
-}
\ No newline at end of file
diff --git a/js/viewjs/consume.js b/js/viewjs/consume.js
index 8069c18c..28af6a55 100644
--- a/js/viewjs/consume.js
+++ b/js/viewjs/consume.js
@@ -1,6 +1,11 @@
import { BoolVal } from '../helpers/extensions';
import { WindowMessageBag } from '../helpers/messagebag';
+Grocy.Use("productamountpicker");
+Grocy.Use("productcard");
+Grocy.Use("productpicker");
+Grocy.Use("recipepicker");
+
$('#save-consume-button').on('click', function(e)
{
e.preventDefault();
diff --git a/js/viewjs/equipmentform.js b/js/viewjs/equipmentform.js
index f442d666..31dfbd81 100644
--- a/js/viewjs/equipmentform.js
+++ b/js/viewjs/equipmentform.js
@@ -1,6 +1,8 @@
import { RandomString } from '../helpers/extensions';
import { ResizeResponsiveEmbeds } from '../helpers/embeds';
+Grocy.Use("userfieldsform");
+
$('#save-equipment-button').on('click', function(e)
{
e.preventDefault();
diff --git a/js/viewjs/inventory.js b/js/viewjs/inventory.js
index 7eee6e47..09757fd7 100644
--- a/js/viewjs/inventory.js
+++ b/js/viewjs/inventory.js
@@ -1,5 +1,17 @@
import { WindowMessageBag } from '../helpers/messagebag';
+Grocy.Use("datetimepicker");
+if (Grocy.UserSettings.show_purchased_date_on_purchase)
+{
+ Grocy.Use("datetimepicker2");
+}
+Grocy.Use("locationpicker");
+Grocy.Use("numberpicker");
+Grocy.Use("productpicker");
+Grocy.Use("productamountpicker");
+Grocy.Use("productcard");
+Grocy.Use("shoppinglocationpicker");
+
$('#save-inventory-button').on('click', function(e)
{
e.preventDefault();
diff --git a/js/viewjs/locationform.js b/js/viewjs/locationform.js
index 55d819ed..2b626bc3 100644
--- a/js/viewjs/locationform.js
+++ b/js/viewjs/locationform.js
@@ -1,5 +1,7 @@
import { WindowMessageBag } from '../helpers/messagebag';
+Grocy.Use("userfieldsform");
+
$('#save-location-button').on('click', function(e)
{
e.preventDefault();
diff --git a/js/viewjs/mealplan.js b/js/viewjs/mealplan.js
index aee7b85d..c5790bc2 100644
--- a/js/viewjs/mealplan.js
+++ b/js/viewjs/mealplan.js
@@ -10,6 +10,10 @@ import '@fullcalendar/core/main.css';
import '@fullcalendar/daygrid/main.css';
import '@fullcalendar/bootstrap/main.css';
+Grocy.Use("numberpicker");
+Grocy.Use("productamountpicker");
+Grocy.Use("recipepicker");
+
var setLocale = false;
if (__t('fullcalendar_locale').replace(" ", "") !== "" && __t('fullcalendar_locale') != 'x')
{
diff --git a/js/viewjs/productbarcodeform.js b/js/viewjs/productbarcodeform.js
index 1fbcb6f6..b9bd75c0 100644
--- a/js/viewjs/productbarcodeform.js
+++ b/js/viewjs/productbarcodeform.js
@@ -1,5 +1,9 @@
import { WindowMessageBag } from '../helpers/messagebag';
+Grocy.Use('barcodescanner');
+Grocy.Use("productamountpicker");
+Grocy.Use("userfieldsform");
+
$('#save-barcode-button').on('click', function(e)
{
e.preventDefault();
diff --git a/js/viewjs/productform.js b/js/viewjs/productform.js
index 480416dc..bc47bdb7 100644
--- a/js/viewjs/productform.js
+++ b/js/viewjs/productform.js
@@ -1,5 +1,9 @@
import { BoolVal } from '../helpers/extensions';
+Grocy.Use("numberpicker");
+Grocy.Use("shoppinglocationpicker");
+Grocy.Use("userfieldsform");
+
function saveProductPicture(result, location, jsonData)
{
var productId = Grocy.EditObjectId || result.created_object_id;
diff --git a/js/viewjs/productgroupform.js b/js/viewjs/productgroupform.js
index 1c8acad5..8496e6c2 100644
--- a/js/viewjs/productgroupform.js
+++ b/js/viewjs/productgroupform.js
@@ -1,5 +1,7 @@
import { WindowMessageBag } from '../helpers/messagebag';
+Grocy.Use("userfieldsform");
+
$('#save-product-group-button').on('click', function(e)
{
e.preventDefault();
diff --git a/js/viewjs/purchase.js b/js/viewjs/purchase.js
index e5002052..f7c3427c 100644
--- a/js/viewjs/purchase.js
+++ b/js/viewjs/purchase.js
@@ -1,6 +1,17 @@
import { BoolVal } from '../helpers/extensions';
import { WindowMessageBag } from '../helpers/messagebag';
+Grocy.Use("datetimepicker");
+if (Grocy.UserSettings.show_purchased_date_on_purchase)
+{
+ Grocy.Use("datetimepicker2");
+}
+Grocy.Use("locationpicker");
+Grocy.Use("numberpicker");
+Grocy.Use("productamountpicker");
+Grocy.Use("productcard");
+Grocy.Use("shoppinglocationpicker");
+
var CurrentProductDetails;
$('#save-purchase-button').on('click', function(e)
diff --git a/js/viewjs/quantityunitconversionform.js b/js/viewjs/quantityunitconversionform.js
index f64d9665..4b3d26bf 100644
--- a/js/viewjs/quantityunitconversionform.js
+++ b/js/viewjs/quantityunitconversionform.js
@@ -1,5 +1,8 @@
import { WindowMessageBag } from '../helpers/messagebag';
+Grocy.Use("numberpicker");
+Grocy.Use("userfieldsform");
+
$('#save-quconversion-button').on('click', function(e)
{
e.preventDefault();
diff --git a/js/viewjs/quantityunitform.js b/js/viewjs/quantityunitform.js
index 583020eb..f752ceda 100644
--- a/js/viewjs/quantityunitform.js
+++ b/js/viewjs/quantityunitform.js
@@ -1,5 +1,7 @@
import { WindowMessageBag } from '../helpers/messagebag';
+Grocy.Use("userfieldsform");
+
$('.save-quantityunit-button').on('click', function(e)
{
e.preventDefault();
diff --git a/js/viewjs/quantityunitpluraltesting.js b/js/viewjs/quantityunitpluraltesting.js
index 1e4169af..742aa960 100644
--- a/js/viewjs/quantityunitpluraltesting.js
+++ b/js/viewjs/quantityunitpluraltesting.js
@@ -1,4 +1,6 @@
-$("#qu_id").change(function(event)
+Grocy.Use("numberpicker");
+
+$("#qu_id").change(function(event)
{
RefreshQuPluralTestingResult();
});
diff --git a/js/viewjs/recipeform.js b/js/viewjs/recipeform.js
index b38674f9..cffa1f54 100644
--- a/js/viewjs/recipeform.js
+++ b/js/viewjs/recipeform.js
@@ -1,5 +1,9 @@
import { WindowMessageBag } from '../helpers/messagebag';
+Grocy.Use("numberpicker");
+Grocy.Use("recipepicker");
+Grocy.Use("userfieldsform");
+
function saveRecipePicture(result, location, jsonData)
{
var recipeId = Grocy.EditObjectId || result.created_object_id;
diff --git a/js/viewjs/recipeposform.js b/js/viewjs/recipeposform.js
index cb94b2e1..67edb759 100644
--- a/js/viewjs/recipeposform.js
+++ b/js/viewjs/recipeposform.js
@@ -1,4 +1,9 @@
import { WindowMessageBag } from '../helpers/messagebag';
+
+Grocy.Use("numberpicker");
+Grocy.Use("productamountpicker");
+Grocy.Use("productcard");
+
Grocy.RecipePosFormInitialLoadDone = false;
$('#save-recipe-pos-button').on('click', function(e)
diff --git a/js/viewjs/recipes.js b/js/viewjs/recipes.js
index 5d7a8a22..0abd9c66 100644
--- a/js/viewjs/recipes.js
+++ b/js/viewjs/recipes.js
@@ -1,4 +1,6 @@
-var recipesTables = $('#recipes-table').DataTable({
+Grocy.Use("numberpicker");
+
+var recipesTables = $('#recipes-table').DataTable({
'order': [[1, 'asc']],
'columnDefs': [
{ 'orderable': false, 'targets': 0 },
diff --git a/js/viewjs/shoppinglist.js b/js/viewjs/shoppinglist.js
index b24ee4ac..43251b5b 100644
--- a/js/viewjs/shoppinglist.js
+++ b/js/viewjs/shoppinglist.js
@@ -3,6 +3,9 @@
import bwipjs from '../../node_modules/bwip-js/dist/bwip-js.mjs';
import { WindowMessageBag } from '../helpers/messagebag';
+Grocy.Use("calendarcard");
+Grocy.Use("productcard");
+
var shoppingListTable = $('#shoppinglist-table').DataTable({
'order': [[1, 'asc']],
"orderFixed": [[3, 'asc']],
diff --git a/js/viewjs/shoppinglistform.js b/js/viewjs/shoppinglistform.js
index eeafdb9e..36b00a33 100644
--- a/js/viewjs/shoppinglistform.js
+++ b/js/viewjs/shoppinglistform.js
@@ -1,5 +1,7 @@
import { WindowMessageBag } from '../helpers/messagebag';
+Grocy.Use("userfieldsform");
+
$('#save-shopping-list-button').on('click', function(e)
{
e.preventDefault();
diff --git a/js/viewjs/shoppinglistitemform.js b/js/viewjs/shoppinglistitemform.js
index 74e48892..113e3d23 100644
--- a/js/viewjs/shoppinglistitemform.js
+++ b/js/viewjs/shoppinglistitemform.js
@@ -1,5 +1,8 @@
import { WindowMessageBag } from '../helpers/messagebag';
+Grocy.Use("productamountpicker");
+Grocy.Use("userfieldsform");
+
Grocy.ShoppingListItemFormInitialLoadDone = false;
$('#save-shoppinglist-button').on('click', function(e)
diff --git a/js/viewjs/shoppinglocationform.js b/js/viewjs/shoppinglocationform.js
index 0a0d9364..fc72bc6e 100644
--- a/js/viewjs/shoppinglocationform.js
+++ b/js/viewjs/shoppinglocationform.js
@@ -1,5 +1,7 @@
import { WindowMessageBag } from '../helpers/messagebag';
+Grocy.Use("userfieldsform");
+
$('#save-shopping-location-button').on('click', function(e)
{
e.preventDefault();
diff --git a/js/viewjs/stockentries.js b/js/viewjs/stockentries.js
index bcc3973a..41075476 100644
--- a/js/viewjs/stockentries.js
+++ b/js/viewjs/stockentries.js
@@ -1,4 +1,7 @@
-var stockEntriesTable = $('#stockentries-table').DataTable({
+Grocy.Use("productcard");
+Grocy.Use("productpicker");
+
+var stockEntriesTable = $('#stockentries-table').DataTable({
'order': [[2, 'asc']],
'columnDefs': [
{ 'orderable': false, 'targets': 0 },
diff --git a/js/viewjs/stockentryform.js b/js/viewjs/stockentryform.js
index cfd1d79c..160cb7cf 100644
--- a/js/viewjs/stockentryform.js
+++ b/js/viewjs/stockentryform.js
@@ -1,5 +1,11 @@
import { WindowMessageBag } from '../helpers/messagebag';
+Grocy.Use("datetimepicker");
+Grocy.Use("datetimepicker2");
+Grocy.Use("locationpicker");
+Grocy.Use("numberpicker");
+Grocy.Use("shoppinglocationpicker");
+
$('#save-stockentry-button').on('click', function(e)
{
e.preventDefault();
diff --git a/js/viewjs/stockoverview.js b/js/viewjs/stockoverview.js
index 5fa17c02..857afef8 100755
--- a/js/viewjs/stockoverview.js
+++ b/js/viewjs/stockoverview.js
@@ -1,4 +1,6 @@
-var stockOverviewTable = $('#stock-overview-table').DataTable({
+Grocy.Use("productcard");
+
+var stockOverviewTable = $('#stock-overview-table').DataTable({
'order': [[5, 'asc']],
'columnDefs': [
{ 'orderable': false, 'targets': 0 },
diff --git a/js/viewjs/stocksettings.js b/js/viewjs/stocksettings.js
index 5b0a321f..bfc9e18a 100644
--- a/js/viewjs/stocksettings.js
+++ b/js/viewjs/stocksettings.js
@@ -1,5 +1,7 @@
import { BoolVal } from '../helpers/extensions';
+Grocy.Use("numberpicker");
+
$("#product_presets_location_id").val(Grocy.UserSettings.product_presets_location_id);
$("#product_presets_product_group_id").val(Grocy.UserSettings.product_presets_product_group_id);
$("#product_presets_qu_id").val(Grocy.UserSettings.product_presets_qu_id);
diff --git a/js/viewjs/taskform.js b/js/viewjs/taskform.js
index 734969a4..baccc9c9 100644
--- a/js/viewjs/taskform.js
+++ b/js/viewjs/taskform.js
@@ -1,5 +1,8 @@
import { WindowMessageBag } from '../helpers/messagebag';
+Grocy.Use("datetimepicker");
+Grocy.Use("userfieldsform");
+
$('#save-task-button').on('click', function(e)
{
e.preventDefault();
diff --git a/js/viewjs/tasks.js b/js/viewjs/tasks.js
index 85c27dcf..b37d78a5 100644
--- a/js/viewjs/tasks.js
+++ b/js/viewjs/tasks.js
@@ -1,4 +1,6 @@
-var tasksTable = $('#tasks-table').DataTable({
+Grocy.Use("userpicker");
+
+var tasksTable = $('#tasks-table').DataTable({
'order': [[2, 'asc']],
'columnDefs': [
{ 'orderable': false, 'targets': 0 },
diff --git a/js/viewjs/taskssettings.js b/js/viewjs/taskssettings.js
index c9cf81d3..2498ce6a 100644
--- a/js/viewjs/taskssettings.js
+++ b/js/viewjs/taskssettings.js
@@ -1,3 +1,5 @@
-$("#tasks_due_soon_days").val(Grocy.UserSettings.tasks_due_soon_days);
+Grocy.Use("numberpicker");
+
+$("#tasks_due_soon_days").val(Grocy.UserSettings.tasks_due_soon_days);
RefreshLocaleNumberInput();
diff --git a/js/viewjs/transfer.js b/js/viewjs/transfer.js
index 94743352..dfd265c1 100644
--- a/js/viewjs/transfer.js
+++ b/js/viewjs/transfer.js
@@ -1,5 +1,9 @@
import { WindowMessageBag } from '../helpers/messagebag';
+Grocy.Use("productpicker");
+Grocy.Use("productamountpicker");
+Grocy.Use("productcard");
+
$('#save-transfer-button').on('click', function(e)
{
e.preventDefault();
diff --git a/js/viewjs/userfieldform.js b/js/viewjs/userfieldform.js
index f834bb41..18776554 100644
--- a/js/viewjs/userfieldform.js
+++ b/js/viewjs/userfieldform.js
@@ -1,5 +1,7 @@
import { WindowMessageBag } from '../helpers/messagebag';
+Grocy.Use("numberpicker");
+
$('#save-userfield-button').on('click', function(e)
{
e.preventDefault();
diff --git a/js/viewjs/userform.js b/js/viewjs/userform.js
index f8da5e7e..9b244b28 100644
--- a/js/viewjs/userform.js
+++ b/js/viewjs/userform.js
@@ -1,4 +1,6 @@
-function SaveUserPicture(result, jsonData)
+Grocy.Use("userfieldsform");
+
+function SaveUserPicture(result, jsonData)
{
Grocy.Components.UserfieldsForm.Save(() =>
{
diff --git a/js/viewjs/userobjectform.js b/js/viewjs/userobjectform.js
index 053c8b3f..0cb1ad09 100644
--- a/js/viewjs/userobjectform.js
+++ b/js/viewjs/userobjectform.js
@@ -1,5 +1,7 @@
import { WindowMessageBag } from '../helpers/messagebag';
+Grocy.Use("userfieldsform");
+
$('#save-userobject-button').on('click', function(e)
{
e.preventDefault();
diff --git a/views/components/barcodescanner.blade.php b/views/components/barcodescanner.blade.php
index f34bf0fe..0d34b69e 100644
--- a/views/components/barcodescanner.blade.php
+++ b/views/components/barcodescanner.blade.php
@@ -1,13 +1,5 @@
@if (!GROCY_DISABLE_BROWSER_BARCODE_CAMERA_SCANNING)
-@push('componentScripts')
-
-@endpush
-
-@push('pageScripts')
-
-@endpush
-
@push('pageStyles')