3
0
mirror of https://github.com/snipe/snipe-it.git synced 2026-08-18 11:15:42 +00:00

Convert custom fields edit/create to livewire

This commit is contained in:
snipe
2026-07-17 17:03:40 +01:00
parent 4a5b5aacd4
commit a32e64ac7f
3 changed files with 905 additions and 0 deletions

View File

@ -0,0 +1,384 @@
<?php
namespace App\Livewire;
use App\Helpers\Helper;
use App\Models\CustomField;
use App\Models\CustomFieldset;
use Illuminate\Support\Facades\Validator;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Locked;
use Livewire\Component;
class CustomFieldEditor extends Component
{
// Field identifier — null when creating, set when editing.
#[Locked]
public ?int $fieldId = null;
// Whether this is an edit (vs create). Controls validation uniqueness
// and format-lock behaviour.
#[Locked]
public bool $isEdit = false;
// Core field properties wired to the form.
public string $name = '';
public string $element = 'text';
public string $format = 'ANY';
public string $custom_format = '';
public bool $field_encrypted = false;
public string $field_values = '';
public string $help_text = '';
public bool $is_unique = false;
public bool $show_in_email = false;
public bool $display_in_user_view = false;
public bool $show_in_listview = false;
public bool $show_in_requestable_list = false;
public bool $display_checkin = false;
public bool $display_checkout = false;
public bool $display_audit = false;
public bool $auto_add_to_fieldsets = false;
// Array of fieldset IDs to associate. Keys are fieldset IDs, values are
// the fieldset ID (matching the associate_fieldsets[id]=id POST pattern).
public array $associate_fieldsets = [];
public function mount(?int $fieldId = null): void
{
if ($fieldId) {
$field = CustomField::findOrFail($fieldId);
$this->authorize('update', CustomField::class);
$this->fieldId = $field->id;
$this->isEdit = true;
$this->name = $field->name ?? '';
$this->element = $field->element ?? 'text';
$this->format = $field->format ?? 'ANY';
$this->custom_format = '';
if (stripos((string) $field->format, 'regex') === 0
&& $field->format !== CustomField::PREDEFINED_FORMATS['MAC']
) {
$this->format = 'CUSTOM REGEX';
$this->custom_format = $field->format;
}
$this->field_encrypted = (bool) $field->field_encrypted;
$this->field_values = $field->field_values ?? '';
$this->help_text = $field->help_text ?? '';
$this->is_unique = (bool) $field->is_unique;
$this->show_in_email = (bool) $field->show_in_email;
$this->display_in_user_view = (bool) $field->display_in_user_view;
$this->show_in_listview = (bool) $field->show_in_listview;
$this->show_in_requestable_list = (bool) $field->show_in_requestable_list;
$this->display_checkin = (bool) $field->display_checkin;
$this->display_checkout = (bool) $field->display_checkout;
$this->display_audit = (bool) $field->display_audit;
$this->auto_add_to_fieldsets = (bool) $field->auto_add_to_fieldsets;
$this->associate_fieldsets = $field->fieldset->pluck('id')->mapWithKeys(
fn ($id) => [$id => $id]
)->toArray();
} else {
$this->authorize('create', CustomField::class);
}
}
// Returns the list of element keys that are allowed given the current format.
#[Computed]
public function allowedElementKeys(): array
{
return CustomField::allowedElementKeysForFormat($this->format);
}
// Whether the format dropdown is locked entirely (readonly). Only true
// when editing a field that ALREADY has DATE/DATETIME format on disk,
// because those are backed by native date columns and can't be altered
// without a schema change. Other format changes are allowed but the
// UI warns the user first (see showFormatChangeWarning).
#[Computed]
public function isFormatLockedForEdit(): bool
{
if (! $this->isEdit || ! $this->fieldId) {
return false;
}
$orig = CustomField::find($this->fieldId)?->getOriginalFormat();
return in_array($orig, ['DATE', 'DATETIME']);
}
// Whether to show a warning that changing format may invalidate
// existing asset values under the new validation rule. Shown when
// editing an existing (non-locked) field where the user has picked a
// format different from what's persisted.
#[Computed]
public function showFormatChangeWarning(): bool
{
if (! $this->isEdit || ! $this->fieldId) {
return false;
}
$orig = CustomField::find($this->fieldId)?->getOriginalFormat();
return $orig !== null && $orig !== $this->format;
}
// Whether the current element is forced by the format (no user choice).
#[Computed]
public function elementForcedByFormat(): bool
{
return in_array($this->format, ['DATE', 'DATETIME']);
}
// Whether encryption is allowed for the current element/format combo.
// Layers the UI-only rule (no encryption toggle on edit) on top of the
// model's canEncryptFor() base compatibility check.
#[Computed]
public function canEncrypt(): bool
{
if ($this->isEdit) {
return false;
}
return CustomField::canEncryptFor($this->element, $this->format);
}
// Whether the field_values textarea should be shown.
#[Computed]
public function showFieldValues(): bool
{
return CustomField::elementRequiresFieldValues($this->element);
}
// Whether the custom regex input should be shown.
#[Computed]
public function showCustomRegex(): bool
{
return $this->format === 'CUSTOM REGEX';
}
// Whether to show the help note explaining encrypt use with date_picker/datetime_picker.
#[Computed]
public function showFormatPickerNote(): bool
{
return in_array($this->element, ['date_picker', 'datetime_picker'])
&& $this->format === 'ANY';
}
// Whether to show the encrypt-disabled note inside the encryption section.
#[Computed]
public function showEncryptDisabledNote(): bool
{
return in_array($this->format, ['DATE', 'DATETIME']);
}
// Full list of all element options for use in the view.
public function elementOptions(): array
{
return [
'text' => trans('admin/custom_fields/general.types.text'),
'listbox' => trans('admin/custom_fields/general.types.listbox'),
'textarea' => trans('admin/custom_fields/general.types.textarea'),
'markdown-textarea' => trans('admin/custom_fields/general.types.markdown-textarea'),
'checkbox' => trans('admin/custom_fields/general.types.checkbox'),
'radio' => trans('admin/custom_fields/general.types.radio'),
'date_picker' => trans('admin/custom_fields/general.types.date_picker'),
'datetime_picker' => trans('admin/custom_fields/general.types.datetime_picker'),
];
}
// Livewire lifecycle: fires when any property is updated.
public function updated(string $property, mixed $value): void
{
if ($property === 'format') {
$this->enforceElementForFormat();
}
if ($property === 'field_encrypted' && $value) {
// Encryption disables these display options.
$this->show_in_email = false;
$this->display_in_user_view = false;
$this->is_unique = false;
$this->show_in_requestable_list = false;
}
// Clear field_encrypted if element becomes checkbox/radio.
if ($property === 'element' && in_array($value, ['checkbox', 'radio'])) {
$this->field_encrypted = false;
}
}
// When format changes, force element to a valid choice.
protected function enforceElementForFormat(): void
{
$allowed = $this->allowedElementKeys;
if ($this->format === 'DATE') {
$this->element = 'date_picker';
$this->field_encrypted = false;
return;
}
if ($this->format === 'DATETIME') {
$this->element = 'datetime_picker';
$this->field_encrypted = false;
return;
}
// If current element is no longer in the allowed set, fall back to first.
if (! in_array($this->element, $allowed)) {
$this->element = $allowed[0] ?? 'text';
}
// If element was date_picker/datetime_picker but format is now not DATE/DATETIME,
// reset to text.
if (in_array($this->element, ['date_picker', 'datetime_picker'])
&& ! in_array($this->format, ['DATE', 'DATETIME'])
) {
$this->element = 'text';
}
}
// Validate and persist the field, then redirect.
public function save(): void
{
$this->authorize($this->isEdit ? 'update' : 'create', CustomField::class);
// Build validation rules matching CustomFieldRequest.
$nameRule = $this->isEdit ? 'required' : 'required|unique:custom_fields,name';
$rules = [
'name' => $nameRule,
'element' => 'required|in:text,listbox,textarea,markdown-textarea,checkbox,radio,date_picker,datetime_picker',
'format' => 'nullable|string|max:191',
'custom_format' => 'valid_regex',
];
$validator = Validator::make(
[
'name' => $this->name,
'element' => $this->element,
'format' => $this->format,
'custom_format' => $this->custom_format,
'associate_fieldsets' => $this->associate_fieldsets,
],
$rules
);
if ($validator->fails()) {
foreach ($validator->errors()->messages() as $field => $messages) {
foreach ($messages as $msg) {
$this->addError($field, $msg);
}
}
return;
}
// Resolve the effective format value.
$effectiveFormat = $this->format;
if ($this->format === 'CUSTOM REGEX' && $this->custom_format !== '') {
$effectiveFormat = $this->custom_format;
}
// checkbox/radio always use ANY format.
if (in_array($this->element, ['checkbox', 'radio'])) {
$effectiveFormat = 'ANY';
}
if ($this->isEdit) {
$field = CustomField::findOrFail($this->fieldId);
$field->name = trim($this->name);
$field->element = $this->element;
$field->field_values = $this->field_values;
$field->help_text = $this->help_text;
$field->show_in_listview = $this->show_in_listview;
$field->auto_add_to_fieldsets = $this->auto_add_to_fieldsets;
$field->display_checkin = $this->display_checkin;
$field->display_checkout = $this->display_checkout;
$field->display_audit = $this->display_audit;
// Only update display options when not encrypted.
if (! $field->field_encrypted) {
$field->show_in_email = $this->show_in_email;
$field->display_in_user_view = $this->display_in_user_view;
$field->is_unique = $this->is_unique;
$field->show_in_requestable_list = $this->show_in_requestable_list;
}
$field->format = $effectiveFormat;
} else {
$showInEmail = $this->field_encrypted ? false : $this->show_in_email;
$displayInUserView = $this->field_encrypted ? false : $this->display_in_user_view;
$field = new CustomField([
'name' => trim($this->name),
'element' => $this->element,
'help_text' => $this->help_text,
'field_values' => $this->field_values,
'field_encrypted' => $this->field_encrypted ? 1 : 0,
'show_in_email' => $showInEmail ? 1 : 0,
'is_unique' => $this->is_unique ? 1 : 0,
'display_in_user_view' => $displayInUserView ? 1 : 0,
'auto_add_to_fieldsets' => $this->auto_add_to_fieldsets ? 1 : 0,
'show_in_listview' => $this->show_in_listview ? 1 : 0,
'show_in_requestable_list' => $this->show_in_requestable_list ? 1 : 0,
'display_checkin' => $this->display_checkin ? 1 : 0,
'display_checkout' => $this->display_checkout ? 1 : 0,
'display_audit' => $this->display_audit ? 1 : 0,
]);
// Assigned directly rather than through mass assignment because
// created_by is intentionally not in $fillable — we don't want
// it settable from arbitrary request payloads.
$field->created_by = auth()->id();
$field->format = $effectiveFormat;
}
if ($field->save()) {
// Sync fieldset associations. Filter to truthy values then take the keys
// which are the fieldset IDs.
$fieldsetIds = array_keys(array_filter($this->associate_fieldsets));
$field->fieldset()->sync($fieldsetIds);
$message = $this->isEdit
? trans('admin/custom_fields/message.field.update.success')
: trans('admin/custom_fields/message.field.create.success');
$this->redirect(route('fields.index'), navigate: false);
session()->flash('success', $message);
return;
}
$this->addError(
'name',
$this->isEdit
? trans('admin/custom_fields/message.field.update.error')
: trans('admin/custom_fields/message.field.create.error')
);
}
public function render()
{
return view('livewire.custom-field-editor', [
'fieldsets' => CustomFieldset::orderBy('name')->get(),
'predefinedFormats' => Helper::predefined_formats(),
'elementOptions' => $this->elementOptions(),
]);
}
}

View File

@ -0,0 +1,123 @@
@props([
'name' => '',
'element' => 'text',
'format' => 'ANY',
'helpText' => '',
'fieldValues' => '',
])
@php
$displayName = trim((string) $name) !== '' ? $name : trans('admin/custom_fields/general.field_name');
$valuesArray = array_values(array_filter(array_map('trim', preg_split('/\r?\n/', (string) $fieldValues)), fn ($v) => $v !== ''));
$formatIcon = \App\Models\CustomField::iconForFormat($format);
@endphp
{{--
aria-hidden hides the preview from assistive tech (it's a visual
representation, not a real form field). Interactive widgets — date
pickers and select2 listboxes — need pointer events and focus to
open their popups, so pointer-events is applied per-element instead
of on the wrapper. onkeydown blocks Enter from triggering the outer
Livewire form's wire:submit while still allowing typing into the
interactive widgets themselves (select2 search, picker input).
--}}
<div
class="form-horizontal js-custom-field-preview"
aria-hidden="true"
onkeydown="if (event.key === 'Enter') { event.preventDefault(); event.stopPropagation(); }"
>
<div class="form-group">
<label class="col-md-4 control-label">{{ $displayName }}</label>
<div class="col-md-8">
@switch($element)
@case('text')
@if ($formatIcon)
<div class="input-group" style="pointer-events: none;">
<input type="text" class="form-control" tabindex="-1" placeholder="{{ trans('admin/custom_fields/general.types.text') }}">
<span class="input-group-addon"><x-icon :type="$formatIcon" /></span>
</div>
@else
<input type="text" class="form-control" tabindex="-1" style="pointer-events: none;" placeholder="{{ trans('admin/custom_fields/general.types.text') }}">
@endif
@break
@case('listbox')
<select
wire:key="preview-listbox"
class="select2 form-control js-preview-select2"
style="width: 100%;"
>
<option value=""></option>
@foreach ($valuesArray as $value)
<option>{{ $value }}</option>
@endforeach
</select>
@break
@case('textarea')
@case('markdown-textarea')
<textarea class="form-control" rows="3" tabindex="-1" style="pointer-events: none;" placeholder="{{ $element === 'markdown-textarea' ? 'Markdown' : trans('admin/custom_fields/general.types.textarea') }}"></textarea>
@break
@case('checkbox')
@forelse ($valuesArray as $value)
<label class="form-control" style="pointer-events: none;">
<input type="checkbox" tabindex="-1"> {{ $value }}
</label>
@empty
<label class="form-control" style="pointer-events: none;">
<input type="checkbox" tabindex="-1"> {{ trans('admin/custom_fields/general.field_values') }}
</label>
@endforelse
@break
@case('radio')
@forelse ($valuesArray as $value)
<label class="form-control" style="pointer-events: none;">
<input type="radio" tabindex="-1"> {{ $value }}
</label>
@empty
<label class="form-control" style="pointer-events: none;">
<input type="radio" tabindex="-1"> {{ trans('admin/custom_fields/general.field_values') }}
</label>
@endforelse
@break
@case('date_picker')
<div
wire:key="preview-date-picker"
class="input-group date js-preview-datetimepicker"
data-provide="datetimepicker"
data-format="YYYY-MM-DD"
data-default-now="false"
>
<input type="text" class="form-control" placeholder="YYYY-MM-DD">
<span class="input-group-addon"><x-icon type="calendar" /></span>
</div>
@break
@case('datetime_picker')
<div
wire:key="preview-datetime-picker"
class="input-group date js-preview-datetimepicker"
data-provide="datetimepicker"
data-format="YYYY-MM-DD HH:mm:ss"
data-default-now="false"
>
<input type="text" class="form-control" placeholder="YYYY-MM-DD HH:MM:SS">
<span class="input-group-addon"><x-icon type="calendar" /></span>
</div>
@break
@default
<input type="text" class="form-control" tabindex="-1" style="pointer-events: none;">
@endswitch
@if (trim((string) $helpText) !== '')
<p class="help-block">{{ $helpText }}</p>
@endif
</div>
</div>
</div>

View File

@ -0,0 +1,398 @@
<div>
<form wire:submit.prevent="save" class="form-horizontal" autocomplete="off" role="form">
<x-container columns="2">
<x-page-column class="col-md-8">
<x-box top_submit>
<x-form.row
:label="trans('admin/custom_fields/general.field_name')"
name="name"
required
>
<x-slot:input>
<input
type="text"
id="name"
class="form-control"
maxlength="191"
wire:model.live="name"
aria-label="{{ trans('admin/custom_fields/general.field_name') }}"
required
>
</x-slot:input>
</x-form.row>
<x-form.row
:label="trans('admin/custom_fields/general.field_format')"
name="format"
required
>
<x-slot:input>
@if ($this->isFormatLockedForEdit)
<input
type="text"
id="format"
class="form-control"
value="{{ $format }}"
readonly
aria-label="{{ trans('admin/custom_fields/general.field_format') }}"
>
<x-form.help name="format-locked">
{{ trans('admin/custom_fields/general.format_locked_for_native_column', ['format' => $format]) }}
</x-form.help>
@else
<x-input.select
forLivewire
id="format"
name="format"
wire:model.live="format"
class="format form-control"
style="width:100%"
aria-label="format"
>
@foreach ($predefinedFormats as $key => $label)
<option
value="{{ $key }}"
@selected($format === $key)
@disabled($isEdit && in_array($key, ['DATE', 'DATETIME'], true))
>{{ $label }}</option>
@endforeach
</x-input.select>
<x-form.help name="format">
{{ trans('admin/custom_fields/general.field_format_help') }}
</x-form.help>
@if ($isEdit)
<x-form.help name="date_formats_disabled">
{{ trans('admin/custom_fields/general.date_formats_disabled_for_edit') }}
</x-form.help>
@endif
@if ($this->showFormatChangeWarning)
<x-callout type="warning" icon="warning" style="margin-top: 10px;">
{{ trans('admin/custom_fields/general.format_change_warning') }}
</x-callout>
@endif
@if ($this->showFormatPickerNote)
<x-form.help name="format_picker_note">
{{ trans('admin/custom_fields/general.format_any_with_date_picker_help') }}
</x-form.help>
@endif
@endif
</x-slot:input>
</x-form.row>
<x-form.row
:label="trans('admin/custom_fields/general.field_element')"
name="element"
required
>
<x-slot:input>
<x-input.select
forLivewire
id="element"
name="element"
wire:model.live="element"
class="field_element form-control"
style="width: 100%;"
aria-label="element"
>
@foreach ($elementOptions as $key => $label)
<option
value="{{ $key }}"
@selected($element === $key)
@disabled(! in_array($key, $this->allowedElementKeys))
>{{ $label }}</option>
@endforeach
</x-input.select>
<x-form.help name="element">
{{ trans('admin/custom_fields/general.field_element_help') }}
</x-form.help>
</x-slot:input>
</x-form.row>
@if ($this->showFieldValues)
<x-form.row
:label="trans('admin/custom_fields/general.field_values')"
name="field_values"
required
:help_text="trans('admin/custom_fields/general.field_values_help')"
>
<x-slot:input>
<textarea
id="field_values"
class="form-control"
wire:model.live="field_values"
style="width: 100%"
rows="4"
aria-label="field_values"
>{{ $field_values }}</textarea>
</x-slot:input>
</x-form.row>
@endif
@if ($this->showCustomRegex)
<x-form.row
:label="trans('admin/custom_fields/general.field_custom_format')"
name="custom_format"
required
:help_html="trans('admin/custom_fields/general.field_custom_format_help')"
>
<x-slot:input>
<input
type="text"
id="custom_format"
class="form-control"
maxlength="191"
placeholder="regex:/^[0-9]{15}$/"
wire:model.live="custom_format"
aria-label="custom_format"
>
</x-slot:input>
</x-form.row>
@endif
<x-form.row
:label="trans('admin/custom_fields/general.help_text')"
name="help_text"
:help_text="trans('admin/custom_fields/general.help_text_description')"
>
<x-slot:input>
<input
type="text"
id="help_text"
class="form-control"
wire:model.live="help_text"
aria-label="help_text"
>
</x-slot:input>
</x-form.row>
@if (! $isEdit)
<x-form.checkbox-row
name="field_encrypted"
:label="trans('admin/custom_fields/general.encrypt_field')"
:checked="$field_encrypted"
:disabled="! $this->canEncrypt"
:help_text="$this->showEncryptDisabledNote ? trans('admin/custom_fields/general.encrypt_disabled_for_date_format') : null"
wire:model.live="field_encrypted"
/>
@if ($field_encrypted)
<div class="form-group">
<div class="col-md-9 col-md-offset-3">
<x-callout type="danger" icon="warning" live="assertive">
{{ trans('admin/custom_fields/general.encrypt_field_help') }}
</x-callout>
</div>
</div>
@endif
@endif
@if ($isEdit && $field_encrypted)
<div class="form-group">
<div class="col-md-9 col-md-offset-3">
<x-alert type="warning" icon="warning" :title="trans('general.notification_warning')">
{{ trans('admin/custom_fields/general.encrypted_options') }}
</x-alert>
</div>
</div>
@endif
@if (! $field_encrypted)
<x-form.checkbox-row
name="is_unique"
:label="trans('admin/custom_fields/general.is_unique')"
:checked="$is_unique"
wire:model.live="is_unique"
/>
@endif
<fieldset>
<x-form.legend>
{{ trans('admin/custom_fields/general.section_visibility') }}
</x-form.legend>
<x-form.checkbox-row
name="show_in_listview"
:label="trans('admin/custom_fields/general.show_in_listview')"
:checked="$show_in_listview"
wire:model.live="show_in_listview"
/>
@if (! $field_encrypted)
<x-form.checkbox-row
name="show_in_requestable_list"
:label="trans('admin/custom_fields/general.show_in_requestable_list')"
:checked="$show_in_requestable_list"
wire:model.live="show_in_requestable_list"
/>
<x-form.checkbox-row
name="show_in_email"
:label="trans('admin/custom_fields/general.show_in_email')"
:checked="$show_in_email"
wire:model.live="show_in_email"
/>
<x-form.checkbox-row
name="display_in_user_view"
:label="trans('admin/custom_fields/general.display_in_user_view')"
:checked="$display_in_user_view"
wire:model.live="display_in_user_view"
/>
@endif
</fieldset>
<fieldset>
<x-form.legend>
{{ trans('admin/custom_fields/general.section_display_on_forms') }}
</x-form.legend>
<x-form.checkbox-row
name="display_checkout"
:label="trans('admin/custom_fields/general.display_checkout')"
:checked="$display_checkout"
wire:model.live="display_checkout"
/>
<x-form.checkbox-row
name="display_checkin"
:label="trans('admin/custom_fields/general.display_checkin')"
:checked="$display_checkin"
wire:model.live="display_checkin"
/>
<x-form.checkbox-row
name="display_audit"
:label="trans('admin/custom_fields/general.display_audit')"
:checked="$display_audit"
wire:model.live="display_audit"
/>
</fieldset>
</x-box>
</x-page-column>
<x-page-column class="col-md-4">
<x-box :header="trans('general.preview')">
<x-custom-field-preview
:name="$name"
:element="$element"
:format="$format"
:help-text="$help_text"
:field-values="$field_values"
/>
</x-box>
<x-box :header="trans('admin/custom_fields/general.fieldsets')">
@if ($fieldsets->count() > 0)
<label class="form-control">
<x-input.checkbox
id="lw-check-all-fieldsets"
aria-label="{{ trans('general.select_all') }}"
/>
{{ trans('general.select_all') }}
</label>
@foreach ($fieldsets as $fieldset)
<label class="form-control">
<x-input.checkbox
:name="'associate_fieldsets.'.$fieldset->id"
:value="$fieldset->id"
:checked="(bool) ($associate_fieldsets[$fieldset->id] ?? false)"
class="lw-fieldset-check"
:aria-label="$fieldset->name"
wire:model.live="associate_fieldsets.{{ $fieldset->id }}"
/>
{{ $fieldset->name }}
</label>
@endforeach
@endif
<label class="form-control">
<x-input.checkbox
name="auto_add_to_fieldsets"
:checked="$auto_add_to_fieldsets"
aria-label="auto_add_to_fieldsets"
wire:model.live="auto_add_to_fieldsets"
/>
{{ trans('admin/custom_fields/general.auto_add_to_fieldsets') }}
</label>
</x-box>
</x-page-column>
</x-container>
</form>
@script
<script>
(function () {
const checkAll = document.getElementById('lw-check-all-fieldsets');
if (checkAll) {
checkAll.addEventListener('change', function () {
document.querySelectorAll('.lw-fieldset-check').forEach(function (cb) {
cb.checked = checkAll.checked;
cb.dispatchEvent(new Event('change'));
});
});
}
// Preview widgets (select2 on listbox, eonasdan datetimepicker on
// date/datetime pickers) need to be (re-)initialized every time
// Livewire morphs the DOM — when the user changes the element type
// or edits field_values, the preview markup is fresh and any
// previous widget wrappers are gone. On initial page load the
// global inits in snipeit.js catch these; the interceptor below
// catches every subsequent morph.
//
// We destroy any existing widget instance before re-init because
// Livewire may reuse the same DOM node between morphs (wire:key
// on the preview blade forces replacement for element-type
// changes; this covers the in-place case too — e.g., editing
// field_values on a listbox, or if a wire:key ever gets missed).
const initPreviewWidgets = function () {
const $scope = $('.js-custom-field-preview');
if (!$scope.length) return;
$scope.find('.js-preview-select2').each(function () {
const $el = $(this);
if ($el.hasClass('select2-hidden-accessible')) {
$el.select2('destroy');
}
$el.select2();
});
$scope.find('.js-preview-datetimepicker').each(function () {
const $el = $(this);
const existing = $el.data('DateTimePicker');
if (existing) {
existing.destroy();
}
});
if (typeof window.snipeitInitDatetimepickers === 'function') {
window.snipeitInitDatetimepickers($scope);
}
};
Livewire.interceptMessage(({ onFinish }) => {
onFinish(() => queueMicrotask(initPreviewWidgets));
});
initPreviewWidgets();
})();
</script>
@endscript
</div>