From e67c1c075c6997a82ff3d24a1eccb54318b20805 Mon Sep 17 00:00:00 2001 From: Ryan Cramer Date: Thu, 22 Apr 2021 10:47:02 -0400 Subject: [PATCH] Add InputfieldTextTags module to core --- .../InputfieldTextTags/InputfieldTextTags.css | 1 + .../InputfieldTextTags/InputfieldTextTags.js | 99 +++ .../InputfieldTextTags.min.js | 1 + .../InputfieldTextTags.module | 746 ++++++++++++++++++ .../InputfieldTextTags.scss | 33 + 5 files changed, 880 insertions(+) create mode 100644 wire/modules/Inputfield/InputfieldTextTags/InputfieldTextTags.css create mode 100644 wire/modules/Inputfield/InputfieldTextTags/InputfieldTextTags.js create mode 100644 wire/modules/Inputfield/InputfieldTextTags/InputfieldTextTags.min.js create mode 100644 wire/modules/Inputfield/InputfieldTextTags/InputfieldTextTags.module create mode 100644 wire/modules/Inputfield/InputfieldTextTags/InputfieldTextTags.scss diff --git a/wire/modules/Inputfield/InputfieldTextTags/InputfieldTextTags.css b/wire/modules/Inputfield/InputfieldTextTags/InputfieldTextTags.css new file mode 100644 index 00000000..971c98db --- /dev/null +++ b/wire/modules/Inputfield/InputfieldTextTags/InputfieldTextTags.css @@ -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} diff --git a/wire/modules/Inputfield/InputfieldTextTags/InputfieldTextTags.js b/wire/modules/Inputfield/InputfieldTextTags/InputfieldTextTags.js new file mode 100644 index 00000000..dcbd8b72 --- /dev/null +++ b/wire/modules/Inputfield/InputfieldTextTags/InputfieldTextTags.js @@ -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 '
' + escape(item.label) + '
'; + }, + option: function(item, escape) { + if(typeof item.label === "undefined" || !item.label.length) item.label = item.value; + return '
' + escape(item.label) + '
'; + } + } + }); + }); + } +} + +jQuery(document).ready(function($) { + InputfieldTextTags(); + $(document).on('reloaded', '.InputfieldTextTags', function() { InputfieldTextTags($(this)); }); +}); \ No newline at end of file diff --git a/wire/modules/Inputfield/InputfieldTextTags/InputfieldTextTags.min.js b/wire/modules/Inputfield/InputfieldTextTags/InputfieldTextTags.min.js new file mode 100644 index 00000000..1ce9e54d --- /dev/null +++ b/wire/modules/Inputfield/InputfieldTextTags/InputfieldTextTags.min.js @@ -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"+escape(item.label)+""},option:function(item,escape){if(typeof item.label==="undefined"||!item.label.length)item.label=item.value;return"
"+escape(item.label)+"
"}}})})}}jQuery(document).ready(function($){InputfieldTextTags();$(document).on("reloaded",".InputfieldTextTags",function(){InputfieldTextTags($(this))})}); \ No newline at end of file diff --git a/wire/modules/Inputfield/InputfieldTextTags/InputfieldTextTags.module b/wire/modules/Inputfield/InputfieldTextTags/InputfieldTextTags.module new file mode 100644 index 00000000..6c6dbb24 --- /dev/null +++ b/wire/modules/Inputfield/InputfieldTextTags/InputfieldTextTags.module @@ -0,0 +1,746 @@ +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 = ""; + + 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 "
  • $tag: $label
  • "; + * } + * ~~~~~ + * + * #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; + } + +} diff --git a/wire/modules/Inputfield/InputfieldTextTags/InputfieldTextTags.scss b/wire/modules/Inputfield/InputfieldTextTags/InputfieldTextTags.scss new file mode 100644 index 00000000..4204ad65 --- /dev/null +++ b/wire/modules/Inputfield/InputfieldTextTags/InputfieldTextTags.scss @@ -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; + } +}