mirror of
https://github.com/filamentphp/filament.git
synced 2026-10-24 23:35:30 +08:00
File uploads + file specific validation rules.
This commit is contained in:
Vendored
+134848
-1
File diff suppressed because one or more lines are too long
Vendored
+1850
-1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"/js/app.js": "/js/app.js?id=f4941a7bc57a4dca4d67",
|
||||
"/css/app.css": "/css/app.css?id=0870e53e0181c1bfacce"
|
||||
"/js/app.js": "/js/app.js?id=857538578ac2a532a0c9",
|
||||
"/css/app.css": "/css/app.css?id=136cbb0e08cc3a2b0537"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<label {{ $attributes->merge(['class' => 'inline-flex items-center']) }}>
|
||||
<input
|
||||
type="{{ $type }}"
|
||||
class="form-checkbox dark:bg-gray-900 dark:border-gray-700 h-4 w-4 transition duration-150 ease-in-out
|
||||
class="form-{{ $type }} dark:bg-gray-900 dark:border-gray-700 h-4 w-4 transition duration-150 ease-in-out
|
||||
@error($name)
|
||||
text-red-500
|
||||
@else
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
@auth
|
||||
@pushonce('scripts')
|
||||
<script data-turbolinks-eval="false">
|
||||
<script>
|
||||
/*
|
||||
* Code is modified from kdion4891 and inspired by Pastor Ryan Hayden
|
||||
* https://github.com/livewire/livewire/issues/106
|
||||
@@ -21,11 +21,16 @@
|
||||
document.querySelectorAll('input[type="file"]').forEach(file => {
|
||||
file.addEventListener('change', event => {
|
||||
let form_data = new FormData();
|
||||
|
||||
form_data.append('component', @json(get_class($this)));
|
||||
form_data.append('field_name', file.name);
|
||||
form_data.append('validation_rules', file.getAttribute('data-validation-rules'));
|
||||
form_data.append('validation_messages', file.getAttribute('data-validation-messages'));
|
||||
|
||||
for (let i = 0; i < event.target.files.length; i++) {
|
||||
form_data.append('files[]', event.target.files[i]);
|
||||
}
|
||||
|
||||
fetch('{{ route('filament.admin.file-upload') }}', {
|
||||
method: 'POST',
|
||||
body: form_data,
|
||||
@@ -38,9 +43,13 @@
|
||||
}
|
||||
return Promise.reject(response);
|
||||
}).then(function (data) {
|
||||
window.livewire.emit('filament.fileUpdate', data.field_name, data.uploaded_files);
|
||||
if (data.errors) {
|
||||
window.livewire.emit('filament.fileUploadError', data.field_name, data.errors);
|
||||
} else {
|
||||
window.livewire.emit('filament.fileUpdate', data.field_name, data.uploaded_files);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.warn('File upload error', error);
|
||||
window.livewire.emit('filament.fileUploadError', file.name, error.statusText);
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<ol class="list-unstyled">
|
||||
@foreach ($field->options as $value => $label)
|
||||
<li class="mb-1">
|
||||
<x-filament-checkbox :name="$field->key" :label="$label" :value="$value" :model="$field->key.'.'.$loop->index" />
|
||||
<x-filament-checkbox :name="$field->key" :label="$label" :value="$value" :model="$field->key" />
|
||||
</li>
|
||||
@endforeach
|
||||
</ol>
|
||||
|
||||
@@ -12,11 +12,15 @@
|
||||
<input
|
||||
name="{{ $field->name }}"
|
||||
type="file"
|
||||
class="form-input dark:text-gray-50 dark:bg-gray-900 dark:border-gray-700 block w-full sm:text-sm sm:leading-5
|
||||
class="
|
||||
form-input dark:text-gray-50 dark:bg-gray-900 dark:border-gray-700 block w-full sm:text-sm sm:leading-5
|
||||
@error($field->key)
|
||||
pr-10 border-red-300 dark:border-red-500 text-red-900 placeholder-red-500 dark:placeholder-red-500 focus:border-red-500 dark-focus:border-red-500 focus:shadow-outline-red dark-focus:shadow-outline-red
|
||||
@enderror"
|
||||
@enderror
|
||||
"
|
||||
{{ $field->file_multiple ? 'multiple' : '' }}
|
||||
data-validation-rules='@json($field->file_rules)'
|
||||
data-validation-messages='@json($field->file_validation_messages)'
|
||||
>
|
||||
@error($field->key)
|
||||
<div class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||
@@ -40,7 +44,6 @@
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
{{ var_dump($this->form_data[$field->name]) }}
|
||||
@endif
|
||||
@include('filament::fields.error-help')
|
||||
</fieldset>
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
<div class="mb-5">
|
||||
<x-filament-checkbox type="radio" :name="$field->key" :label="$field->placeholder ?? $field->label" :value="$field->value" :model="$field->key" />
|
||||
<fieldset class="mb-5">
|
||||
<legend class="text-sm font-medium leading-5 mb-1">
|
||||
{{ $field->label }}
|
||||
</legend>
|
||||
<ol class="list-unstyled">
|
||||
@foreach ($field->options as $value => $label)
|
||||
<li class="mb-1">
|
||||
<x-filament-checkbox type="radio" :name="$field->key" :label="$label" :value="$value" :model="$field->key" />
|
||||
</li>
|
||||
@endforeach
|
||||
</ol>
|
||||
@include('filament::fields.error-help')
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
<div class="flex-shrink-0">
|
||||
@switch($type)
|
||||
@case('success')
|
||||
{{ Filament::svg('heroicons/outline-md/md-check-circle', 'w-5 h-5 text-green-400') }}
|
||||
{{ Filament::svg('heroicons/outline-md/md-check-circle', 'w-5 h-5 text-green-500') }}
|
||||
@break
|
||||
@case('error')
|
||||
{{ Filament::svg('heroicons/solid-sm/sm-exclamation-circle', 'h-5 w-5 text-red-400') }}
|
||||
{{ Filament::svg('heroicons/outline-md/md-exclamation-circle', 'h-5 w-5 text-red-500') }}
|
||||
@break
|
||||
@default
|
||||
{{ Filament::svg('heroicons/outline-md/md-information-circle', 'w-5 h-5 text-green-400') }}
|
||||
{{ Filament::svg('heroicons/outline-md/md-information-circle', 'w-5 h-5 text-green-500') }}
|
||||
@endswitch
|
||||
</div>
|
||||
<div class="ml-3 flex-grow mr-4 text-sm leading-5">
|
||||
|
||||
@@ -17,7 +17,12 @@ class UserEdit extends FormComponent
|
||||
->rules(['required', 'string', 'max:255'])
|
||||
->group('account'),
|
||||
Field::make('Avatar')
|
||||
->rules('array')
|
||||
->file()
|
||||
->fileRules('image')
|
||||
->fileValidationMessages([
|
||||
'image' => __('The Avatar must be a valid image.'),
|
||||
])
|
||||
->group('account'),
|
||||
Field::make('Email')
|
||||
->input('email')
|
||||
@@ -44,8 +49,9 @@ class UserEdit extends FormComponent
|
||||
->help(__('filament::permissions.super_admin_info'))
|
||||
->group('permissions'),
|
||||
Field::make('filament::permissions.roles', 'roles')
|
||||
->checkboxes($this->roles->pluck('id', 'name')->all())
|
||||
->default($this->model->roles->pluck('id')->all())
|
||||
->checkboxes($this->roleIds)
|
||||
->default($this->userRoleIds)
|
||||
->rules([Rule::exists('roles', 'id')])
|
||||
->group('permissions'),
|
||||
];
|
||||
}
|
||||
@@ -64,7 +70,7 @@ class UserEdit extends FormComponent
|
||||
}
|
||||
|
||||
$this->model->update($input->all());
|
||||
$this->model->syncRoles($this->getRoleIds($input->get('roles')));
|
||||
$this->model->syncRoles($input->get('roles'));
|
||||
|
||||
$this->emit('filament.notification.notify', [
|
||||
'type' => 'success',
|
||||
@@ -91,15 +97,14 @@ class UserEdit extends FormComponent
|
||||
return redirect()->route('filament.admin.users.index');
|
||||
}
|
||||
|
||||
public function getRoleIds(array $roles)
|
||||
{
|
||||
$roleInput = array_filter($roles);
|
||||
return $this->roles->intersectByKeys($roleInput)->pluck('id')->toArray();
|
||||
}
|
||||
|
||||
public function getRolesProperty()
|
||||
public function getRoleIdsProperty()
|
||||
{
|
||||
$roleClass = app(RoleContract::class);
|
||||
return $roleClass::orderBy('name')->get();
|
||||
return $roleClass::orderBy('name')->pluck('id', 'name')->all();
|
||||
}
|
||||
|
||||
public function getUserRoleIdsProperty()
|
||||
{
|
||||
return array_map('strval', $this->model->roles->pluck('id')->all());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ class Field extends BaseField
|
||||
protected $label;
|
||||
protected $key;
|
||||
protected $file_multiple;
|
||||
protected $array_fields = [];
|
||||
protected $array_sortable = false;
|
||||
protected $file_rules = ['file'];
|
||||
protected $file_validation_messages = ['file' => 'Must be a valid file.'];
|
||||
|
||||
public function __construct($label, $name, $key_prefix = true)
|
||||
{
|
||||
@@ -30,25 +30,24 @@ class Field extends BaseField
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fileRules($rules)
|
||||
{
|
||||
$this->file_rules = (array) $rules;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fileValidationMessages(array $messages)
|
||||
{
|
||||
$this->file_validation_messages = $messages;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function multiple()
|
||||
{
|
||||
$this->file_multiple = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function array($fields = [])
|
||||
{
|
||||
$this->type = 'array';
|
||||
$this->array_fields = $fields;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function sortable()
|
||||
{
|
||||
$this->array_sortable = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function errorMessage($message)
|
||||
{
|
||||
return str_replace('form data.', '', $message);
|
||||
|
||||
@@ -19,7 +19,10 @@ class FormComponent extends Component
|
||||
private static $storage_disk;
|
||||
private static $storage_path;
|
||||
|
||||
protected $listeners = ['filament.fileUpdate' => 'fileUpdate'];
|
||||
protected $listeners = [
|
||||
'filament.fileUploadError' => 'fileUploadError',
|
||||
'filament.fileUpdate' => 'fileUpdate',
|
||||
];
|
||||
|
||||
public function mount($model = null, $goback = null)
|
||||
{
|
||||
@@ -47,6 +50,16 @@ class FormComponent extends Component
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getField($name)
|
||||
{
|
||||
foreach ($this->fields() as $field) {
|
||||
if ($field->name == $name) {
|
||||
return $field;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function updated($field)
|
||||
{
|
||||
$this->validateOnly($field, $this->rules(true));
|
||||
|
||||
@@ -13,15 +13,6 @@ trait FollowsRules
|
||||
if ($field->rules) {
|
||||
$rules[$field->key] = $this->fieldRules($field, $rules_ignore);
|
||||
}
|
||||
|
||||
// File fields need more complex logic since they are technically arrays
|
||||
// Right now we can only do simple validation with file fields
|
||||
|
||||
foreach ($field->array_fields as $array_field) {
|
||||
if ($array_field->rules) {
|
||||
$rules[$field->key . '.*.' . $array_field->name] = $this->fieldRules($array_field, $rules_ignore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $rules;
|
||||
|
||||
@@ -4,42 +4,6 @@ namespace Filament\Traits\Fields;
|
||||
|
||||
trait HandlesArrays
|
||||
{
|
||||
public function arrayAdd($field_name)
|
||||
{
|
||||
$array_fields = [];
|
||||
|
||||
foreach ($this->fields() as $field) {
|
||||
if ($field->name == $field_name) {
|
||||
foreach ($field->array_fields as $array_field) {
|
||||
$array_fields[$array_field->name] = $array_field->default ?? ($array_field->type == 'checkboxes' ? [] : null);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->form_data[$field_name][] = $array_fields;
|
||||
$this->updated('form_data.' . $field_name);
|
||||
}
|
||||
|
||||
public function arrayMoveUp($field_name, $key)
|
||||
{
|
||||
if ($key > 0) {
|
||||
$prev = $this->form_data[$field_name][$key - 1];
|
||||
$this->form_data[$field_name][$key - 1] = $this->form_data[$field_name][$key];
|
||||
$this->form_data[$field_name][$key] = $prev;
|
||||
}
|
||||
}
|
||||
|
||||
public function arrayMoveDown($field_name, $key)
|
||||
{
|
||||
if (($key + 1) < count($this->form_data[$field_name])) {
|
||||
$next = $this->form_data[$field_name][$key + 1];
|
||||
$this->form_data[$field_name][$key + 1] = $this->form_data[$field_name][$key];
|
||||
$this->form_data[$field_name][$key] = $next;
|
||||
}
|
||||
}
|
||||
|
||||
public function arrayRemove($field_name, $key)
|
||||
{
|
||||
unset($this->form_data[$field_name][$key]);
|
||||
|
||||
@@ -2,10 +2,28 @@
|
||||
|
||||
namespace Filament\Traits\Fields;
|
||||
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
trait UploadsFiles
|
||||
{
|
||||
public static function fileUpload()
|
||||
{
|
||||
$field_name = request()->input('field_name');
|
||||
$validation_rules = json_decode(request()->input('validation_rules'), true);
|
||||
$validation_messages = json_decode(request()->input('validation_messages'), true);
|
||||
|
||||
$validator = Validator::make(request()->all(), [
|
||||
'files.*' => $validation_rules,
|
||||
], $validation_messages);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return [
|
||||
'field_name' => $field_name,
|
||||
'errors' => $validator->errors()->get('files.*'),
|
||||
];
|
||||
}
|
||||
|
||||
$storage_disk = self::$storage_disk ?? config('filament.storage_disk');
|
||||
$storage_path = self::$storage_path ?? config('filament.storage_path');
|
||||
$files = [];
|
||||
@@ -20,7 +38,17 @@ trait UploadsFiles
|
||||
];
|
||||
}
|
||||
|
||||
return ['field_name' => request()->input('field_name'), 'uploaded_files' => $files];
|
||||
return ['field_name' => $field_name, 'uploaded_files' => $files];
|
||||
}
|
||||
|
||||
public function fileUploadError($field_name, $error)
|
||||
{
|
||||
$message = is_array($error) ? collect(Arr::first($error))->implode(PHP_EOL) : $error;
|
||||
$field = $this->getField($field_name);
|
||||
if ($field) {
|
||||
$errorBag = $this->getErrorBag();
|
||||
$errorBag->add($field->key, $message);
|
||||
}
|
||||
}
|
||||
|
||||
public function fileUpdate($field_name, $uploaded_files)
|
||||
|
||||
Reference in New Issue
Block a user