"Tags" as a formfield
Omais-Rana opened this issue · 0 comments
Omais-Rana commented
Laravel version
10.0
PHP version
8.1
Voyager version
1.6
Description of problem
Desperately needed a formfield type that would be helpful in creating tags and I know there could be the approach of storing a string and exploding via separation through commas but that's not neat at all and a bit complex as well. So I made a custom formfield that accepts tags at the time of input and stores it as an array. It works similary to the Multiple Dropdown formfield but instead of having to declare options beforehand and only being able to choose from those, this approach lets you create them dynamically. Its not the cleanest looking but it works. I hope voyager makes a default tags formfield in near future.
Proposed solution
Steps to reproduce:
- Make a folder named "FormFields" inside Laravel's "App" folder so the directory looks like this "App/FormFields"
- Inside FormFields folder create a php file named "TagsFormField.php" and use this code
<?php
namespace App\FormFields;
use TCG\Voyager\FormFields\AbstractHandler;
class TagsFormField extends AbstractHandler
{
protected $codename = 'tags';
public function createContent($row, $dataType, $dataTypeContent, $options)
{
return view('voyager::formfields.tags', [
'row' => $row,
'dataType' => $dataType,
'dataTypeContent' => $dataTypeContent,
'options' => $options,
]);
}
}
- Now go to "vendor\tcg\voyager\resources\views\formfields" and create a blade file named "tags.blade.php". Paste this code there
@php
$uniqueId = uniqid();
$currentTags = !empty($dataTypeContent->{$row->field}) ? json_decode($dataTypeContent->{$row->field}) : [];
$currentTags = is_array($currentTags) ? $currentTags : [];
@endphp
<div id="{{ $row->field }}-tag-container-{{ $uniqueId }}">
@foreach ($currentTags as $tag)
<span class="badge badge-primary mr-1">
{{ $tag }}
<button type="button" class="btn btn-sm btn-danger ml-1"
onclick="removeTag('{{ $uniqueId }}', '{{ $tag }}')">
X
</button>
</span>
@endforeach
</div>
<div class="form-group">
<input type="text" class="form-control" id="{{ $row->field }}-tag-input-{{ $uniqueId }}">
<input type="hidden" name="{{ $row->field }}" id="{{ $row->field }}-hidden-{{ $uniqueId }}"
value='{{ json_encode($currentTags) }}'>
<small class="form-text text-muted">Press Enter to add tags</small>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
function initializeTagInput(uniqueId) {
const tagContainer = document.getElementById('{{ $row->field }}-tag-container-' + uniqueId);
const tagInput = document.getElementById('{{ $row->field }}-tag-input-' + uniqueId);
const tagsHidden = document.getElementById('{{ $row->field }}-hidden-' + uniqueId);
let tags = {!! json_encode($currentTags) !!};
updateTagDisplay();
tagInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && tagInput.value.trim() !== '') {
e.preventDefault();
const tag = tagInput.value.trim();
if (!tags.includes(tag)) {
tags.push(tag);
updateTagDisplay();
}
tagInput.value = '';
}
});
function updateTagDisplay() {
tagContainer.innerHTML = '';
tags.forEach(tag => {
const tagElement = document.createElement('span');
tagElement.classList.add('badge', 'badge-primary', 'mr-1');
tagElement.textContent = tag;
const removeButton = document.createElement('button');
removeButton.type = 'button';
removeButton.classList.add('btn', 'btn-sm', 'btn-danger', 'ml-1');
removeButton.innerHTML = 'X';
removeButton.onclick = function() {
removeTag(tag);
};
tagElement.appendChild(removeButton);
tagContainer.appendChild(tagElement);
});
tagsHidden.value = JSON.stringify(tags);
}
function removeTag(tagToRemove) {
tags = tags.filter(tag => tag !== tagToRemove);
updateTagDisplay();
}
window.removeTag = function(id, tag) {
if (id === uniqueId) {
removeTag(tag);
}
};
}
initializeTagInput('{{ $uniqueId }}');
});
</script>
- Now the last step is to go to "App/Providers/AppServiceProvider.php" and paste this code
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\FormFields\TagsFormField;
use TCG\Voyager\Facades\Voyager;
use Illuminate\Support\Facades\View;
use App\Http\ViewComposers\CartComposer;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot()
{
Voyager::addFormField(TagsFormField::class);
View::composer('*', CartComposer::class);
}
}
Alternatives considered
No response
Additional context
Screenshots attached