Complete taglist form widget

This commit is contained in:
Samuel Georges 2016-05-10 06:02:35 +10:00
parent 5d27682f25
commit 5400ec7d2d
7 changed files with 156 additions and 28 deletions

View File

@ -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 ' ';
}
}
}

View File

@ -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>

View File

@ -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;
}

View File

@ -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')

View File

@ -242,6 +242,12 @@
display: none !important;
}
.select2-dropdown.select-hide-selected {
li[aria-selected=true] {
display: none !important;
}
}
// Single select
//------------------------------------

View File

@ -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)})

View File

@ -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}