Wiki source code of Suggest Widget

Last modified by Vincent Massol on 2024/07/31

Show last authors
1 {{box cssClass="floatinginfobox" title="**Contents**"}}
2 {{toc start="2"/}}
3 {{/box}}
4
5 The suggest widget is implemented using [[Selectize.js>>http://selectize.github.io/selectize.js/]] with its Bootstrap skin and is bundled by default in XWiki platform.
6
7 == Predefined Suggesters ==
8
9 A couple of suggesters are available by default in XWiki. Each suggester provides:
10
11 * dedicated helper Velocity macros (to import required resources and to output the required HTML)
12 * a jQuery plugin to activate the suggest behavior on any input or select element
13 * a CSS class that activates the suggest behavior automatically
14
15 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:
16
17 |=Data|=Velocity Macros|=jQuery Plugin|=CSS Class
18 |Pages|pagePicker, pagePicker_import|suggestPages|suggest-pages
19 |Attachments|attachmentPicker, attachmentPicker_import|suggestAttachments|suggest-attachments
20 |Users|userPicker, userPicker_input, userPicker_import|suggestUsers|suggest-users
21 |Groups|groupPicker|suggestGroups|suggest-groups
22 |Property Values|xpropertySuggestInputDisplayer|suggestPropertyValues|suggest-propertyValues
23
24 === Usage ===
25
26 There are 3 ways in which you can activate a predefined suggester:
27
28 * from Velocity (by calling the dedicated Velocity macro)
29 * from JavaScript (by calling the dedicated jQyery plugin)
30 * from HTML (by marking form fields with the dedicated CSS class)
31
32 Use the one that suits you the best.
33
34 ==== From Velocity ====
35
36 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>>https://github.com/selectize/selectize.js/blob/master/docs/usage.md#configuration]] widget. Each predefined suggester might also have custom parameters you can set, check their documentation below.
37
38 {{code language="none"}}
39 {{velocity}}
40 {{html}}
41 #set ($pagePickerParams = {
42 'name': 'targetPages',
43 'value': ['Path.To.Alice', 'Path.To.Bob'],
44 'multiple': 'multiple'
45 })
46 #pagePicker($pagePickerParams)
47 {{/html}}
48 {{/velocity}}
49 {{/code}}
50
51 ==== From JavaScript ====
52
53 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>>https://github.com/selectize/selectize.js/blob/master/docs/usage.md#configuration]] widget) or specific to each suggester (see below).
54
55 {{code language="js"}}
56 require.config({
57 paths: {
58 'xwiki-suggestAttachments': "$xwiki.getSkinFile('uicomponents/suggest/suggestAttachments.js', true)" +
59 "?v=$escapetool.url($xwiki.version)"
60 }
61 });
62
63 require(['jquery', 'xwiki-suggestAttachments'], function($) {
64 $('input[name="logo"]').suggestAttachments({
65 accept: 'image/,.mp4,.pdf',
66 uploadAllowed: true
67 });
68 });
69 {{/code}}
70
71 Of course, you still need to load your skin extension code (from the content of a wiki page):
72
73 {{code language="none"}}
74 {{velocity}}
75 {{html}}
76 #set ($discard = $xwiki.jsx.use('Path.To.Your.JSX'))
77 <input type="text" name="logo" value="Sandbox.WebHome@XWikiLogo.png"/>
78 {{/html}}
79 {{/velocity}}
80 {{/code}}
81
82 ==== From HTML ====
83
84 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>>https://github.com/selectize/selectize.js/blob/master/docs/usage.md#configuration]] widget. Each predefined suggester might also have custom attributes you can set, check their documentation below.
85
86 {{code language="none"}}
87 {{velocity output="false"}}
88 #userPicker_import
89 {{/velocity}}
90
91 {{html}}
92 <input type="text" name="manager" value="XWiki.mflorea" class="suggest-users"/>
93 {{/html}}
94 {{/code}}
95
96 === Suggest Pages ===
97
98 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.
99
100 {{code language="none"}}
101 {{velocity}}
102 {{html}}
103 #set ($pagePickerParams = {
104 ...
105 })
106 #pagePicker($pagePickerParams)
107 {{/html}}
108 {{/velocity}}
109 {{/code}}
110
111 {{image reference="pagePicker.png"/}}
112
113 Custom configuration parameters:
114
115 |=Name|=Description|=Default Value
116 |data-document-reference|The document where the selected values are saved. Stored document references will be relative to this document.|Current page
117 |data-search-scope|(((Where to look for pages. The following is supported:
118 * "wiki:wikiName" look for pages in the specified wiki
119 * "space:spaceReference" look for pages in the specified space)))|Current wiki
120
121 === Suggest Attachments ===
122
123 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).
124
125 {{code language="none"}}
126 {{velocity}}
127 {{html}}
128 #set ($attachmentPickerParams = {
129 ...
130 })
131 #attachmentPicker($attachmentPickerParams)
132 {{/html}}
133 {{/velocity}}
134 {{/code}}
135
136 {{image reference="attachmentPicker.png"/}}
137
138 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.
139
140 Custom configuration parameters:
141
142 |=Name|=Description|=Default Value
143 |data-document-reference|The document where the selected values are saved and where new files are being uploaded. Stored attachment references will be relative this document.|Current page
144 |data-search-scope|(((Where to look for attachments. The following is supported:
145 * "wiki:wikiName" look for attachments in the specified wiki
146 * "space:spaceReference" look for attachments in the specified space
147 * "document:documentReference" look for attachments in the specified document)))|Current wiki
148 |data-upload-allowed|Whether to allow the user to upload files|false
149 |data-accept|(((Indicates the type of files that can be selected or uploaded. The value is a comma separated list of:
150 * file name extensions (e.g. .png,.pdf)
151 * complete or partial media types (e.g. image/,video/mpeg)
152
153 If nothing is specified then no restriction is applied.)))|None
154
155 === Suggest Users ===
156
157 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).
158
159 The users listed are sorted alphabetically based first on their first name and then last names, independently of the case. When no input is specified, all the users from wiki or the farm are matched. The first 10 users that are accessible to the current user are displayed.
160
161 {{code language="none"}}
162 {{velocity}}
163 {{html}}
164 #set ($userPickerParams = {
165 ...
166 })
167 #userPicker(true, $userPickerParams)
168 {{/html}}
169 {{/velocity}}
170 {{/code}}
171
172 {{image reference="userPicker.png"/}}
173
174 The ##userPicker## macro requires two parameters: the first one indicates whether the picker accepts multiple values or not, and the second one contains complementary configuration parameters:
175
176 |=Name|=Description|=Default Value
177 |data-userScope|Where to retrieve the user suggestions from. Supported values are: LOCAL_ONLY, GLOBAL_ONLY, LOCAL_AND_GLOBAL|User scope of the current wiki
178
179 === Suggest Groups ===
180
181 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).
182
183 {{code language="none"}}
184 {{velocity}}
185 {{html}}
186 #set ($groupPickerParams = {
187 ...
188 })
189 #groupPicker(true, $groupPickerParams)
190 {{/html}}
191 {{/velocity}}
192 {{/code}}
193
194 {{image reference="groupPicker.png"/}}
195
196 Like the ##userPicker## macro, ##groupPicker## requires two parameters: the first one indicates whether the picker accepts multiple values or not, and the second one contains complementary configuration parameters:
197
198 |=Name|=Description|=Default Value
199 |data-userScope|Where 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 groups|User scope of the current wiki
200
201 === Suggest Property Values ===
202
203 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.
204
205 {{code language="none"}}
206 {{velocity}}
207 $doc.display('myDatabaseListProperty')
208 {{/velocity}}
209 {{/code}}
210
211 {{image reference="propertyValuePicker.png"/}}
212
213 If you don't have a property to display then you can also enable this suggester by calling the generic ##suggestInput## macro like this:
214
215 {{code language="none"}}
216 {{velocity}}
217 {{html}}
218 #set ($discard = $xwiki.jsfx.use('uicomponents/suggest/suggestPropertyValues.js',
219 {'forceSkinAction': true, 'language': $xcontext.locale}))
220 #set ($suggestParams = {
221 'class': 'suggest-propertyValues',
222 'placeholder': 'Select the movie director',
223 'data-className': 'Help.Applications.Movies.Code.MoviesClass',
224 'data-propertyName': 'databaseList1'
225 })
226 #suggestInput($suggestParams)
227 {{/html}}
228 {{/velocity}}
229 {{/code}}
230
231 If you need tag suggestions then you can use this configuration:
232
233 {{code language="none"}}
234 #set ($suggestParams = {
235 'class': 'suggest-propertyValues',
236 'multiple': 'multiple',
237 'data-className': 'XWiki.TagClass',
238 'data-propertyName': 'tags'
239 })
240 {{/code}}
241
242 Custom configuration parameters:
243
244 |=Name|=Description|=Default Value
245 |data-className|The class where the property is defined|None
246 |data-propertyName|The property whose values are to be retrieved as suggestions|None
247 |data-freeText|Whether free text is allowed or forbidden. Possible values are: allowed, forbidden|None
248
249 == Custom Suggesters ==
250
251 === Suggest from Static Data ===
252
253 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).
254
255 {{html wiki="true"}}
256 <div>
257 <ul class="nav nav-tabs" role="tablist">
258 <li role="presentation" class="active">
259 <a href="#staticDataV1" aria-controls="staticDataV1" role="tab" data-toggle="tab">V1: The Basics</a>
260 </li>
261 <li role="presentation">
262 <a href="#staticDataV2" aria-controls="staticDataV2" role="tab" data-toggle="tab">V2: Separate Data</a>
263 </li>
264 <li role="presentation">
265 <a href="#staticDataV3" aria-controls="staticDataV3" role="tab" data-toggle="tab">V3: Helper Macros</a>
266 </li>
267 <li role="presentation">
268 <a href="#staticDataV4" aria-controls="staticDataV4" role="tab" data-toggle="tab">V4: Reusable Code</a>
269 </li>
270 </ul>
271
272 <div class="tab-content">
273 <div role="tabpanel" class="tab-pane active" id="staticDataV1">
274 <p>The following code:</p>
275
276 * loads the resources (JavaScript and CSS) needed by the suggest widget
277 * outputs the HTML (select element) needed by the suggest widget
278
279 <p>Note that the select element is marked with the ##xwiki-selectize## CSS class which activates the suggest widget.</p>
280
281 {{code language="none"}}
282 {{velocity}}
283 #set ($discard = $xwiki.linkx.use($services.webjars.url('selectize.js', 'css/selectize.bootstrap3.css'),
284 {'type': 'text/css', 'rel': 'stylesheet'}))
285 #set ($discard = $xwiki.ssfx.use('uicomponents/suggest/xwiki.selectize.css', true))
286 #set ($discard = $xwiki.jsfx.use('uicomponents/suggest/xwiki.selectize.js', true))
287 {{/velocity}}
288
289 {{html}}
290 <select class="xwiki-selectize">
291 <option value="">Select the country</option>
292 <option value="fr">France</option>
293 <option value="de">Germany</option>
294 <option value="ro">Romania</option>
295 </select>
296 {{/html}}
297 {{/code}}
298
299 </div>
300
301 <div role="tabpanel" class="tab-pane" id="staticDataV2">
302 <p>In this version we separate the code that generates the data used to provide suggestions. The data is usually retrieved from the database.</p>
303
304 {{code language="none"}}
305 {{velocity}}
306 {{html}}
307 #set ($discard = $xwiki.linkx.use($services.webjars.url('selectize.js', 'css/selectize.bootstrap3.css'),
308 {'type': 'text/css', 'rel': 'stylesheet'}))
309 #set ($discard = $xwiki.ssfx.use('uicomponents/suggest/xwiki.selectize.css', true))
310 #set ($discard = $xwiki.jsfx.use('uicomponents/suggest/xwiki.selectize.js', true))
311 #set ($countries = [
312 {'name': 'France', 'code': 'fr'},
313 {'name': 'Germany', 'code': 'de'},
314 {'name': 'Romania', 'code': 'ro'}
315 ])
316 ## See https://github.com/selectize/selectize.js/blob/master/docs/usage.md#configuration
317 #set ($selectizeSettings = {})
318 <select class="xwiki-selectize" data-xwiki-selectize="$escapetool.xml($jsontool.serialize($selectizeSettings))">
319 <option value="">Select the country</option>
320 #foreach ($country in $countries)
321 <option value="$!escapetool.xml($country.code)">$!escapetool.xml($country.name)</option>
322 #end
323 </select>
324 {{/html}}
325 {{/velocity}}
326 {{/code}}
327
328 </div>
329
330 <div role="tabpanel" class="tab-pane" id="staticDataV3">
331 <p>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.</p>
332
333 {{code language="none"}}
334 {{velocity output="false"}}
335 #set ($countries = [
336 {'name': 'France', 'code': 'fr'},
337 {'name': 'Germany', 'code': 'de'},
338 {'name': 'Romania', 'code': 'ro'}
339 ])
340
341 #macro (countryPicker_displayOptions $selectedValues)
342 #foreach ($country in $countries)
343 <option value="$!escapetool.xml($country.code)"#if ($selectedValues.contains($country.code)) selected="selected"#end
344 >$!escapetool.xml($country.name)</option>
345 #end
346 #end
347 {{/velocity}}
348
349 {{velocity}}
350 {{html}}
351 #picker_import
352 #set ($discard = $xwiki.jsfx.use('uicomponents/suggest/xwiki.selectize.js', true))
353 ## See https://github.com/selectize/selectize.js/blob/master/docs/usage.md#configuration
354 #set ($selectizeSettings = {})
355 #set ($suggestInputAttributes = {
356 'name': 'country',
357 'class': 'xwiki-selectize',
358 'data-xwiki-selectize': $selectizeSettings,
359 'value': 'ro',
360 'placeholder': 'Select the country'
361 })
362 #suggestInput($suggestInputAttributes 'countryPicker_displayOptions')
363 {{/html}}
364 {{/velocity}}
365 {{/code}}
366
367 </div>
368
369 <div role="tabpanel" class="tab-pane" id="staticDataV4">
370 <p>In this last version we make the code reusable by creating the ##countryPicker## and ##countryPicker_import## Velocity macros.</p>
371
372 {{code language="none"}}
373 {{velocity output="false"}}
374 #set ($countries = [
375 {'name': 'France', 'code': 'fr'},
376 {'name': 'Germany', 'code': 'de'},
377 {'name': 'Romania', 'code': 'ro'}
378 ])
379
380 #macro (countryPicker_displayOptions $selectedValues)
381 #foreach ($country in $countries)
382 <option value="$!escapetool.xml($country.code)"#if ($selectedValues.contains($country.code)) selected="selected"#end
383 >$!escapetool.xml($country.name)</option>
384 #end
385 #end
386
387 #macro (countryPicker_import)
388 #picker_import
389 #set ($discard = $xwiki.jsfx.use('uicomponents/suggest/xwiki.selectize.js', true))
390 #end
391
392 #macro (countryPicker $parameters)
393 #countryPicker_import
394 #if ("$!parameters" == "")
395 #set ($parameters = {})
396 #end
397 #set ($discard = $parameters.put('class', "$!parameters.get('class') xwiki-selectize suggest-countries"))
398 #if (!$parameters.containsKey('placeholder'))
399 #set ($parameters.placeholder = 'Select the country')
400 #end
401 #suggestInput($parameters 'countryPicker_displayOptions')
402 #end
403 {{/velocity}}
404
405 {{velocity}}
406 {{html}}
407 #set ($countryPickerParams = {
408 'name': 'country',
409 'value': 'ro'
410 })
411 #countryPicker($countryPickerParams)
412 {{/html}}
413 {{/velocity}}
414 {{/code}}
415
416 </div>
417 </div>
418 </div>
419 {{/html}}
420
421 === Suggest from Remote Data ===
422
423 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.
424
425 {{html wiki="true"}}
426 <div>
427 <ul class="nav nav-tabs" role="tablist">
428 <li role="presentation" class="active">
429 <a href="#remoteDataV1" aria-controls="remoteDataV1" role="tab" data-toggle="tab">V1: The Basics</a>
430 </li>
431 <li role="presentation">
432 <a href="#remoteDataV2" aria-controls="remoteDataV2" role="tab" data-toggle="tab">V2: Reusable Code</a>
433 </li>
434 </ul>
435
436 <div class="tab-content">
437 <div role="tabpanel" class="tab-pane active" id="remoteDataV1">
438 <p>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.</p>
439
440 {{code language="js"}}
441 require.config({
442 paths: {
443 'xwiki-selectize': "$xwiki.getSkinFile('uicomponents/suggest/xwiki.selectize.js', true)" +
444 "?v=$escapetool.url($xwiki.version)"
445 }
446 });
447
448 require(['jquery', 'xwiki-selectize'], function($) {
449 var settings = {
450 load: function(typedText, callback) {
451 $.getJSON('your/service/url', {
452 text: typedText
453 }).done(callback).fail(callback);
454 },
455 loadSelected: function(selectedValue, callback) {
456 $.getJSON('your/service/url', {
457 text: selectedValue,
458 exactMatch: true
459 }).done(callback).fail(callback);
460 }
461 };
462
463 $('.suggest-countries').xwikiSelectize(settings);
464 });
465 {{/code}}
466
467 {{code language="none"}}
468 {{velocity}}
469 #set ($discard = $xwiki.linkx.use($services.webjars.url('selectize.js', 'css/selectize.bootstrap3.css'),
470 {'type': 'text/css', 'rel': 'stylesheet'}))
471 #set ($discard = $xwiki.ssfx.use('uicomponents/suggest/xwiki.selectize.css', true))
472 #set ($discard = $xwiki.jsx.use('Path.To.Your.JSX'))
473 {{/velocity}}
474
475 {{html}}
476 <select class="suggest-countries">
477 <option value="">Select the country</option>
478 </select>
479 {{/html}}
480 {{/code}}
481
482 </div>
483
484 <div role="tabpanel" class="tab-pane" id="remoteDataV2">
485 <p>In this last version we make the code reusable by creating a jQuery plugin and some helper Velocity macros.</p>
486
487 {{code language="js"}}
488 require.config({
489 paths: {
490 'xwiki-selectize': "$xwiki.getSkinFile('uicomponents/suggest/xwiki.selectize.js', true)" +
491 "?v=$escapetool.url($xwiki.version)"
492 }
493 });
494
495 define('xwiki-suggestCountries', ['jquery', 'xwiki-selectize'], function($) {
496 var getSettings = function(select) {
497 return {
498 load: function(typedText, callback) {
499 $.getJSON('your/service/url', {
500 text: typedText
501 }).done(callback).fail(callback);
502 },
503 loadSelected: function(selectedValue, callback) {
504 $.getJSON('your/service/url', {
505 text: selectedValue,
506 exactMatch: true
507 }).done(callback).fail(callback);
508 }
509 };
510 };
511
512 $.fn.suggestCountries = function(settings) {
513 return this.each(function() {
514 $(this).xwikiSelectize($.extend(getSettings($(this)), settings));
515 });
516 };
517 });
518
519 require(['jquery', 'xwiki-suggestCountries', 'xwiki-events-bridge'], function($) {
520 var init = function(event, data) {
521 var container = $((data && data.elements) || document);
522 container.find('.suggest-countries').suggestCountries();
523 };
524
525 $(document).on('xwiki:dom:loaded xwiki:dom:updated', init);
526 XWiki.domIsLoaded && init();
527 });
528 {{/code}}
529
530 {{code language="none"}}
531 {{velocity output="false"}}
532 #macro (countryPicker_import)
533 #picker_import
534 #set ($discard = $xwiki.jsx.use('Path.To.Your.JSX'))
535 #end
536
537 #macro (countryPicker $parameters)
538 #countryPicker_import
539 #if ("$!parameters" == "")
540 #set ($parameters = {})
541 #end
542 #set ($discard = $parameters.put('class', "$!parameters.get('class') suggest-countries"))
543 #if (!$parameters.containsKey('placeholder'))
544 #set ($parameters.placeholder = 'Select the country')
545 #end
546 #suggestInput($parameters)
547 #end
548 {{/velocity}}
549
550 {{velocity}}
551 {{html}}
552 #set ($countryPickerParams = {
553 'name': 'country',
554 'value': 'ro'
555 })
556 #countryPicker($countryPickerParams)
557 {{/html}}
558 {{/velocity}}
559 {{/code}}
560
561 </div>
562 </div>
563 </div>
564 {{/html}}
565
566 The remote data should have the following JSON format:
567
568 {{code language="js"}}
569 [
570 {
571 'label': '...',
572 'value': '...',
573 'url': '...',
574 'icon': {
575 'iconSetName': '...',
576 'iconSetType': 'IMAGE or FONT',
577 'cssClass': 'for font icons',
578 'url': 'for image icons'
579 },
580 'hint': '...'
581 },
582 ...
583 ]
584 {{/code}}

Get Connected