Suggest Widget

Last modified by Marius Dumitru Florea on 2019/06/21

The suggest widget is implemented using Selectize.js with its Bootstrap skin and is bundled by default in XWiki platform.

Predefined Suggesters

A couple of suggesters are available by default in XWiki. Each suggester provides:

  • dedicated helper Velocity macros (to import required resources and to output the required HTML)
  • a jQuery plugin to activate the suggest behavior on any input or select element
  • a CSS class that activates the suggest behavior automatically

The dedicated Velocity macros are in fact loading a JavaScript code that calls the dedicated jQuery plugin on all the elements (input or select) that are marked with the dedicated CSS class. Here's the list of available suggesters:

DataVelocity MacrosjQuery PluginCSS Class
PagespagePicker, pagePicker_importsuggestPagessuggest-pages
AttachmentsattachmentPicker, attachmentPicker_importsuggestAttachmentssuggest-attachments
UsersuserPicker, userPicker_input, userPicker_importsuggestUserssuggest-users
GroupsgroupPickersuggestGroupssuggest-groups
Property ValuesxpropertySuggestInputDisplayersuggestPropertyValuessuggest-propertyValues

Usage

There are 3 ways in which you can activate a predefined suggester:

  • from Velocity (by calling the dedicated Velocity macro)
  • from JavaScript (by calling the dedicated jQyery plugin)
  • from HTML (by marking form fields with the dedicated CSS class)

Use the one that suits you the best.

From Velocity

You can do this by putting the following code in the content of a wiki page. Here we're calling the dedicated pagePicker Velocity macro. Note that you can pass configuration options. Most of these settings are translated into HTML attributes (of the generated suggest input element) read by the dedicated jQuery plugin. You can use the data-xwiki-selectize parameter to pass configuration options to the Selectize.js widget. Each predefined suggester might also have custom parameters you can set, check their documentation below.

{{velocity}}
{{html}}
#set ($pagePickerParams = {
  'name': 'targetPages',
  'value': ['Path.To.Alice', 'Path.To.Bob'],
  'multiple': 'multiple'
})
#pagePicker($pagePickerParams)
{{/html}}
{{/velocity}}

From JavaScript

You can do this from a JavaScript Skin extension. First you need to configure the path to the suggester you want to use and then you need to call the dedicated jQuery plugin (suggestAttachments) from a require block. Note that you can pass configuration options when activating the suggester. These configuration options are either generic (for the Selectize widget) or specific to each suggester (see below).

require.config({
  paths: {
   'xwiki-suggestAttachments': "$xwiki.getSkinFile('uicomponents/suggest/suggestAttachments.js', true)" +
     "?v=$escapetool.url($xwiki.version)"
  }
});

require(['jquery', 'xwiki-suggestAttachments'], function($) {
  $('input[name="logo"]').suggestAttachments({
    accept: 'image/,.mp4,.pdf',
    uploadAllowed: true
  });
});

Of course, you still need to load your skin extension code (from the content of a wiki page):

{{velocity}}
{{html}}
#set ($discard = $xwiki.jsx.use('Path.To.Your.JSX'))
<input type="text" name="logo" value="Sandbox.WebHome@XWikiLogo.png"/>
{{/html}}
{{/velocity}}

From HTML

Put the following code in the content of a wiki page. First you need to import the resources required by the suggester and then you need to mark the form fields you wish to enhance (using the dedicated CSS class, suggest-users here). Note that you can use the data-xwiki-selectize attribute to pass configuration options in JSON format to the Selectize.js widget. Each predefined suggester might also have custom attributes you can set, check their documentation below.

{{velocity output="false"}}
#userPicker_import
{{/velocity}}

{{html}}
<input type="text" name="manager" value="XWiki.mflorea" class="suggest-users"/>
{{/html}}

Suggest Pages

This suggester displays the page rendered title and the page location. The value that is saved in the end is the page reference. The hidden page user preference is taken into account.

{{velocity}}
{{html}}
#set ($pagePickerParams = {
  ...
})
#pagePicker($pagePickerParams)
{{/html}}
{{/velocity}}

pagePicker.png

Custom configuration parameters:

NameDescriptionDefault Value
data-suggest-spaceRestrict the suggestions to a specific locationNone

Suggest Attachments

This suggester displays the attachment (file) name and the attachment location. The suggestion icon is either a preview, if the attachment is an image or an icon that depends on the attachment media type. The value that is saved in the end is the attachment reference. The hidden page user preference is taken into account (e.g. attachments from hidden pages are not displayed by default).

{{velocity}}
{{html}}
#set ($attachmentPickerParams = {
  ...
})
#attachmentPicker($attachmentPickerParams)
{{/html}}
{{/velocity}}

attachmentPicker.png

It supports local file upload, either by selecting the files from the file browser or by dropping them on the suggest input. It also supports drag and drop of attachment links from the Attachment tab at the bottom of the wiki page.

Custom configuration parameters:

NameDescriptionDefault Value
data-document-referenceThe document where the selected values are saved and where new files are being uploaded. Stored attachment references will be relative this document.Current page
data-search-scope

Where to look for attachments. The following is supported:

  • "wiki:wikiName" look for attachments in the specified wiki
  • "space:spaceReference": look for attachments in the specified space
  • "document:documentReference" look for attachments in the specified document
Current wiki
data-upload-allowedWhether to allow the user to upload filesfalse
data-accept

Indicates the type of files that can be selected or uploaded. The value is a comma separated list of:

  • file name extensions (e.g. .png,.pdf)
  • complete or partial media types (e.g. image/,video/mpeg)

If nothing is specified then no restriction is applied.

None

Suggest Users

This suggester displays the user avatar and their name. The value that is saved in the end is the user reference (the reference of the user profile page).

{{velocity}}
{{html}}
#set ($userPickerParams = {
  ...
})
#userPicker($userPickerParams)
{{/html}}
{{/velocity}}

userPicker.png

Custom configuration parameters:

NameDescriptionDefault Value
data-userScopeWhere to retrieve the user suggestions from. Supported values are: LOCAL_ONLY, GLOBAL_ONLY, LOCAL_AND_GLOBALUser scope of the current wiki

Suggest Groups

The suggester displays the group logo and its name. The group logo is the first image attachment found on the group page. The value that is saved in the end is the group reference (the reference of the group page).

{{velocity}}
{{html}}
#set ($groupPickerParams = {
  ...
})
#groupPicker($groupPickerParams)
{{/html}}
{{/velocity}}

groupPicker.png

Custom configuration parameters:

NameDescriptionDefault Value
data-userScopeWhere to retrieve the group suggestions from. Supported values are: LOCAL_ONLY, GLOBAL_ONLY, LOCAL_AND_GLOBAL. Note that GLOBAL_ONLY has the same effect as LOCAL_AND_GLOBAL because a wiki with only global users can have local groupsUser scope of the current wiki

Suggest Property Values

This suggester displays the label assocaited with a property value and saves the raw property value. The following property types are supported by this suggester: Static List and Database List. Note that these property types have default custom displayers associated that load the suggest widget when the "Use suggest" meta property is on. So normally the only thing you need to do is to display the property.

{{velocity}}
$doc.display('myDatabaseListProperty')
{{/velocity}}

propertyValuePicker.png

If you don't have a property to display then you can also enable this suggester by calling the generic suggestInput macro like this:

{{velocity}}
{{html}}
#set ($discard = $xwiki.jsfx.use('uicomponents/suggest/suggestPropertyValues.js',
  {'forceSkinAction': true, 'language': $xcontext.locale}))
#set ($suggestParams = {
  'class': 'suggest-propertyValues',
  'placeholder': 'Select the movie director',
  'data-className': 'Help.Applications.Movies.Code.MoviesClass',
  'data-propertyName': 'databaseList1'
})
#suggestInput($suggestParams)
{{/html}}
{{/velocity}}

If you need tag suggestions then you can use this configuration:

#set ($suggestParams = {
  'class': 'suggest-propertyValues',
  'multiple': 'multiple',
  'data-className': 'XWiki.TagClass',
  'data-propertyName': 'tags'
})

Custom configuration parameters:

NameDescriptionDefault Value
data-classNameThe class where the property is definedNone
data-propertyNameThe property whose values are to be retrieved as suggestionsNone
data-freeTextWhether free text is allowed or forbidden. Possible values are: allowed, forbiddenNone

Custom Suggesters

Suggest from Static Data

Let's see how you can use the suggest widget with static data (i.e. data that is available when the web page is loaded).

The following code:

  • loads the resources (JavaScript and CSS) needed by the suggest widget
  • outputs the HTML (select element) needed by the suggest widget

Note that the select element is marked with the xwiki-selectize CSS class which activates the suggest widget.

{{velocity}}
#set ($discard = $xwiki.linkx.use($services.webjars.url('selectize.js', 'css/selectize.bootstrap3.css'),
  {'type': 'text/css', 'rel': 'stylesheet'}))
#set ($discard = $xwiki.ssfx.use('uicomponents/suggest/xwiki.selectize.css', true))
#set ($discard = $xwiki.jsfx.use('uicomponents/suggest/xwiki.selectize.js', true))
{{/velocity}}

{{html}}
<select class="xwiki-selectize">
  <option value="">Select the country</option>
  <option value="fr">France</option>
  <option value="de">Germany</option>
  <option value="ro">Romania</option>
</select>
{{/html}}

In this version we separate the code that generates the data used to provide suggestions. The data is usually retrieved from the database.

{{velocity}}
{{html}}
#set ($discard = $xwiki.linkx.use($services.webjars.url('selectize.js', 'css/selectize.bootstrap3.css'),
  {'type': 'text/css', 'rel': 'stylesheet'}))
#set ($discard = $xwiki.ssfx.use('uicomponents/suggest/xwiki.selectize.css', true))
#set ($discard = $xwiki.jsfx.use('uicomponents/suggest/xwiki.selectize.js', true))
#set ($countries = [
  {'name': 'France', 'code': 'fr'},
  {'name': 'Germany', 'code': 'de'},
  {'name': 'Romania', 'code': 'ro'}
])
## See https://github.com/selectize/selectize.js/blob/master/docs/usage.md#configuration
#set ($selectizeSettings = {})
<select class="xwiki-selectize" data-xwiki-selectize="$escapetool.xml($jsontool.serialize($selectizeSettings))">
  <option value="">Select the country</option>
  #foreach ($country in $countries)
    <option value="$!escapetool.xml($country.code)">$!escapetool.xml($country.name)</option>
  #end
</select>
{{/html}}
{{/velocity}}

In this version we use some helper Velocity macros: picker_import and suggestInput. Notice the way we specify the selected value and the placeholder by passing parameters to the suggestInput macro.

{{velocity output="false"}}
#set ($countries = [
  {'name': 'France', 'code': 'fr'},
  {'name': 'Germany', 'code': 'de'},
  {'name': 'Romania', 'code': 'ro'}
])

#macro (countryPicker_displayOptions $selectedValues)
  #foreach ($country in $countries)
    <option value="$!escapetool.xml($country.code)"#if ($selectedValues.contains($country.code)) selected="selected"#end
      >$!escapetool.xml($country.name)</option>
  #end
#end
{{/velocity}}

{{velocity}}
{{html}}
#picker_import
#set ($discard = $xwiki.jsfx.use('uicomponents/suggest/xwiki.selectize.js', true))
## See https://github.com/selectize/selectize.js/blob/master/docs/usage.md#configuration
#set ($selectizeSettings = {})
#set ($suggestInputAttributes = {
  'name': 'country',
  'class': 'xwiki-selectize',
  'data-xwiki-selectize': $selectizeSettings,
  'value': 'ro',
  'placeholder': 'Select the country'
})
#suggestInput($suggestInputAttributes 'countryPicker_displayOptions')
{{/html}}
{{/velocity}}

In this last version we make the code reusable by creating the countryPicker and countryPicker_import Velocity macros.

{{velocity output="false"}}
#set ($countries = [
  {'name': 'France', 'code': 'fr'},
  {'name': 'Germany', 'code': 'de'},
  {'name': 'Romania', 'code': 'ro'}
])

#macro (countryPicker_displayOptions $selectedValues)
  #foreach ($country in $countries)
    <option value="$!escapetool.xml($country.code)"#if ($selectedValues.contains($country.code)) selected="selected"#end
      >$!escapetool.xml($country.name)</option>
  #end
#end

#macro (countryPicker_import)
  #picker_import
  #set ($discard = $xwiki.jsfx.use('uicomponents/suggest/xwiki.selectize.js', true))
#end

#macro (countryPicker $parameters)
  #countryPicker_import
  #if ("$!parameters" == "")
    #set ($parameters = {})
  #end
  #set ($discard = $parameters.put('class', "$!parameters.get('class') xwiki-selectize suggest-countries"))
  #if (!$parameters.containsKey('placeholder'))
    #set ($parameters.placeholder = 'Select the country')
  #end
  #suggestInput($parameters 'countryPicker_displayOptions')
#end
{{/velocity}}

{{velocity}}
{{html}}
#set ($countryPickerParams = {
  'name': 'country',
  'value': 'ro'
})
#countryPicker($countryPickerParams)
{{/html}}
{{/velocity}}

Suggest from Remote Data

Most of the time you will want to retrive suggestions when the user types rather than loading all the suggestions on page load. Let's see how you can do this.

In order to retrieve suggestions as you type you need to write some JavaScript code that makes an asynchronous HTTP request to retrive the data. You can put this code in a JavaScript Skin extension object. Then you need to load this JavaScript code on the page where you want to activate the suggest widget.

require.config({
  paths: {
   'xwiki-selectize': "$xwiki.getSkinFile('uicomponents/suggest/xwiki.selectize.js', true)" +
     "?v=$escapetool.url($xwiki.version)"
  }
});

require(['jquery', 'xwiki-selectize'], function($) {
 var settings = {
    load: function(typedText, callback) {
      $.getJSON('your/service/url', {
        text: typedText
      }).done(callback).fail(callback);
    },
    loadSelected: function(selectedValue, callback) {
      $.getJSON('your/service/url', {
        text: typedText,
        exactMatch: true
      }).done(callback).fail(callback);
    }
  };

  $('.suggest-countries').xwikiSelectize(settings);
});
{{velocity}}
#set ($discard = $xwiki.linkx.use($services.webjars.url('selectize.js', 'css/selectize.bootstrap3.css'),
  {'type': 'text/css', 'rel': 'stylesheet'}))
#set ($discard = $xwiki.ssfx.use('uicomponents/suggest/xwiki.selectize.css', true))
#set ($discard = $xwiki.jsx.use('Path.To.Your.JSX'))
{{/velocity}}

{{html}}
<select class="suggest-countries">
  <option value="">Select the country</option>
</select>
{{/html}}

In this last version we make the code reusable by creating a jQuery plugin and some helper Velocity macros.

require.config({
  paths: {
   'xwiki-selectize': "$xwiki.getSkinFile('uicomponents/suggest/xwiki.selectize.js', true)" +
     "?v=$escapetool.url($xwiki.version)"
  }
});

define('xwiki-suggestCountries', ['jquery', 'xwiki-selectize'], function($) {
 var getSettings = function(select) {
   return {
      load: function(typedText, callback) {
        $.getJSON('your/service/url', {
          text: typedText
        }).done(callback).fail(callback);
      },
      loadSelected: function(selectedValue, callback) {
        $.getJSON('your/service/url', {
          text: typedText,
          exactMatch: true
        }).done(callback).fail(callback);
      }
    };
  };

  $.fn.suggestCountries = function(settings) {
   return this.each(function() {
      $(this).xwikiSelectize($.extend(getSettings($(this)), settings));
    });
  };
});

require(['jquery', 'xwiki-suggestCountries', 'xwiki-events-bridge'], function($) {
 var init = function(event, data) {
   var container = $((data && data.elements) || document);
    container.find('.suggest-countries').suggestCountries();
  };

  $(document).on('xwiki:dom:loaded xwiki:dom:updated', init);
  XWiki.domIsLoaded && init();
});
{{velocity output="false"}}
#macro (countryPicker_import)
  #picker_import
  #set ($discard = $xwiki.jsx.use('Path.To.Your.JSX'))
#end

#macro (countryPicker $parameters)
  #countryPicker_import
  #if ("$!parameters" == "")
    #set ($parameters = {})
  #end
  #set ($discard = $parameters.put('class', "$!parameters.get('class') suggest-countries"))
  #if (!$parameters.containsKey('placeholder'))
    #set ($parameters.placeholder = 'Select the country')
  #end
  #suggestInput($parameters)
#end
{{/velocity}}

{{velocity}}
{{html}}
#set ($countryPickerParams = {
  'name': 'country',
  'value': 'ro'
})
#countryPicker($countryPickerParams)
{{/html}}
{{/velocity}}

The remote data should have the following JSON format:

[
  {
   'label': '...',
   'value': '...',
   'url': '...',
   'icon': {
     'iconSetName': '...',
     'iconSetType': 'IMAGE or FONT',
     'cssClass': 'for font icons',
     'url': 'for image icons'
    },
   'hint': '...'
  },
  ...
]
Created by Vincent Massol on 2017/09/04
   

Get Connected