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:
384
app/Livewire/CustomFieldEditor.php
Normal file
384
app/Livewire/CustomFieldEditor.php
Normal 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(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
123
resources/views/blade/custom-field-preview.blade.php
Normal file
123
resources/views/blade/custom-field-preview.blade.php
Normal 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>
|
||||
398
resources/views/livewire/custom-field-editor.blade.php
Normal file
398
resources/views/livewire/custom-field-editor.blade.php
Normal 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>
|
||||
Reference in New Issue
Block a user