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:
@@ -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(
|
||||
|
@@ -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 */
|
||||
|
@@ -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);
|
||||
|
@@ -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>…</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>…</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))})});
|
@@ -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 aren’t 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');
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user