Skip to content

Commit

Permalink
New: Spatie Tags field Livewire component (docs coming)
Browse files Browse the repository at this point in the history
New: Add support for array field updated(), method naming, (not supported in Livewire by default)
New: method fillField() Example use: emitted Event in frontend via AlpineJS or from other Livewire component
Fix: field inline property for labels
Fix: default value in field, mixed type instead of string
Fix: urlencode previous url to comply with :portnumbers
Change: set model in create-component stub for conditional fields
  • Loading branch information
tanthammar committed Aug 17, 2020
1 parent b53dc81 commit c19ecc4
Show file tree
Hide file tree
Showing 13 changed files with 210 additions and 18 deletions.
4 changes: 3 additions & 1 deletion resources/stubs/create-component.stub
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ class DummyComponent extends FormComponent
}


// Mandatory method, if $this->action = 'create'
public function create($form_data)
{
DummyModel::create($form_data);
// Set the $model property in order to conditionally display fields when the model instance exists, on saveAndStayResponse()
$this->model = DummyModel::create($form_data);
}

public function fields()
Expand Down
2 changes: 1 addition & 1 deletion resources/views/fields/array.blade.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<x-tall-field-wrapper :inline="$inline" :field="$field->name" :label="$field->label" :labelW="$field->labelW" :fieldW="$field->fieldW">
<x-tall-field-wrapper :inline="$field->inline ?? $inline" :field="$field->name" :label="$field->label" :labelW="$field->labelW" :fieldW="$field->fieldW">
@include('tall-forms::fields.error-help')
<div class="w-full mt-2">
@if(isset($form_data[$field->name]) && $form_data[$field->name])
Expand Down
2 changes: 1 addition & 1 deletion resources/views/fields/checkbox.blade.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<x-tall-field-wrapper align="items-center" :inline="$field->inline" :field="$field->name" :label="$field->label" :labelW="$field->labelW" :fieldW="$field->fieldW">
<x-tall-field-wrapper align="items-center" :inline="$field->inline ?? $inline" :field="$field->name" :label="$field->label" :labelW="$field->labelW" :fieldW="$field->fieldW">
<x-tall-checkbox :field="$field->key" :id="$field->name"
:label="$field->placeholder ?? $field->label" :help="$field->help" :errorMsg="$field->errorMsg" />
</x-tall-field-wrapper>
2 changes: 1 addition & 1 deletion resources/views/fields/checkboxes.blade.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<x-tall-field-wrapper align="items-start" :inline="$field->inline" :field="$field->name" :label="$field->label" :labelW="$field->labelW" :fieldW="$field->fieldW">
<x-tall-field-wrapper align="items-start" :inline="$field->inline ?? $inline" :field="$field->name" :label="$field->label" :labelW="$field->labelW" :fieldW="$field->fieldW">
@foreach($field->options as $value => $label)
<x-tall-checkbox field="{{$field->key}}" id="{{$field->name}}.{{$loop->index}}" :label="$label" :help="$field->help" :errorMsg="$field->errorMsg" :value="$value" />
@endforeach
Expand Down
2 changes: 1 addition & 1 deletion resources/views/fields/keyval.blade.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<x-tall-field-wrapper :inline="$inline" :field="$field->name" :label="$field->label" :labelW="$field->labelW" :fieldW="$field->fieldW">
<x-tall-field-wrapper :inline="$field->inline ?? $inline" :field="$field->name" :label="$field->label" :labelW="$field->labelW" :fieldW="$field->fieldW">
@include('tall-forms::fields.error-help')
<div class="w-full mt-2">
<div class="flex flex-col divide-y mb-2 {{ $field->group_class }}">
Expand Down
2 changes: 1 addition & 1 deletion resources/views/fields/radio.blade.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<x-tall-field-wrapper align="items-start" :inline="$field->inline" :field="$field->name" :label="$field->label" :labelW="$field->labelW" :fieldW="$field->fieldW">
<x-tall-field-wrapper align="items-start" :inline="$field->inline ?? $inline" :field="$field->name" :label="$field->label" :labelW="$field->labelW" :fieldW="$field->fieldW">
@foreach($field->options as $value => $label)
<x-tall-radio :field="$field->key" :label="$label" id="{{$field->name}}.{{$loop->index}}" :value="$value" />
@endforeach
Expand Down
10 changes: 10 additions & 0 deletions resources/views/fields/tags.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<x-tall-field-wrapper :inline="$field->inline ?? $inline" :field="$field->name" :label="$field->label" :labelW="$field->labelW" :fieldW="$field->fieldW">
@livewire('tall-tags', [
'model' => $model,
'tagType' => $field->tagType,
'field' => $field->name,
'tags' => data_get($form_data, $field->name),
'help' =>$field->help,
'errorMsg' => $field->errorMsg
])
</x-tall-field-wrapper>
30 changes: 30 additions & 0 deletions resources/views/livewire/tags.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<div class="bg-grey-lighter w-full">
@error('search')<p class="error">@lang('fields.tag_error')</p>@enderror
@error($field)<p class="error">{{ $errorMsg ?? $message }}</p>@enderror
<div class="flex flex-1 flex-wrap bg-white border rounded shadow-sm pl-2 pr-4 pt-2 pb-1">
@foreach ($tags as $i => $tag)
<span
class="tags-input-tag inline-flex leading-4 items-center text-sm bg-blue-300 text-blue-800 rounded py-1 px-2 mr-2 mb-1"
style="user-select: none">
<span>{{ $tag }}</span>
<button wire:click="removeTag({{$i}})" type="button"
class="pl-1 tags-input-remove text-gray-500 text-lg leading-4 focus:outline-none">
&times;
</button>
</span>
@endforeach
<input autofocus wire:model.debounce.500ms="search" wire:keydown.enter.prevent="addFromSearch" name="search"
class="tags-input-text flex-1 outline-none pt-1 pb-1 ml-2"
style="min-width:10rem" placeholder="Add tag...">
</div>
@if($help)<p class="help py-1">{{ $help }}</p>@endif
<div class="flex items-center py-2">
@foreach($options as $option)
<button wire:click.prevent="addFromOptions('{{$option}}')" type="button">
<span class="bg-blue-100 text-blue-800 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium leading-4 whitespace-no-wrap">
{{ $option ?? null }}
</span>
</button>
@endforeach
</div>
</div>
2 changes: 1 addition & 1 deletion src/BaseField.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public function custom(): BaseField
return $this;
}

public function default(string $default): BaseField
public function default($default): BaseField
{
$this->default = $default;
return $this;
Expand Down
7 changes: 3 additions & 4 deletions src/Field.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ class Field extends BaseField
protected $livewireComponent;
protected $livewireParams;
protected $tagType;
protected $tagLocale;
protected $inline;


Expand Down Expand Up @@ -60,11 +59,11 @@ public function keyval($fields = []): Field
return $this;
}

public function tags($tagType = "", $tagLocale = null): Field
public function tags($tagType = "", $tagTypeSuffix = null): Field
{
$this->is_custom = true;
$this->type = 'tags';
$this->tagType = $tagType;
$this->tagLocale = $tagLocale ?? app()->getLocale();
$this->tagType = filled($tagTypeSuffix) ? $tagType . '-' . $tagTypeSuffix : $tagType;
return $this;
}

Expand Down
44 changes: 37 additions & 7 deletions src/FormComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,19 @@ class FormComponent extends Component
public $spaLayout;
private static $storage_disk;
private static $storage_path;
protected $custom_data;
public $form_wrapper = 'max-w-screen-lg mx-auto';
public $previous;

protected $listeners = ['fileUpdate'];
protected $listeners = ['fileUpdate', 'fillField'];

public function mount_form($model = null)
{
$this->model = $model;
$this->beforeFormProperties();
$this->setFormProperties($this->model);
$this->afterFormProperties();
$this->previous = \URL::previous(); //used for saveAndGoBack
$this->previous = urlencode(\URL::previous()); //used for saveAndGoBack
$this->spaLayout = config('tall-forms.spa-layout');
}

Expand Down Expand Up @@ -93,14 +94,43 @@ public function fields()
public function updated($field)
{
$this->fields_updated($field);

$function = $this->parseUpdateFunctionFrom($field);
if (method_exists($this, $function)) $this->$function();

$this->validateOnly($field, $this->rules(true));
}

/**
* Executes before field validation, creds to "@roni", livewire discord channel member
* @param string $field
* @return string
*/
protected function parseUpdateFunctionFrom(string $field): string
{
return 'updated' . \Str::of($field)->replace('.', '_')->studly();
}

public function fields_updated($field)
{
return null;
}


public function fillField($array)
{
$this->form_data[$array['field']] = $array['value'];
}

public function syncTags($field, $tagType = null)
{
$tags = data_get($this->custom_data, $field);
if (filled($tags = explode(",", $tags)) && filled($this->model)) {
clock($tagType, $tags);
filled($tagType) ? $this->model->syncTagsWithType($tags, $tagType) : $this->model->syncTags($tags);
}
}

public function submit()
{
$this->validate($this->rules());
Expand All @@ -122,13 +152,13 @@ public function submit()
}

$relationship_data = Arr::only($this->form_data, $relationship_names);
$custom_data = Arr::only($this->form_data, $custom_names);
$this->custom_data = Arr::only($this->form_data, $custom_names); //custom_data also used by syncTags() after save if create form
$this->form_data = Arr::only($this->form_data, $field_names);

//make sure to create the model before attaching any relations
$this->success(); //creates or updates the model
if (filled($this->model)) $this->relations($relationship_data);
$this->custom_fields($custom_data);
$this->custom_fields($this->custom_data);
}

public function errorMessage($message)
Expand All @@ -140,7 +170,7 @@ public function success()
{
($this->action == 'update')
? $this->model->update($this->form_data)
: $this->create($this->form_data);
: $this->create($this->form_data); // you have to add the create() method to your component
}

public function relations(array $relationship_data)
Expand All @@ -164,7 +194,7 @@ public function delete()
if (optional($this->model)->exists) {
$this->model->delete();
session()->flash('success', 'The object was deleted');
return redirect($this->previous);
return redirect(urldecode($this->previous));
}
return null;
}
Expand All @@ -178,7 +208,7 @@ public function saveAndStayResponse()
public function saveAndGoBackResponse()
{
//return back(); //does not work with livewire
return redirect($this->previous);
return redirect(urldecode($this->previous));
}

public function saveAndStay()
Expand Down
2 changes: 2 additions & 0 deletions src/FormServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public function boot()
$this->publishes([__DIR__ . '/../config/tall-forms.php' => config_path('tall-forms.php')], 'form-config');
$this->publishes([__DIR__ . '/../resources/views' => resource_path('views/vendor/tall-forms')], 'form-views');

\Livewire::component('tall-tags', TagsField::class);

$this->bootViews();
}

Expand Down
119 changes: 119 additions & 0 deletions src/TagsField.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php

namespace Tanthammar\TallForms;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Livewire\Component;
use Spatie\Tags\Tag;

class TagsField extends Component
{

public $model;
public $type;
public $tags;
public $search = "";
public $options = [];
public $field;
public $help;
public $errorMsg;

public function mount(string $field, string $tagType = null, Model $model = null, string $tags = null, string $help = null, string $errorMsg = null)
{
$this->model = $model;
$this->type = $tagType;
$this->tags = filled($this->model)
? $this->getExisting()
: (filled($tags)
? explode(",", $tags)
: []
);
$this->field = $field;
$this->help = $help;
$this->errorMsg = $errorMsg;
}

public function getExisting()
{
$query = filled($this->type) ? $this->model->tagsWithType($this->type) : $this->model->tags;
clock($this->type);
return array_filter(
$query->pluck('name')->unique()->toArray()
);
}

public function getRules()
{
return ['search' => 'nullable|string|between:3,40'];
}

public function updatedSearch()
{
$slug = Str::slug($this->search, '-');
if (filled($slug)) {
$this->options = array_filter(
Tag::where("slug", 'like', '%' . $slug . '%')
->where('type', $this->type)
->orderBy("name", 'asc')
->take(10)
->pluck('name')
->unique()
->toArray()
);
}
}

public function syncTags()
{
$cleaned = collect(array_sort($this->tags))->unique()->toArray();
filled($this->model)
? (filled($this->type) ? $this->model->syncTagsWithType($cleaned, $this->type) : $this->model->syncTags($cleaned))
: $this->emitUp('fillField', [
'field' => $this->field,
'value' => implode(",", $cleaned)
]);
$this->tags = $cleaned;
$this->search = "";

//$this->notify();
}

public function addTag($tag)
{
$tag = Str::of($tag)->trim()->trim(",")->title()->__toString();
if (!in_array($tag, $this->tags)) {
array_push($this->tags, $tag);
}
$this->syncTags();
}

public function removeTag(int $i): void
{
unset($this->tags[$i]);
$this->search = "";
//$this->tags = array_values($this->tags);
$this->syncTags();
}

public function addFromSearch()
{
$this->validate($this->getRules());
$this->addTag($this->search);
$this->search = "";
}

public function addFromOptions($option)
{
//validate that the option has not been modified in frontend
if (in_array($option, $this->options)) {
$this->addTag($option);
}
$this->search = "";
}

public function render()
{
return view('tall-forms::livewire.tags');
}
}

0 comments on commit c19ecc4

Please sign in to comment.