ProcessWire 3.0.177 and the InputfieldTextTags (Selectize) module

This week we focus in on a new and unique Inputfield module added to the core that enables a lot of useful new input capabilities for tags, sortable multiple selection and custom user input.

Using InputfieldTextTags with a "Categories" Page reference field on this site. This represents the un-focused state. Options can be dragged and dropped to a desired order.

Intro

Often times these blog posts later migrate to becoming a documentation page in the site, and that's kind of the intention with this one. This week we're going to look a new module added to the core (InputfieldTextTags) that has a lot of potential use cases and options. It goes a little beyond the usual definition of an Inputfield and I thought it needed and deserved some added information. This post is an attempt to explain a lot of it, help you identify the cases where you might use it, and walk you through how to set it up.

Origins

The InputfieldTextTags was intended as a very simple means to improve and merge the tags handling on the Templates and Fields editors in the ProcessWire admin (and potentially other parts). It was motivated by that need, but also by a feature request by Robin S. to add Selectize support to our InputfieldText module. (Selectize is a JS plugin for managing tags). It started as a basic addition to the InputfieldText module. But once I started working with it more seriously, it really needed to be its own module. It started out with the name InputfieldTags but was later renamed to InputfieldTextTags, as InputfieldTags was already used at some point by a 3rd party module. Now being its own module, there was a lot more flexibility to take it further than originally intended.

Using InputfieldTextTags with a "Categories" Page reference field on this site. This represents the focused state and I have started to type an option I want to select or add.

About Selectize

Selectize is really an interesting and powerful JS plugin, and the InputfieldTextTags module is built around it. On one hand, Selectize is a simple way to make an <input type='text'> behave as a tags input. In fact, we've been using it that way for our files/images fields for years. But there's so much more Selectize can do… building an Inputfield module around it just begs to go further.

Not only is Selectize a nice interface for inputting tags, but it's also a good solution for multiple selection. Unlike a <select multiple> or a bunch of checkboxes, you can also sort the selected options by drag-and-drop. In that respect, it's a nice alternative to many cases where you might use PW’s native AsmSelect. But unlike AsmSelect, Selectize can also behave as an auto-complete, and with Ajax-loaded options. That sounds kind of like PW’s native PageAutocomplete input, but Selectize is also quite different from that too, and I think likely an improvement for some cases, as Selectize can work in cases where PageAutocomplete can't (more on that later).

Inputs + contexts

InputfieldTextTags is kind of a unique module in the ProcessWire ecosystem in that it doesn't have a accompanying FieldtypeTextTags module, but can be selected as an input method for multiple unrelated Fieldtypes. It focuses on being a standalone input method (for forms outside the page editor) or an input method that can be used with other Fieldtype modules (for the page editor). Specifically, the InputfieldTextTags module can be selected as an input option for the following types of fields:

  • Single line text fields (FieldtypeText)
  • Page reference fields (FieldtypePage or InputfieldPage)
  • Options fields (FieldtypeOptions)
  • It can also be used on its own (in other forms)

We'll cover each of these use cases in detail below.

Using with Text fields

In ProcessWire 3.0.177+ TextTags can be an input option for any single-line Text field. Make sure it is installed at Modules > Core > Inputfield. Following that, you'll see an option to select it on the "Details" tab when editing a text Field (in Setup > Fields).

To use it with a Text field, select "TextTags" as the input type, save, then visit the "Input" tab for more configuration options. Next, determine whether you want the field to: 1) provide only predefined selectable options/tags; 2) have the user type in the options/tags; or 3) some combination of the two.

  • If you want to provide predefined selectable options, you can specify them now with the field settings, or you can have it load them dynamically (Ajax) from a URL you provide.

  • If you don't want to provide predefined selectable options, then you can leave the settings at their defaults (and don't enter any predefined tags), and then set the "Allow user to enter their own tags" option to "Yes".

  • If you want a combination of both predefined selectable options/tags and user-input tags, then set the "Allow user to enter their own tags" to "Yes", and see the section further down on "How to use predefined selectable options/tags".

Deciding on a tag delimiter

When using with Text fields, you also have an option to choose the "Tag delimiter". This determines what separates tags in the text. For single word tags (like you might use with Twitter or similar) choose "Space" as the tag delimiter. For multi-word tags, you'll likely want to choose "Comma" as the tag delimiter. Though if your tags can include commas, then you may want to choose "Pipe" as the tag delimiter.

Front-end output

Let's say that your text field is named "tags" and you want to output those tags on the front-end of your site. The value of your "tags" field is a single line of text with tags separated by the delimiter you selected (whether space, comma or pipe). If your delimiter was space and your selected tags were the names of colors (red, blue, green) then the value of your $page->tags field would literally be the string red blue green. You could render that in a list like this:

<?php if($page->tags): ?>
<ul>
  <?php foreach(explode(' ', $page->tags) as $tag): ?>
  <li><?=$tag?></li>
  <?php endforeach; ?>
</ul>
<?php endif; ?>

Prefer less code? Here's another way you could do it:

if($page->tags) {
  echo '<ul><li>' . str_replace(' ', '</li><li>', $page->tags) . '</li></ul>';
}

As you can see, tags are just a simple string of delimited text, so how you work with them has to reflect that. There is no FieldtypeTextTags module to convert them to an array for you in this output scenario. Though it's hard to argue with the simplicity of a string of plain text.

Using tags with labels

If you are using any predefined selectable tags, you may have noticed there's an option to define a separate label for each tag. This is useful if you want to isolate the tags from their labels (aka definitions). Maybe tags are being used to define the properties of a vehicle, so there might be a tag like 4WD with label "Four Wheel Drive" and tag AWD with label "All Wheel Drive", and perhaps they are even translated for each of your languages. Because there is no FieldtypeTextTags module to provide this, the InputfieldTextTags provides a static method that can convert your string of tags to an array of [ 'tag' => 'label' ], which is handy for front-end output. Here's how you could use that:

$field = $fields->get('tags');
$tags = InputfieldTextTags::tagsArray($field, $page->tags);

// tags is an associative array of [ 'tag' => 'label' ]
foreach($tags as $tag => $label) {
  echo "<li>$tag: $label</li>";
}

If you had multi-language labels, then the label is in the correct language (according to $user->language). Or, if you didn't have labels at all, then both the $tag and $label are the same. I realize that using a static function like this is a little different in PW, but in this case, because TextTags is flexible to work with different Fieldtypes, it's a worthwhile helper in lieu of a dedicated FieldtypeTextTags module. Please note that the tags and labels returned by this helper method are entity-encoded when the current $page output formatting is on, and left alone when it is off.

Another front-end output example

Let's say you are using a space-separated TextTags field named "features" to identify different layout features on your website. If the tag "sidebar" is present, then you want to present a layout that has a sidebar. Perhaps the following would be adequate:

<?php if(strpos($page->features, 'sidebar') !== false): ?>
  <div id="sidebar">...</div>
<?php endif; ?>

Though if there might be other tags with "sidebar" as part of it, like "sidebars" or "asidebar" you'd want to be more thorough:

<?php if(in_array('sidebar', explode(' ', $page->features))): ?>
  <div id="sidebar">...</div>
<?php endif; ?>

When it gets down to it, these examples above aren't so much specific to TextTags and instead are more about how you might use the plain text delimited string that FieldtypeText provides. If you just simply remember that you are dealing with a single line of text that is delimited by spaces, commas or pipes (depending on how you configured it) then you will find it very simple to work with on the front-end.

The other usage cases for TextTags are Page fields and Options fields and in those cases TextTags has no relation to your front-end code at all, so you won't see equivalent code examples for those.

Using with Page fields

With Page reference fields, you can select "TextTags" as the "Input field type" on the "Input" tab of the field editor, for any Page field. It is under the group "Multiple page selection (sortable)" though can also be used for single page selection. (If you use it for single page selection just remember to later set the "Max tags/options" to "1" in the TextTags settings). TextTags is particularly useful in cases where you might have previously chosen AsmSelect or PageAutocomplete, as its feature set can combine both of them in several ways.

When using with a Page field you will naturally be using TextTags with predefined options (the selectable pages), which are already configured with your other Page field settings.

If your Page field is for input in the page editor, and its settings have the "Allow new pages to be created from field?" option selected, then TextTags will also allow the user to type in new options and they will be created as new pages when saved.

Using Ajax options/pages

When TextTags is setup for use with your Page field, you'll have a "Settings specific to Text Tags" fieldset with your Page field settings. This is where you can configure additional options for it. One of the most interesting to consider is the "Use Ajax options/pages?" setting. When enabled, you can provide an Ajax endpoint URL for the field to search for pages and populate selectable options in an auto-complete fashion.

While the PageAutocomplete input option does something similar, TextTags gives you more control than PageAutocomplete because you are providing the implementation (via your ajax handler URL) rather than ProcessWire. It also takes a little more work on your part, though not that much, as TextTags even gives you code that you can copy and paste into your /site/init.php file (and optionally modify as needed).

Below is an example of an Ajax handler in /site/init.php provided as copy/paste code by TextTags. As you can see, it uses a couple other relatively new features in ProcessWire: URL/path hooks and $pages->findRaw():

$wire->addHook("/find-categories/", function($e) {
  $q = $e->input->get("q", "text,selectorValue");
  if(strlen($q) < 3) return [];
  $selector = "parent=/blog/categories/, title%=$q";
  $fields = [ "id" => "value", "title" => "label" ];
  return array_values($e->pages->findRaw($selector, $fields));
});

The benefit with the Ajax approach is that not only does it give you more control and customization potential (since you control the code), but it means that TextTags can be used for Ajax auto-complete on the front-end for selection of pages in addition to the admin (as PageAutocomplete only works in the admin). This means it's feasible to use TextTags as an Ajax input option not just in the page editor, but also in FormBuilder, LoginRegisterPro or other front-end forms powered by Inputfields.

When to use the Ajax option

Consider the Ajax option when the potential number of pages to match is large and would either consume a lot of memory or take a long time to render in your form. With Ajax options, only options matching a query are presented for selection, which can greatly reduce overhead while significantly increasing the scale you can deliver.

Keeping your Ajax handler up-to-date

When using the Ajax input option, something to be aware of is that changes to your "Selectable pages" or "Label field" settings for the Page field settings would also affect how you approach your Ajax handling function. For instance, if the pages you match previously were in /blog/categories/, but later changed to /topics/, then you'd need to update your code in the function to reflect that.

Page fields work exactly the same from the API regardless of what input method is used in the admin, so there is nothing new that you need to consider from the API side when using TextTags for input.

Using with Options fields

Using TextTags with Options fields is the simplest use case. That's because you don't have to define the options, since they are already defined by the Options field. And there is no need to consider an Ajax input option, since Options fields don't support it, nor do they need to. A page-editing user can't add new selectable options to an Options field, so the ability to add new items is also not a consideration.

When paired with an Options field, TextTags simply provides a nice alternative input option that is comparable in functionality to the AsmSelect option but consumes less vertical space.

Using TextTags on its own

Like most Inputfield modules that aren't married to a Fieldtype, InputfieldTextTags can be used on its own in any form powered by ProcessWire's Inputfields. This means any module in the PW admin can use it, and any front-end forms that use Inputfields can also use it (such as FormBuilder and LoginRegisterPro). I also anticipate increasing its use in the core modules and configuration pages in cases where it is a good fit. API usage is simple:

// create a TextTags Inputifeld and name it 'tags'
$f = $modules->get('InputfieldTextTags');
$f->attr('name', 'tags');

// allow for user-entered tags input (true or false)
$f->allowUserTags = true;

// set the tag/option delimiter (s=space, c=comma, p=pipe)
$f->delimiter = 's';

// predefined selectable tags (tag and optional label)
$f->addOption('foo');
$f->addOption('bar', 'This is Bar'); // optional label
$f->addOption('baz', 'This is Baz'); // optional label

// set currently entered/selected tags
$f->val('foo bar');
$f->val([ 'foo', 'bar' ]); // array also works

// add to an InputfieldForm or other InputfieldWrapper
$form->add($f);

Tags, labels + multi-language

TextTags can work with just tags, or a tag and label combination. When using TextTags with a Page field, behind the scenes, the tags are actually the page IDs (numbers) and the labels are the page title (or other page field). Options fields work in a similar way. It's only when you use TextTags with a Text field or from the API that you will be more likely to consider the difference between tags and labels.

In a multi-language environment, the "tag" portion is the same among all languages, while the "label" is unique to each language (as it can be translated). When it comes to front-end output or back-end selection, the correct label is presented according to the user's language.

Outside of a multi-language environment, separate labels are less likely to be used (especially when not using space as a delimiter), but depending on your needs, you still might consider the label to be the definition or description of the tag — the label that people see when making a selection in the page editor viewing the output on the front-end.

In cases where you are supporting new user-input tags with Text fields (rather than just selectable options) you likely won't want to differentiate between tags and labels at all, as there is no distinction between tags or labels when the consuming user adds a new tag. With newly added pages in Page fields there also is no need to differentiate, as the tag is always the page ID and the label is always the page label (typically the title), and all of this is handled internally.

Select single vs. select multiple mode

One of the configurable options with TextTags is the maximum number of selectable items. If set to 0 (default), there is no max, and if set to 1 or more, then the max is limited to that number. When set to a max of 1, it also changes the behavior of the TextTags input: rather than appearing as a text input <input type="text">, it takes on more of an enhanced <select> appearance and behavior. Though you can still type within it for matching and auto-complete.

Using predefined selectable options/tags

When used with a Text field you can define the selectable options with the TextTags settings (on the Input tab of the field editor). You enter one selectable option/tag per line. You can just enter the tag, or you can enter a tag=label combination on each line.

In a multi-language environment, you will have a tab for each language. This enables you to use the same tag in each of the languages, but provide a separate translated label for each language. If you've configured an Options field before, this type of definition will be somewhat familiar.

How to configure options/tags input by the user

When using TextTags with a regular Text field, you can choose whether or not the user will be able to add their own tags with the "Allow user to enter their own tags?" Yes/No toggle. This can optionally be combined with predefined selectable options. But if set to "No" then the user will only be able to select predefined options/tags.

When using TextTags with a Page reference field, the option for the user to add their own options/tags is also present, but not with a TextTags setting. Instead, TextTags looks at your Page field's "Allow new pages to be created from field?" setting on your Page field's "Input" tab (in the field editor). This is the same setting you may have previously used with other Page fields. If that setting allows the User to add new pages, then TextTags will likewise allow them to do so using the TextTags input.

That sums up our coverage of the TextTags Inputfield, though this Inputfield provides a lot and there may be more yet to add before this post eventually becomes a documentation page. If you have any questions about this new Inputfield or how to use it, please leave a comment below. Thanks for reading and tune in for the latest ProcessWire news and updates every week at ProcessWire Weekly.

Comments

  • horst

    horst

    • 3 years ago
    • 81

    Oh, I have fallen in love with InputfieldTextTags!

  • Peter Burlingham

    Peter Burlingham

    • 3 years ago
    • 20

    Love it!

    My additional wish for the AJAX version would be for it to return and store values not related to a page at all.

    I work with a lot of external tables sometimes where it would be beneficial to store a pipe-separated list of values returned from separate table.

    In fact, could it not store ad a row in a table for the field but not be a page ID as an option? Even working with non-PW tables you'll likely be storing and integer anyway. Just a thought - so some setting that tells the field whether it's an external value and have the AJAX code translate the value to the label.

    I know that normally wouldn't be an option, but since the AJAX mode requires some code anyway... just a thought ☺️

    I've been using Select2 a lot on frontend forms the last few years and this is quite similar so looking forward to using it!

    • Peter Burlingham

      Peter Burlingham

      • 3 years ago
      • 00

      That should say something like "could it not store an ID in a row in the table unrelated to a page ". Typing too fast on my phone.

      I have a site with 40k users and 60k competitor's (multiple competitor's under a user account) and the competitors table is standalone but still contains integers. It would be fantastic to store competitor IDs with this field in the AJAX version and it's up to me how to output it usefully on the frontend is what I'm thinking.

      • ryan

        ryan

        • 3 years ago
        • 21

        Hi Pete, It's only when used with a Page field that your ajax handler has to return page ID as the "value" and some other text (typically the page title) as the "label".

        If you chose the ajax option for a Text field, then there are no restrictions on what it can return -- your ajax handler can reference whatever you like, whether local or remote. It just has to return a plain array of options/tags that match the 'q' input string. But the options/tags can be whatever you'd like.

        For instance, you mentioned storing competitor IDs, and while I don't know exactly what that is, let's say it's an ID number and company name. So perhaps a search for the word "company" your ajax handler could return an array like this below, where you've included the competitor ID and name in the tag value:

        [ "123: Company LTD", "456: Another Company LLC", "789: Bigger Company Corp." ]

        Let's say you decide to select the first 2 of them, and that your chosen delimiter is pipe. This is the value that would be stored in your Text field:

        123: Company LTD|456: Another Company LLC

        You can use that value however you'd like on your front-end. Using explode('|', $page->competitors); would split them into an array, and using explode(':', $value, 2) on any one of them would give you an array of competitor ID and name.

 

NextProcessWire 3.0.178 core updates

1

ProcessWire 3.0.178 focuses largely in adding pull requests (PRs), which are code contributions by ProcessWire users. We had quite a few great pull requests pending, and in total we have added 26 of them in 3.0.178. More 

Latest news

  • ProcessWire Weekly #533
    In the 533rd issue of ProcessWire Weekly we'll check out some newly released third party modules and highlight a new site of the week. Read on!
    Weekly.pw / 27 July 2024
  • Page List Custom Children module
    This simple module gives you the ability to customize the parent/child relationship as it appears in the admin page list, enabling child pages to appear under more than one parent.
    Blog / 28 June 2024
  • Subscribe to weekly ProcessWire news

“To Drupal, or to ProcessWire? The million dollar choice. We decided to make an early switch to PW. And in retrospect, ProcessWire was probably the best decision we made. Thanks are due to ProcessWire and the amazing system and set of modules that are in place.” —Unni Krishnan, Founder of PigtailPundits