Select 2 is a jquery/javascript based tool that makes using select boxes less painful when there are many options. You can search available options and get a much better display of the options compared to a usual select box. One of the capabilities is to use the REST interface in roundup to allow searching of the options.
This page provides an example of using select2 to replace the usual text box (e.g. superceder field in the classic template) used to edit multilinks to other issues. The tracker it came from is the sysadmin demo tracker.
By making this change replaces
with
When the user selects the widget, they get prompted to enter a couple of characters
Typing a few characters generates a scrollbox list with matching options. If there are more than 100 options, new options are loaded when the scrollbar hits the bottom.
when the user clicks on the matching entry, it is added to the select2 widget
Implementation
To follow along with this, create a jslibraries subdirectory in your tracker's html directory. After downloading the select2 files, expand the zip file into the jslibraries directory. (Note you can also use a CDN. You must change the url's appropriately and should include a subresource integrity attribute in the script tag and css link.) Also download jquery libraries and install them in the same subdirectory.
html page changes
Edit page.html to load select2's css and javascript library. Add:
1 <link rel="stylesheet" href="@@file/jslibraries/select2.css">
inside the head tag and add:
1 <script tal:attributes="nonce request/client/client_nonce"
2 src="@@file/jslibraries/jquery-3.4.1.js"></script>
3 <script tal:attributes="nonce request/client/client_nonce"
4 src="@@file/jslibraries/select2.js"></script>
5 <script tal:attributes="nonce request/client/client_nonce"
6 src="@@file/tracker.js"></script>
at the end of the body tag so that loading the javascript libraries doesn't delay the display of the page.
The contents of tracker.js will be discussed later. It provides the trigger that activates select2 on the select boxes on the page.
The example below is done using a dependson multilink to issues. This is the same relationship as the superseder field in the classic tracker. Translate the field names as appropriate for your tracker.
In issue.item.html look for the tag with the field call for your multilink relationship. For example:
Replace this with:
1 <span tal:condition="not:context/dependson/is_edit_ok"
2 tal:content="structure
3 python:context.dependson.field(showid=1,
4 size=20, id='dependsrelation') or default">
5 No dependencies
6 </span>
7 <input tal:condition="context/dependson/is_edit_ok"
8 type="hidden" name="dependson" value="" />
9 <select tal:condition="context/dependson/is_edit_ok"
10 id="dependson" name="dependson" multiple>
11 <tal:block tal:repeat="issue context/dependson">
12 <option selected tal:attributes="value issue/id"
13 tal:content="string:${issue/title}
14 ([issue${issue/id}] by ${issue/requestedby/username})">
15 </option>
16 </tal:block>
17 </select>
18 <noscript>Enable javascript to add items to the list.</noscript>
A few things to note:
- The hidden input type is required to allow the field to be emptied. If you don't have it removing all dependson items from the list will have no effect.
- The value for the name attribute is the same one you will see if you use debugging tools on the original html generated by the field call.
The use of tal:condition="context/dependson/is_edit_ok" or it's negated form displays a select box (that appears editable but is not within a form) only if the user has edit rights. IF the user doesn't have edit rights, a list of issues is generated by the field() call.
When the user has edit rights, this creates a select tag with an option for each issue currently linked using dependson. The displayed value for the issue consists of:
- The issue title followed by the issue number in square brackets followed by the user who requested/created the issue.
javascript code
Now create html/trigger.js and add this to the file:
1 jQuery(document).ready(function () {
2 jQuery("select#dependson").select2({
3 width: "100%", // adjust to set width of replacement
4 minimumInputLength: 2, // type two chars before searching happens
5 delay: 250, // wait for 1/4 second typing pause before searching
6 placeholder: "Select issues", // text to show in empty box
7 ajax: { // configure the remote call
8 url: 'https://roundup.example.net/demo/rest/data/issue', // Change Me
9 dataType: 'json',
10 data: function (params) {
11 var query = {
12 'title': params.term, // search by title
13 '@fields': "title,requestedby", // get title and requestedby
14 '@verbose': "2", // get username in requestedby dict
15 '@page_size': "100", // number of entries to return
16 '@page_index': params.page, // which page to return
17 '@sort': "-id" // return most recent issues first
18 }
19 // Query parameters will be:
20 // ?title=[term]&@fields=title,requesteby&@verbose=2&
21 // @page_size=100&@page_index=...@sort="-id"
22 // @verbose=2 gives me the requestedby username in the
23 // response.
24 return query
25 },
26 processResults: function (data) {
27 // select2 expects a pagination: more item in the dict
28 // indicating that there are more pages of results to get.
29 // If we have a next link in data.data['@links'] we have
30 // more data
31 var more = (data.data["@links"].next) ? true: false
32
33 // Transforms the top-level key of the response object
34 // extract title into text field and annotate with
35 // issue number and requestor
36 var data = jQuery.map(data.data.collection, function (obj) {
37 obj.text = obj.text ||
38 obj.title +
39 ` ([issue${obj.id}] by ${obj.requestedby.username})`;
40 return obj;
41 });
42
43 return {
44 results: data,
45 pagination: { "more": more }
46 }
47 },
48 }
49 });
50
51 /* hide the sibling list link for enhanced multilselects */
52 jQuery("select[multiple]").parent().siblings("a.classhelp").hide();
53 });
edit the URL at line 8 replacing it with the rest endpoint of your roundup instance.
This code configures a function to call when jquery is fully loaded and the page is ready to be displayed. This function is called on the select tag with the id dependson (as specified by "select#dependson" at line 2). The jQuery command at line 52 hides the link for the javascript selector popup.
Open issues
If the user does not have javascript enabled but has edit rights, they will be presented with a standard multi-select box showing all the issues. The user can remove items by control-clicking, but can't add any new items to the list. New items are only available by ajax calls requiring javascript. The noscript tag lets the user know that the standard select box needs javascript on order to add items. It would be nice to have a better solution. See IsJavascriptAvailable for a way to emit a standard text input box with a list of item id's when javascript is disabled.
The template code to differentiate between having edit rights and not having edit rights is not beautiful but effective. It should be easier/more beautiful.
CategoryInterfaceRest CategoryInterfaceWeb CategoryJavascript