Complete taglist form widget
This commit is contained in:
parent
5d27682f25
commit
5400ec7d2d
|
|
@ -7,6 +7,12 @@ use Backend\Classes\FormWidgetBase;
|
|||
*/
|
||||
class TagList extends FormWidgetBase
|
||||
{
|
||||
use \Backend\Traits\FormModelWidget;
|
||||
|
||||
const MODE_ARRAY = 'array';
|
||||
const MODE_STRING = 'string';
|
||||
const MODE_RELATION = 'relation';
|
||||
|
||||
//
|
||||
// Configurable properties
|
||||
//
|
||||
|
|
@ -14,12 +20,27 @@ class TagList extends FormWidgetBase
|
|||
/**
|
||||
* @var string Tag separator: space, comma.
|
||||
*/
|
||||
public $separator = 'space';
|
||||
public $separator = 'comma';
|
||||
|
||||
/**
|
||||
* @var bool Allows custom tags to be entered manually by the user.
|
||||
*/
|
||||
public $useCustom = true;
|
||||
public $customTags = true;
|
||||
|
||||
/**
|
||||
* @var mixed Predefined options settings. Set to true to get from model.
|
||||
*/
|
||||
public $options = null;
|
||||
|
||||
/**
|
||||
* @var string Mode for the return value. Values: string, array, relation.
|
||||
*/
|
||||
public $mode = 'string';
|
||||
|
||||
/**
|
||||
* @var string If mode is relation, model column to use for the name reference.
|
||||
*/
|
||||
public $nameFrom = 'name';
|
||||
|
||||
//
|
||||
// Object properties
|
||||
|
|
@ -37,7 +58,9 @@ class TagList extends FormWidgetBase
|
|||
{
|
||||
$this->fillFromConfig([
|
||||
'separator',
|
||||
'useCustom',
|
||||
'customTags',
|
||||
'options',
|
||||
'mode',
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -55,11 +78,10 @@ class TagList extends FormWidgetBase
|
|||
*/
|
||||
public function prepareVars()
|
||||
{
|
||||
$this->vars['customSeparators'] = $this->getCustomSeparators();
|
||||
$this->vars['field'] = $this->formField;
|
||||
$this->vars['name'] = $this->formField->getName();
|
||||
$this->vars['value'] = $this->getLoadValue();
|
||||
$this->vars['model'] = $this->model;
|
||||
$this->vars['fieldOptions'] = $this->getFieldOptions();
|
||||
$this->vars['selectedValues'] = $this->getLoadValue();
|
||||
$this->vars['customSeparators'] = $this->getCustomSeparators();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -67,24 +89,102 @@ class TagList extends FormWidgetBase
|
|||
*/
|
||||
public function getSaveValue($value)
|
||||
{
|
||||
if ($this->mode === static::MODE_RELATION) {
|
||||
return $this->hydrateRelationSaveValue($value);
|
||||
}
|
||||
|
||||
if (is_array($value) && $this->mode === static::MODE_STRING) {
|
||||
return implode($this->getSeparatorCharacter(), $value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array suitable for saving against a relation (array of keys).
|
||||
* This method also creates non-existent tags.
|
||||
* @return array
|
||||
*/
|
||||
protected function hydrateRelationSaveValue($names)
|
||||
{
|
||||
if (!$names) {
|
||||
return $names;
|
||||
}
|
||||
|
||||
$relationModel = $this->getRelationModel();
|
||||
$existingTags = $relationModel
|
||||
->whereIn($this->nameFrom, $names)
|
||||
->lists($this->nameFrom, $relationModel->getKeyName())
|
||||
;
|
||||
|
||||
$newTags = $this->customTags ? array_diff($names, $existingTags) : [];
|
||||
|
||||
foreach ($newTags as $newTag) {
|
||||
$newModel = $relationModel::create([$this->nameFrom => $newTag]);
|
||||
$existingTags[$newModel->id] = $newTag;
|
||||
}
|
||||
|
||||
return array_keys($existingTags);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getLoadValue()
|
||||
{
|
||||
$value = parent::getLoadValue();
|
||||
|
||||
if ($this->mode === static::MODE_RELATION) {
|
||||
return $this->getRelationObject()->lists($this->nameFrom);
|
||||
}
|
||||
|
||||
return $this->mode === static::MODE_STRING
|
||||
? explode($this->getSeparatorCharacter(), $value)
|
||||
: $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns defined field options, or from the relation if available.
|
||||
* @return array
|
||||
*/
|
||||
public function getFieldOptions()
|
||||
{
|
||||
$options = $this->formField->options();
|
||||
|
||||
if (!$options && $this->mode === static::MODE_RELATION) {
|
||||
$options = $this->getRelationModel()->lists($this->nameFrom);
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns character(s) to use for separating keywords.
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getCustomSeparators()
|
||||
{
|
||||
if (!$this->useCustom) {
|
||||
if (!$this->customTags) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$separators = [];
|
||||
|
||||
$separators[] = $this->separator == 'comma' ? ',' : ' ';
|
||||
$separators[] = $this->getSeparatorCharacter();
|
||||
|
||||
return implode('|', $separators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the character word to the singular character.
|
||||
* @return string
|
||||
*/
|
||||
protected function getSeparatorCharacter()
|
||||
{
|
||||
switch (strtolower($this->separator)) {
|
||||
case 'comma': return ',';
|
||||
case 'space': return ' ';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
<?php
|
||||
$fieldOptions = $field->options();
|
||||
$availableOptions = array_unique(array_merge($selectedValues, $fieldOptions));
|
||||
?>
|
||||
<!-- Tag List -->
|
||||
<select
|
||||
id="<?= $field->getId() ?>"
|
||||
name="<?= $field->getName() ?>"
|
||||
class="form-control custom-select <?= !count($fieldOptions) ? 'select-no-dropdown' : '' ?>"
|
||||
name="<?= $field->getName() ?>[]"
|
||||
class="form-control custom-select <?= !count($fieldOptions) ? 'select-no-dropdown' : '' ?> select-hide-selected"
|
||||
<?php if ($customSeparators): ?>data-token-separators="<?= $customSeparators ?>"<?php endif ?>
|
||||
multiple
|
||||
<?= $field->getAttributes() ?>>
|
||||
<?php foreach ($fieldOptions as $option): ?>
|
||||
<option value="<?= e(trans($option)) ?>"><?= e(trans($option)) ?></option>
|
||||
<?php foreach ($availableOptions as $option): ?>
|
||||
<?php if (!strlen($option)) continue ?>
|
||||
<option value="<?= e($option) ?>" <?= in_array($option, $selectedValues) ? 'selected="selected"' : '' ?>><?= e(trans($option)) ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
|
|
|
|||
|
|
@ -690,7 +690,7 @@ class Form extends WidgetBase
|
|||
/*
|
||||
* Get field options from model
|
||||
*/
|
||||
$optionModelTypes = ['dropdown', 'radio', 'checkboxlist', 'taglist', 'balloon-selector'];
|
||||
$optionModelTypes = ['dropdown', 'radio', 'checkboxlist', 'balloon-selector'];
|
||||
if (in_array($field->type, $optionModelTypes, false)) {
|
||||
|
||||
/*
|
||||
|
|
@ -770,6 +770,18 @@ class Form extends WidgetBase
|
|||
|
||||
$widget = $this->makeFormWidget($widgetClass, $field, $widgetConfig);
|
||||
|
||||
/*
|
||||
* If options config is defined, request options from the model.
|
||||
*/
|
||||
if (isset($field->config['options'])) {
|
||||
$field->options(function () use ($field) {
|
||||
$fieldOptions = $field->config['options'];
|
||||
if ($fieldOptions === true) $fieldOptions = null;
|
||||
$fieldOptions = $this->getOptionsFromModel($field, $fieldOptions);
|
||||
return $fieldOptions;
|
||||
});
|
||||
}
|
||||
|
||||
return $this->formWidgets[$field->fieldName] = $widget;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,10 @@
|
|||
*/
|
||||
$('select.custom-select').each(function(){
|
||||
var $element = $(this),
|
||||
extraOptions = {}
|
||||
extraOptions = {
|
||||
dropdownCssClass: '',
|
||||
containerCssClass: ''
|
||||
}
|
||||
|
||||
// Prevent duplicate loading
|
||||
if ($element.data('select2') != null) {
|
||||
|
|
@ -61,23 +64,27 @@
|
|||
if ($element.hasClass('select-no-search')) {
|
||||
extraOptions.minimumResultsForSearch = Infinity
|
||||
}
|
||||
|
||||
if ($element.hasClass('select-no-dropdown')) {
|
||||
extraOptions.dropdownCssClass = 'select-no-dropdown'
|
||||
extraOptions.containerCssClass = 'select-no-dropdown'
|
||||
extraOptions.dropdownCssClass += ' select-no-dropdown'
|
||||
extraOptions.containerCssClass += ' select-no-dropdown'
|
||||
}
|
||||
|
||||
if ($element.hasClass('select-hide-selected')) {
|
||||
extraOptions.dropdownCssClass += ' select-hide-selected'
|
||||
}
|
||||
|
||||
var separators = $element.data('token-separators')
|
||||
if (separators) {
|
||||
extraOptions.tags = true
|
||||
extraOptions.selectOnClose = true
|
||||
extraOptions.closeOnSelect = false
|
||||
extraOptions.tokenSeparators = separators.split('|')
|
||||
|
||||
/*
|
||||
* When the dropdown is hidden, force the first option to be selected always.
|
||||
*/
|
||||
if ($element.hasClass('select-no-dropdown')) {
|
||||
extraOptions.selectOnClose = true
|
||||
extraOptions.closeOnSelect = false
|
||||
|
||||
$element.on('select2:closing', function() {
|
||||
$('.select2-dropdown.select-no-dropdown:first .select2-results__option--highlighted').removeClass('select2-results__option--highlighted')
|
||||
$('.select2-dropdown.select-no-dropdown:first .select2-results__option:first').addClass('select2-results__option--highlighted')
|
||||
|
|
|
|||
|
|
@ -242,6 +242,12 @@
|
|||
display: none !important;
|
||||
}
|
||||
|
||||
.select2-dropdown.select-hide-selected {
|
||||
li[aria-selected=true] {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Single select
|
||||
//------------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -2234,19 +2234,20 @@ if(imageSrc)
|
|||
return'<img class="select-image" src="'+imageSrc+'" alt="" /> '+state.text
|
||||
return state.text}
|
||||
var selectOptions={templateResult:formatSelectOption,templateSelection:formatSelectOption,escapeMarkup:function(m){return m},width:'style'}
|
||||
$('select.custom-select').each(function(){var $element=$(this),extraOptions={}
|
||||
$('select.custom-select').each(function(){var $element=$(this),extraOptions={dropdownCssClass:'',containerCssClass:''}
|
||||
if($element.data('select2')!=null){return true;}
|
||||
$element.attr('data-disposable','data-disposable')
|
||||
$element.one('dispose-control',function(){if($element.data('select2')){$element.select2('destroy')}})
|
||||
if($element.hasClass('select-no-search')){extraOptions.minimumResultsForSearch=Infinity}
|
||||
if($element.hasClass('select-no-dropdown')){extraOptions.dropdownCssClass='select-no-dropdown'
|
||||
extraOptions.containerCssClass='select-no-dropdown'}
|
||||
if($element.hasClass('select-no-dropdown')){extraOptions.dropdownCssClass+=' select-no-dropdown'
|
||||
extraOptions.containerCssClass+=' select-no-dropdown'}
|
||||
if($element.hasClass('select-hide-selected')){extraOptions.dropdownCssClass+=' select-hide-selected'}
|
||||
var separators=$element.data('token-separators')
|
||||
if(separators){extraOptions.tags=true
|
||||
extraOptions.selectOnClose=true
|
||||
extraOptions.closeOnSelect=false
|
||||
extraOptions.tokenSeparators=separators.split('|')
|
||||
if($element.hasClass('select-no-dropdown')){$element.on('select2:closing',function(){$('.select2-dropdown.select-no-dropdown:first .select2-results__option--highlighted').removeClass('select2-results__option--highlighted')
|
||||
if($element.hasClass('select-no-dropdown')){extraOptions.selectOnClose=true
|
||||
extraOptions.closeOnSelect=false
|
||||
$element.on('select2:closing',function(){$('.select2-dropdown.select-no-dropdown:first .select2-results__option--highlighted').removeClass('select2-results__option--highlighted')
|
||||
$('.select2-dropdown.select-no-dropdown:first .select2-results__option:first').addClass('select2-results__option--highlighted')})}}
|
||||
$element.select2($.extend({},selectOptions,extraOptions))})})
|
||||
$(document).on('disable','select.custom-select',function(event,status){$(this).select2('enable',!status)})
|
||||
|
|
|
|||
|
|
@ -2283,6 +2283,7 @@ html.cssanimations .cursor-loading-indicator.hide{display:none}
|
|||
.select2-container--default .select2-dropdown--above{margin-top:1px;-webkit-box-shadow:0 -3px 6px rgba(0,0,0,0.075);box-shadow:0 -3px 6px rgba(0,0,0,0.075)}
|
||||
.select2-container--default .select2-results > .select2-results__options{font-size:14px;max-height:200px;overflow-y:auto}
|
||||
.select2-container--default .select2-dropdown.select-no-dropdown{display:none !important}
|
||||
.select2-container--default .select2-dropdown.select-hide-selected li[aria-selected=true]{display:none !important}
|
||||
.select2-container--default .select2-selection--single{height:38px;line-height:1.42857143;padding:8px 25px 8px 13px}
|
||||
.select2-container--default .select2-selection--single .select2-selection__arrow{position:absolute;bottom:0;right:13px;top:0;width:4px}
|
||||
.select2-container--default .select2-selection--single .select2-selection__arrow b{position:absolute;top:50%;height:9px;width:8px;right:3px;margin-top:-5px;line-height:9px}
|
||||
|
|
|
|||
Loading…
Reference in New Issue