25 November 2015

Written by Jon Bevan

TL;DR Don't use it! Use Select2 instead

How data is stored internally

Using a data attribute on the option elements themselves... I didn't even know you could store arbitrary JS objects as attributes on HTML elements... somehow... It turns out that it is jQuery that enables this. If you're curious see the jQuery documentation

Each item/option in a SingleSelect is represented by a ItemDescriptor object:

var ItemDescriptor = require('jira/ajs/list/item-descriptor');
new ItemDescriptor({
    title: "Some awesome tooltip text that nobody will ever see",
    value: 12345,
    label: "Pick me!",
    allowDuplicate: false
});

These ItemDescriptor objects are stored and retrieved from the "descriptor" data attribute on an element (there are a few additional to the one you've specified as the target for your SingleSelect) in the DOM.

Here's some code from JIRA to prove it:

src/jira-project/jira-components/jira-webapp-common/src/main/webapp/includes/ajs/select/SelectModel.js

/**
 * Gets an array of JSON descriptors for selected options
 *
 * @method getDisplayableSelectedDescriptors
 * @return {Array}
 */
getDisplayableSelectedDescriptors: function () {
    var descriptors = [];
    this.$element.find("option:selected").not(this._getExclusions()).each(function () {
        descriptors.push(jQuery.data(this, "descriptor"));
    });
    return descriptors;
}

Don't do that. The DOM was not invented as a data store for arbitrary JS objects.

How options are filtered

Not using the suggestionsHandler... that only handles providing suggestions (select options). Filtering those suggestions based on what you type is performed using the "matchingStrategy" option...

Also, the matchingStrategy option is a string which is converted into a RegExp and is also used for highlighting the part of each option that matches what you've typed.

So it all breaks if your matchingStrategy option value doesn't fit the structure that the highlighting code is expecting.

Here's the code used to do the highlighting of segments of your options based on what you've typed:

src/jira-project/jira-components/jira-webapp-common/src/main/webapp/includes/ajs/list/List.js

var labelRegex = "(^|.*?(\\s+|\\())({0})(.*)"; // Default matchingStrategy value: match start of words, including after '('
var replacementText = item.label().replace(labelRegex, function (_, prefix, spaceOrParenthesis, match, suffix) {
    var div = jQuery("<div>");

    prefix && div.append(jQuery("<span>").text(prefix));
    div.append(jQuery("<em>").text(match));
    suffix && div.append(jQuery("<span>").text(suffix));

    return div.html();
});

An exercise for the reader is to generate a different matchingStrategy regex that will handle full-text matching, instead of just start-of-word matching, whilst retaining the highlight functionality as implemented above...

To prevent filtering (and handle filtering within your suggestionsHandler) set the matchingStrategy option to something falsey like "" or false.

var _ = require('underscore');
var SingleSelect = require('jira/ajs/select/single-select');
var DefaultSuggestHandler = require('jira/ajs/select/suggestions/default-suggest-handler');


var searchFulltext = DefaultSuggestHandler.extend({
    formatSuggestions: function (descriptors, query) {
        if (query == "") return descriptors;

        return _.filter(descriptors, function(descriptor) {
            return descriptor.properties.label.indexOf(query) > -1;
        });
    }
});


var mySingleSelect = new SingleSelect({
    element: el,
    suggestionsHandler: searchFulltext,
    matchingStrategy: ""   // This prevents SingleSelect from subsequent filtering of the
                            // options provided by the suggestionsHandler
});

How to override the stupid behaviour of JIRA's dialog smart forms during save/validation

So, JIRA has some JS that triggers an "enable" event on all input's in a form during an AJAX save of that form. Obviously. Oh, and it removes the "disabled" attribute from those input fields as well, for good measure.

SingleSelect's listen for such events and will therefore enable when an AJAX form is submitted, even if validation of the form fails... like when you forget to fill in a Summary or something...

You need to create your own implementation of a SingleSelect that re-disables the input field if required:

var ConsistentlyDisabledSingleSelectRegardlessOfHowHardJIRATries = SingleSelect.extend({
    init: function(options){
        // Work around for JIRA's stupid behaviour of enabling all form fields on form AJAX submit
        this._events.container.enable = function() {
            if (false /* some business logic should go here */) {
                this.enable();
            } else {
                // Apply the 'disabled' attribute again, because JIRA's already removed it by this point...
                this.$container.find('input').attr('disabled', 'disabled');
            }
        };
        // Obviously JS is classical OO inheritance, not prototypal...
        this._super(options);
    }
});


blog comments powered by Disqus