From 5cc6bad2ada5f3952efe51847c2457a2032b84d1 Mon Sep 17 00:00:00 2001 From: snipe Date: Thu, 2 Jul 2026 12:38:19 +0100 Subject: [PATCH] Added blade components for checkboxes, radios, and inline checkboxes --- resources/views/account/profile.blade.php | 26 +-- .../blade/form/checkbox-inline.blade.php | 41 ++++ .../views/blade/form/checkbox-row.blade.php | 144 +++++++++++++ .../views/blade/form/radio-row.blade.php | 87 ++++++++ .../views/blade/input/checkbox.blade.php | 19 ++ resources/views/blade/input/radio.blade.php | 19 ++ .../views/hardware/bulk-checkin.blade.php | 20 +- resources/views/hardware/bulk.blade.php | 40 ++-- resources/views/hardware/checkin.blade.php | 20 +- resources/views/models/bulk-edit.blade.php | 28 +-- .../views/reports/custom/component.blade.php | 41 +--- resources/views/settings/alerts.blade.php | 56 ++--- resources/views/settings/general.blade.php | 116 ++++------ tests/Feature/Blade/CheckboxInlineTest.php | 130 +++++++++++ tests/Feature/Blade/CheckboxRowTest.php | 197 +++++++++++++++++ tests/Feature/Blade/RadioRowTest.php | 202 ++++++++++++++++++ tests/Unit/Rules/ExternalUrlTest.php | 5 +- 17 files changed, 964 insertions(+), 227 deletions(-) create mode 100644 resources/views/blade/form/checkbox-inline.blade.php create mode 100644 resources/views/blade/form/checkbox-row.blade.php create mode 100644 resources/views/blade/form/radio-row.blade.php create mode 100644 resources/views/blade/input/checkbox.blade.php create mode 100644 resources/views/blade/input/radio.blade.php create mode 100644 tests/Feature/Blade/CheckboxInlineTest.php create mode 100644 tests/Feature/Blade/CheckboxRowTest.php create mode 100644 tests/Feature/Blade/RadioRowTest.php diff --git a/resources/views/account/profile.blade.php b/resources/views/account/profile.blade.php index 84f72c2f30..5c74e8fa5a 100755 --- a/resources/views/account/profile.blade.php +++ b/resources/views/account/profile.blade.php @@ -84,23 +84,17 @@ -
-
- -
-
+ -
-
- -
-
+ diff --git a/resources/views/blade/form/checkbox-inline.blade.php b/resources/views/blade/form/checkbox-inline.blade.php new file mode 100644 index 0000000000..c6312bb70e --- /dev/null +++ b/resources/views/blade/form/checkbox-inline.blade.php @@ -0,0 +1,41 @@ +@props([ + 'name' => null, + 'item' => null, + 'label' => null, + 'value' => '1', + 'required' => null, + 'disabled' => false, +]) + +@php + // Old-input aware check-state. On a fresh render, session()->hasOldInput() + // is false, so we fall back to the model. On a validation-failure redisplay, + // hasOldInput() is true and we trust the (possibly missing) old value — an + // unchecked box comes back correctly unchecked instead of falling through + // to the stale $item->{$name}. + $is_redisplay = session()->hasOldInput(); + $checked = $is_redisplay + ? (bool) old($name) + : (bool) ($item?->{$name} ?? false); + + // Helper::checkIfRequired dereferences $item statically via $item::rules(), + // so it needs a real class/object. Fall back to false when no model was + // supplied (transient forms have no persistent model). + $really_required = $required ?? ($item ? Helper::checkIfRequired($item, $name) : false); +@endphp + +{{-- Inline variant: no form-group wrapper. The caller drops this into an + existing form row (e.g. next to a text input in a bulk-edit view) and + controls the containing column themselves. --}} + diff --git a/resources/views/blade/form/checkbox-row.blade.php b/resources/views/blade/form/checkbox-row.blade.php new file mode 100644 index 0000000000..1f25267884 --- /dev/null +++ b/resources/views/blade/form/checkbox-row.blade.php @@ -0,0 +1,144 @@ +@props([ + 'name' => null, + 'item' => null, + 'label' => null, + 'options' => null, + 'selected' => null, + 'value' => '1', + 'required' => null, + 'disabled' => false, + 'help_text' => null, + 'info_tooltip_text' => null, + // Default input column: only skip the offset when a left-hand label + // column is being rendered (i.e. multi mode with a label). Single mode + // never has a left label; multi mode without a label lays out the same + // way. Keeping the grid classes centralized here means a future Bootstrap / + // AdminLTE upgrade only has to touch this file, not every callsite. + // Note: Blade evaluates @props defaults twice (once via extractPropNames + // before caller attrs are bound, once when applying defaults after). The + // `?? null` guards against "undefined variable" on the first pass; isset() + // is inherently safe against undefined vars, is_array() is not. + 'input_div_class' => (is_array($options ?? null) && isset($label)) ? 'col-md-8' : 'col-md-8 col-md-offset-3', +]) + +@php + // Multi-checkbox mode kicks in when the caller supplies an $options map; + // otherwise this renders as a single boolean checkbox. + $is_multi = is_array($options); + + // Old-input aware check-state. On a fresh render, session()->hasOldInput() + // is false, so we fall back to the model (or supplied :selected). On a + // validation-failure redisplay, hasOldInput() is true and we trust the + // (possibly missing) old value — an unchecked box comes back correctly + // unchecked instead of falling through to the stale $item->{$name}. + $is_redisplay = session()->hasOldInput(); + + if (! $is_multi) { + $single_checked = $is_redisplay + ? (bool) old($name) + : (bool) ($item?->{$name} ?? false); + + // Helper::checkIfRequired dereferences $item statically via $item::rules(), + // so it needs a real class/object. Fall back to false when no model was + // supplied (transient forms have no persistent model). + $really_required = $required ?? ($item ? Helper::checkIfRequired($item, $name) : false); + } else { + // For multi mode, callers can pass :selected as an array of currently- + // selected values, a comma-joined string (common when the model stores + // it that way, e.g. Setting's modellist_displays), or a callable + // (value): bool for per-value predicates. When :selected is omitted + // the same fallback is applied to $item->{$name}. + if ($selected === null) { + $selected = $item?->{$name}; + } + + if (is_string($selected)) { + $selected = $selected === '' ? [] : array_map('trim', explode(',', $selected)); + } + + $old_values = is_array(old($name)) ? old($name) : []; + + $is_checked = function ($value) use ($is_redisplay, $old_values, $selected) { + if ($is_redisplay) { + return in_array($value, $old_values); + } + if (is_callable($selected)) { + return (bool) $selected($value); + } + return in_array($value, is_array($selected) ? $selected : []); + }; + } + + $errors_class = $errors->has($name) ? ' has-error' : ''; +@endphp + +
merge(['class' => 'form-group'.$errors_class]) }}> + + @if (! $is_multi) + + {{-- Single checkbox: no left-hand label column; label wraps the input. --}} +
+ +
+ + @else + + {{-- Multi: standard left-hand label + a stack of wrapped checkboxes on the right. --}} + @if (isset($label)) + {{ $label }} + @endif + +
+ @foreach ($options as $option_value => $option_label) + + @endforeach +
+ + @endif + + @if ($info_tooltip_text) +
+ + {{ $info_tooltip_text }} + +
+ @endif + + @error($name) +
+ +
+ @enderror + + @if ($help_text) +
+

+ {!! $help_text !!} +

+
+ @endif + +
diff --git a/resources/views/blade/form/radio-row.blade.php b/resources/views/blade/form/radio-row.blade.php new file mode 100644 index 0000000000..a19ce52300 --- /dev/null +++ b/resources/views/blade/form/radio-row.blade.php @@ -0,0 +1,87 @@ +@props([ + 'name' => null, + 'item' => null, + 'label' => null, + 'options' => [], + 'selected' => null, + 'required' => null, + 'disabled' => false, + 'help_text' => null, + 'info_tooltip_text' => null, + // Default input column depends on whether the row has a left-hand label. + // With a label, the row already spends col-md-3 on the left; without one + // the options need to be offset. Concentrating the grid class here means + // a future Bootstrap / AdminLTE upgrade only has to touch this file, not + // every place it's invoked. + 'input_div_class' => isset($label) ? 'col-md-8' : 'col-md-8 col-md-offset-3', +]) + +@php + // Redisplay-safe current value. On validation-failure redisplay + // session()->hasOldInput() is true and we trust old($name); on fresh + // render we take the caller's :selected value, or fall back to the + // model attribute. This is the same guard as checkbox-row: an old-input + // value of null on redisplay means "nothing selected", not "fall back + // to the stale model default". + $is_redisplay = session()->hasOldInput(); + if ($is_redisplay) { + $current_value = old($name); + } else { + $current_value = $selected ?? $item?->{$name}; + } + + // Helper::checkIfRequired dereferences $item statically via $item::rules(), + // so it needs a real class/object. Fall back to false when no model was + // supplied (transient forms like bulk checkin have no persistent model). + $really_required = $required ?? ($item ? Helper::checkIfRequired($item, $name) : false); + $errors_class = $errors->has($name) ? ' has-error' : ''; +@endphp + +
merge(['class' => 'form-group'.$errors_class]) }}> + + @if (isset($label)) + {{ $label }} + @endif + +
+ @foreach ($options as $option_value => $option_label) + + @endforeach +
+ + @if ($info_tooltip_text) +
+ + {{ $info_tooltip_text }} + +
+ @endif + + @error($name) +
+ +
+ @enderror + + @if ($help_text) +
+

+ {!! $help_text !!} +

+
+ @endif + +
diff --git a/resources/views/blade/input/checkbox.blade.php b/resources/views/blade/input/checkbox.blade.php new file mode 100644 index 0000000000..350017c665 --- /dev/null +++ b/resources/views/blade/input/checkbox.blade.php @@ -0,0 +1,19 @@ +@props([ + 'name' => null, + 'value' => '1', + 'checked' => false, + 'required' => false, + 'disabled' => false, + 'id' => null, +]) + + diff --git a/resources/views/blade/input/radio.blade.php b/resources/views/blade/input/radio.blade.php new file mode 100644 index 0000000000..b547cf1d93 --- /dev/null +++ b/resources/views/blade/input/radio.blade.php @@ -0,0 +1,19 @@ +@props([ + 'name' => null, + 'value' => null, + 'checked' => false, + 'required' => false, + 'disabled' => false, + 'id' => null, +]) + + diff --git a/resources/views/hardware/bulk-checkin.blade.php b/resources/views/hardware/bulk-checkin.blade.php index 2de782c236..5c2310f03b 100644 --- a/resources/views/hardware/bulk-checkin.blade.php +++ b/resources/views/hardware/bulk-checkin.blade.php @@ -81,18 +81,14 @@ /> -
-
- - -
-
+
diff --git a/resources/views/hardware/bulk.blade.php b/resources/views/hardware/bulk.blade.php index 7d3868334c..c1da699313 100755 --- a/resources/views/hardware/bulk.blade.php +++ b/resources/views/hardware/bulk.blade.php @@ -46,10 +46,10 @@ :message') !!}
- +
@@ -67,10 +67,10 @@ {!! $errors->first('purchase_date', '') !!}
- +
@@ -87,10 +87,10 @@ {!! $errors->first('expected_checkin', '') !!}
- +
@@ -107,10 +107,10 @@ {!! $errors->first('asset_eol_date', '') !!}
- +
@@ -225,10 +225,10 @@ {!! $errors->first('next_audit_date', '') !!}
- +

{!! trans('general.next_audit_date_help') !!}

diff --git a/resources/views/hardware/checkin.blade.php b/resources/views/hardware/checkin.blade.php index 036f09052b..45e8aab4ea 100755 --- a/resources/views/hardware/checkin.blade.php +++ b/resources/views/hardware/checkin.blade.php @@ -145,18 +145,14 @@ /> -
-
- - -
-
+
diff --git a/resources/views/models/bulk-edit.blade.php b/resources/views/models/bulk-edit.blade.php index 4108c1252b..2bb458d99c 100644 --- a/resources/views/models/bulk-edit.blade.php +++ b/resources/views/models/bulk-edit.blade.php @@ -102,25 +102,15 @@
-
-
- - - - - - -
-
+ @foreach ($models as $model) diff --git a/resources/views/reports/custom/component.blade.php b/resources/views/reports/custom/component.blade.php index c5263f13b6..112a189088 100644 --- a/resources/views/reports/custom/component.blade.php +++ b/resources/views/reports/custom/component.blade.php @@ -420,38 +420,15 @@
-
- - - -
+ diff --git a/resources/views/settings/alerts.blade.php b/resources/views/settings/alerts.blade.php index 733abeee34..3090254820 100644 --- a/resources/views/settings/alerts.blade.php +++ b/resources/views/settings/alerts.blade.php @@ -47,24 +47,18 @@ -
-
- -
-
+ -
-
- -
-
+ @@ -106,28 +100,14 @@ {!! $errors->first('admin_cc_email', '
') !!} -
-
- - -
-
+
diff --git a/resources/views/settings/general.blade.php b/resources/views/settings/general.blade.php index d807b521be..c1267b501b 100644 --- a/resources/views/settings/general.blade.php +++ b/resources/views/settings/general.blade.php @@ -39,18 +39,12 @@ {{ trans('admin/settings/general.legends.scoping') }} -
-
- - {!! $errors->first('full_multiple_companies_support', '') !!} -

- {{ trans('admin/settings/general.full_multiple_companies_support_help_text') }} -

-
-
+ @@ -62,18 +56,13 @@ -
-
- - {!! $errors->first('null_company_is_floater', '') !!} -

- {{ trans('admin/settings/general.null_company_is_floater_help_text') }} -

-
-
+
@@ -156,16 +145,12 @@ -
-
- - {!! $errors->first('require_accept_signature', '') !!} -

{{ trans('admin/settings/general.require_accept_signature_help_text') }}

-
-
+ @@ -204,53 +189,34 @@ -
-
- {{ trans('admin/settings/general.show_in_model_list') }} -
-
- - - - -
-
+ -
-
- - {!! $errors->first('shortcuts_enabled', '') !!} -

{!!trans('admin/settings/general.shortcuts_help_text') !!}

-
-
+ -
-
- - {!! $errors->first('show_archived_in_list', '') !!} -
-
+
diff --git a/tests/Feature/Blade/CheckboxInlineTest.php b/tests/Feature/Blade/CheckboxInlineTest.php new file mode 100644 index 0000000000..6c83be3073 --- /dev/null +++ b/tests/Feature/Blade/CheckboxInlineTest.php @@ -0,0 +1,130 @@ +withoutMiddleware([ + CheckForSetup::class, + CheckForDebug::class, + ]); + if ($oldInput !== null) { + $call = $call->withSession(['_old_input' => $oldInput]); + } + + return $call->get('/__test/checkbox-inline')->assertOk()->getContent(); + } + + private function item(): object + { + return new class + { + public bool $enabled = true; + + public bool $disabled_flag = false; + + public static function rules() + { + return ['enabled' => 'required|boolean']; + } + }; + } + + public function test_does_not_emit_form_group_wrapper() + { + // Inline variant must not carry a form-group class; the caller + // controls its own row layout in bulk-edit views. + $html = $this->render([ + 'name' => 'null_name', + 'label' => 'Set to null', + ]); + + $this->assertStringNotContainsString('form-group', $html); + $this->assertStringContainsString('