mirror of
https://github.com/processwire/processwire.git
synced 2025-08-10 00:37:02 +02:00
Add InputfieldTextTags module to core
This commit is contained in:
@@ -0,0 +1 @@
|
||||
.InputfieldTextTags label.pw-hidden{display:none}.InputfieldTextTags input.InputfieldTextTagsSelect:not(.selectized),.InputfieldTextTags input.InputfieldTextTagsInput:not(.selectized){color:#f0f3f7}.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}
|
@@ -0,0 +1,99 @@
|
||||
function InputfieldTextTags($parent) {
|
||||
|
||||
if(typeof $parent === "undefined") $parent = $('.InputfieldForm');
|
||||
|
||||
var $inputs = jQuery('.InputfieldTextTagsInput:not(.selectized)', $parent);
|
||||
var $selects = jQuery('.InputfieldTextTagsSelect:not(.selectized)', $parent);
|
||||
|
||||
if($inputs.length) {
|
||||
$inputs.selectize({
|
||||
plugins: ['remove_button', 'drag_drop'],
|
||||
delimiter: ' ',
|
||||
persist: false,
|
||||
createOnBlur: true,
|
||||
submitOnReturn: false,
|
||||
create: function(input) {
|
||||
return {
|
||||
value: input,
|
||||
text: input
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if($selects.length) {
|
||||
$selects.each(function() {
|
||||
var $select = $(this);
|
||||
var configName = $select.attr('data-cfgname');
|
||||
var allowUserTags = $select.hasClass('InputfieldTextTagsSelectOnly') ? false : true;
|
||||
var tags = [];
|
||||
var tagsList = [];
|
||||
var n = 0;
|
||||
if(configName.length) {
|
||||
tags = ProcessWire.config[configName];
|
||||
} else {
|
||||
tags = $select.attr('data-tags');
|
||||
tags = JSON.parse(tags);
|
||||
}
|
||||
for(var tag in tags) {
|
||||
var label = tags[tag];
|
||||
tagsList[n] = { value: tag, label: label };
|
||||
n++;
|
||||
}
|
||||
$select.selectize({
|
||||
plugins: ['remove_button', 'drag_drop'],
|
||||
delimiter: ' ',
|
||||
persist: true,
|
||||
submitOnReturn: false,
|
||||
closeAfterSelect: true,
|
||||
copyClassesToDropdown: false,
|
||||
createOnBlur: true,
|
||||
maxItems: null,
|
||||
valueField: 'value',
|
||||
labelField: 'label',
|
||||
searchField: ['value', 'label'],
|
||||
options: tagsList,
|
||||
create: function(input) {
|
||||
return {
|
||||
value: input,
|
||||
text: input
|
||||
}
|
||||
},
|
||||
createFilter: function(input) {
|
||||
if(allowUserTags) return true;
|
||||
allow = false;
|
||||
for(var n = 0; n < tags.length; n++) {
|
||||
if(typeof tagsList[input] !== "undefined") {
|
||||
allow = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return allow;
|
||||
},
|
||||
/*
|
||||
onDropdownOpen: function($dropdown) {
|
||||
$dropdown.closest('li, .InputfieldImageEdit').css('z-index', 100);
|
||||
},
|
||||
onDropdownClose: function($dropdown) {
|
||||
$dropdown.closest('li, .InputfieldImageEdit').css('z-index', 'auto');
|
||||
},
|
||||
*/
|
||||
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>';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
jQuery(document).ready(function($) {
|
||||
InputfieldTextTags();
|
||||
$(document).on('reloaded', '.InputfieldTextTags', function() { InputfieldTextTags($(this)); });
|
||||
});
|
1
wire/modules/Inputfield/InputfieldTextTags/InputfieldTextTags.min.js
vendored
Normal file
1
wire/modules/Inputfield/InputfieldTextTags/InputfieldTextTags.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
function InputfieldTextTags($parent){if(typeof $parent==="undefined")$parent=$(".InputfieldForm");var $inputs=jQuery(".InputfieldTextTagsInput:not(.selectized)",$parent);var $selects=jQuery(".InputfieldTextTagsSelect:not(.selectized)",$parent);if($inputs.length){$inputs.selectize({plugins:["remove_button","drag_drop"],delimiter:" ",persist:false,createOnBlur:true,submitOnReturn:false,create:function(input){return{value:input,text:input}}})}if($selects.length){$selects.each(function(){var $select=$(this);var configName=$select.attr("data-cfgname");var allowUserTags=$select.hasClass("InputfieldTextTagsSelectOnly")?false:true;var tags=[];var tagsList=[];var n=0;if(configName.length){tags=ProcessWire.config[configName]}else{tags=$select.attr("data-tags");tags=JSON.parse(tags)}for(var tag in tags){var label=tags[tag];tagsList[n]={value:tag,label:label};n++}$select.selectize({plugins:["remove_button","drag_drop"],delimiter:" ",persist:true,submitOnReturn:false,closeAfterSelect:true,copyClassesToDropdown:false,createOnBlur:true,maxItems:null,valueField:"value",labelField:"label",searchField:["value","label"],options:tagsList,create:function(input){return{value:input,text:input}},createFilter:function(input){if(allowUserTags)return true;allow=false;for(var n=0;n<tags.length;n++){if(typeof tagsList[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>"}}})})}}jQuery(document).ready(function($){InputfieldTextTags();$(document).on("reloaded",".InputfieldTextTags",function(){InputfieldTextTags($(this))})});
|
@@ -0,0 +1,746 @@
|
||||
<?php namespace ProcessWire;
|
||||
|
||||
/**
|
||||
* An Inputfield for handling predefined or user input tags
|
||||
*
|
||||
* ~~~~~
|
||||
* // create a text tags Inputifeld
|
||||
* $f = $modules->get('InputfieldTextTags');
|
||||
* $f->attr('name', 'tags');
|
||||
*
|
||||
* // allow for user-entered tags input (true or false, default=false)
|
||||
* $f->allowUserTags = true;
|
||||
*
|
||||
* // predefined selectable tags (tag and optional label)
|
||||
* $f->addTag('foo');
|
||||
* $f->addTag('bar', 'This is Bar'); // optional label
|
||||
* $f->addTag('baz', 'This is Baz'); // optional label
|
||||
*
|
||||
* // set currently entered/selected tags
|
||||
* $f->val('foo bar');
|
||||
* $f->val([ 'foo', 'bar' ]); // this also works
|
||||
* ~~~~~
|
||||
*
|
||||
* @property array|string $tagsList Array of tags [ 'tag' => 'label' ], or newline separated string of "tag=label", or use addTag() to populate.
|
||||
* @property int|bool $allowUserTags Allow user-entered tags?
|
||||
* @property string $value
|
||||
* @property-read array $arrayValue
|
||||
*
|
||||
* ProcessWire 3.x, Copyright 2021 by Ryan Cramer
|
||||
* https://processwire.com
|
||||
*
|
||||
*/
|
||||
class InputfieldTextTags extends Inputfield
|
||||
implements InputfieldHasTextValue, InputfieldSupportsArrayValue, InputfieldHasSelectableOptions, InputfieldHasSortableValue {
|
||||
|
||||
public static function getModuleInfo() {
|
||||
return array(
|
||||
'title' => __('Text Tags', __FILE__), // Module Title
|
||||
'summary' => __('Enables input of user entered tags or selection of predefined tags.', __FILE__), // Module Summary
|
||||
'version' => 1,
|
||||
'icon' => 'tags',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->set('tagsList', array());
|
||||
$this->set('allowUserTags', 0);
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wired to PW
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
*/
|
||||
public function wired() {
|
||||
parent::wired();
|
||||
$languages = $this->wire()->languages;
|
||||
if($languages) {
|
||||
foreach($languages as $language) {
|
||||
if(!$language->isDefault()) $this->set("tagsList$language->id", array());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get property
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param string $key
|
||||
* @return array|mixed|null|string
|
||||
*
|
||||
*/
|
||||
public function get($key) {
|
||||
if($key === 'arrayValue') return $this->getArrayValue();
|
||||
return parent::get($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set property
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return Inputfield|InputfieldTextTags|WireData
|
||||
*
|
||||
*/
|
||||
public function set($key, $value) {
|
||||
if(strpos($key, 'tagsList') === 0) {
|
||||
if($key === 'tagsList') return $this->setTagsList($value);
|
||||
list(,$languageId) = explode('tagsList', $key, 2);
|
||||
return $this->setTagsList($value, (int) $languageId);
|
||||
}
|
||||
return parent::set($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attribute
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param array|string $key
|
||||
* @param array|int|string $value
|
||||
* @return self|Inputfield
|
||||
*
|
||||
*/
|
||||
public function setAttribute($key, $value) {
|
||||
if($key === 'value') {
|
||||
if(is_object($value) && $value instanceof WireArray) {
|
||||
$value = explode('|', (string) $value);
|
||||
}
|
||||
if(is_array($value)) {
|
||||
$value = $this->tagArrayToString($value);
|
||||
}
|
||||
}
|
||||
return parent::setAttribute($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array value
|
||||
*
|
||||
* For InputfieldSupportsArrayValue interface
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
*/
|
||||
public function getArrayValue() {
|
||||
$value = parent::getAttribute('value');
|
||||
$value = $this->tagStringToArray($value);
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set value as an array
|
||||
*
|
||||
* For InputfieldSupportsArrayValue interface
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param array $value
|
||||
*
|
||||
*/
|
||||
public function setArrayValue(array $value) {
|
||||
$this->setAttribute('value', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert string of tags to array
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param string $tagString
|
||||
* @return array
|
||||
*
|
||||
*/
|
||||
public function tagStringToArray($tagString) {
|
||||
$tagString = trim($tagString);
|
||||
$tagArray = array();
|
||||
if(!strlen($tagString)) return $tagArray;
|
||||
$a = explode(' ', $tagString);
|
||||
foreach($a as $key => $tag) {
|
||||
$tag = trim("$tag");
|
||||
if(!strlen($tag)) continue;
|
||||
if(strpos($tag, '_') === 0 && ctype_digit(substr($tag, 1))) $tag = ltrim($tag, '_');
|
||||
$tagArray[$tag] = $tag;
|
||||
}
|
||||
return $tagArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert array of tags to string
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param array $tagArray
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public function tagArrayToString(array $tagArray) {
|
||||
return trim(implode(' ', $tagArray));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given tags string or array, return array of [ 'tag' => 'label' ]
|
||||
*
|
||||
* Public API usages likely would prefer the static tagsLabels() method instead.
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param string|array $tags
|
||||
* @param Language|int|string|null $language
|
||||
* @return array
|
||||
*
|
||||
*/
|
||||
public function tagsToLabels($tags, $language = null) {
|
||||
if(!is_array($tags)) $tags = $this->tagStringToArray($tags);
|
||||
if(empty($tags)) return array();
|
||||
$labels = $this->getTagLabels($language);
|
||||
$a = array();
|
||||
foreach($tags as $tag) {
|
||||
$label = isset($labels[$tag]) ? $labels[$tag] : $tag;
|
||||
$a[$tag] = $label;
|
||||
}
|
||||
return $a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert string of tagsList (tag definitions) to array
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param string $tagString
|
||||
* @param bool $allowLabels
|
||||
* @return array
|
||||
*
|
||||
*/
|
||||
protected function tagsListStringToArray($tagString, $allowLabels = true) {
|
||||
|
||||
$tagString = trim($tagString);
|
||||
$tagArray = array();
|
||||
|
||||
if(!strlen($tagString)) return $tagArray;
|
||||
|
||||
$regex = $allowLabels ? '/[\r\n\t]+/' : '/[\s\r\n\t]+/';
|
||||
$a = preg_split($regex, $tagString);
|
||||
|
||||
foreach($a as $key => $tag) {
|
||||
$tag = trim("$tag");
|
||||
if(!strlen($tag)) continue;
|
||||
if(strpos($tag, '=') !== false) {
|
||||
list($tag, $label) = explode('=', $tag, 2);
|
||||
if(!$allowLabels) $label = $tag;
|
||||
} else {
|
||||
$label = $tag;
|
||||
}
|
||||
if(strpos($tag, '_') === 0 && ctype_digit(substr($tag, 1))) {
|
||||
$tagIsLabel = $tag === $label;
|
||||
$tag = ltrim($tag, '_');
|
||||
if($tagIsLabel) $label = $tag;
|
||||
}
|
||||
$tagArray[$tag] = $label;
|
||||
}
|
||||
|
||||
return $tagArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert given tags array to tagsList definition string
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param array $tags
|
||||
* @param string $delimiter
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
protected function tagsListArrayToString(array $tags, $delimiter = "\n") {
|
||||
$items = array();
|
||||
foreach($tags as $tagName => $tagLabel) {
|
||||
if($tagName === $tagLabel) {
|
||||
$items[$tagName] = $tagName;
|
||||
} else {
|
||||
$items[$tagName] = "$tagName=$tagLabel";
|
||||
}
|
||||
}
|
||||
return implode($delimiter, $items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all selectable tags and labels, optionally for specific language
|
||||
*
|
||||
* #pw-group-settings
|
||||
*
|
||||
* @param Language|int|string|null $language
|
||||
* @param bool $getArray
|
||||
* @return array|string
|
||||
*
|
||||
*/
|
||||
public function getTagsList($language = null, $getArray = true) {
|
||||
$tags = parent::get('tagsList'); /** @var array $tags */
|
||||
if($language) {
|
||||
$key = $this->languageKey($language, 'tagsList');
|
||||
if($key !== 'tagsList') {
|
||||
$langTags = parent::get($key); /** @var array $langTags */
|
||||
$tags = array_merge($tags, $langTags);
|
||||
}
|
||||
}
|
||||
if(!$getArray) return $this->tagsListArrayToString($tags);
|
||||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all selectable tags and labels, optionally for specific language
|
||||
*
|
||||
* #pw-group-settings
|
||||
*
|
||||
* @param array|string $tags Array of [ 'tag' => 'label', 'tag2' => 'label2' ] or newline string of "tag=label\ntag2=label2\n..."
|
||||
* @param Language|int|string|null $language
|
||||
* @return self
|
||||
*
|
||||
*/
|
||||
public function setTagsList($tags, $language = null) {
|
||||
if(is_string($tags)) $tags = $this->tagsListStringToArray($tags);
|
||||
$key = $language === null ? 'tagsList' : $this->languageKey($language, 'tagsList');
|
||||
parent::set($key, $tags);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a predefined tag
|
||||
*
|
||||
* #pw-group-settings
|
||||
*
|
||||
* @param string $tag
|
||||
* @param string $label
|
||||
* @param Language|int|string|null $language
|
||||
* @return self
|
||||
*
|
||||
*/
|
||||
public function addTag($tag, $label = '', $language = null) {
|
||||
$key = $this->languageKey($language, 'tagsList');
|
||||
$tagsList = $this->get($key);
|
||||
if(!strlen($label)) $label = $tag;
|
||||
$tagsList[$tag] = $label;
|
||||
parent::set($key, $tagsList);
|
||||
if($language && $key !== 'tagsList') {
|
||||
$tagsList = $this->tagsList;
|
||||
if(!isset($tagsList[$tag])) {
|
||||
$tagsList[$tag] = $tag;
|
||||
parent::set('tagsList', $tagsList);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove tag
|
||||
*
|
||||
* #pw-group-settings
|
||||
*
|
||||
* @param string $tag
|
||||
* @return self
|
||||
*
|
||||
*/
|
||||
public function removeTag($tag) {
|
||||
$tagsList = parent::get('tagsList');
|
||||
unset($tagsList[$tag]);
|
||||
$languages = $this->wire()->languages;
|
||||
if(!$languages) return $this;
|
||||
foreach($languages as $language) {
|
||||
if($language->isDefault()) continue;
|
||||
$tagsList = parent::get("tagsList$language");
|
||||
if(!is_array($tagsList)) continue;
|
||||
if(!empty($tagsList[$tag])) unset($tagsList[$tag]);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get labels for all tags
|
||||
*
|
||||
* #pw-group-settings
|
||||
*
|
||||
* @param Language|int|string|null $language
|
||||
* @return array
|
||||
*
|
||||
*/
|
||||
public function getTagLabels($language = null) {
|
||||
return $this->getTagsList($language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get label for given tag
|
||||
*
|
||||
* #pw-group-settings
|
||||
*
|
||||
* - Returns given tag if it has no label.
|
||||
* - Returns blank string if given tag is not in list and user entered tags are not allowed.
|
||||
*
|
||||
* @param string $tag
|
||||
* @param Language|int|string|null $language
|
||||
* @return mixed
|
||||
*
|
||||
*/
|
||||
public function getTagLabel($tag, $language = null) {
|
||||
if(!$language && $this->wire()->langauges) $language = $this->wire()->user->language;
|
||||
$tags = $this->getTagsList($language);
|
||||
if(isset($tags[$tag])) return $tags[$tag];
|
||||
if($this->allowUserTags) return $tag;
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set label for tag
|
||||
*
|
||||
* #pw-group-settings
|
||||
*
|
||||
* @param string $tag
|
||||
* @param string $label
|
||||
* @param Language|int|string|null $language
|
||||
* @return self
|
||||
*
|
||||
*/
|
||||
public function setTagLabel($tag, $label, $language = null) {
|
||||
return $this->addTag($tag, $label, $language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get property name for non-default language
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param string|int|Language $language
|
||||
* @param string $key
|
||||
* @return string
|
||||
* @throws WireException
|
||||
*
|
||||
*/
|
||||
protected function languageKey($language, $key) {
|
||||
if(!$language) return $key;
|
||||
$languages = $this->wire()->languages;
|
||||
if(!$languages) return $key;
|
||||
if(!wireInstanceOf($language, 'Language')) $language = $languages->get($language);
|
||||
if(!$language) throw new WireException('Invalid language');
|
||||
if(!$language->isDefault()) $key .= $language->id;
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render ready
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param Inputfield $parent
|
||||
* @param bool $renderValueMode
|
||||
* @return bool
|
||||
* @throws WireException
|
||||
*
|
||||
*/
|
||||
public function renderReady(Inputfield $parent = null, $renderValueMode = false) {
|
||||
/** @var JqueryUI $jQueryUI */
|
||||
$jQueryUI = $this->wire()->modules->get('JqueryUI');
|
||||
$jQueryUI->use('selectize');
|
||||
$config = $this->wire()->config;
|
||||
$url = $config->urls($this->className());
|
||||
$config->scripts->add($url . 'InputfieldTextTags.js');
|
||||
$config->styles->add($url . 'InputfieldTextTags.css');
|
||||
$this->addClass('InputfieldNoFocus', 'wrapClass');
|
||||
return parent::renderReady($parent, $renderValueMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Inputfield
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public function ___render() {
|
||||
|
||||
$attrs = $this->getAttributes();
|
||||
unset($attrs['class']);
|
||||
|
||||
$language = $this->wire()->user->language;
|
||||
$tags = $this->getTagsList($language && $language->id ? $language : null);
|
||||
$classes = array();
|
||||
$classes[] = count($tags) ? 'InputfieldTextTagsSelect' : 'InputfieldTextTagsInput';
|
||||
|
||||
if($this->allowUserTags) {
|
||||
$value = $this->tagStringToArray($this->val());
|
||||
foreach($value as $tag) {
|
||||
if(!isset($tags[$tag])) $tags[$tag] = $tag;
|
||||
}
|
||||
} else {
|
||||
$classes[] = 'InputfieldTextTagsSelectOnly';
|
||||
}
|
||||
|
||||
$a = $tags;
|
||||
$tags = array();
|
||||
foreach($a as $tag => $label) {
|
||||
// ensure no digit-only tags which do not survive json_encode()
|
||||
if(ctype_digit("$tag")) $tag = "_$tag";
|
||||
$tags[$tag] = $label;
|
||||
}
|
||||
|
||||
$config = $this->wire()->config;
|
||||
$class = $this->className();
|
||||
|
||||
if($this->hasField) {
|
||||
// page editor
|
||||
$name = $class . '_' . $this->hasField->name . '__tags';
|
||||
$data = $config->$name ? $config->$name : array();
|
||||
$data = array_unique(array_merge($data, $tags));
|
||||
$config->js($name, $data);
|
||||
$attrs['data-cfgname'] = $name;
|
||||
} else {
|
||||
// other usages
|
||||
$attrs['data-cfgname'] = '';
|
||||
$attrs['data-tags'] = json_encode($tags, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
$attrs['class'] = trim(implode(' ', $classes));
|
||||
$attrs['value'] = $this->encodeNumericTags($this->val());
|
||||
if(empty($attrs['type'])) $attrs['type'] = 'text';
|
||||
$attrStr = $this->getAttributesString($attrs);
|
||||
|
||||
$out = "<input $attrStr />";
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render value
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public function ___renderValue() {
|
||||
return $this->wire()->sanitizer->entities($this->val());
|
||||
}
|
||||
|
||||
/**
|
||||
* Process input
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param WireInputData $input
|
||||
* @return $this
|
||||
*
|
||||
*/
|
||||
public function ___processInput(WireInputData $input) {
|
||||
parent::___processInput($input);
|
||||
$val = $this->val();
|
||||
$value = $this->validateValue($val);
|
||||
if($val !== $value) $this->val($value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode numeric tags (like page IDs) so they aren’t lost by JSON encoding
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param string|array $tags
|
||||
* @param bool $getArray
|
||||
* @return array|string
|
||||
*
|
||||
*/
|
||||
protected function encodeNumericTags($tags, $getArray = false) {
|
||||
if(!is_array($tags)) $tags = $this->tagStringToArray($tags);
|
||||
foreach($tags as $key => $tag) {
|
||||
if(ctype_digit("$tag")) $tags[$key] = "_$tag";
|
||||
}
|
||||
return $getArray ? $tags : implode(' ', $tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and return given tags string
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param string|array $tags
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
protected function validateValue($tags) {
|
||||
if(!is_array($tags)) {
|
||||
$tags = $this->tagStringToArray($tags);
|
||||
}
|
||||
if(!$this->allowUserTags) {
|
||||
$validTags = $this->getTagsList();
|
||||
foreach(array_keys($tags) as $tag) {
|
||||
if(!isset($validTags[$tag])) {
|
||||
unset($tags[$tag]);
|
||||
$this->error(sprintf($this->_('Removed invalid tag value: %s'), $tag));
|
||||
}
|
||||
}
|
||||
}
|
||||
return trim(implode(' ', $tags));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a selectable option
|
||||
*
|
||||
* For InputfieldHasSelectableOptions interface
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param string|int $value
|
||||
* @param string|null $label
|
||||
* @param array|null $attributes
|
||||
* @return self|$this
|
||||
*
|
||||
*/
|
||||
public function addOption($value, $label = null, array $attributes = null) {
|
||||
return $this->addTag($value, $label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add selectable option with label, optionally for specific language
|
||||
*
|
||||
* For InputfieldHasSelectableOptions interface
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param string|int $value
|
||||
* @param string $label
|
||||
* @param Language|null $language
|
||||
* @return self|$this
|
||||
*
|
||||
*/
|
||||
public function addOptionLabel($value, $label, $language = null) {
|
||||
return $this->addTag($value, $label, $language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Static utility function to convert a tags string to an array of [ 'tag' => 'label' ]
|
||||
*
|
||||
* There isn’t currently a dedicated FieldtypeTextTags module, so if you want to convert a string of tags
|
||||
* (as would be returned from a $page “text” field value) you can use this static helper method to convert
|
||||
* the string of tags to an array of labels indexed by tag.
|
||||
*
|
||||
* Note: returned tags and labels are entity-encoded when current $page API var output formatting is ON.
|
||||
*
|
||||
* ~~~~~
|
||||
* $field = $fields->get('tags'); // tags field using FieldtypeText
|
||||
* $tags = $page->get('tags'); // page value (string of tags, i.e. "foo bar baz")
|
||||
* $labels = InputfieldTextTags::tagsLabels($field, $tags);
|
||||
* foreach($labels as $tag => $label) {
|
||||
* echo "<li>$tag: $label</li>";
|
||||
* }
|
||||
* ~~~~~
|
||||
*
|
||||
* #pw-group-helpers
|
||||
*
|
||||
* @param Field $field
|
||||
* @param string|array|null $tags
|
||||
* @return array
|
||||
*
|
||||
*/
|
||||
public static function tagsLabels(Field $field, $tags = null) {
|
||||
if(is_string($tags) && !strlen($tags)) return array();
|
||||
/** @var InputfieldTextTags $inputfield */
|
||||
$inputfield = $field->wire()->modules->getModule('InputfieldTextTags', array('noInit' => true));
|
||||
$inputfield->setTagsList($field->get('tagsList'));
|
||||
$languages = $field->wire()->languages;
|
||||
if($languages) {
|
||||
$userLanguage = $field->wire()->user->language;
|
||||
foreach($languages as $language) {
|
||||
if($language->isDefault() || $language->id != $userLanguage->id) continue;
|
||||
$tagsList = $field->get("tagsList$language");
|
||||
if(!empty($tagsList)) $inputfield->setTagsList($tagsList, $language);
|
||||
}
|
||||
} else {
|
||||
$userLanguage = null;
|
||||
}
|
||||
if($tags === null) {
|
||||
// return all tags to labels
|
||||
$labels = $inputfield->getTagsList($userLanguage);
|
||||
} else {
|
||||
// return tags to labels matching given tags
|
||||
$labels = $inputfield->tagsToLabels($tags, $userLanguage);
|
||||
}
|
||||
if($field->wire()->page->of()) {
|
||||
// entity encode labels when page output formatting is on
|
||||
$sanitizer = $field->wire()->sanitizer;
|
||||
$a = $labels;
|
||||
$labels = array();
|
||||
foreach($a as $tag => $label) {
|
||||
$tag = $sanitizer->entities1($tag);
|
||||
$label = $sanitizer->entities($label);
|
||||
$labels[$tag] = $label;
|
||||
}
|
||||
}
|
||||
return $labels;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Config
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @return InputfieldWrapper
|
||||
*
|
||||
*/
|
||||
public function ___getConfigInputfields() {
|
||||
|
||||
$moduleInfo = self::getModuleInfo();
|
||||
$modules = $this->wire()->modules;
|
||||
$inputfields = $this->hasFieldtype ? new InputfieldWrapper() : parent::___getConfigInputfields();
|
||||
$languages = $this->wire()->languages;
|
||||
|
||||
/** @var InputfieldFieldset $fieldset */
|
||||
$fieldset = $modules->get('InputfieldFieldset');
|
||||
$fieldset->attr('name', '_tags_settings');
|
||||
$fieldset->label = $moduleInfo['title'];
|
||||
$fieldset->icon = 'tags';
|
||||
$inputfields->prepend($fieldset);
|
||||
|
||||
if($this->hasFieldtype && $this->hasFieldtype != 'FieldtypeText') {
|
||||
$fieldset->description = $this->_('There are currently no configurable settings.');
|
||||
return $inputfields;
|
||||
}
|
||||
|
||||
/** @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);
|
||||
|
||||
/** @var InputfieldTextarea $f */
|
||||
$f = $modules->get('InputfieldTextarea');
|
||||
$f->attr('name', 'tagsList');
|
||||
$f->label = $this->label = $this->_('Predefined tags');
|
||||
$f->description = $this->_('Enter predefined tags, 1 per line. To define separate tag and label, specify `tag=label` on the line.');
|
||||
$f->notes = $this->_('Tags may not contain whitespace but labels can.');
|
||||
$f->val($this->tagsListArrayToString($this->tagsList));
|
||||
if($languages) {
|
||||
$f->description .= ' ' . $this->_('To define separate labels per-language, re-enter each tag (with label) for each language.');
|
||||
$f->useLanguages = true;
|
||||
foreach($languages as $language) {
|
||||
if(!$language->isDefault()) $f->set("value$language", $this->tagsListArrayToString($this->get("tagsList$language")));
|
||||
}
|
||||
}
|
||||
$fieldset->add($f);
|
||||
|
||||
return $inputfields;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
$tag-background-color: #f0f3f7;
|
||||
$tag-border-color: #cbd7e3;
|
||||
$tag-border-colors: #b1c3d4 #cbd7e3 #cbd7e3 #cbd7e3;
|
||||
|
||||
.InputfieldTextTags {
|
||||
label.pw-hidden {
|
||||
display: none;
|
||||
}
|
||||
input.InputfieldTextTagsSelect:not(.selectized),
|
||||
input.InputfieldTextTagsInput:not(.selectized) {
|
||||
color: $tag-background-color;
|
||||
}
|
||||
}
|
||||
|
||||
.Inputfield {
|
||||
.selectize-input {
|
||||
border: 1px solid $tag-border-colors;
|
||||
border-color: $tag-border-colors;
|
||||
box-shadow: none;
|
||||
}
|
||||
.selectize-control .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-input:not(.has-items) {
|
||||
background: $tag-background-color;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user