mirror of
https://github.com/processwire/processwire.git
synced 2025-08-26 16:14:35 +02:00
Update InputfieldTextTags with support for single-select mode, option to specify max allowed items, and support for a placeholder attribute
This commit is contained in:
@@ -1 +1 @@
|
|||||||
.InputfieldTextTags label.pw-hidden{display:none}.InputfieldTextTags input.InputfieldTextTagsSelect:not(.selectized),.InputfieldTextTags input.InputfieldTextTagsInput:not(.selectized){color:#f0f3f7}.InputfieldTextTags .InputfieldPageAdd{display:none}.Inputfield .selectize-input{border:1px solid #b1c3d4 #cbd7e3 #cbd7e3 #cbd7e3;border-color:#b1c3d4 #cbd7e3 #cbd7e3 #cbd7e3;box-shadow:none}.Inputfield .selectize-control .selectize-input.has-items>div{background:#f0f3f7;white-space:nowrap;border:1px solid #cbd7e3;border-radius:3px}.Inputfield .selectize-control .selectize-input.has-items>div a.remove{color:#555}.Inputfield .selectize-input:not(.has-items){background:#f0f3f7}
|
.InputfieldTextTags label.pw-hidden{display:none}.InputfieldTextTags input.InputfieldTextTagsSelect:not(.selectized),.InputfieldTextTags input.InputfieldTextTagsInput:not(.selectized){color:#f0f3f7}.InputfieldTextTags .InputfieldPageAdd{display:none}.Inputfield .selectize-input{border:1px solid #b1c3d4 #cbd7e3 #cbd7e3 #cbd7e3;border-color:#b1c3d4 #cbd7e3 #cbd7e3 #cbd7e3;box-shadow:none}.Inputfield .selectize-input:not(.has-items){background:#f0f3f7}.Inputfield .selectize-input.dropdown-active{padding-bottom:7px}.Inputfield .selectize-control.multi .selectize-input.has-items>div{background:#f0f3f7;white-space:nowrap;border:1px solid #cbd7e3;border-radius:3px}.Inputfield .selectize-control.multi .selectize-input.has-items>div a.remove{color:#555}.Inputfield .selectize-control.single .selectize-input{background:#f0f3f7;font-size:14px}
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
function InputfieldTextTags($parent) {
|
function InputfieldTextTags($parent) {
|
||||||
|
|
||||||
if(typeof $parent === "undefined") $parent = $('.InputfieldForm');
|
if(typeof $parent === "undefined") $parent = jQuery('.InputfieldForm');
|
||||||
|
|
||||||
|
var pluginsMulti = [ 'remove_button', 'drag_drop' ];
|
||||||
|
var pluginsSingle = [];
|
||||||
|
|
||||||
var defaults = {
|
var defaults = {
|
||||||
plugins: [ 'remove_button', 'drag_drop' ],
|
|
||||||
delimiter: ' ',
|
delimiter: ' ',
|
||||||
persist: true, // If false, items created by the user will not show up as available options once they are unselected.
|
persist: true, // If false, items created by the user will not show up as available options once they are unselected.
|
||||||
submitOnReturn: false,
|
submitOnReturn: false,
|
||||||
@@ -29,6 +31,8 @@ function InputfieldTextTags($parent) {
|
|||||||
options.closeAfterSelect = o.closeAfterSelect;
|
options.closeAfterSelect = o.closeAfterSelect;
|
||||||
options.createOnBlur = o.createOnBlur;
|
options.createOnBlur = o.createOnBlur;
|
||||||
options.persist = false;
|
options.persist = false;
|
||||||
|
options.maxItems = (o.maxItems > 0 ? o.maxItems : null);
|
||||||
|
options.plugins = (o.maxItems === 1 ? pluginsSingle : pluginsMulti);
|
||||||
$input.selectize(options);
|
$input.selectize(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +55,8 @@ function InputfieldTextTags($parent) {
|
|||||||
delimiter: o.delimiter,
|
delimiter: o.delimiter,
|
||||||
closeAfterSelect: o.closeAfterSelect,
|
closeAfterSelect: o.closeAfterSelect,
|
||||||
createOnBlur: o.createOnBlur,
|
createOnBlur: o.createOnBlur,
|
||||||
|
maxItems: (o.maxItems > 0 ? o.maxItems : null),
|
||||||
|
plugins: (o.maxItems === 1 ? pluginsSingle : pluginsMulti),
|
||||||
persist: true,
|
persist: true,
|
||||||
valueField: 'value',
|
valueField: 'value',
|
||||||
labelField: 'label',
|
labelField: 'label',
|
||||||
@@ -126,14 +132,14 @@ function InputfieldTextTags($parent) {
|
|||||||
|
|
||||||
if($inputs.length) {
|
if($inputs.length) {
|
||||||
$inputs.each(function() {
|
$inputs.each(function() {
|
||||||
$input = $(this);
|
$input = jQuery(this);
|
||||||
initInput($input);
|
initInput($input);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if($selects.length) {
|
if($selects.length) {
|
||||||
$selects.each(function() {
|
$selects.each(function() {
|
||||||
var $select = $(this);
|
var $select = jQuery(this);
|
||||||
initSelect($select);
|
initSelect($select);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1 +1 @@
|
|||||||
function InputfieldTextTags($parent){if(typeof $parent==="undefined")$parent=$(".InputfieldForm");var defaults={plugins:["remove_button","drag_drop"],delimiter:" ",persist:true,submitOnReturn:false,openOnFocus:true,closeAfterSelect:true,copyClassesToDropdown:false,createOnBlur:false,selectOnTab:true,maxItems:null,create:function(input){return{value:input,text:input}}};function initInput($input){var o=JSON.parse($input.attr("data-opts"));var options=defaults;options.delimiter=o.delimiter;options.closeAfterSelect=o.closeAfterSelect;options.createOnBlur=o.createOnBlur;options.persist=false;$input.selectize(options)}function initSelect($select){var o=JSON.parse($select.attr("data-opts"));var cfgName=typeof o.cfgName==="undefined"?"":o.cfgName;var tags=cfgName.length?ProcessWire.config[cfgName]:o.tags;var tagsList=[];var n=0;for(var tag in tags){var label=tags[tag];tagsList[n]={value:tag,label:label};n++}var options=jQuery.extend(defaults,{allowUserTags:o.allowUserTags,delimiter:o.delimiter,closeAfterSelect:o.closeAfterSelect,createOnBlur:o.createOnBlur,persist:true,valueField:"value",labelField:"label",searchField:["value","label"],options:tagsList,createFilter:function(input){if(o.allowUserTags)return true;allow=false;for(var n=0;n<tags.length;n++){if(typeof tags[input]!=="undefined"){allow=true;break}}return allow},render:{item:function(item,escape){if(typeof item.label==="undefined"||!item.label.length)item.label=item.value;return"<div>"+escape(item.label)+"</div>"},option:function(item,escape){if(typeof item.label==="undefined"||!item.label.length)item.label=item.value;return"<div>"+escape(item.label)+"</div>"}}});if(o.tagsUrl.length){options.load=function(query,callback){if(!query.length)return callback();var tagsUrl=o.tagsUrl.replace("{q}",encodeURIComponent(query));Inputfields.startSpinner($select);jQuery.ajax({url:tagsUrl,type:"GET",error:function(){Inputfields.stopSpinner($select);callback()},success:function(items){for(var n=0;n<items.length;n++){var item=items[n];if(typeof item==="object"){if(typeof item.label==="undefined"){item.label=item.value;items[n]=item}}else{items[n]={value:item,label:item}}}Inputfields.stopSpinner($select);callback(items)}})}}$select.selectize(options)}var $inputs=jQuery(".InputfieldTextTagsInput:not(.selectized)",$parent);var $selects=jQuery(".InputfieldTextTagsSelect:not(.selectized)",$parent);if($inputs.length){$inputs.each(function(){$input=$(this);initInput($input)})}if($selects.length){$selects.each(function(){var $select=$(this);initSelect($select)})}}jQuery(document).ready(function($){InputfieldTextTags();$(document).on("reloaded",".InputfieldTextTags",function(){InputfieldTextTags($(this))})});
|
function InputfieldTextTags($parent){if(typeof $parent==="undefined")$parent=jQuery(".InputfieldForm");var pluginsMulti=["remove_button","drag_drop"];var pluginsSingle=[];var defaults={delimiter:" ",persist:true,submitOnReturn:false,openOnFocus:true,closeAfterSelect:true,copyClassesToDropdown:false,createOnBlur:false,selectOnTab:true,maxItems:null,create:function(input){return{value:input,text:input}}};function initInput($input){var o=JSON.parse($input.attr("data-opts"));var options=defaults;options.delimiter=o.delimiter;options.closeAfterSelect=o.closeAfterSelect;options.createOnBlur=o.createOnBlur;options.persist=false;options.maxItems=o.maxItems>0?o.maxItems:null;options.plugins=o.maxItems===1?pluginsSingle:pluginsMulti;$input.selectize(options)}function initSelect($select){var o=JSON.parse($select.attr("data-opts"));var cfgName=typeof o.cfgName==="undefined"?"":o.cfgName;var tags=cfgName.length?ProcessWire.config[cfgName]:o.tags;var tagsList=[];var n=0;for(var tag in tags){var label=tags[tag];tagsList[n]={value:tag,label:label};n++}var options=jQuery.extend(defaults,{allowUserTags:o.allowUserTags,delimiter:o.delimiter,closeAfterSelect:o.closeAfterSelect,createOnBlur:o.createOnBlur,maxItems:o.maxItems>0?o.maxItems:null,plugins:o.maxItems===1?pluginsSingle:pluginsMulti,persist:true,valueField:"value",labelField:"label",searchField:["value","label"],options:tagsList,createFilter:function(input){if(o.allowUserTags)return true;allow=false;for(var n=0;n<tags.length;n++){if(typeof tags[input]!=="undefined"){allow=true;break}}return allow},render:{item:function(item,escape){if(typeof item.label==="undefined"||!item.label.length)item.label=item.value;return"<div>"+escape(item.label)+"</div>"},option:function(item,escape){if(typeof item.label==="undefined"||!item.label.length)item.label=item.value;return"<div>"+escape(item.label)+"</div>"}}});if(o.tagsUrl.length){options.load=function(query,callback){if(!query.length)return callback();var tagsUrl=o.tagsUrl.replace("{q}",encodeURIComponent(query));Inputfields.startSpinner($select);jQuery.ajax({url:tagsUrl,type:"GET",error:function(){Inputfields.stopSpinner($select);callback()},success:function(items){for(var n=0;n<items.length;n++){var item=items[n];if(typeof item==="object"){if(typeof item.label==="undefined"){item.label=item.value;items[n]=item}}else{items[n]={value:item,label:item}}}Inputfields.stopSpinner($select);callback(items)}})}}$select.selectize(options)}var $inputs=jQuery(".InputfieldTextTagsInput:not(.selectized)",$parent);var $selects=jQuery(".InputfieldTextTagsSelect:not(.selectized)",$parent);if($inputs.length){$inputs.each(function(){$input=jQuery(this);initInput($input)})}if($selects.length){$selects.each(function(){var $select=jQuery(this);initSelect($select)})}}jQuery(document).ready(function($){InputfieldTextTags();$(document).on("reloaded",".InputfieldTextTags",function(){InputfieldTextTags($(this))})});
|
@@ -25,9 +25,11 @@
|
|||||||
* @property string $tagsUrl Remote URL to find tags from, must have a '{q}' in it somewhere, which will be replaced with the query.
|
* @property string $tagsUrl Remote URL to find tags from, must have a '{q}' in it somewhere, which will be replaced with the query.
|
||||||
* @property int|bool $allowUserTags Allow user-entered tags?
|
* @property int|bool $allowUserTags Allow user-entered tags?
|
||||||
* @property int|bool $closeAfterSelect Close select dropdown box after user makes selection?
|
* @property int|bool $closeAfterSelect Close select dropdown box after user makes selection?
|
||||||
|
* @property int $maxItems Max selectable items, 0 for no limit, 1 for single-select mode, or 2+ to limit selection to that number.
|
||||||
* @property bool|int $useAjax
|
* @property bool|int $useAjax
|
||||||
* @property string $delimiter One of 's' (for space ' '), 'p' (for pipe '|') or 'c' (for comma).
|
* @property string $delimiter One of 's' (for space ' '), 'p' (for pipe '|') or 'c' (for comma).
|
||||||
* @property string $value
|
* @property string $value
|
||||||
|
* @property string $placeholder Placeholder string to show when no options selected.
|
||||||
* @property-read array $arrayValue
|
* @property-read array $arrayValue
|
||||||
* @property string|null $pageSelector
|
* @property string|null $pageSelector
|
||||||
*
|
*
|
||||||
@@ -43,7 +45,7 @@ class InputfieldTextTags extends Inputfield implements
|
|||||||
return array(
|
return array(
|
||||||
'title' => __('Text Tags', __FILE__), // Module Title
|
'title' => __('Text Tags', __FILE__), // Module Title
|
||||||
'summary' => __('Enables input of user entered tags or selection of predefined tags.', __FILE__), // Module Summary
|
'summary' => __('Enables input of user entered tags or selection of predefined tags.', __FILE__), // Module Summary
|
||||||
'version' => 3,
|
'version' => 4,
|
||||||
'icon' => 'tags',
|
'icon' => 'tags',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -68,7 +70,9 @@ class InputfieldTextTags extends Inputfield implements
|
|||||||
$this->set('allowUserTags', 0);
|
$this->set('allowUserTags', 0);
|
||||||
$this->set('closeAfterSelect', 1);
|
$this->set('closeAfterSelect', 1);
|
||||||
$this->set('delimiter', 's');
|
$this->set('delimiter', 's');
|
||||||
|
$this->set('maxItems', 0);
|
||||||
parent::set('useAjax', false); // parent and boolean intentional
|
parent::set('useAjax', false); // parent and boolean intentional
|
||||||
|
$this->setAttribute('placeholder', '');
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +87,9 @@ class InputfieldTextTags extends Inputfield implements
|
|||||||
$languages = $this->wire()->languages;
|
$languages = $this->wire()->languages;
|
||||||
if($languages) {
|
if($languages) {
|
||||||
foreach($languages as $language) {
|
foreach($languages as $language) {
|
||||||
if(!$language->isDefault()) $this->set("tagsList$language->id", array());
|
if($language->isDefault()) continue;
|
||||||
|
$this->set("tagsList$language->id", array());
|
||||||
|
parent::set("placeholder$language->id", '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,12 +123,31 @@ class InputfieldTextTags extends Inputfield implements
|
|||||||
if($key === 'tagsList') return $this->setTagsList($value);
|
if($key === 'tagsList') return $this->setTagsList($value);
|
||||||
list(,$languageId) = explode('tagsList', $key, 2);
|
list(,$languageId) = explode('tagsList', $key, 2);
|
||||||
return $this->setTagsList($value, (int) $languageId);
|
return $this->setTagsList($value, (int) $languageId);
|
||||||
} else if($key === 'allowUserTags' || $key === 'closeAfterSelect' || $key === 'useAjax') {
|
} else if($key === 'allowUserTags' || $key === 'closeAfterSelect' || $key === 'useAjax' || $key === 'maxItems') {
|
||||||
$value = (int) $value;
|
$value = (int) $value;
|
||||||
}
|
}
|
||||||
return parent::set($key, $value);
|
return parent::set($key, $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all attributes in an associative array
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function getAttributes() {
|
||||||
|
$attrs = parent::getAttributes();
|
||||||
|
if(!empty($attrs['placeholder'])) {
|
||||||
|
// placeholder attribute, languages support
|
||||||
|
list($languages, $language) = array($this->wire()->languages, $this->wire()->user->language);
|
||||||
|
if($languages && "$language" && !$language->isDefault()) {
|
||||||
|
$placeholder = parent::get("placeholder$language->id");
|
||||||
|
if(strlen($placeholder)) $attrs['placeholder'] = $placeholder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $attrs;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set attribute
|
* Set attribute
|
||||||
*
|
*
|
||||||
@@ -531,6 +556,7 @@ class InputfieldTextTags extends Inputfield implements
|
|||||||
'closeAfterSelect' => $this->closeAfterSelect,
|
'closeAfterSelect' => $this->closeAfterSelect,
|
||||||
'createOnBlur' => $this->allowUserTags() && $this->isTextField(),
|
'createOnBlur' => $this->allowUserTags() && $this->isTextField(),
|
||||||
'delimiter' => $this->delimiter(),
|
'delimiter' => $this->delimiter(),
|
||||||
|
'maxItems' => $this->maxItems,
|
||||||
'tagsUrl' => $tagsUrl,
|
'tagsUrl' => $tagsUrl,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -625,9 +651,14 @@ class InputfieldTextTags extends Inputfield implements
|
|||||||
$isPageField = $this->isPageField();
|
$isPageField = $this->isPageField();
|
||||||
$validTags = $this->getTagsList();
|
$validTags = $this->getTagsList();
|
||||||
$delimiter = $this->delimiter();
|
$delimiter = $this->delimiter();
|
||||||
|
$maxItems = (int) $this->maxItems;
|
||||||
|
|
||||||
if(!is_array($tags)) $tags = $this->tagStringToArray($tags);
|
if(!is_array($tags)) $tags = $this->tagStringToArray($tags);
|
||||||
|
|
||||||
|
if($maxItems > 0) {
|
||||||
|
while(count($tags) > $maxItems) array_pop($tags);
|
||||||
|
}
|
||||||
|
|
||||||
foreach(array_keys($tags) as $tag) {
|
foreach(array_keys($tags) as $tag) {
|
||||||
if(isset($validTags[$tag])) {
|
if(isset($validTags[$tag])) {
|
||||||
// tag is known/valid
|
// tag is known/valid
|
||||||
@@ -906,7 +937,8 @@ class InputfieldTextTags extends Inputfield implements
|
|||||||
$f->description .= ' ' . $this->_('To define separate labels per-language, re-enter each tag `value=label` for each language, where the `value` is the same for each while the `label` differs.');
|
$f->description .= ' ' . $this->_('To define separate labels per-language, re-enter each tag `value=label` for each language, where the `value` is the same for each while the `label` differs.');
|
||||||
$f->useLanguages = true;
|
$f->useLanguages = true;
|
||||||
foreach($languages as $language) {
|
foreach($languages as $language) {
|
||||||
if(!$language->isDefault()) $f->set("value$language", $this->tagsListArrayToString($this->get("tagsList$language")));
|
if($language->isDefault()) continue;
|
||||||
|
$f->set("value$language", $this->tagsListArrayToString($this->get("tagsList$language")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$fieldset->add($f);
|
$fieldset->add($f);
|
||||||
@@ -1020,6 +1052,33 @@ class InputfieldTextTags extends Inputfield implements
|
|||||||
$fieldset->add($f);
|
$fieldset->add($f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @var InputfieldInteger $f */
|
||||||
|
$f = $modules->get('InputfieldInteger');
|
||||||
|
$f->attr('name', 'maxItems');
|
||||||
|
$f->label = $this->_('Max tags/options');
|
||||||
|
$f->description =
|
||||||
|
$this->_('Use 0 for no limit or enter the max number of options/tags the user may select or input.') . ' ' .
|
||||||
|
$this->_('Note that entering 1 makes the input appear more like a select than a text input.');
|
||||||
|
$f->val($this->maxItems);
|
||||||
|
$fieldset->add($f);
|
||||||
|
|
||||||
|
/** @var InputfieldText $f */
|
||||||
|
$f = $modules->get('InputfieldText');
|
||||||
|
$f->attr('name', 'placeholder');
|
||||||
|
$f->label = $this->_('Placeholder text');
|
||||||
|
$f->val($this->attr('placeholder'));
|
||||||
|
$f->description = $this->_('Optional placeholder text that appears in the field when blank.');
|
||||||
|
$f->collapsed = Inputfield::collapsedBlank;
|
||||||
|
if($languages) {
|
||||||
|
$f->useLanguages = true;
|
||||||
|
foreach($languages as $language) {
|
||||||
|
if($language->isDefault()) continue;
|
||||||
|
$value = $this->getSetting("placeholder$language");
|
||||||
|
if($value !== null) $f->set("value$language", $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$fieldset->add($f);
|
||||||
|
|
||||||
return $inputfields;
|
return $inputfields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -16,21 +16,37 @@ $tag-border-colors: #b1c3d4 #cbd7e3 #cbd7e3 #cbd7e3;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.Inputfield {
|
.Inputfield {
|
||||||
|
|
||||||
.selectize-input {
|
.selectize-input {
|
||||||
border: 1px solid $tag-border-colors;
|
border: 1px solid $tag-border-colors;
|
||||||
border-color: $tag-border-colors;
|
border-color: $tag-border-colors;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
&:not(.has-items) {
|
||||||
.selectize-control .selectize-input.has-items > div {
|
background: $tag-background-color;
|
||||||
background: $tag-background-color;
|
}
|
||||||
white-space: nowrap;
|
&.dropdown-active {
|
||||||
border: 1px solid $tag-border-color;
|
padding-bottom: 7px; // rather than 8px, to offset 1 px in dropdown
|
||||||
border-radius: 3px;
|
|
||||||
a.remove {
|
|
||||||
color: #555;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.selectize-input:not(.has-items) {
|
|
||||||
background: $tag-background-color;
|
.selectize-control.multi {
|
||||||
|
// in multi-selection mode
|
||||||
|
.selectize-input.has-items > div {
|
||||||
|
background: $tag-background-color;
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 1px solid $tag-border-color;
|
||||||
|
border-radius: 3px;
|
||||||
|
a.remove {
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectize-control.single {
|
||||||
|
// in single selection mode
|
||||||
|
.selectize-input {
|
||||||
|
background: $tag-background-color;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user