1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-08 07:47:00 +02:00

Update InputfieldTextTags to support page selection for pages having digit-only titles (such as "2024"). Plus add support for single-page selection mode (previously it only supported multi-page selection mode).

This commit is contained in:
Ryan Cramer
2024-06-20 09:41:40 -04:00
parent 38a5320f61
commit 6d225f3c99
6 changed files with 165 additions and 21 deletions

View File

@@ -85,7 +85,18 @@ class InputfieldPage extends Inputfield implements ConfigurableModule {
'derefAsPage' => 0,
'addable' => 0,
'allowUnpub' => 0, // This option configured by FieldtypePage:Advanced
);
);
/**
* Core inputfield classes that support both single and multi-selection mode
*
* @var string[]
*
*/
protected $singleOrMultiInputfields = array(
'InputfieldPageAutocomplete',
'InputfieldTextTags',
);
/**
* Contains true when this module is in configuration state (via it's getConfigInputfields function)
@@ -1505,7 +1516,13 @@ class InputfieldPage extends Inputfield implements ConfigurableModule {
$fieldset->collapsed = Inputfield::collapsedYes;
$inClass = $inputfield->className();
$fieldset->showIf = 'inputfield=' . $inClass;
if($inClass == 'InputfieldPageAutocomplete') $fieldset->showIf .= "|_$inClass";
if(in_array($inClass, $this->singleOrMultiInputfields)) {
$fieldset->showIf .= "|_$inClass";
if($inClass == 'InputfieldTextTags' && $this->derefAsPage > 0) {
$f = $fieldset->getChildByName('maxItems');
if($f) $f->val(1);
}
}
$inputfields->insertAfter($fieldset, $inputfieldSelection);
}
}
@@ -1547,7 +1564,9 @@ class InputfieldPage extends Inputfield implements ConfigurableModule {
} else {
$singles[$class] = $label;
}
if($class == 'InputfieldPageAutocomplete') $singles["_$class"] = $label;
if(in_array($class, $this->singleOrMultiInputfields)) {
$singles["_$class"] = $label . ' ' . $this->_('(single)');
}
}
return array(

View File

@@ -1 +1,46 @@
.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}
.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: inherit;
}
.AdminThemeUikit .Inputfield .selectize-control.single .selectize-input:not(.uk-form-small) {
padding-top: 10px;
padding-bottom: 8px;
height: 40px;
}
.AdminThemeUikit .Inputfield .selectize-control.single:has(.selectize-input:not(.uk-form-small)) {
height: 40px;
}
/*# sourceMappingURL=InputfieldTextTags.css.map */

View File

@@ -61,6 +61,7 @@ function InputfieldTextTags($parent) {
var tags = cfgName.length ? ProcessWire.config[cfgName] : o.tags;
var tagsList = [];
var n = 0;
var isPageField = $select.closest('.InputfieldPage').length > 0;
for(var tag in tags) {
var label = tags[tag];
@@ -118,12 +119,23 @@ function InputfieldTextTags($parent) {
for(var n = 0; n < items.length; n++) {
var item = items[n];
if(typeof item === "object") {
if(typeof item.value === 'number' && isPageField) {
item.value = '_' + item.value.toString()
}
if(typeof item.label === "undefined") {
item.label = item.value;
items[n] = item;
}
} else {
items[n] = { value: item, label: item };
var value, label;
if(isPageField && (typeof item === 'number' || item.match(/^\d+$/))) {
value = '_' + item;
label = '' + item
} else {
value = item;
label = item;
}
items[n] = { value: value, label: label };
}
}
Inputfields.stopSpinner($select);

View File

@@ -1 +1 @@
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 getRenderOptions(addLabel){return{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>"},option_create:function(data,escape){return'<div class="create">'+addLabel+" <strong>"+escape(data.input)+"</strong>&hellip;</div>"}}}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;options.render=getRenderOptions(o.addLabel);$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;var allow=false;for(var n=0;n<tags.length;n++){if(typeof tags[input]!=="undefined"){allow=true;break}}return allow},render:getRenderOptions(o.addLabel)});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, .InputfieldPage",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 getRenderOptions(addLabel){return{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>"},option_create:function(data,escape){return'<div class="create">'+addLabel+" <strong>"+escape(data.input)+"</strong>&hellip;</div>"}}}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;options.render=getRenderOptions(o.addLabel);$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;var isPageField=$select.closest(".InputfieldPage").length>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;var allow=false;for(var n=0;n<tags.length;n++){if(typeof tags[input]!=="undefined"){allow=true;break}}return allow},render:getRenderOptions(o.addLabel)});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.value==="number"&&isPageField){item.value="_"+item.value.toString()}if(typeof item.label==="undefined"){item.label=item.value;items[n]=item}}else{var value,label;if(isPageField&&(typeof item==="number"||item.match(/^\d+$/))){value="_"+item;label=""+item}else{value=item;label=item}items[n]={value:value,label:label}}}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, .InputfieldPage",function(){InputfieldTextTags($(this))})});

View File

@@ -26,6 +26,7 @@
* @property int|bool $allowUserTags Allow user-entered tags?
* @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 int $maxSelectedItems Alias of maxItems (as used by InputfieldPage)
* @property bool|int $useAjax
* @property string $delimiter One of 's' (for space ' '), 'p' (for pipe '|') or 'c' (for comma).
* @property string $value
@@ -33,7 +34,7 @@
* @property-read array $arrayValue
* @property string|null $pageSelector
*
* ProcessWire 3.x, Copyright 2023 by Ryan Cramer
* ProcessWire 3.x, Copyright 2024 by Ryan Cramer
* https://processwire.com
*
*/
@@ -45,7 +46,7 @@ class InputfieldTextTags extends Inputfield implements
return array(
'title' => __('Text Tags', __FILE__), // Module Title
'summary' => __('Enables input of user entered tags or selection of predefined tags.', __FILE__), // Module Summary
'version' => 6,
'version' => 7,
'icon' => 'tags',
);
}
@@ -133,6 +134,8 @@ class InputfieldTextTags extends Inputfield implements
return $this->setTagsList($value, (int) $languageId);
} else if($key === 'allowUserTags' || $key === 'closeAfterSelect' || $key === 'useAjax' || $key === 'maxItems') {
$value = (int) $value;
} else if($key === 'maxSelectedItems') {
$key = 'maxItems';
}
return parent::set($key, $value);
}
@@ -170,9 +173,13 @@ class InputfieldTextTags extends Inputfield implements
if($key === 'value') {
if($value instanceof WireArray) {
$value = explode('|', (string) $value);
}
} else if($value instanceof Page) {
$value = $value->id ? array("$value->id") : array();
}
if(is_array($value)) {
$value = $this->tagArrayToString($value);
} else if("$value" === "0") {
$value = '';
}
}
return parent::setAttribute($key, $value);
@@ -352,6 +359,9 @@ class InputfieldTextTags extends Inputfield implements
}
}
}
foreach($tags as $key => $tag) {
if($key === 0 && "$tag" === "0") unset($tags[$key]);
}
if(!$getArray) return $this->tagsListArrayToString($tags);
return $tags;
}
@@ -584,6 +594,19 @@ class InputfieldTextTags extends Inputfield implements
$out = "<input $attrStr />";
if($config->ajax && !empty($opts['cfgName'])) {
// when renderReady was called during non-ajax, it may not have included the current value
// as would be the case when there are TextTags fields in repeaters, PageEditChildren, etc.
// so we add them here to the JS ProcessWire.config as part of the output
$script = 'script';
$cfgName = $opts['cfgName'];
$out .= "<$script>";
foreach($tags as $val => $label) {
$out .= "ProcessWire.config." . $cfgName . "['$val'] = '$label';";
}
$out .= "</$script>";
}
return $out;
}
@@ -653,6 +676,7 @@ class InputfieldTextTags extends Inputfield implements
*
*/
public function ___processInput(WireInputData $input) {
if($this->isPageField()) $this->prepareInputPage($input);
parent::___processInput($input);
$val = $this->val();
$value = $this->validateValue($val);
@@ -665,6 +689,39 @@ class InputfieldTextTags extends Inputfield implements
return $this;
}
/**
* Prepare input for processing when values are Page IDs
*
* This enables it to differenate between page IDs and numeric page titles.
*
* @param WireInputData $input
*
*/
protected function prepareInputPage(WireInputData $input) {
$val = $input->get($this->name);
if(is_string($val) && strpos($val, '|')) {
$val = explode('|', $val);
}
// prepend "+" to indicate it is a newly-added numeric page title
// like a year (i.e. "2024") and not an existing page ID
if(is_string($val)) {
if(ctype_digit($val)) $val = "+$val";
} else if(is_array($val)) {
foreach($val as $k => $v) {
if(ctype_digit($v)) $val[$k] = "+$v";
}
$val = implode('|', $val);
}
// stuff back in post vars for processing by parent::processInput
$input->set($this->name, $val);
$_POST[$this->name] = $val;
}
/**
* Encode numeric tags (like page IDs) so they arent lost by JSON encoding
*
@@ -707,10 +764,10 @@ class InputfieldTextTags extends Inputfield implements
while(count($tags) > $maxItems) array_pop($tags);
}
foreach(array_keys($tags) as $tag) {
foreach($tags as $tag => $label) {
if(isset($validTags[$tag])) {
// tag is known/valid
} else if($isPageField && $this->tagsUrl && ctype_digit(ltrim($tag, '_'))) {
} else if($isPageField && $this->tagsUrl && ctype_digit(ltrim("$tag", '_'))) {
// tag is page ID from ajax: will be validated by InputfieldPage
} else if(!$allowUserTags && ($isPageField || !$this->tagsUrl)) {
// user tags not allowed
@@ -718,6 +775,7 @@ class InputfieldTextTags extends Inputfield implements
$this->error(sprintf($this->_('Removed invalid tag value: %s'), $tag));
} else {
// newly added tag
if($isPageField && strpos($tag, "+") === 0) $tag = ltrim($tag, "+");
$tag = $sanitizer->text($tag);
$label = $tag;
if(strpos($tag, $delimiter) !== false) $tag = str_replace($delimiter, '-', $tag);
@@ -1004,15 +1062,14 @@ class InputfieldTextTags extends Inputfield implements
$f->set("value$language", $this->tagsListArrayToString($this->get("tagsList$language")));
}
}
$fieldset->add($f);
} else {
/** @var InputfieldHidden $f */
$f = $modules->get('InputfieldHidden');
$f->attr('name', 'tagsList');
$f->val('');
$fieldset->add($f);
}
$fieldset->add($f);
if($isTextField || $isPageField) {
/** @var InputfieldText $f */
$inputName = $this->name;
@@ -1071,30 +1128,28 @@ class InputfieldTextTags extends Inputfield implements
$f->error($this->_('The placeholder “{q}” is required somewhere in your Ajax URL.'));
}
$f->showIf = 'useAjax=1';
$fieldset->add($f);
} else {
/** @var InputfieldHidden $f */
$f = $modules->get('InputfieldHidden');
$f->attr('name', 'tagsUrl');
$f->val('');
$fieldset->add($f);
}
$fieldset->add($f);
if($isTextField) {
/** @var InputfieldToggle $f */
$f = $modules->get('InputfieldToggle');
$f->attr('name', 'allowUserTags');
$f->label = $this->_('Allow user to enter their own tags?');
$f->val($this->allowUserTags);
$fieldset->add($f);
} else {
/** @var InputfieldHidden $f */
$f = $modules->get('InputfieldHidden');
$f->attr('name', 'allowUserTags');
$f->val('0');
$fieldset->add($f);
}
$fieldset->add($f);
/** @var InputfieldToggle $f */
$f = $modules->get('InputfieldToggle');
$f->attr('name', 'closeAfterSelect');

View File

@@ -46,7 +46,20 @@ $tag-border-colors: #b1c3d4 #cbd7e3 #cbd7e3 #cbd7e3;
// in single selection mode
.selectize-input {
background: $tag-background-color;
font-size: 14px;
font-size: inherit;
}
}
}
.AdminThemeUikit .Inputfield {
.selectize-control.single {
.selectize-input:not(.uk-form-small) {
padding-top: 10px;
padding-bottom: 8px;
height: 40px;
}
}
.selectize-control.single:has(.selectize-input:not(.uk-form-small)) {
height: 40px;
}
}