scoping: refactor components, GrocyProxy.

This commit is contained in:
Katharina Bogad 2021-06-23 19:50:13 +02:00
parent 843b3f28af
commit e744aa8dc7
21 changed files with 1189 additions and 1394 deletions

View File

@ -0,0 +1,99 @@
class BasePicker
{
constructor(Grocy, basename, scopeSelector = null)
{
this.Grocy = Grocy;
this.scopeSelector = scopeSelector;
this.scope = scopeSelector != null ? $(scope) : $(document);
this.$ = scopeSelector != null ? $(scope).find : $;
this.picker = null;
this.input_element;
this.basename = basename;
}
prefill()
{
if (this.picker == null)
{
console.error("Cannot prefill " + this.basename + ", picker not set.");
return;
}
var doFokus = false;
var prefillByName = this.picker.parent().data('prefill-by-name').toString();
if (typeof prefillByName !== "undefined")
{
var possibleOptionElement = $(this.basename + " option:contains(\"" + prefillByName + "\")").first();
if (possibleOptionElement.length > 0)
{
doFokus = true;
this.picker.val(possibleOptionElement.val());
}
}
var prefillById = this.picker.parent().data('prefill-by-id').toString();
if (typeof prefillById !== "undefined")
{
doFokus = true;
this.picker.val(prefillById);
}
if (doFokus)
{
this.picker.data('combobox').refresh();
this.picker.trigger('change');
this.$(this.picker.parent().data('next-input-selector').toString()).focus();
}
}
initCombobox(selector)
{
this.$(selector).combobox({
appendId: '_text_input',
bsVersion: '4',
clearIfNoMatch: false
});
}
GetPicker()
{
return this.picker;
}
GetInputElement()
{
return this.input_element;
}
GetValue()
{
return this.picker.val();
}
SetValue(value)
{
this.input_element.val(value);
this.input_element.trigger('change');
}
SetId(value)
{
this.picker.val(value);
this.picker.data('combobox').refresh();
this.input_element.trigger('change');
}
Clear()
{
this.SetValue('');
this.SetId(null);
}
}
export default BasePicker;

View File

@ -1,11 +1,159 @@
import Quagga from '@ericblade/quagga2/dist/quagga'; import Quagga from '@ericblade/quagga2/dist/quagga';
function barcodescanner(Grocy) class barcodescanner
{ {
Grocy.Components.BarcodeScanner = {}; constructor(Grocy, scopeSelector = null)
{
this.Grocy = Grocy;
Grocy.Components.BarcodeScanner.LiveVideoSizeAdjusted = false; this.scopeSelector = scopeSelector;
Grocy.Components.BarcodeScanner.CheckCapabilities = async function() this.scope = scopeSelector != null ? $(scope) : $(document);
this.$ = scopeSelector != null ? $(scope).find : $;
// init component
this.LiveVideoSizeAdjusted = false;
var self = this;
Quagga.onDetected(function(result)
{
$.each(result.codeResult.decodedCodes, function(id, error)
{
if (error.error != undefined)
{
self.DecodedCodesCount++;
self.DecodedCodesErrorCount += parseFloat(error.error);
}
});
if (self.DecodedCodesErrorCount / self.DecodedCodesCount < 0.15)
{
self.StopScanning();
$(document).trigger("Grocy.BarcodeScanned", [result.codeResult.code, self.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 });
}
}
});
this.scope.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;
}
self.CurrentTarget = inputElement.attr("data-target");
var dialog = bootbox.dialog({
message: '<div id="barcodescanner-container" class="col"><div id="barcodescanner-livestream"></div><div id="debug"></div></div>',
title: Grocy.translate('Scan a barcode'),
onEscape: function()
{
self.StopScanning();
},
size: 'big',
backdrop: true,
closeButton: true,
buttons: {
torch: {
label: '<i class="far fa-lightbulb"></i>',
className: 'btn-warning responsive-button torch',
callback: function()
{
self.TorchOn(Quagga.CameraAccess.getActiveTrack());
return false;
}
},
cancel: {
label: Grocy.translate('Cancel'),
className: 'btn-secondary responsive-button',
callback: function()
{
self.StopScanning();
}
}
}
});
// Add camera select to existing dialog
dialog.find('.bootbox-body').append('<div class="form-group py-0 my-1 cameraSelect-wrapper"><select class="custom-control custom-select cameraSelect"><select class="custom-control custom-select cameraSelect" style="display: none"></select></div>');
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();
self.StartScanning();
};
}
self.StartScanning();
});
setTimeout(function()
{
this.$scope(".barcodescanner-input:visible").each(function()
{
if ($(this).hasAttr("disabled"))
{
$(this).after('<a id="barcodescanner-start-button" class="btn btn-sm btn-primary text-white disabled" data-target="' + $(this).attr("data-target") + '"><i class="fas fa-camera"></i></a>');
}
else
{
$(this).after('<a id="barcodescanner-start-button" class="btn btn-sm btn-primary text-white" data-target="' + $(this).attr("data-target") + '"><i class="fas fa-camera"></i></a>');
}
});
}, 50);
}
async CheckCapabilities()
{ {
var track = Quagga.CameraAccess.getActiveTrack(); var track = Quagga.CameraAccess.getActiveTrack();
var capabilities = {}; var capabilities = {};
@ -16,61 +164,64 @@ function barcodescanner(Grocy)
// If there is more than 1 camera, show the camera selection // If there is more than 1 camera, show the camera selection
var cameras = await Quagga.CameraAccess.enumerateVideoDevices(); var cameras = await Quagga.CameraAccess.enumerateVideoDevices();
var cameraSelect = document.querySelector('.cameraSelect-wrapper'); var cameraSelect = this.$('.cameraSelect-wrapper');
if (cameraSelect) if (cameraSelect.length)
{ {
cameraSelect.style.display = cameras.length > 1 ? 'inline-block' : 'none'; cameraSelect[0].style.display = cameras.length > 1 ? 'inline-block' : 'none';
} }
// Check if the camera is capable to turn on a torch. // Check if the camera is capable to turn on a torch.
var canTorch = typeof capabilities.torch === 'boolean' && capabilities.torch var canTorch = typeof capabilities.torch === 'boolean' && capabilities.torch
// Remove the torch button, if either the device can not torch or AutoTorchOn is set. // Remove the torch button, if either the device can not torch or AutoTorchOn is set.
var node = document.querySelector('.torch'); var node = this.$('.torch');
if (node) if (node.length)
{ {
node.style.display = canTorch && !Grocy.FeatureFlags.GROCY_FEATURE_FLAG_AUTO_TORCH_ON_WITH_CAMERA ? 'inline-block' : 'none'; node[0].style.display = canTorch && !this.Grocy.FeatureFlags.GROCY_FEATURE_FLAG_AUTO_TORCH_ON_WITH_CAMERA ? 'inline-block' : 'none';
} }
// If AutoTorchOn is set, turn on the torch. // If AutoTorchOn is set, turn on the torch.
if (canTorch && Grocy.FeatureFlags.GROCY_FEATURE_FLAG_AUTO_TORCH_ON_WITH_CAMERA) if (canTorch && this.Grocy.FeatureFlags.GROCY_FEATURE_FLAG_AUTO_TORCH_ON_WITH_CAMERA)
{ {
Grocy.Components.BarcodeScanner.TorchOn(track); this.TorchOn(track);
} }
// Reduce the height of the video, if it's higher than then the viewport // Reduce the height of the video, if it's higher than then the viewport
if (!Grocy.Components.BarcodeScanner.LiveVideoSizeAdjusted) if (!this.LiveVideoSizeAdjusted)
{ {
var bc = document.getElementById('barcodescanner-container'); var bc = this.$('barcodescanner-container');
if (bc) if (bc.length)
{ {
bc = bc[0]
var bcAspectRatio = bc.offsetWidth / bc.offsetHeight; var bcAspectRatio = bc.offsetWidth / bc.offsetHeight;
var settings = track.getSettings(); var settings = track.getSettings();
if (bcAspectRatio > settings.aspectRatio) if (bcAspectRatio > settings.aspectRatio)
{ {
var v = document.querySelector('#barcodescanner-livestream video') var v = this.$('#barcodescanner-livestream video')
if (v) if (v.length)
{ {
var c = document.querySelector('#barcodescanner-livestream canvas') v = v[0];
var c = this.$('#barcodescanner-livestream canvas')[0]
var newWidth = v.clientWidth / bcAspectRatio * settings.aspectRatio + 'px'; var newWidth = v.clientWidth / bcAspectRatio * settings.aspectRatio + 'px';
v.style.width = newWidth; v.style.width = newWidth;
c.style.width = newWidth; c.style.width = newWidth;
} }
} }
Grocy.Components.BarcodeScanner.LiveVideoSizeAdjusted = true; this.LiveVideoSizeAdjusted = true;
} }
} }
} }
Grocy.Components.BarcodeScanner.StartScanning = function() StartScanning()
{ {
Grocy.Components.BarcodeScanner.DecodedCodesCount = 0; this.DecodedCodesCount = 0;
Grocy.Components.BarcodeScanner.DecodedCodesErrorCount = 0; this.DecodedCodesErrorCount = 0;
var self = this;
Quagga.init({ Quagga.init({
inputStream: { inputStream: {
name: "Live", name: "Live",
type: "LiveStream", type: "LiveStream",
target: document.querySelector("#barcodescanner-livestream"), target: this.$("#barcodescanner-livestream")[0],
constraints: { constraints: {
facingMode: "environment", facingMode: "environment",
...(window.localStorage.getItem('cameraId') && { deviceId: window.localStorage.getItem('cameraId') }) // If preferred cameraId is set, request to use that specific camera ...(window.localStorage.getItem('cameraId') && { deviceId: window.localStorage.getItem('cameraId') }) // If preferred cameraId is set, request to use that specific camera
@ -124,8 +275,8 @@ function barcodescanner(Grocy)
console.error(error); console.error(error);
if (error) if (error)
{ {
Grocy.FrontendHelpers.ShowGenericError("Error while initializing the barcode scanning library", error.message); self.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")); toastr.info(self.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"); window.localStorage.removeItem("cameraId");
setTimeout(function() setTimeout(function()
{ {
@ -134,23 +285,23 @@ function barcodescanner(Grocy)
return; return;
} }
Grocy.Components.BarcodeScanner.CheckCapabilities(); this.CheckCapabilities();
Quagga.start(); Quagga.start();
}); });
} }
Grocy.Components.BarcodeScanner.StopScanning = function() StopScanning()
{ {
Quagga.stop(); Quagga.stop();
Grocy.Components.BarcodeScanner.DecodedCodesCount = 0; this.DecodedCodesCount = 0;
Grocy.Components.BarcodeScanner.DecodedCodesErrorCount = 0; this.DecodedCodesErrorCount = 0;
bootbox.hideAll(); bootbox.hideAll();
} }
Grocy.Components.BarcodeScanner.TorchOn = function(track) TorchOn(track)
{ {
if (track) if (track)
{ {
@ -163,144 +314,6 @@ function barcodescanner(Grocy)
}); });
} }
} }
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: '<div id="barcodescanner-container" class="col"><div id="barcodescanner-livestream"></div><div id="debug"></div></div>',
title: Grocy.translate('Scan a barcode'),
onEscape: function()
{
Grocy.Components.BarcodeScanner.StopScanning();
},
size: 'big',
backdrop: true,
closeButton: true,
buttons: {
torch: {
label: '<i class="far fa-lightbulb"></i>',
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('<div class="form-group py-0 my-1 cameraSelect-wrapper"><select class="custom-control custom-select cameraSelect"><select class="custom-control custom-select cameraSelect" style="display: none"></select></div>');
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('<a id="barcodescanner-start-button" class="btn btn-sm btn-primary text-white disabled" data-target="' + $(this).attr("data-target") + '"><i class="fas fa-camera"></i></a>');
}
else
{
$(this).after('<a id="barcodescanner-start-button" class="btn btn-sm btn-primary text-white" data-target="' + $(this).attr("data-target") + '"><i class="fas fa-camera"></i></a>');
}
});
}, 50);
} }
export { barcodescanner } export { barcodescanner }

View File

@ -1,28 +1,37 @@
import { EmptyElementWhenMatches } from '../helpers/extensions' import { EmptyElementWhenMatches } from '../helpers/extensions'
import { RefreshContextualTimeago } from '../configs/timeago' import { RefreshContextualTimeago } from '../configs/timeago'
function batterycard(Grocy) class batterycard
{ {
constructor(Grocy, scopeSelector = null)
Grocy.Components.BatteryCard = {};
Grocy.Components.BatteryCard.Refresh = function(batteryId)
{ {
Grocy.Api.Get('batteries/' + batteryId, this.Grocy = Grocy;
this.scopeSelector = scopeSelector;
this.scope = scopeSelector != null ? $(scope) : $(document);
this.$ = scopeSelector != null ? $(scope).find : $;
}
Refresh(batteryId)
{
var self = this;
this.Grocy.Api.Get('batteries/' + batteryId,
function(batteryDetails) function(batteryDetails)
{ {
$('#batterycard-battery-name').text(batteryDetails.battery.name); self.$('#batterycard-battery-name').text(batteryDetails.battery.name);
$('#batterycard-battery-used_in').text(batteryDetails.battery.used_in); self.$('#batterycard-battery-used_in').text(batteryDetails.battery.used_in);
$('#batterycard-battery-last-charged').text((batteryDetails.last_charged || Grocy.translate('never'))); self.$('#batterycard-battery-last-charged').text((batteryDetails.last_charged || self.Grocy.translate('never')));
$('#batterycard-battery-last-charged-timeago').attr("datetime", batteryDetails.last_charged || ''); self.$('#batterycard-battery-last-charged-timeago').attr("datetime", batteryDetails.last_charged || '');
$('#batterycard-battery-charge-cycles-count').text((batteryDetails.charge_cycles_count || '0')); self.$('#batterycard-battery-charge-cycles-count').text((batteryDetails.charge_cycles_count || '0'));
$('#batterycard-battery-edit-button').attr("href", Grocy.FormatUrl("/battery/" + batteryDetails.battery.id.toString())); self.$('#batterycard-battery-edit-button').attr("href", self.Grocy.FormatUrl("/battery/" + batteryDetails.battery.id.toString()));
$('#batterycard-battery-journal-button').attr("href", Grocy.FormatUrl("/batteriesjournal?embedded&battery=" + batteryDetails.battery.id.toString())); self.$('#batterycard-battery-journal-button').attr("href", self.Grocy.FormatUrl("/batteriesjournal?embedded&battery=" + batteryDetails.battery.id.toString()));
$('#batterycard-battery-edit-button').removeClass("disabled"); self.$('#batterycard-battery-edit-button').removeClass("disabled");
$('#batterycard-battery-journal-button').removeClass("disabled"); self.$('#batterycard-battery-journal-button').removeClass("disabled");
EmptyElementWhenMatches('#batterycard-battery-last-charged-timeago', Grocy.translate('timeago_nan')); EmptyElementWhenMatches(self.$('#batterycard-battery-last-charged-timeago'), self.Grocy.translate('timeago_nan'));
// ToDo: Unscoped
RefreshContextualTimeago(".batterycard"); RefreshContextualTimeago(".batterycard");
}, },
function(xhr) function(xhr)

View File

@ -1,45 +1,54 @@
function calendarcard(Grocy) class calendarcard
{ {
$('#calendar').datetimepicker( constructor(Grocy, scopeSelector = null)
{ {
format: 'L', this.Grocy = Grocy;
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'); this.scopeSelector = scopeSelector;
this.scope = scopeSelector != null ? $(scope) : $(document);
this.$ = scopeSelector != null ? $(scope).find : $;
this.$('#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) { }
}
});
this.$('#calendar').datetimepicker('show');
}
} }
export { calendarcard } export { calendarcard }

View File

@ -1,36 +1,44 @@
import { EmptyElementWhenMatches } from '../helpers/extensions' import { EmptyElementWhenMatches } from '../helpers/extensions'
import { RefreshContextualTimeago } from '../configs/timeago' import { RefreshContextualTimeago } from '../configs/timeago'
function chorecard(Grocy) class chorecard
{ {
Grocy.Components.ChoreCard = {}; constructor(Grocy, scopeSelector = null)
Grocy.Components.ChoreCard.Refresh = function(choreId)
{ {
Grocy.Api.Get('chores/' + choreId, this.Grocy = Grocy;
this.scopeSelector = scopeSelector;
this.scope = scopeSelector != null ? $(scope) : $(document);
this.$ = scopeSelector != null ? $(scope).find : $;
}
Refresh(choreId)
{
var self = this;
this.Grocy.Api.Get('chores/' + choreId,
function(choreDetails) function(choreDetails)
{ {
$('#chorecard-chore-name').text(choreDetails.chore.name); self.$('#chorecard-chore-name').text(choreDetails.chore.name);
$('#chorecard-chore-last-tracked').text((choreDetails.last_tracked || Grocy.translate('never'))); self.$('#chorecard-chore-last-tracked').text((choreDetails.last_tracked || self.Grocy.translate('never')));
$('#chorecard-chore-last-tracked-timeago').attr("datetime", choreDetails.last_tracked || ''); self.$('#chorecard-chore-last-tracked-timeago').attr("datetime", choreDetails.last_tracked || '');
$('#chorecard-chore-tracked-count').text((choreDetails.tracked_count || '0')); self.$('#chorecard-chore-tracked-count').text((choreDetails.tracked_count || '0'));
$('#chorecard-chore-last-done-by').text((choreDetails.last_done_by.display_name || Grocy.translate('Unknown'))); self.$('#chorecard-chore-last-done-by').text((choreDetails.last_done_by.display_name || self.Grocy.translate('Unknown')));
$('#chorecard-chore-edit-button').attr("href", Grocy.FormatUrl("/chore/" + choreDetails.chore.id.toString())); self.$('#chorecard-chore-edit-button').attr("href", self.Grocy.FormatUrl("/chore/" + choreDetails.chore.id.toString()));
$('#chorecard-chore-journal-button').attr("href", Grocy.FormatUrl("/choresjournal?embedded&chore=" + choreDetails.chore.id.toString())); self.$('#chorecard-chore-journal-button').attr("href", self.Grocy.FormatUrl("/choresjournal?embedded&chore=" + choreDetails.chore.id.toString()));
$('#chorecard-chore-edit-button').removeClass("disabled"); self.$('#chorecard-chore-edit-button').removeClass("disabled");
$('#chorecard-chore-journal-button').removeClass("disabled"); self.$('#chorecard-chore-journal-button').removeClass("disabled");
if (choreDetails.chore.track_date_only == 1) if (choreDetails.chore.track_date_only == 1)
{ {
$("#chorecard-chore-last-tracked-timeago").addClass("timeago-date-only"); self.$("#chorecard-chore-last-tracked-timeago").addClass("timeago-date-only");
} }
else else
{ {
$("#chorecard-chore-last-tracked-timeago").removeClass("timeago-date-only"); self.$("#chorecard-chore-last-tracked-timeago").removeClass("timeago-date-only");
} }
EmptyElementWhenMatches('#chorecard-chore-last-tracked-timeago', Grocy.translate('timeago_nan')); EmptyElementWhenMatches(self.$('#chorecard-chore-last-tracked-timeago'), self.Grocy.translate('timeago_nan'));
RefreshContextualTimeago(".chorecard"); RefreshContextualTimeago(".chorecard");
}, },
function(xhr) function(xhr)

View File

@ -1,100 +1,119 @@
import { EmptyElementWhenMatches } from '../helpers/extensions' import { EmptyElementWhenMatches } from '../helpers/extensions'
import { RefreshContextualTimeago } from '../configs/timeago' import { RefreshContextualTimeago } from '../configs/timeago'
function datetimepicker(Grocy) class datetimepicker
{ {
Grocy.Components.DateTimePicker = {}; constructor(Grocy, scopeSelector = null, basename = "datetimepicker")
Grocy.Components.DateTimePicker.GetInputElement = function()
{ {
return $('.datetimepicker').find('input').not(".form-check-input"); this.Grocy = Grocy;
this.scopeSelector = scopeSelector;
this.scope = scopeSelector != null ? $(scope) : $(document);
this.$ = scopeSelector != null ? $(scope).find : $;
this.basename = basename;
var inputElement = this.GetInputElement();
var self = this;
this.startDate = null;
if (inputElement.data('init-with-now') === true)
{
this.startDate = moment().format(inputElement.data('format'));
}
if (inputElement.data('init-value').length > 0)
{
this.startDate = moment(inputElement.data('init-value')).format(inputElement.data('format'));
}
this.limitDate = moment('2999-12-31 23:59:59');
if (inputElement.data('limit-end-to-now') === true)
{
this.limitDate = moment();
}
// set some event handlers
inputElement.on('keyup', (e) => self.keyupHandler(this, e));
inputElement.on('input', (e) => self.inputHandler(this, e));
this.$('.' + this.basename).on('update.datetimepicker', () => self.stateTrigger());
this.$('.' + this.basename).on('hide.datetimepicker', () => self.stateTrigger());
this.$("#" + this.basename + "-shortcut").on("click", () => self.handleShortcut(this));
this.Init()
} }
Grocy.Components.DateTimePicker.GetValue = function() GetInputElement()
{ {
return Grocy.Components.DateTimePicker.GetInputElement().val(); return this.$('.' + this.basename).find('input').not(".form-check-input");
} }
Grocy.Components.DateTimePicker.SetValue = function(value) GetValue()
{
return this.GetInputElement().val();
}
SetValue(value, triggerEvents = true)
{ {
// "Click" the shortcut checkbox when the desired value is // "Click" the shortcut checkbox when the desired value is
// not the shortcut value and it is currently set // not the shortcut value and it is currently set
var shortcutValue = $("#datetimepicker-shortcut").data("datetimepicker-shortcut-value"); var shortcutValue = this.$("#" + this.basename + "-shortcut").data(this.basename + "-shortcut-value");
if (value != shortcutValue && $("#datetimepicker-shortcut").is(":checked")) if (value != shortcutValue && this.$("#" + this.basename + "-shortcut").is(":checked"))
{ {
$("#datetimepicker-shortcut").click(); this.$("#" + this.basename + "-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(''); var inputElement = this.GetInputElement();
inputElement.val(value);
if (triggerEvents)
{
inputElement.trigger('change');
inputElement.keyup();
}
} }
Grocy.Components.DateTimePicker.ChangeFormat = function(format) Clear()
{ {
$(".datetimepicker").datetimepicker("destroy"); this.$("." + this.basename).datetimepicker("destroy");
Grocy.Components.DateTimePicker.GetInputElement().data("format", format); this.Init();
Grocy.Components.DateTimePicker.Init(); this.SetValue("", false);
this.$('#' + this.basename + '-timeago').text('');
}
ChangeFormat(format)
{
this.$("." + this.basename).datetimepicker("destroy");
var elem = this.GetInputElement();
elem.data("format", format);
this.Init();
if (format == "YYYY-MM-DD") if (format == "YYYY-MM-DD")
{ {
Grocy.Components.DateTimePicker.GetInputElement().addClass("date-only-datetimepicker"); elem.addClass("date-only-datetimepicker");
} }
else else
{ {
Grocy.Components.DateTimePicker.GetInputElement().removeClass("date-only-datetimepicker"); elem.removeClass("date-only-datetimepicker");
} }
} }
var startDate = null; Init()
var inputElement = Grocy.Components.DateTimePicker.GetInputElement();
if (inputElement.data('init-with-now') === true)
{ {
startDate = moment().format(inputElement.data('format')); this.$('.' + this.basename).datetimepicker(
}
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'), format: this.GetInputElement().data('format'),
buttons: { buttons: {
showToday: true, showToday: true,
showClose: true showClose: true
}, },
calendarWeeks: Grocy.CalendarShowWeekNumbers, calendarWeeks: this.Grocy.CalendarShowWeekNumbers,
maxDate: limitDate, maxDate: this.limitDate,
locale: moment.locale(), locale: moment.locale(),
defaultDate: startDate, defaultDate: this.startDate,
useCurrent: false, useCurrent: false,
icons: { icons: {
time: 'far fa-clock', time: 'far fa-clock',
@ -125,28 +144,30 @@ function datetimepicker(Grocy)
} }
}); });
} }
Grocy.Components.DateTimePicker.Init();
Grocy.Components.DateTimePicker.GetInputElement().on('keyup', function(e)
keyupHandler(_this, e)
{ {
$('.datetimepicker').datetimepicker('hide'); this.$('.' + this.basename).datetimepicker('hide');
var value = Grocy.Components.DateTimePicker.GetValue(); var inputElement = this.GetInputElement();
var value = this.GetValue();
var now = new Date(); var now = new Date();
var centuryStart = Number.parseInt(now.getFullYear().toString().substring(0, 2) + '00'); var centuryStart = Number.parseInt(now.getFullYear().toString().substring(0, 2) + '00');
var centuryEnd = Number.parseInt(now.getFullYear().toString().substring(0, 2) + '99'); var centuryEnd = Number.parseInt(now.getFullYear().toString().substring(0, 2) + '99');
var format = Grocy.Components.DateTimePicker.GetInputElement().data('format'); var format = inputElement.data('format');
var nextInputElement = $(Grocy.Components.DateTimePicker.GetInputElement().data('next-input-selector')); var nextInputElement = this.$(inputElement.data('next-input-selector'));
//If input is empty and any arrow key is pressed, set date to today //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)) 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)); this.SetValue(moment(new Date(), format, true).format(format));
nextInputElement.focus(); nextInputElement.focus();
} }
else if (value === 'x' || value === 'X') else if (value === 'x' || value === 'X')
{ {
Grocy.Components.DateTimePicker.SetValue(moment('2999-12-31 23:59:59').format(format)); this.SetValue(moment('2999-12-31 23:59:59').format(format));
nextInputElement.focus(); nextInputElement.focus();
} }
else if (value.length === 4 && !(Number.parseInt(value) > centuryStart && Number.parseInt(value) < centuryEnd)) else if (value.length === 4 && !(Number.parseInt(value) > centuryStart && Number.parseInt(value) < centuryEnd))
@ -156,18 +177,18 @@ function datetimepicker(Grocy)
{ {
date.add(1, "year"); date.add(1, "year");
} }
Grocy.Components.DateTimePicker.SetValue(date.format(format)); this.SetValue(date.format(format));
nextInputElement.focus(); nextInputElement.focus();
} }
else if (value.length === 8 && $.isNumeric(value)) else if (value.length === 8 && $.isNumeric(value))
{ {
Grocy.Components.DateTimePicker.SetValue(value.replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3')); this.SetValue(value.replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3'));
nextInputElement.focus(); nextInputElement.focus();
} }
else if (value.length === 7 && $.isNumeric(value.substring(0, 6)) && (value.substring(6, 7).toLowerCase() === "e" || value.substring(6, 7).toLowerCase() === "+")) 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"); var endOfMonth = moment(value.substring(0, 4) + "-" + value.substring(4, 6) + "-01").endOf("month");
Grocy.Components.DateTimePicker.SetValue(endOfMonth.format(format)); this.SetValue(endOfMonth.format(format));
nextInputElement.focus(); nextInputElement.focus();
} }
else else
@ -221,23 +242,23 @@ function datetimepicker(Grocy)
} }
//Custom validation //Custom validation
value = Grocy.Components.DateTimePicker.GetValue(); value = this.GetValue();
dateObj = moment(value, format, true); dateObj = moment(value, format, true);
var element = Grocy.Components.DateTimePicker.GetInputElement()[0]; var element = inputElement[0];
if (!dateObj.isValid()) if (!dateObj.isValid())
{ {
if ($(element).hasAttr("required")) if (inputElement.hasAttr("required"))
{ {
element.setCustomValidity("error"); element.setCustomValidity("error");
} }
} }
else else
{ {
if (Grocy.Components.DateTimePicker.GetInputElement().data('limit-end-to-now') === true && dateObj.isAfter(moment())) if (inputElement.data('limit-end-to-now') === true && dateObj.isAfter(moment()))
{ {
element.setCustomValidity("error"); element.setCustomValidity("error");
} }
else if (Grocy.Components.DateTimePicker.GetInputElement().data('limit-start-to-now') === true && dateObj.isBefore(moment())) else if (inputElement.data('limit-start-to-now') === true && dateObj.isBefore(moment()))
{ {
element.setCustomValidity("error"); element.setCustomValidity("error");
} }
@ -246,72 +267,68 @@ function datetimepicker(Grocy)
element.setCustomValidity(""); element.setCustomValidity("");
} }
var earlierThanLimit = Grocy.Components.DateTimePicker.GetInputElement().data("earlier-than-limit"); var earlierThanLimit = inputElement.data("earlier-than-limit");
if (!earlierThanLimit.isEmpty()) if (!earlierThanLimit.isEmpty())
{ {
if (moment(value).isBefore(moment(earlierThanLimit))) if (moment(value).isBefore(moment(earlierThanLimit)))
{ {
$("#datetimepicker-earlier-than-info").removeClass("d-none"); this.$("#" + this.basename + "-earlier-than-info").removeClass("d-none");
} }
else else
{ {
$("#datetimepicker-earlier-than-info").addClass("d-none"); this.$("#" + this.basename + "-earlier-than-info").addClass("d-none");
} }
} }
} }
// "Click" the shortcut checkbox when the shortcut value was // "Click" the shortcut checkbox when the shortcut value was
// entered manually and it is currently not set // entered manually and it is currently not set
var shortcutValue = $("#datetimepicker-shortcut").data("datetimepicker-shortcut-value"); var shortcutValue = this.$("#" + this.basename + "-shortcut").data(this.basename + "-shortcut-value");
if (value == shortcutValue && !$("#datetimepicker-shortcut").is(":checked")) if (value == shortcutValue && !this.$("#" + this.basename + "-shortcut").is(":checked"))
{ {
$("#datetimepicker-shortcut").click(); this.$("#" + this.basename + "-shortcut").click();
} }
}); }
Grocy.Components.DateTimePicker.GetInputElement().on('input', function(e) inputHandler(_this, e)
{ {
$('#datetimepicker-timeago').attr("datetime", Grocy.Components.DateTimePicker.GetValue()); this.$('#' + this.basename + '-timeago').attr("datetime", this.GetValue());
EmptyElementWhenMatches('#datetimepicker-timeago', Grocy.translate('timeago_nan')); EmptyElementWhenMatches(this.$('#' + this.basename + '-timeago'), this.Grocy.translate('timeago_nan'));
RefreshContextualTimeago("#datetimepicker-wrapper");
});
$('.datetimepicker').on('update.datetimepicker', function(e) // TODO: scoping
{ RefreshContextualTimeago("#" + this.basename + "-wrapper");
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) stateTrigger()
{ {
Grocy.Components.DateTimePicker.GetInputElement().trigger('input'); var linputElement = this.GetInputElement();
Grocy.Components.DateTimePicker.GetInputElement().trigger('change'); linputElement.trigger('input')
Grocy.Components.DateTimePicker.GetInputElement().trigger('keypress'); .trigger('change')
Grocy.Components.DateTimePicker.GetInputElement().trigger('keyup'); .trigger('keypress')
}); .trigger('keyup');
}
$("#datetimepicker-shortcut").on("click", function() handleShortcut(_this)
{ {
if (this.checked)
{ {
var value = $("#datetimepicker-shortcut").data("datetimepicker-shortcut-value"); var linputElement = this.GetInputElement();
Grocy.Components.DateTimePicker.SetValue(value); if (_this.checked)
Grocy.Components.DateTimePicker.GetInputElement().attr("readonly", ""); {
$(Grocy.Components.DateTimePicker.GetInputElement().data('next-input-selector')).focus(); var value = this.$("#" + this.basename + "-shortcut").data(this.basename + "-shortcut-value");
} this.SetValue(value);
else this.GetInputElement().attr("readonly", "");
{ this.$(linputElement.data('next-input-selector')).focus();
Grocy.Components.DateTimePicker.SetValue(""); }
Grocy.Components.DateTimePicker.GetInputElement().removeAttr("readonly"); else
Grocy.Components.DateTimePicker.GetInputElement().focus(); {
} this.SetValue("");
linputElement.removeAttr("readonly");
linputElement.focus();
}
Grocy.Components.DateTimePicker.GetInputElement().trigger('input'); this.stateTrigger();
Grocy.Components.DateTimePicker.GetInputElement().trigger('change'); }
Grocy.Components.DateTimePicker.GetInputElement().trigger('keypress'); }
});
} }
export { datetimepicker } export { datetimepicker }

View File

@ -1,317 +1,13 @@
import { EmptyElementWhenMatches } from '../helpers/extensions' import { EmptyElementWhenMatches } from '../helpers/extensions'
import { RefreshContextualTimeago } from '../configs/timeago' import { RefreshContextualTimeago } from '../configs/timeago'
import { datetimepicker } from './datetimepicker'
function datetimepicker2(Grocy) class datetimepicker2 extends datetimepicker
{ {
Grocy.Components.DateTimePicker2 = {}; constructor(Grocy, scopeSelector = null)
Grocy.Components.DateTimePicker2.GetInputElement = function()
{ {
return $('.datetimepicker2').find('input').not(".form-check-input"); super(Grocy, scopeSelector, "datetimepicker2");
} }
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 } export { datetimepicker2 }

View File

@ -1,79 +1,17 @@
function locationpicker(Grocy) import BasePicker from "./BasePicker";
class locationpicker extends BasePicker
{ {
constructor(Grocy, scopeSelector = null)
Grocy.Components.LocationPicker = {};
Grocy.Components.LocationPicker.GetPicker = function()
{ {
return $('#location_id'); super(Grocy, "#location_id", scopeSelector);
}
Grocy.Components.LocationPicker.GetInputElement = function() this.picker = this.$(this.basename);
{ this.inputElement = this.$('#location_id_text_input');
return $('#location_id_text_input');
}
Grocy.Components.LocationPicker.GetValue = function() this.initCombobox('.location-combobox');
{
return $('#location_id').val();
}
Grocy.Components.LocationPicker.SetValue = function(value) this.prefill();
{
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();
} }
} }

View File

@ -1,99 +1,164 @@
function numberpicker(Grocy) class numberpicker
{ {
$(".numberpicker-down-button").unbind('click').on("click", function() constructor(Grocy, scopeSelector = null)
{ {
var inputElement = $(this).parent().parent().find('input[type="number"]'); this.Grocy = Grocy;
inputElement.val(parseFloat(inputElement.val() || 1) - 1);
inputElement.trigger('keyup');
inputElement.trigger('change');
});
$(".numberpicker-up-button").unbind('click').on("click", function() this.scopeSelector = scopeSelector;
{ this.scope = scopeSelector != null ? $(scope) : $(document);
var inputElement = $(this).parent().parent().find('input[type="number"]'); this.$ = scopeSelector != null ? $(scope).find : $;
inputElement.val(parseFloat(inputElement.val() || 0) + 1); var self = this;
inputElement.trigger('keyup');
inputElement.trigger('change');
});
$(".numberpicker").on("keyup", function() this.$(".numberpicker-down-button").unbind('click').on("click", () => self.valueDownHandler(this));
{ this.$(".numberpicker-up-button").unbind('click').on("click", () => self.valueUpHandler(this));
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() this.$(".numberpicker").on("keyup", function()
{
new MutationObserver(function(mutations)
{ {
mutations.forEach(function(mutation) $this = $(this)
if ($this.attr("data-not-equal") && !$this.attr("data-not-equal").toString().isEmpty() && $this.attr("data-not-equal") == $this.val())
{ {
if (mutation.type == "attributes" && (mutation.attributeName == "min" || mutation.attributeName == "max" || mutation.attributeName == "data-not-equal" || mutation.attributeName == "data-initialised")) $this[0].setCustomValidity("error");
}
else
{
$this[0].setCustomValidity("");
}
});
this.$(".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();
}
});
var observer = new MutationObserver((mutations) => self.handleObservedChange(mutations));
this.$(".numberpicker").each(() => observer.observe(this, {
attributes: true
}));
this.$(".numberpicker").attr("data-initialised", "true"); // Dummy change to trigger MutationObserver above once
}
modifyValueHandler(_this, newValue)
{
var inputElement = this.$(_this).parent().parent().find('input[type="number"]');
if (newValue instanceof Function)
newValue = newValue(_this, inputElement);
inputElement.val(newValue);
inputElement.trigger('keyup').trigger('change');
}
valueUpHandler(_this)
{
this.modifyValueHandler(_this, (_this, inputElement) => { return parseFloat(inputElement.val() || 0) + 1; });
}
valueDownHandler(_this)
{
this.modifyValueHandler(_this, (_this, inputElement) => { return parseFloat(inputElement.val() || 1) - 1; });
}
handleObservedChange(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")
{ {
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()) 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)); element.parent().find(".invalid-feedback").text(
this.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 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)); element.parent().find(".invalid-feedback").text(
this.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)
);
} }
}
});
}).observe(this, {
attributes: true
});
});
$(".numberpicker").attr("data-initialised", "true"); // Dummy change to trigger MutationObserver above once
$(".numberpicker").on("keydown", function(e) return;
{ }
if (e.key == "ArrowUp") }
{
e.preventDefault(); if (max.isEmpty())
$(this).parent().find(".numberpicker-up-button").click(); {
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)
);
}
} }
else if (e.key == "ArrowDown") }
{
e.preventDefault();
$(this).parent().find(".numberpicker-down-button").click();
}
});
} }
export { numberpicker } export { numberpicker }

View File

@ -1,22 +1,37 @@
function productamountpicker(Grocy) class productamountpicker
{ {
Grocy.Use("numberpicker"); constructor(Grocy, scopeSelector = null)
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); this.Grocy = Grocy;
if (!Grocy.Components.ProductAmountPicker.AllowAnyQuEnabled) this.scopeSelector = scopeSelector;
this.scope = scopeSelector != null ? $(scope) : $(document);
this.$ = scopeSelector != null ? $(scope).find : $;
Grocy.Use("numberpicker");
this.AllowAnyQuEnabled = false;
this.qu_id = this.$("#qu_id");
this.display_amount = this.$("#display_amount");
var self = this;
this.$(".input-group-productamountpicker").on("change", () => self.onChangeHandler(this));
this.$("#display_amount").on("keyup", () => self.$(".input-group-productamountpicker").trigger("change"));
}
Reload(productId, destinationQuId, forceInitialDisplayQu = false)
{
var conversionsForProduct = this.Grocy.QuantityUnitConversionsResolved.filter(elem => elem.product_id == productId);
if (!this.AllowAnyQuEnabled)
{ {
var qu = Grocy.QuantityUnits.find(elem => elem.id == destinationQuId); var qu = thisGrocy.QuantityUnits.find(elem => elem.id == destinationQuId);
$("#qu_id").find("option").remove().end(); this.qu_id.find("option").remove().end();
$("#qu_id").attr("data-destination-qu-name", qu.name); this.qu_id.attr("data-destination-qu-name", qu.name);
$("#qu_id").attr("data-destination-qu-name-plural", qu.name_plural); this.qu_id.attr("data-destination-qu-name-plural", qu.name_plural);
conversionsForProduct.forEach(conversion => for (let conversion of conversionsForProduct)
{ {
var factor = parseFloat(conversion.factor); var factor = parseFloat(conversion.factor);
if (conversion.to_qu_id == destinationQuId) if (conversion.to_qu_id == destinationQuId)
@ -24,100 +39,115 @@ function productamountpicker(Grocy)
factor = 1; factor = 1;
} }
if (!$('#qu_id option[value="' + conversion.to_qu_id + '"]').length) // Don't add the destination QU multiple times if (!this.$('#qu_id option[value="' + conversion.to_qu_id + '"]').length) // Don't add the destination QU multiple times
{ {
$("#qu_id").append('<option value="' + conversion.to_qu_id + '" data-qu-factor="' + factor + '">' + conversion.to_qu_name + '</option>'); this.qu_id.append('<option value="' + conversion.to_qu_id + '" data-qu-factor="' + factor + '">' + conversion.to_qu_name + '</option>');
} }
}); };
} }
if (!Grocy.Components.ProductAmountPicker.InitialValueSet || forceInitialDisplayQu) if (!this.InitialValueSet || forceInitialDisplayQu)
{ {
$("#qu_id").val($("#qu_id").attr("data-initial-qu-id")); this.qu_id.val(this.qu_id.attr("data-initial-qu-id"));
} }
if (!Grocy.Components.ProductAmountPicker.InitialValueSet) if (!this.InitialValueSet)
{ {
var convertedAmount = $("#display_amount").val() * $("#qu_id option:selected").attr("data-qu-factor"); var convertedAmount = this.display_amount.val() * this.$("#qu_id option:selected").attr("data-qu-factor");
$("#display_amount").val(convertedAmount); this.display_amount.val(convertedAmount);
Grocy.Components.ProductAmountPicker.InitialValueSet = true; this.InitialValueSet = true;
} }
if (conversionsForProduct.length === 1 && !forceInitialDisplayQu) if (conversionsForProduct.length === 1 && !forceInitialDisplayQu)
{ {
$("#qu_id").val($("#qu_id option:first").val()); this.qu_id.val(this.$("#qu_id option:first").val());
} }
if ($('#qu_id option').length == 1) if (this.$('#qu_id option').length == 1)
{ {
$("#qu_id").attr("disabled", ""); this.qu_id.attr("disabled", "");
} }
else else
{ {
$("#qu_id").removeAttr("disabled"); this.qu_id.removeAttr("disabled");
} }
$(".input-group-productamountpicker").trigger("change"); this.$(".input-group-productamountpicker").trigger("change");
} }
Grocy.Components.ProductAmountPicker.SetQuantityUnit = function(quId) SetQuantityUnit(quId)
{ {
$("#qu_id").val(quId); this.qu_id.val(quId);
} }
Grocy.Components.ProductAmountPicker.AllowAnyQu = function(keepInitialQu = false) AllowAnyQu(keepInitialQu = false)
{ {
Grocy.Components.ProductAmountPicker.AllowAnyQuEnabled = true; this.AllowAnyQuEnabled = true;
$("#qu_id").find("option").remove().end(); this.qu_id.find("option").remove().end();
Grocy.QuantityUnits.forEach(qu => for (let qu of this.Grocy.QuantityUnits)
{ {
$("#qu_id").append('<option value="' + qu.id + '" data-qu-factor="1">' + qu.name + '</option>'); this.qu_id.append('<option value="' + qu.id + '" data-qu-factor="1">' + qu.name + '</option>');
}); }
if (keepInitialQu) if (keepInitialQu)
{ {
Grocy.Components.ProductAmountPicker.SetQuantityUnit($("#qu_id").attr("data-initial-qu-id")); this.SetQuantityUnit(this.qu_id.attr("data-initial-qu-id"));
} }
$("#qu_id").removeAttr("disabled"); this.qu_id.removeAttr("disabled");
$(".input-group-productamountpicker").trigger("change"); this.$(".input-group-productamountpicker").trigger("change");
} }
Grocy.Components.ProductAmountPicker.Reset = function() Reset()
{ {
$("#qu_id").find("option").remove(); this.qu_id.find("option").remove();
$("#qu-conversion-info").addClass("d-none"); this.$("#qu-conversion-info").addClass("d-none");
$("#qu-display_amount-info").val(""); this.$("#qu-display_amount-info").val("");
} }
$(".input-group-productamountpicker").on("change", function() onChangeHandler(_this)
{ {
var selectedQuName = $("#qu_id option:selected").text(); var selectedQuName = this.$("#qu_id option:selected").text();
var quFactor = $("#qu_id option:selected").attr("data-qu-factor"); var quFactor = this.$("#qu_id option:selected").attr("data-qu-factor");
var amount = $("#display_amount").val(); var amount = this.display_amount.val();
var destinationAmount = amount / quFactor; var destinationAmount = amount / quFactor;
var destinationQuName = Grocy.translaten(destinationAmount, $("#qu_id").attr("data-destination-qu-name"), $("#qu_id").attr("data-destination-qu-name-plural")) var destinationQuName = this.Grocy.translaten(destinationAmount,
this.qu_id.attr("data-destination-qu-name"),
this.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()) var conversionInfo = this.$("#qu-conversion-info");
if (this.qu_id.attr("data-destination-qu-name") == selectedQuName ||
this.AllowAnyQuEnabled ||
amount.toString().isEmpty() ||
selectedQuName.toString().isEmpty())
{ {
$("#qu-conversion-info").addClass("d-none"); conversionInfo.addClass("d-none");
} }
else else
{ {
$("#qu-conversion-info").removeClass("d-none"); conversionInfo.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)); conversionInfo.text(this.Grocy.translate("This equals %1$s %2$s",
destinationAmount.toLocaleString({
minimumFractionDigits: 0,
maximumFractionDigits: this.Grocy.UserSettings.stock_decimal_places_amounts
}),
destinationQuName)
);
} }
$("#amount").val(destinationAmount.toFixed(Grocy.UserSettings.stock_decimal_places_amounts).replace(/0*$/g, '')); $("#amount").val(
}); destinationAmount
.toFixed(this.Grocy.UserSettings.stock_decimal_places_amounts)
$("#display_amount").on("keyup", function() .replace(/0*$/g, '')
{ );
$(".input-group-productamountpicker").trigger("change"); }
});
} }
export { productamountpicker } export { productamountpicker }

View File

@ -2,123 +2,146 @@ import Chart from 'chart.js';
import { EmptyElementWhenMatches } from '../helpers/extensions' import { EmptyElementWhenMatches } from '../helpers/extensions'
import { RefreshContextualTimeago } from '../configs/timeago' import { RefreshContextualTimeago } from '../configs/timeago'
function productcard(Grocy) class productcard
{ {
constructor(Grocy, scopeSelector = null)
Grocy.Components.ProductCard = {};
Grocy.Components.ProductCard.Refresh = function(productId)
{ {
Grocy.Api.Get('stock/products/' + productId, this.Grocy = Grocy;
this.scopeSelector = scopeSelector;
this.scope = scopeSelector != null ? $(scope) : $(document);
this.$ = scopeSelector != null ? $(scope).find : $;
this.PriceHistoryChart = null;
var self = this;
this.$("#productcard-product-description").on("shown.bs.collapse", function()
{
self.$(".expandable-text")
.find("a[data-toggle='collapse']")
.text(self.Grocy.translate("Show less"));
})
this.$("#productcard-product-description").on("hidden.bs.collapse", function()
{
self.$(".expandable-text")
.find("a[data-toggle='collapse']")
.text(self.Grocy.translate("Show more"));
})
}
Refresh(productId)
{
var self = this;
this.Grocy.Api.Get('stock/products/' + productId,
function(productDetails) function(productDetails)
{ {
var stockAmount = productDetails.stock_amount || '0'; var stockAmount = productDetails.stock_amount || '0';
var stockValue = productDetails.stock_value || '0'; var stockValue = productDetails.stock_value || '0';
var stockAmountOpened = productDetails.stock_amount_opened || '0'; var stockAmountOpened = productDetails.stock_amount_opened || '0';
$('#productcard-product-name').text(productDetails.product.name); self.$('#productcard-product-name').text(productDetails.product.name);
$('#productcard-product-description').html(productDetails.product.description); self.$('#productcard-product-description').html(productDetails.product.description);
$('#productcard-product-stock-amount').text(stockAmount); self.$('#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)); self.$('#productcard-product-stock-qu-name').text(self.Grocy.translaten(stockAmount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural));
$('#productcard-product-stock-value').text(stockValue + ' ' + Grocy.Currency); self.$('#productcard-product-stock-value').text(stockValue + ' ' + self.Grocy.Currency);
$('#productcard-product-last-purchased').text((productDetails.last_purchased || '2999-12-31').substring(0, 10)); self.$('#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'); self.$('#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)); self.$('#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'); self.$('#productcard-product-last-used-timeago').attr("datetime", productDetails.last_used || '2999-12-31');
if (productDetails.location != null) if (productDetails.location != null)
{ {
$('#productcard-product-location').text(productDetails.location.name); self.$('#productcard-product-location').text(productDetails.location.name);
} }
$('#productcard-product-spoil-rate').text((parseFloat(productDetails.spoil_rate_percent) / 100).toLocaleString(undefined, { style: "percent" })); self.$('#productcard-product-spoil-rate').text((parseFloat(productDetails.spoil_rate_percent) / 100).toLocaleString(undefined, { style: "percent" }));
if (productDetails.is_aggregated_amount == 1) if (productDetails.is_aggregated_amount == 1)
{ {
$('#productcard-product-stock-amount-aggregated').text(productDetails.stock_amount_aggregated); self.$('#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)); self.$('#productcard-product-stock-qu-name-aggregated').text(self.Grocy.translaten(productDetails.stock_amount_aggregated, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural));
if (productDetails.stock_amount_opened_aggregated > 0) if (productDetails.stock_amount_opened_aggregated > 0)
{ {
$('#productcard-product-stock-opened-amount-aggregated').text(Grocy.translate('%s opened', productDetails.stock_amount_opened_aggregated)); self.$('#productcard-product-stock-opened-amount-aggregated').text(self.Grocy.translate('%s opened', productDetails.stock_amount_opened_aggregated));
} }
else else
{ {
$('#productcard-product-stock-opened-amount-aggregated').text(""); self.$('#productcard-product-stock-opened-amount-aggregated').text("");
} }
$("#productcard-aggregated-amounts").removeClass("d-none"); self.$("#productcard-aggregated-amounts").removeClass("d-none");
} }
else else
{ {
$("#productcard-aggregated-amounts").addClass("d-none"); self.$("#productcard-aggregated-amounts").addClass("d-none");
} }
if (productDetails.product.description != null && !productDetails.product.description.isEmpty()) if (productDetails.product.description != null && !productDetails.product.description.isEmpty())
{ {
$("#productcard-product-description-wrapper").removeClass("d-none"); self.$("#productcard-product-description-wrapper").removeClass("d-none");
} }
else else
{ {
$("#productcard-product-description-wrapper").addClass("d-none"); self.$("#productcard-product-description-wrapper").addClass("d-none");
} }
if (productDetails.average_shelf_life_days == -1) if (productDetails.average_shelf_life_days == -1)
{ {
$('#productcard-product-average-shelf-life').text(Grocy.translate("Unknown")); self.$('#productcard-product-average-shelf-life').text(self.Grocy.translate("Unknown"));
} }
else if (parseInt(productDetails.average_shelf_life_days) > 73000) // > 200 years aka forever else if (parseInt(productDetails.average_shelf_life_days) > 73000) // > 200 years aka forever
{ {
$('#productcard-product-average-shelf-life').text(Grocy.translate("Unlimited")); self.$('#productcard-product-average-shelf-life').text(self.Grocy.translate("Unlimited"));
} }
else else
{ {
$('#productcard-product-average-shelf-life').text(moment.duration(productDetails.average_shelf_life_days, "days").humanize()); self.$('#productcard-product-average-shelf-life').text(moment.duration(productDetails.average_shelf_life_days, "days").humanize());
} }
if (stockAmountOpened > 0) if (stockAmountOpened > 0)
{ {
$('#productcard-product-stock-opened-amount').text(Grocy.translate('%s opened', stockAmountOpened)); self.$('#productcard-product-stock-opened-amount').text(self.Grocy.translate('%s opened', stockAmountOpened));
} }
else else
{ {
$('#productcard-product-stock-opened-amount').text(""); self.$('#productcard-product-stock-opened-amount').text("");
} }
$('#productcard-product-edit-button').attr("href", Grocy.FormatUrl("/product/" + productDetails.product.id.toString() + '?' + 'returnto=' + encodeURIComponent(Grocy.CurrentUrlRelative))); self.$('#productcard-product-edit-button').attr("href", self.Grocy.FormatUrl("/product/" + productDetails.product.id.toString() + '?' + 'returnto=' + encodeURIComponent(self.Grocy.CurrentUrlRelative)));
$('#productcard-product-journal-button').attr("href", Grocy.FormatUrl("/stockjournal?embedded&product=" + productDetails.product.id.toString())); self.$('#productcard-product-journal-button').attr("href", self.Grocy.FormatUrl("/stockjournal?embedded&product=" + productDetails.product.id.toString()));
$('#productcard-product-stock-button').attr("href", Grocy.FormatUrl("/stockentries?embedded&product=" + productDetails.product.id.toString())); self.$('#productcard-product-stock-button').attr("href", self.Grocy.FormatUrl("/stockentries?embedded&product=" + productDetails.product.id.toString()));
$('#productcard-product-stock-button').removeClass("disabled"); self.$('#productcard-product-stock-button').removeClass("disabled");
$('#productcard-product-edit-button').removeClass("disabled"); self.$('#productcard-product-edit-button').removeClass("disabled");
$('#productcard-product-journal-button').removeClass("disabled"); self.$('#productcard-product-journal-button').removeClass("disabled");
if (productDetails.last_price !== null) if (productDetails.last_price !== null)
{ {
$('#productcard-product-last-price').text(Number.parseFloat(productDetails.last_price).toLocaleString() + ' ' + Grocy.Currency + ' per ' + productDetails.quantity_unit_stock.name); self.$('#productcard-product-last-price').text(Number.parseFloat(productDetails.last_price).toLocaleString() + ' ' + self.Grocy.Currency + ' per ' + productDetails.quantity_unit_stock.name);
} }
else else
{ {
$('#productcard-product-last-price').text(Grocy.translate('Unknown')); self.$('#productcard-product-last-price').text(self.Grocy.translate('Unknown'));
} }
if (productDetails.avg_price !== null) if (productDetails.avg_price !== null)
{ {
$('#productcard-product-average-price').text(Number.parseFloat(productDetails.avg_price).toLocaleString() + ' ' + Grocy.Currency + ' per ' + productDetails.quantity_unit_stock.name); self.$('#productcard-product-average-price').text(Number.parseFloat(productDetails.avg_price).toLocaleString() + ' ' + self.Grocy.Currency + ' per ' + productDetails.quantity_unit_stock.name);
} }
else else
{ {
$('#productcard-product-average-price').text(Grocy.translate('Unknown')); self.$('#productcard-product-average-price').text(self.Grocy.translate('Unknown'));
} }
if (productDetails.product.picture_file_name !== null && !productDetails.product.picture_file_name.isEmpty()) if (productDetails.product.picture_file_name !== null && !productDetails.product.picture_file_name.isEmpty())
{ {
$("#productcard-product-picture").removeClass("d-none"); self.$("#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')); self.$("#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 else
{ {
$("#productcard-product-picture").addClass("d-none"); self.$("#productcard-product-picture").addClass("d-none");
} }
EmptyElementWhenMatches('#productcard-product-last-purchased-timeago', Grocy.translate('timeago_nan')); EmptyElementWhenMatches(self.$('#productcard-product-last-purchased-timeago'), self.Grocy.translate('timeago_nan'));
EmptyElementWhenMatches('#productcard-product-last-used-timeago', Grocy.translate('timeago_nan')); EmptyElementWhenMatches(self.$('#productcard-product-last-used-timeago'), self.Grocy.translate('timeago_nan'));
RefreshContextualTimeago(".productcard"); RefreshContextualTimeago(".productcard");
}, },
function(xhr) function(xhr)
@ -127,22 +150,22 @@ function productcard(Grocy)
} }
); );
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) if (this.Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
{ {
Grocy.Api.Get('stock/products/' + productId + '/price-history', this.Grocy.Api.Get('stock/products/' + productId + '/price-history',
function(priceHistoryDataPoints) function(priceHistoryDataPoints)
{ {
if (priceHistoryDataPoints.length > 0) if (priceHistoryDataPoints.length > 0)
{ {
$("#productcard-product-price-history-chart").removeClass("d-none"); self.$("#productcard-product-price-history-chart").removeClass("d-none");
$("#productcard-no-price-data-hint").addClass("d-none"); self.$("#productcard-no-price-data-hint").addClass("d-none");
Grocy.Components.ProductCard.ReInitPriceHistoryChart(); self.ReInitPriceHistoryChart();
var datasets = {}; var datasets = {};
var chart = Grocy.Components.ProductCard.PriceHistoryChart.data; var chart = self.PriceHistoryChart.data;
priceHistoryDataPoints.forEach((dataPoint) => for (let dataPoint of priceHistoryDataPoints)
{ {
var key = Grocy.translate("Unknown store"); let key = Grocy.translate("Unknown store");
if (dataPoint.shopping_location) if (dataPoint.shopping_location)
{ {
key = dataPoint.shopping_location.name key = dataPoint.shopping_location.name
@ -154,9 +177,9 @@ function productcard(Grocy)
} }
chart.labels.push(moment(dataPoint.date).toDate()); chart.labels.push(moment(dataPoint.date).toDate());
datasets[key].push(dataPoint.price); datasets[key].push(dataPoint.price);
}
}); for (let key of Object.keys(datasets))
Object.keys(datasets).forEach((key) =>
{ {
chart.datasets.push({ chart.datasets.push({
data: datasets[key], data: datasets[key],
@ -164,13 +187,13 @@ function productcard(Grocy)
borderColor: "HSL(" + (129 * chart.datasets.length) + ",100%,50%)", borderColor: "HSL(" + (129 * chart.datasets.length) + ",100%,50%)",
label: key, label: key,
}); });
}); }
Grocy.Components.ProductCard.PriceHistoryChart.update(); self.PriceHistoryChart.update();
} }
else else
{ {
$("#productcard-product-price-history-chart").addClass("d-none"); self.$("#productcard-product-price-history-chart").addClass("d-none");
$("#productcard-no-price-data-hint").removeClass("d-none"); self.$("#productcard-no-price-data-hint").removeClass("d-none");
} }
}, },
function(xhr) function(xhr)
@ -179,17 +202,17 @@ function productcard(Grocy)
} }
); );
} }
}; }
Grocy.Components.ProductCard.ReInitPriceHistoryChart = function() ReInitPriceHistoryChart()
{ {
if (typeof Grocy.Components.ProductCard.PriceHistoryChart !== "undefined") if (this.PriceHistoryChart !== null)
{ {
Grocy.Components.ProductCard.PriceHistoryChart.destroy(); this.PriceHistoryChart.destroy();
} }
var format = 'YYYY-MM-DD'; var format = 'YYYY-MM-DD';
Grocy.Components.ProductCard.PriceHistoryChart = new Chart(document.getElementById("productcard-product-price-history-chart"), { this.PriceHistoryChart = new Chart(this.$("#productcard-product-price-history-chart")[0], {
type: "line", type: "line",
data: { data: {
labels: [ //Date objects labels: [ //Date objects
@ -230,16 +253,6 @@ function productcard(Grocy)
} }
}); });
} }
$("#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 } export { productcard }

View File

@ -1,159 +1,171 @@
function productpicker(Grocy) import BasePicker from "./BasePicker";
class productpicker extends BasePicker
{ {
Grocy.Use('barcodescanner');
Grocy.Components.ProductPicker = {};
Grocy.Components.ProductPicker.GetPicker = function() constructor(Grocy, scopeSelector = null)
{ {
return $('#product_id'); super(Grocy, "#product_id", scopeSelector)
this.Grocy.Use('barcodescanner');
this.picker = this.$('#product_id');
this.inputElement = this.$('#product_id_text_input');
var self = this;
this.embedded = "";
if (this.Grocy.GetUriParam("embedded") !== undefined)
{
this.embedded = "embedded";
}
this.initCombobox('.product-combobox');
this.prefill();
if (this.Grocy.GetUriParam("flow") === "InplaceAddBarcodeToExistingProduct")
{
this.$('#InplaceAddBarcodeToExistingProduct').text(this.Grocy.GetUriParam("barcode"));
this.$('#flow-info-InplaceAddBarcodeToExistingProduct').removeClass('d-none');
this.$('#barcode-lookup-disabled-hint').removeClass('d-none');
this.$('#barcode-lookup-hint').addClass('d-none');
}
this.PopupOpen = false;
this.$('#product_id_text_input').on('blur', (e) => self.onBlurHandler(this, e));
this.scope.on("Grocy.BarcodeScanned", (e, barcode, target) => self.onBarcodeScannedHandler(e, barcode, target));
$(document).on("shown.bs.modal", function(e)
{
self.$(".modal-footer").addClass("d-block").addClass("d-sm-flex");
self.$(".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)
this.$('#product_id_text_input').keydown(function(event)
{
if (event.keyCode === 13) // Enter
{
if (this.picker.hasClass("combobox-menu-visible"))
{
return;
}
self.$("#product_id_text_input").trigger("blur");
}
});
} }
Grocy.Components.ProductPicker.GetInputElement = function() prefill()
{ {
return $('#product_id_text_input'); var doFocus = false;
var prefillProduct = this.Grocy.GetUriParam('product-name');
var prefillProduct2 = this.picker.parent().data('prefill-by-name').toString();
if (!prefillProduct2.isEmpty())
{
prefillProduct = prefillProduct2;
}
if (typeof prefillProduct !== "undefined")
{
var possibleOptionElement = this.$("#product_id option[data-additional-searchdata*=\"" + prefillProduct + "\"]").first();
if (possibleOptionElement.length === 0)
{
possibleOptionElement = this.$("#product_id option:contains(\"" + prefillProduct + "\")").first();
}
if (possibleOptionElement.length > 0)
{
doFocus = true;
this.picker.val(possibleOptionElement.val());
}
}
var prefillProductId = this.Grocy.GetUriParam("product");
var prefillProductId2 = this.picker.parent().data('prefill-by-id').toString();
if (!prefillProductId2.isEmpty())
{
prefillProductId = prefillProductId2;
}
if (typeof prefillProductId !== "undefined")
{
this.picker.val(prefillProductId);
doFocus = true;
}
if (doFocus)
{
this.picker.data('combobox').refresh();
this.picker.trigger('change');
this.$(this.picker.parent().data('next-input-selector').toString())
.focus();
}
} }
Grocy.Components.ProductPicker.GetValue = function() InProductAddWorkflow()
{ {
return $('#product_id').val(); return this.Grocy.GetUriParam('flow') == "InplaceNewProductWithName";
} }
Grocy.Components.ProductPicker.SetValue = function(value) InProductModifyWorkflow()
{ {
Grocy.Components.ProductPicker.GetInputElement().val(value); return this.Grocy.GetUriParam('flow') == "InplaceAddBarcodeToExistingProduct";
Grocy.Components.ProductPicker.GetInputElement().trigger('change');
} }
Grocy.Components.ProductPicker.SetId = function(value) InAnyFlow()
{ {
Grocy.Components.ProductPicker.GetPicker().val(value); return this.InProductAddWorkflow() || this.InProductModifyWorkflow();
Grocy.Components.ProductPicker.GetPicker().data('combobox').refresh();
Grocy.Components.ProductPicker.GetInputElement().trigger('change');
} }
Grocy.Components.ProductPicker.Clear = function() FinishFlow()
{ {
Grocy.Components.ProductPicker.SetValue(''); this.Grocy.RemoveUriParam("flow");
Grocy.Components.ProductPicker.SetId(null); this.Grocy.RemoveUriParam("barcode");
this.Grocy.RemoveUriParam("product-name");
} }
Grocy.Components.ProductPicker.InProductAddWorkflow = function() ShowCustomError(text)
{ {
return Grocy.GetUriParam('flow') == "InplaceNewProductWithName"; var element = this.$("#custom-productpicker-error");
}
Grocy.Components.ProductPicker.InProductModifyWorkflow = function()
{
return Grocy.GetUriParam('flow') == "InplaceAddBarcodeToExistingProduct";
}
Grocy.Components.ProductPicker.InAnyFlow = function()
{
return Grocy.Components.ProductPicker.InProductAddWorkflow() || Grocy.Components.ProductPicker.InProductModifyWorkflow();
}
Grocy.Components.ProductPicker.FinishFlow = function()
{
Grocy.RemoveUriParam("flow");
Grocy.RemoveUriParam("barcode");
Grocy.RemoveUriParam("product-name");
}
Grocy.Components.ProductPicker.ShowCustomError = function(text)
{
var element = $("#custom-productpicker-error");
element.text(text); element.text(text);
element.removeClass("d-none"); element.removeClass("d-none");
} }
Grocy.Components.ProductPicker.HideCustomError = function() HideCustomError()
{ {
$("#custom-productpicker-error").addClass("d-none"); this.$("#custom-productpicker-error").addClass("d-none");
} }
Grocy.Components.ProductPicker.Disable = function() Disable()
{ {
Grocy.Components.ProductPicker.GetInputElement().attr("disabled", ""); this.inputElement.attr("disabled", "");
$("#barcodescanner-start-button").attr("disabled", ""); this.$("#barcodescanner-start-button").attr("disabled", "");
$("#barcodescanner-start-button").addClass("disabled"); this.$("#barcodescanner-start-button").addClass("disabled");
} }
Grocy.Components.ProductPicker.Enable = function() Enable()
{ {
Grocy.Components.ProductPicker.GetInputElement().removeAttr("disabled"); this.inputElement.removeAttr("disabled");
$("#barcodescanner-start-button").removeAttr("disabled"); this.$("#barcodescanner-start-button").removeAttr("disabled");
$("#barcodescanner-start-button").removeClass("disabled"); this.$("#barcodescanner-start-button").removeClass("disabled");
} }
$('.product-combobox').combobox({ onBlurHandler(_this, e)
appendId: '_text_input',
bsVersion: '4',
clearIfNoMatch: false
});
var this_product_picker = Grocy.Components.ProductPicker.GetPicker();
var productpicker_doFocus = false;
var prefillProduct = Grocy.GetUriParam('product-name');
var prefillProduct2 = this_product_picker.parent().data('prefill-by-name').toString();
if (!prefillProduct2.isEmpty())
{ {
prefillProduct = prefillProduct2; var self = this;
}
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) if (this.picker.hasClass("combobox-menu-visible"))
{
productpicker_doFocus = true;
this_product_picker.val(possibleOptionElement.val());
}
}
var prefillProductId = Grocy.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 (Grocy.GetUriParam("flow") === "InplaceAddBarcodeToExistingProduct")
{
$('#InplaceAddBarcodeToExistingProduct').text(Grocy.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; return;
} }
$('#product_id').attr("barcode", "null");
var input = $('#product_id_text_input').val().toString(); this.picker.attr("barcode", "null");
var input = this.$('#product_id_text_input').val().toString();
var possibleOptionElement = []; var possibleOptionElement = [];
// did we enter a grocycode? // did we enter a grocycode?
@ -163,62 +175,66 @@ function productpicker(Grocy)
if (gc[1] == "p") if (gc[1] == "p")
{ {
// find product id // find product id
possibleOptionElement = $("#product_id option[value=\"" + gc[2] + "\"]").first(); possibleOptionElement = this.$("#product_id option[value=\"" + gc[2] + "\"]").first();
$("#product_id").data("grocycode", true); this.picker.data("grocycode", true);
} }
} }
else // process barcode as usual else // process barcode as usual
{ {
possibleOptionElement = $("#product_id option[data-additional-searchdata*=\"" + input + ",\"]").first(); possibleOptionElement = this.$("#product_id option[data-additional-searchdata*=\"" + input + ",\"]").first();
} }
if (Grocy.GetUriParam('flow') === undefined && input.length > 0 && possibleOptionElement.length > 0) if (this.Grocy.GetUriParam('flow') === undefined && input.length > 0 && possibleOptionElement.length > 0)
{ {
$('#product_id').val(possibleOptionElement.val()); this.picker.val(possibleOptionElement.val());
$('#product_id').attr("barcode", input); this.picker.attr("barcode", input);
$('#product_id').data('combobox').refresh(); this.picker.data('combobox').refresh();
$('#product_id').trigger('change'); this.picker.trigger('change');
} }
else else
{ {
if (Grocy.Components.ProductPicker.PopupOpen === true) if (this.PopupOpen === true)
{ {
return; return;
} }
var optionElement = $("#product_id option:contains(\"" + input + "\")").first(); var optionElement = this.$("#product_id option:contains(\"" + input + "\")").first();
if (input.length > 0 && optionElement.length === 0 && Grocy.GetUriParam('flow') === undefined && Grocy.Components.ProductPicker.GetPicker().parent().data('disallow-all-product-workflows').toString() === "false") if (input.length > 0 &&
optionElement.length === 0 &&
this.Grocy.GetUriParam('flow') === undefined &&
this.picker.parent().data('disallow-all-product-workflows').toString() === "false")
{ {
var addProductWorkflowsAdditionalCssClasses = ""; var addProductWorkflowsAdditionalCssClasses = "";
if (Grocy.Components.ProductPicker.GetPicker().parent().data('disallow-add-product-workflows').toString() === "true") if (this.picker.parent().data('disallow-add-product-workflows').toString() === "true")
{ {
addProductWorkflowsAdditionalCssClasses = "d-none"; addProductWorkflowsAdditionalCssClasses = "d-none";
} }
var embedded = "";
if (Grocy.GetUriParam("embedded") !== undefined)
{
embedded = "embedded";
}
var buttons = { var buttons = {
cancel: { cancel: {
label: Grocy.translate('Cancel'), label: this.Grocy.translate('Cancel'),
className: 'btn-secondary responsive-button', className: 'btn-secondary responsive-button',
callback: function() callback: function()
{ {
Grocy.Components.ProductPicker.PopupOpen = false; self.PopupOpen = false;
Grocy.Components.ProductPicker.SetValue(''); self.SetValue('');
} }
}, },
addnewproduct: { addnewproduct: {
label: '<strong>P</strong> ' + Grocy.translate('Add as new product'), label: '<strong>P</strong> ' + this.Grocy.translate('Add as new product'),
className: 'btn-success add-new-product-dialog-button responsive-button ' + addProductWorkflowsAdditionalCssClasses, className: 'btn-success add-new-product-dialog-button responsive-button ' + addProductWorkflowsAdditionalCssClasses,
callback: function() callback: function()
{ {
Grocy.Components.ProductPicker.PopupOpen = false; self.PopupOpen = false;
window.location.href = Grocy.FormatUrl('/product/new?flow=InplaceNewProductWithName&name=' + encodeURIComponent(input) + '&returnto=' + encodeURIComponent(Grocy.CurrentUrlRelative + "?flow=InplaceNewProductWithName&" + embedded) + "&" + embedded); window.location.href = self.Grocy.FormatUrl(
'/product/new?flow=InplaceNewProductWithName' +
'&name=' + encodeURIComponent(input) +
'&returnto=' + encodeURIComponent(
Grocy.CurrentUrlRelative +
"?flow=InplaceNewProductWithName" +
"&" + self.embedded) +
"&" + self.embedded);
} }
}, },
addbarcode: { addbarcode: {
@ -226,8 +242,12 @@ function productpicker(Grocy)
className: 'btn-info add-new-barcode-dialog-button responsive-button', className: 'btn-info add-new-barcode-dialog-button responsive-button',
callback: function() callback: function()
{ {
Grocy.Components.ProductPicker.PopupOpen = false; self.PopupOpen = false;
window.location.href = Grocy.FormatUrl(Grocy.CurrentUrlRelative + '?flow=InplaceAddBarcodeToExistingProduct&barcode=' + encodeURIComponent(input)); window.location.href = Grocy.FormatUrl(
self.Grocy.CurrentUrlRelative +
'?flow=InplaceAddBarcodeToExistingProduct' +
'&barcode=' + encodeURIComponent(input)
);
} }
}, },
addnewproductwithbarcode: { addnewproductwithbarcode: {
@ -235,8 +255,18 @@ function productpicker(Grocy)
className: 'btn-warning add-new-product-with-barcode-dialog-button responsive-button ' + addProductWorkflowsAdditionalCssClasses, className: 'btn-warning add-new-product-with-barcode-dialog-button responsive-button ' + addProductWorkflowsAdditionalCssClasses,
callback: function() callback: function()
{ {
Grocy.Components.ProductPicker.PopupOpen = false; self.PopupOpen = false;
window.location.href = Grocy.FormatUrl('/product/new?flow=InplaceNewProductWithBarcode&barcode=' + encodeURIComponent(input) + '&returnto=' + encodeURIComponent(Grocy.CurrentUrlRelative + "?flow=InplaceAddBarcodeToExistingProduct&barcode=" + input + "&" + embedded) + "&" + embedded); window.location.href = Grocy.FormatUrl(
'/product/new' +
'?flow=InplaceNewProductWithBarcode' +
'&barcode=' + encodeURIComponent(input) +
'&returnto=' + encodeURIComponent(
self.Grocy.CurrentUrlRelative +
"?flow=InplaceAddBarcodeToExistingProduct" +
"&barcode=" + input +
"&" + self.embedded
) +
"&" + self.embedded);
} }
} }
}; };
@ -248,21 +278,21 @@ function productpicker(Grocy)
className: 'btn-primary responsive-button retry-camera-scanning-button', className: 'btn-primary responsive-button retry-camera-scanning-button',
callback: function() callback: function()
{ {
Grocy.Components.ProductPicker.PopupOpen = false; self.PopupOpen = false;
Grocy.Components.ProductPicker.SetValue(''); self.SetValue('');
$("#barcodescanner-start-button").click(); self.$("#barcodescanner-start-button").click();
} }
}; };
} }
Grocy.Components.ProductPicker.PopupOpen = true; this.PopupOpen = true;
bootbox.dialog({ bootbox.dialog({
message: Grocy.translate('"%s" could not be resolved to a product, how do you want to proceed?', input), message: this.Grocy.translate('"%s" could not be resolved to a product, how do you want to proceed?', input),
title: Grocy.translate('Create or assign product'), title: this.Grocy.translate('Create or assign product'),
onEscape: function() onEscape: function()
{ {
Grocy.Components.ProductPicker.PopupOpen = false; self.PopupOpen = false;
Grocy.Components.ProductPicker.SetValue(''); self.SetValue('');
}, },
size: 'large', size: 'large',
backdrop: true, backdrop: true,
@ -272,66 +302,49 @@ function productpicker(Grocy)
{ {
if (e.key === 'B' || e.key === 'b') if (e.key === 'B' || e.key === 'b')
{ {
$('.add-new-barcode-dialog-button').not(".d-none").click(); self.$('.add-new-barcode-dialog-button').not(".d-none").click();
} }
if (e.key === 'p' || e.key === 'P') if (e.key === 'p' || e.key === 'P')
{ {
$('.add-new-product-dialog-button').not(".d-none").click(); self.$('.add-new-product-dialog-button').not(".d-none").click();
} }
if (e.key === 'a' || e.key === 'A') if (e.key === 'a' || e.key === 'A')
{ {
$('.add-new-product-with-barcode-dialog-button').not(".d-none").click(); self.$('.add-new-product-with-barcode-dialog-button').not(".d-none").click();
} }
if (e.key === 'c' || e.key === 'C') if (e.key === 'c' || e.key === 'C')
{ {
$('.retry-camera-scanning-button').not(".d-none").click(); self.$('.retry-camera-scanning-button').not(".d-none").click();
} }
}); });
} }
} }
}); }
$(document).on("Grocy.BarcodeScanned", function(e, barcode, target) onBarcodeScannedHandler(e, barcode, target)
{ {
var self = this;
if (!(target == "@productpicker" || target == "undefined" || target == undefined)) // Default target if (!(target == "@productpicker" || target == "undefined" || target == undefined)) // Default target
{ {
return; return;
} }
// Don't know why the blur event does not fire immediately ... this works... // Don't know why the blur event does not fire immediately ... this works...
Grocy.Components.ProductPicker.GetInputElement().focusout(); this.inputElement
Grocy.Components.ProductPicker.GetInputElement().focus(); .focusout()
Grocy.Components.ProductPicker.GetInputElement().blur(); .focus()
.blur();
Grocy.Components.ProductPicker.GetInputElement().val(barcode); this.inputElement.val(barcode);
setTimeout(function() setTimeout(function()
{ {
Grocy.Components.ProductPicker.GetInputElement().focusout(); self.inputElement
Grocy.Components.ProductPicker.GetInputElement().focus(); .focusout()
Grocy.Components.ProductPicker.GetInputElement().blur(); .focus()
.blur();
}, 200); }, 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 } export { productpicker }

View File

@ -1,76 +1,17 @@
function recipepicker(Grocy) import BasePicker from './BasePicker'
class recipepicker extends BasePicker
{ {
Grocy.Components.RecipePicker = {}; constructor(Grocy, scopeSelector = null)
Grocy.Components.RecipePicker.GetPicker = function()
{ {
return $('#recipe_id'); super(Grocy, "#recipe_id", scopeSelector);
}
Grocy.Components.RecipePicker.GetInputElement = function() this.picker = this.$(this.basename);
{ this.input_element = this.$(this.basename + '_text_input');
return $('#recipe_id_text_input');
}
Grocy.Components.RecipePicker.GetValue = function() this.initCombobox('.recipe-combobox');
{ this.prefill();
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();
} }
} }

View File

@ -1,76 +1,17 @@
function shoppinglocationpicker(Grocy) import BasePicker from "./BasePicker";
class shoppinglocationpicker extends BasePicker
{ {
Grocy.Components.ShoppingLocationPicker = {};
Grocy.Components.ShoppingLocationPicker.GetPicker = function() constructor(Grocy, scopeSelector = null)
{ {
return $('#shopping_location_id'); super(Grocy, "#shopping_location_id", scopeSelector);
}
Grocy.Components.ShoppingLocationPicker.GetInputElement = function() this.picker = this.$(this.basename);
{ this.input_element = this.$(this.basename + '_text_input');
return $('#shopping_location_id_text_input');
}
Grocy.Components.ShoppingLocationPicker.GetValue = function() this.initCombobox('.recipe-combobox');
{ this.prefill();
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();
} }
} }

View File

@ -1,17 +1,28 @@
import { RandomString } from '../helpers/extensions'; import { RandomString } from '../helpers/extensions';
import { LoadImagesLazy } from '../configs/lazy' import { LoadImagesLazy } from '../configs/lazy'
import uuid from 'uuid';
import { datetimepicker } from "./datetimepicker";
function userfieldsform(Grocy) class userfieldsform
{ {
if (document.querySelector('.datetimepicker-input') !== null) constructor(Grocy, scopeSelector = null)
{ {
Grocy.Use("datetimepicker"); this.Grocy = Grocy;
}
Grocy.Components.UserfieldsForm = {};
Grocy.Components.UserfieldsForm.Save = function(success, error) this.scopeSelector = scopeSelector;
this.scope = scopeSelector != null ? $(scope) : $(document);
this.$ = scopeSelector != null ? $(scope).find : $;
this.$(".userfield-link").keyup();
// We need to further scope some of the embedded components.
// Store those instances here.
this.components = []
}
Save(success, error)
{ {
if (!$("#userfields-form").length) if (!this.$("#userfields-form").length)
{ {
if (success) if (success)
{ {
@ -22,8 +33,9 @@ function userfieldsform(Grocy)
} }
var jsonData = {}; var jsonData = {};
var self = this;
$("#userfields-form .userfield-input").not("div").each(function() this.$("#userfields-form .userfield-input").not("div").each(function()
{ {
var input = $(this); var input = $(this);
var fieldName = input.attr("data-userfield-name"); var fieldName = input.attr("data-userfield-name");
@ -42,10 +54,10 @@ function userfieldsform(Grocy)
var oldFile = input.data('old-file') var oldFile = input.data('old-file')
if (oldFile) if (oldFile)
{ {
Grocy.Api.Delete('files/userfiles/' + oldFile, null, null, self.Grocy.Api.Delete('files/userfiles/' + oldFile, null, null,
function(xhr) function(xhr)
{ {
Grocy.FrontendHelpers.ShowGenericError('Could not delete file', xhr); self.Grocy.FrontendHelpers.ShowGenericError('Could not delete file', xhr);
}); });
jsonData[fieldName] = ""; jsonData[fieldName] = "";
} }
@ -56,7 +68,7 @@ function userfieldsform(Grocy)
var fileName = RandomString() + '.' + input[0].files[0].name.split('.').reverse()[0]; var fileName = RandomString() + '.' + input[0].files[0].name.split('.').reverse()[0];
jsonData[fieldName] = btoa(fileName) + '_' + btoa(input[0].files[0].name); jsonData[fieldName] = btoa(fileName) + '_' + btoa(input[0].files[0].name);
Grocy.Api.UploadFile(input[0].files[0], 'userfiles', fileName, self.Grocy.Api.UploadFile(input[0].files[0], 'userfiles', fileName,
function(result) function(result)
{ {
}, },
@ -78,7 +90,7 @@ function userfieldsform(Grocy)
} }
}); });
Grocy.Api.Put('userfields/' + $("#userfields-form").data("entity") + '/' + Grocy.EditObjectId, jsonData, this.Grocy.Api.Put('userfields/' + $("#userfields-form").data("entity") + '/' + this.Grocy.EditObjectId, jsonData,
function(result) function(result)
{ {
if (success) if (success)
@ -96,19 +108,20 @@ function userfieldsform(Grocy)
); );
} }
Grocy.Components.UserfieldsForm.Load = function() Load()
{ {
if (!$("#userfields-form").length) if (!this.$("#userfields-form").length)
{ {
return; return;
} }
var self = this;
Grocy.Api.Get('userfields/' + $("#userfields-form").data("entity") + '/' + Grocy.EditObjectId, Grocy.Api.Get('userfields/' + this.$("#userfields-form").data("entity") + '/' + this.Grocy.EditObjectId,
function(result) function(result)
{ {
$.each(result, function(key, value) $.each(result, function(key, value)
{ {
var input = $(".userfield-input[data-userfield-name='" + key + "']"); var input = self.$(".userfield-input[data-userfield-name='" + key + "']");
if (input.attr("type") == "checkbox" && value == 1) if (input.attr("type") == "checkbox" && value == 1)
{ {
@ -117,7 +130,7 @@ function userfieldsform(Grocy)
else if (input.hasAttr("multiple")) else if (input.hasAttr("multiple"))
{ {
input.val(value.split(",")); input.val(value.split(","));
$(".selectpicker").selectpicker("render"); self.$(".selectpicker").selectpicker("render");
} }
else if (input.attr('type') == "file") else if (input.attr('type') == "file")
{ {
@ -128,16 +141,16 @@ function userfieldsform(Grocy)
var formGroup = input.parent().parent().parent(); var formGroup = input.parent().parent().parent();
formGroup.find("label.custom-file-label").text(fileName); 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").attr('href', self.Grocy.FormatUrl('/files/userfiles/' + value));
formGroup.find('.userfield-file-show').removeClass('d-none'); formGroup.find('.userfield-file-show').removeClass('d-none');
formGroup.find('img.userfield-current-file') 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')); .attr('src', self.Grocy.FormatUrl('/files/userfiles/' + value + '?force_serve_as=picture&best_fit_width=250&best_fit_height=250'));
LoadImagesLazy(); LoadImagesLazy();
formGroup.find('.userfield-file-delete').click( formGroup.find('.userfield-file-delete').click(
function() function()
{ {
formGroup.find("label.custom-file-label").text(Grocy.translate("No file selected")); formGroup.find("label.custom-file-label").text(self.Grocy.translate("No file selected"));
formGroup.find(".userfield-file-show").addClass('d-none'); formGroup.find(".userfield-file-show").addClass('d-none');
input.attr('data-old-file', fileSrc); input.attr('data-old-file', fileSrc);
} }
@ -167,6 +180,16 @@ function userfieldsform(Grocy)
input.val(value); input.val(value);
} }
}); });
self.$('.datetimepicker-wrapper').each(() =>
{
let picker = $(this);
var scopeId = uuid.v4();
picker.attr('id', scopeId);
this.components.push(new datetimepicker(self.Grocy, "#" + scopeId));
});
}, },
function(xhr) function(xhr)
{ {
@ -175,9 +198,10 @@ function userfieldsform(Grocy)
); );
} }
$(".userfield-link").keyup(function(e) onUserfieldInputKeyUp(_this)
{ {
var formRow = $(this).parent().parent();
var formRow = this.$(_this).parent().parent();
var title = formRow.find(".userfield-link-title").val(); var title = formRow.find(".userfield-link-title").val();
var link = formRow.find(".userfield-link-link").val(); var link = formRow.find(".userfield-link-link").val();
@ -187,7 +211,8 @@ function userfieldsform(Grocy)
}; };
formRow.find(".userfield-input").val(JSON.stringify(value)); formRow.find(".userfield-input").val(JSON.stringify(value));
});
}
} }
export { userfieldsform } export { userfieldsform }

View File

@ -1,86 +1,58 @@
function userpicker(Grocy) import BasePicker from "./BasePicker";
class userpicker extends BasePicker
{ {
constructor(Grocy, scopeSelector = null)
Grocy.Components.UserPicker = {};
Grocy.Components.UserPicker.GetPicker = function()
{ {
return $('#user_id'); super(Grocy, "#user_id", scopeSelector);
this.picker = this.$(this.basename);
this.input_element = this.$(this.basename + '_text_input');
this.initCombobox('.recipe-combobox');
this.prefill();
} }
Grocy.Components.UserPicker.GetInputElement = function() prefill()
{ {
return $('#user_id_text_input'); var doFocus = false;
} var possibleOptionElement = null;
Grocy.Components.UserPicker.GetValue = function() var prefillUser = this.picker.parent().data('prefill-by-username').toString();
{ if (typeof prefillUser !== "undefined")
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(); possibleOptionElement = this.$("#user_id option[data-additional-searchdata*=\"" + prefillUser + "\"]").first();
if (possibleOptionElement.length === 0)
{
possibleOptionElement = this.$("#user_id option:contains(\"" + prefillUser + "\")").first();
}
if (possibleOptionElement.length > 0)
{
doFocus = true;
this.picker.val(possibleOptionElement.val());
}
} }
if (possibleOptionElement.length > 0) var prefillUserId = this.picker.parent().data('prefill-by-user-id').toString();
if (typeof prefillUserId !== "undefined")
{ {
user_picker_doFocus = true; possibleOptionElement = this.$("#user_id option[value='" + prefillUserId + "']").first();
this_user_picker.val(possibleOptionElement.val()); if (possibleOptionElement.length > 0)
{
doFocus = true;
this.picker.val(possibleOptionElement.val());
}
} }
}
var prefillUserId = this_user_picker.parent().data('prefill-by-user-id').toString(); if (doFocus)
if (typeof prefillUserId !== "undefined")
{
possibleOptionElement = $("#user_id option[value='" + prefillUserId + "']").first();
if (possibleOptionElement.length > 0)
{ {
user_picker_doFocus = true; this.picker.data('combobox').refresh();
this_user_picker.val(possibleOptionElement.val()); this.picker.trigger('change');
this.$(this.picker.parent().data('next-input-selector').toString())
.focus();
} }
} }
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 } export { userpicker }

View File

@ -50,6 +50,8 @@ class GrocyClass
this.Components = {}; this.Components = {};
this.initComponents = []; this.initComponents = [];
this.RootGrocy = null;
// Init some classes // Init some classes
this.Api = new GrocyApi(this); this.Api = new GrocyApi(this);
@ -217,18 +219,20 @@ class GrocyClass
this.IdleTime += 1; this.IdleTime += 1;
} }
Use(componentName) Use(componentName, scope = null)
{ {
// initialize Components only once let scopeName = scope || "";
if (this.initComponents.find(elem => elem == componentName)) // initialize Components only once per scope
return; if (this.initComponents.find(elem => elem == componentName + scopeName))
return this.components[componentName + scopeName];
if (Object.prototype.hasOwnProperty.call(components, componentName)) if (Object.prototype.hasOwnProperty.call(components, componentName))
{ {
// add-then-init to resolve circular dependencies // add-then-init to resolve circular dependencies
this.initComponents.push(componentName); this.initComponents.push(componentName);
var component = components[componentName](this); var component = components[componentName](this, scope);
this.components[component.key] = component; this.components[componentName + scopeName] = component;
return component;
} }
else else
{ {

View File

@ -96,8 +96,5 @@ export
IsJsonString, IsJsonString,
BoolVal, BoolVal,
GetFileNameFromPath, GetFileNameFromPath,
RemoveUriParam,
UpdateUriParam,
GetUriParam,
EmptyElementWhenMatches EmptyElementWhenMatches
} }

View File

@ -7,7 +7,7 @@ class GrocyProxy
constructor(RootGrocy, scopeSelector, config, url) constructor(RootGrocy, scopeSelector, config, url)
{ {
this.rootGrocy = RootGrocy; this.RootGrocy = RootGrocy;
// proxy-local members, because they might not be set globally. // proxy-local members, because they might not be set globally.
this.QuantityUnits = config.QuantityUnits; this.QuantityUnits = config.QuantityUnits;
@ -38,9 +38,6 @@ class GrocyProxy
this.config = config; this.config = config;
// scoped variants of some helpers
this.FrontendHelpers = new GrocyFrontendHelpers(this, this.Api, this.scopeSelector);
this.configProxy = Proxy.revocable(this.config, { this.configProxy = Proxy.revocable(this.config, {
get: function(target, prop, receiver) get: function(target, prop, receiver)
{ {
@ -56,11 +53,9 @@ class GrocyProxy
}) })
// This is where the magic happens! // This is where the magic happens!
// basically, this Proxy object checks if a member // basically, this Proxy object checks if a member is defined in this proxy class,
// is defined in this proxy class, and returns it // and returns it if so.
// if so. // If not, the prop is handed over to the root grocy instance.
// If not, the prop is handed over to the root
// grocy instance.
this.grocyProxy = Proxy.revocable(this, { this.grocyProxy = Proxy.revocable(this, {
get: function(target, prop, receiver) get: function(target, prop, receiver)
{ {
@ -74,6 +69,9 @@ class GrocyProxy
} }
} }
}); });
// scoped variants of some helpers
this.FrontendHelpers = new GrocyFrontendHelpers(this, RootGrocy.Api, this.scopeSelector);
} }
Unload() Unload()
@ -82,18 +80,20 @@ class GrocyProxy
this.configProxy.revoke(); this.configProxy.revoke();
} }
Use(componentName) Use(componentName, scope = null)
{ {
// initialize Components only once let scopeName = scope || "";
if (this.initComponents.find(elem => elem == componentName)) // initialize Components only once per scope
return; if (this.initComponents.find(elem => elem == componentName + scopeName))
return this.components[componentName + scopeName];
if (Object.prototype.hasOwnProperty.call(components, componentName)) if (Object.prototype.hasOwnProperty.call(components, componentName))
{ {
// add-then-init to resolve circular dependencies // add-then-init to resolve circular dependencies
this.initComponents.push(componentName); this.initComponents.push(componentName);
var component = components[componentName](this, this.scopeSelector); var component = components[componentName](this, scope);
this.components[component.key] = component; this.components[componentName + scopeName] = component;
return component;
} }
else else
{ {
@ -125,6 +125,7 @@ class GrocyProxy
return currentParam[1] === undefined ? true : decodeURIComponent(currentParam[1]); return currentParam[1] === undefined ? true : decodeURIComponent(currentParam[1]);
} }
} }
return undefined;
} }
UpdateUriParam(key, value) UpdateUriParam(key, value)
@ -181,4 +182,6 @@ class GrocyProxy
} }
this.virtualUrl = vurl.substring(1); // remove leading & this.virtualUrl = vurl.substring(1); // remove leading &
} }
} }
export { GrocyProxy }

View File

@ -41,7 +41,8 @@
"swagger-ui-dist": "^3.32.1", "swagger-ui-dist": "^3.32.1",
"tempusdominus-bootstrap-4": "https://github.com/mistressofjellyfish/bootstrap-4.git#master", "tempusdominus-bootstrap-4": "https://github.com/mistressofjellyfish/bootstrap-4.git#master",
"timeago": "^1.6.7", "timeago": "^1.6.7",
"toastr": "^2.1.4" "toastr": "^2.1.4",
"uuid": "^8.3.2"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-commonjs": "^19.0.0", "@rollup/plugin-commonjs": "^19.0.0",

View File

@ -4755,6 +4755,7 @@ fsevents@~2.3.2:
tempusdominus-bootstrap-4: "https://github.com/mistressofjellyfish/bootstrap-4.git#master" tempusdominus-bootstrap-4: "https://github.com/mistressofjellyfish/bootstrap-4.git#master"
timeago: ^1.6.7 timeago: ^1.6.7
toastr: ^2.1.4 toastr: ^2.1.4
uuid: ^8.3.2
vinyl-buffer: ^1.0.1 vinyl-buffer: ^1.0.1
vinyl-source-stream: ^2.0.0 vinyl-source-stream: ^2.0.0
languageName: unknown languageName: unknown
@ -10818,7 +10819,7 @@ fsevents@~2.3.2:
languageName: node languageName: node
linkType: hard linkType: hard
"uuid@npm:^8.2.0, uuid@npm:^8.3.0": "uuid@npm:^8.2.0, uuid@npm:^8.3.0, uuid@npm:^8.3.2":
version: 8.3.2 version: 8.3.2
resolution: "uuid@npm:8.3.2" resolution: "uuid@npm:8.3.2"
bin: bin: