Tagify: Max Suggestion to be displayed from whitelist && some unexpected behaviours!

Created on 18 Nov 2017  路  14Comments  路  Source: yairEO/tagify

Can you please implement a new setting parameter that allows us to set how many suggestion the whitelist should display?
if not that, a callback on the event "on change" would be good too to actually manually handle the whitelist and displaying a maximum number of results. basically something like this :

function(request, response) {
        var results = element.filter(myarray, request.term,maxSuggestions);
        response(results.slice(0, maxSuggestions));
    }

Also please release a pre-compiled version of this to be implemented on vanilla html without needing pre-processors.

Also to correctly display the input like it's supposed to be displayed in the demo i had to manually hide the element i "tagified", i think it should be integrated in the tagify() function itself to actually hide the element that will only hold the value.

Great job btw!

All 14 comments

I'm trying to implement the maxSuggestions function myself right now

ok i figured it out after 7 hour, heres is what i changed to implement it:

added
maxSuggestions: Infinity // maximum number of suggestions to display
to the DEFAULTS settings array

on the onInput event I added the following function call:
this.DOM.datalist = this.buildFiltredDataList(value);
right before:
this.DOM.input.parentNode.appendChild(this.DOM.datalist);
(this calls the function BuildFiltredDataList every time the datalist should be update based on the new input, and assign the datalist value of the tagify element as the value returned by the function)

i created two new function and i added them just before the buildDataFunction

here is the code of the two function I created

        /* return an array with filtred values by the given input
         * 
         * @param {string} inputStr
         * @returns array
         */
        filterTagsSuggestions: function (inputStr) {
            var filtredWhiteList = new Array();
            //filter the input so regexp doesn't break
            inputStr.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
            //filter the whitelist using the input string
            var filterRegExp = new RegExp(inputStr, "i");
            for (i = this.settings.whitelist.length; i--; ) {
                stringToTest = this.settings.whitelist[i];
                if (filterRegExp.test(stringToTest)) {
                    console.log(stringToTest);
                    if (this.settings.maxSuggestions === Infinity || filtredWhiteList.length <= this.settings.maxSuggestions) {
                        //if regexp match, push element into filtred list
                        filtredWhiteList.push(this.settings.whitelist[i]);
                    }
                }
            }
            return filtredWhiteList;
        },
        /* filter the whitelist based on the input
         * return a native html datalist for native autocomplete
         * 
         * @param {type} inputStr   the string to apply in the filter
         * @returns native html datalist element
         */
        buildFiltredDataList: function (inputStr) {
            filtredArray = this.filterTagsSuggestions(inputStr);
            var OPTIONS = "",
                    i,
                    datalist = document.createElement('datalist');

            datalist.id = 'tagifySuggestions' + this.id;
            datalist.innerHTML = "<label> \
                                select from the list: \
                                <select> \
                                    <option value=''></option> \
                                    [OPTIONS] \
                                </select> \
                            </label>";
            for (i = filtredArray.length; i--; )
                OPTIONS += "<option>" + filtredArray[i] + "</option>";
            datalist.innerHTML = datalist.innerHTML.replace('[OPTIONS]', OPTIONS);
            return datalist;
        },

buildFiltredDataList is basically the same as buildDataList but it use a filtred array instead of the whitelist passed in the settings, while the function filterTagsSuggestions is the function that filter the whitelist using a regular expresion that keep track of the current input field value, it also handles the logic of "maxSuggestions", if maxSuggestions is Infinity it don't limit the list, else it takes only the first maxSuggestions elements.

This works in my project, I hope it does in yours too, sorry if i didn't do a pull request but I'm not on my main pc and I don't have git correctly configured here.

still missing the code to automatically hide the original input that gets tagified, if you want to fix it.

what do you mean a pre-compiled version? do you mean post-compiled maybe?

There are two folders in this repo: dist and src, everything is there, it's pretty common practice


Also to correctly display the input like it's supposed to be displayed in the demo i had to manually hide the element i "tagified", i think it should be integrated in the tagify() function itself to actually hide the element that will only hold the value.

Did you use the CSS file from the repo? tagify.css
if you had been using it, and you should, it does say in the CSS file:

tags input,tags textarea {
    border: 0;
    display: none; /* hides the original input */
}

no i didn't realize about that src folder so i got my hands on the css from the demo-page, and it looks like something went missing from there, it's currently working as i want in the project, just double check it works in yours too, i'm referencing the input by a getelementbyid, not with the selector you are using in the example, dunno if that have anything to do with it, just check it out.

About the maxSuggestion function, did you revise it? it's working correctly in my project, but i dunno if it's as optimized as it should, i'm mainly a php back-end developer so i would like someone more expert to optimize it.

Thanks a lot for your time and work so far btw, great plugin.

I did take a look at it. You forgot to put this line filtredArray = this.filterTagsSuggestions(inputStr); inside the var statement a line after it. should be inside.

Anyway, I am in the process of integrating a 3rd party autosuggestion library into this script and only leave datalist support as a fallback, so I don't want to inflate the source code with the filterTagsSuggestions function you have written above. I aim to simply things and go for an external solution which is more flexible in terms of styling and content control.

I have a better idea, wait.
How come is your datalist big anyway? I mean, how is it possible it displays so many matches?

Technically the this.buildDataList() method is called only once, on initialization, so I can't see how filtering it will help, since you would need to filter it on every input event callback and that is a very heavy action performance-wise, I wouldn't recommend it.

I think this method will do the trick, but it would have to be called each on an input event callback:

buildDataList : function(){
    var OPTIONS = "",
        inputValue = this.DOM.input,
        i,
        datalist = document.createElement('datalist'),
        suggestionsCount = this.settings.maxSuggestions || Infinity;

    datalist.id = 'tagifySuggestions' + this.id;
    datalist.innerHTML = "<label> \
                            select from the list: \
                            <select> \
                                <option value=''></option> \
                                [OPTIONS] \
                            </select> \
                        </label>";

    for( i=this.settings.whitelist.length; i--; ){
        var value = this.settings.whitelist[i];
        // make sure the input value matches the beaning part of this suggestion item
        if( value.indexOf(inputValue) == 0 && suggestionsCount-- )
            OPTIONS += "<option>"+ value +"</option>";
        if( suggestionsCount == 0 ) break;
    }

    datalist.innerHTML = datalist.innerHTML.replace('[OPTIONS]', OPTIONS); // inject the options string in the right place

    return datalist;
}

Then bind the input event:

var tagify = new Tagify(input1, {
        maxSuggestions : 5,
        whitelist : [...]  // your list of suggestions
})

// dynamically build the suggestions list when the input changes
tagify.DOM.input.addEventListener('input', function(){
    tagify.DOM.datalist = tagify.buildDataList();
}

because I have a request from a client to generate the suggestions from all the fields already present in the database, without repeated field, but the field he wants to be "autocompleted from already inputed fields" isn't a select, it's a free-text input string, meaning that even a single character difference makes up for a whole new different suggestion, and since the database is HEAVY (i know, it's dumb, but that's what they want, i told them about performance issues and other possible solutions, but they refused to listen, and wanted it no matter what) the suggestion list will have like more than 3k suggestions.

I think this method will do the trick, but it would have to be called each on an input event callback:

i don't see much difference with what i did in the buildFiltredDatalist, it's basically building the datalist eachtime the input change, no?

your version iterate the whole whitelist even when you have maxsuggestion set to 5, my version stop iterating as soon as the suggestion count is reached, no?

Edit:
no my version iterate the whole whitelist too when "filtering" the whitelist, should put an optional "if" there to break out of the loop as soon as maxSuggestions as been reached.

updated the code (see above) to break once the maximum number of suggestions has been met.

There is a huge difference between my solution and yours, which is mine is much less code (which is very important) and also it is easier to understand since it is very intuitive

will try it out tomorrow and let you know if it works out as it should, thanks a lot for your time and help!

just one last doubt, can't i bind it automatically from the already implemented onInput event handler of your plugin instead of this:

// dynamically build the suggestions list when the input changes
tagify.DOM.input.addEventListener('input', function(){
    tagify.DOM.datalist = tagify.buildDataList();
}

setting it up like this:

on the onInput event I added the following function call:
`this.DOM.datalist = this.buildFiltredDataList(value);`
right before:
`this.DOM.input.parentNode.appendChild(this.DOM.datalist);`

switching out this.buildFilredDataList(value) with this.buildDataList() ?

You would have to change the original source code and that is bad practice, since you won't be able to easily sync with this repo once I update it because your code will be different than the repo's.

what you suggest is possible but not a good idea..
I wouldn't recommend implementing any changes to the source code anyway

so are you going to change the buildDataList on the official repo too?

Yes, I will change it completely, to use an outside script which will manage the suggestions list, and then the tagify code will be isolated from the suggestions list, which is something extra, as I see it.
I want this script to focus on the tags and not on the suggestions.

okay, thanks a lot for everything then, looking forward to the new version, by what i understand it will be even more compatible with older browser that don't even have the native datalist!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Synchro picture Synchro  路  6Comments

Hardikraja picture Hardikraja  路  4Comments

Oigen43 picture Oigen43  路  5Comments

pesseyjulien picture pesseyjulien  路  6Comments

raoul2000 picture raoul2000  路  6Comments