Jquery-ui autocomplete dropdown below each word
Asked Answered
A

3

13

I'm using Autocomplete from jquery-ui. In the multiple values, you can get dropdown list for each word after space, but the dropdown appears at the size of the input box. Is it possible to make the dropdown appear below the cursor of each which has a width equivalent to the dropdown words and not the entire length of the input box?

EDIT: Example (Google-like search box):

When you go to google and enter a long sentence as the sentence goes on, after each word, an autocomplete dropdown appears for each single word below the cursor position. So I require a similar dropdown which can be added on to the jQuery Autocomplete

enter image description here

My function is this big because it has features of multiple words and displaying array in order of alphabets. Here is the <script code:

    <script>

    $(function() {

            var availableTags = <?php echo json_encode($sometags); ?>;


            function split( val ) {
            return val.split( / \s*/ );
            }
            function extractLast( term ) {
            return split( term ).pop();
            }
             $( "#query" )
            // don't navigate away from the field on tab when selecting an item
            .bind( "keydown", function( event ) {
            if ( event.keyCode === $.ui.keyCode.TAB &&
            $( this ).data( "autocomplete" ).menu.active ) {
            event.preventDefault();
            }
            })
            .autocomplete({
            minLength: 1,
            source: function( request, response ) {
            // delegate back to autocomplete, but extract the last term
            response( $.ui.autocomplete.filter(
            availableTags, extractLast( request.term ) ) );
            var term = $.ui.autocomplete.escapeRegex(request.term)
            , startsWithMatcher = new RegExp("^" + term, "i")
            , startsWith = $.grep(availableTags, function(value) {
                return startsWithMatcher.test(value.label || value.value || value);
            })
            , containsMatcher = new RegExp(term, "i")
            , contains = $.grep(availableTags, function (value) {
                return $.inArray(value, startsWith) < 0 &&
                    containsMatcher.test(value.label || value.value || value);
            });

            response(startsWith.concat(contains));
            },
            focus: function() {
            // prevent value inserted on focus
            return false;
            },
            select: function( event, ui ) {
            var terms = split( this.value );
            // remove the current input
            terms.pop();
            // add the selected item
            terms.push( ui.item.value );
            // add placeholder to get the comma-and-space at the end
            terms.push( "" );
            this.value = terms.join( " " );
            return false;
            }
            });

    });
    </script>

EDIT: Just like the google-box, the dropdown should contain within the width of the input box meaning for example, the dropdown box for the last word in the input box should resize not to the right but to the left. The right edge of the dropdown box should be in line with the right edge of the inputbox and the total width of the dropdown (in case of words as big or bigger than input box) should not exceed the width of the input box.

UPDATE: Here is the final mod of the google-like autocomplete: Fiddle (Updated 16/2/2013)
Features:
1) Fixed multiple words sort alphabetically against suggestions (jQuery-ui autocomplete multiple values sort results alphabetically)
2) Retrieve suggestions from 2 arrays with first suggestion opening at full width of input box like in google and rest of suggestions at the width of the longest suggestion
3) Fixed bugs of dropdown opening before input of word after 'space' (multiple values).
4) Prevent dropdown from being kept open at the end while adding words in between.
5) 16/2/2013 Update: When the length of tags inputted from suggestion box exceeds the length of the input box and on input of the next tag, the input box displays tags from first or it moves back to the first tag position and not from where the cursor was last placed as seen here - http://jqueryui.com/autocomplete/#multiple. This has been fixed.

This is a similar fiddle with only ONE array used and suggestions always at width of the longest suggestion - FIDDLE

Thanks to Jeffery To who provided the main solution to the question, and to Vadim and Dom whose answers provided ideas that were useful in creating the above mod.

Adnah answered 3/2, 2013 at 12:23 Comment(12)
When you go to google and enter a long sentence as the sentence goes on, after each word, an autocomplete dropdown appears for each single word below the cursor position. So I require a similar dropdown which can be added on to the jQuery AutocompleteAdnah
Something like this? jsfiddle.net/ExplosionPIlls/9DbTwLieutenant
@Explosion Pills - That's a simple autocomplete what I require is one that is like in google (see the above comment) and for me a new dropdown appears even for the second word meaning, in your fiddle, if I type 'word' the list comes but if I type 'word' again after the first 'word' atleast even the same dropdown doesn't appear.Adnah
@JavierBrooklyn why would you type word word? Where is the source of your list coming from?Lieutenant
@Explosion Pills - Because in your fiddle all the arrays start with word. So the only way of checking whether for a new word, a new dropdown would appear is only by typing 'word word'. In my js, I have an array that has many words that are not spaced and if I type 'hello world', I get a dropdown containing words starting with 'h' when I press 'h' and a dropdown containing words starting with 'w' when I press 'w'. But my dropdown has the same length of the box for each word but I want the box to appear only below the cursor and at the size of the word.Adnah
@BalusC - That is a tag where each word will have a box with a 'x' button which I have and which is not what I want, see my second comment under this question to help you understand what kind of autocomplete I want with the exception that since I like the themes of Jquery I want it as an addon to the jQuery Autocomplete.Adnah
@Explosion Pills - You can check multiple words dropdown at - jqueryui.com/autocomplete/#multipleAdnah
This shows you how to find the location of the caret in a textbox.Yemen
@Yemen - Yes maybe that would be the main function in getting the dropdown, but its hard merging that function with the current functions of jQuery autocomplete.Adnah
Continuing modding of jQuery autocomplete - #14786754Adnah
@Javier Brooklyn How did you handle a fullstop in case if there is a sentence end?Decontaminate
Can you add the functionality where a user can try to edit any word in the sentence (not just the last word) and the dropdown appears for that word?Rosemare
C
11

As the other answers have suggested, we measure the width of the text in the input field (using a hidden element), then offset the autocomplete box. We can also reset the width of the autocomplete box so that it's only as wide as the longest suggestion (demo):

open: function( event, ui ) {
    var input = $( event.target ),
        widget = input.autocomplete( "widget" ),
        style = $.extend( input.css( [
            "font",
            "border-left",
            "padding-left"
        ] ), {
            position: "absolute",
            visibility: "hidden",
            "padding-right": 0,
            "border-right": 0,
            "white-space": "pre"
        } ),
        div = $( "<div/>" ),
        pos = {
            my: "left top",
            collision: "none"
        },
        offset = -7; // magic number to align the first letter
                     // in the text field with the first letter
                     // of suggestions
                     // depends on how you style the autocomplete box

    widget.css( "width", "" );

    div
        .text( input.val().replace( /\S*$/, "" ) )
        .css( style )
        .insertAfter( input );
    offset = Math.min(
        Math.max( offset + div.width(), 0 ),
        input.width() - widget.width()
    );
    div.remove();

    pos.at = "left+" + offset + " bottom";
    input.autocomplete( "option", "position", pos );

    widget.position( $.extend( { of: input }, pos ) );
}

Update: Fixed autocomplete box positioning

Update 2: Another autocomplete box positioning fix

Cotsen answered 8/2, 2013 at 2:45 Comment(14)
This code works well especially that the dropdown doesn't move as the cursor moves but appears just below the cursor at the start of the next word. But the problem is same as the answer of @Dom where if the position of the input box is changed to a new position, the dropdown appears in the old position. For example, in the jsfiddle, when you try drag the middle bar between css, html, js and result, to the left side, meaning, if the input box positiong is changed to the right of the label and not below the label as it is now, the dropdown appears separately below the label.Adnah
The other advantages of your code is that you eliminated the requirement of html <span or <div. The dropdown not appearing below the real location of input box I guess is because of implementing the code using open: under autocomplete function. Among the answers, the code of @Yemen is quite good as he is implementing it as an external function using .on but the drawback is that his code requires <span unlike your code and also in his code the dropdown moves along with the cursor move. If the position problem as in the above comment could be solved then your code would be the answer.Adnah
Superb! But suppose the input box has words like "This is a long sentence", lets assume the letter 's' has some suggestions that are bigger, so what happens is, that being the last word, suggestions of it fall outside the input box width, is possible to make it contain within the box width always? Meaning suppose a word "strategically" is suggested, the increase in size of the suggestion shouldn't move rightside but to the left of the input box, and if the suggestion is as big or bigger than the input box, then dropdown should have only input box width as maximum.Adnah
@JavierBrooklyn You can bound the offset calculation so that it does not exceed (width of input) - (width of popup). This would not be hard.Cotsen
I changed the 'offset =' of your code to offset = Math.min( offset + div.width(), input.width() - 0.62*input.width());. It works because it displays the lesser width of the terms and stays within the size of the input. In your code, you had kept the size of dropdown to be dependent on word size, how is it possible to make a value to get the width of that 'word dependent dropdown' like div.width() or input.width()? So I can replace input.width() - 0.62*input.width()); to input.width() - (width of that dropdown box that is different for each word).Adnah
Using div.width() doesn't work instead of (width of that dropdown box that is different for each word) as div.width is the total width of the input.Adnah
Alternatively I tried using ul.width() and li.width but I have found that width of hidden elements cannot be retrieved. But some users were able to get the width like - #4754994 and #1472803Adnah
@JavierBrooklyn I've updated my answer. If you're not sure how it works, I can provide more explanation.Cotsen
@JefferyTo How to handle a fullstop in case if there is a sentence end?Decontaminate
@Decontaminate I'm not sure I understand your questionCotsen
this autocomplete works only if there is a space before the word being typed or it is the first word .What if I want to make it work after a fullstop??Decontaminate
@JefferyTo Hope u got me this time :)Decontaminate
@Decontaminate Sorry, but the OP's question was about styling the autocomplete box. I didn't write the actual autocomplete code, nor had I studied it in any detail. I suggest posting a new question if you are stuck.Cotsen
@JefferyTo I have posted a question related to that..you can view my question #35862053Decontaminate
K
3

Using JQuery's multiple demo, what you could do is create a hidden span element

<span id="characterSpan" style="visibility: hidden;"></span>

and use it to store the input's val().

From there, use .width() to get the span's width and use the autocomplete's open( event, ui ) event :

open: function( event, ui ) {
        var span = $('#characterSpan');
        var width = span.width();
        width > $('#query').width() ? 
        width = parseInt($('#query').position().left + $('#query').width()) : 
        width = parseInt($('#query').position().left + width);

        $('.ui-autocomplete.ui-menu').css('left', width + 'px');
    }

DEMO: http://jsfiddle.net/dirtyd77/5ySF9/8/

Hope I explained this well enough and let me know if you have any questions!

Kinin answered 7/2, 2013 at 17:43 Comment(6)
Well this code works too but I found a bug or problem with this one, when you go to jsfiddle, currently the input box is below the label 'tag program....' so it appears correctly but when the window pane of 'Result' or the window that shows the result is resized by dragging the middle bar to the left, such that the input box appears to the right side of the label, the dropdown is appearing as it appears in its first position, meaning the code thinks that the dropdown box is still below the label rather than to the new position i.e., to the right of the label.Adnah
In select: function( event, ui ) {}, did you add: $('#characterSpan').html($(this).val());? Make sure to copy/paste what is used in my jsfiddle. Did that help? If not, please provide example of what your current code looks like.Kinin
Yes I added that, I tested my above comment on the same fiddle you provided but the problem is that when the input box is in a different location, the dropdown appears in a fixed location. So to test that out, in jsfiddle there are 4 panels, html, css, js, result, and there is a middle bar, I dragged the middle bar to the left so that the position of the Result input box changes and the dropdown was appearing where it was before I dragged the bar. But your code could do something like how I wanted.Adnah
OH! I see what you are saying now! Then simply get the input's position using .position().left. I have made changes to my initial post. That should fix it, but let me know if you have any other questions!Kinin
Well it seems that the edit is not working either, because I have problems that when I type the word and backspace it, the dropdown still remains where I typed the last word and the also the change in location of dropdown with respect to the input box location is not getting corrected.Adnah
Then you would need to add use .bind( "keydown", function( event ) {} and have it overwrite the span (look at my jsfiddle line 40). This is just to give a general idea on how to go about it.Kinin
Y
1

I've based the answer on the answer to this question. As mentioned in that answer, IE<9 will need some extra code to make this work.

Basically you create a span into which you write out the contents of the input element. You then use the width of this span to calculate the offset. JqueryUI provides a position hook where you can actually offset the value. so the HTML

<input id="query" style="width: 300px" />
<span id="faux" style="display:none" />

And the JS

    var query= $("#query");
    var faux = $("#faux");
    query.autocomplete({
        source: mySource
    }).on("keydown", function() {
        var off = query.selectionStart;
        faux.text(query.val().substring(0, off).replace(/\s/g, "\u00a0"));
        query.autocomplete("option", "position", 
              {my: "left top", at: "left+" + faux.outerWidth() + " bottom"});
    });

Working JSFiddle

Yemen answered 7/2, 2013 at 16:25 Comment(5)
Your code works at jsfiddle but its not working in the function where I'm calling the autocomplete. Wait i'll update my question with the code.Adnah
What version of jQuery are you using @JavierBrooklyn? You could try replacing the on call with bind. Or just place it in your existing keydown method.Yemen
I'm using 1.90 and 1.10.0.0 (I don't know if these are the versions but the js files are jquery-1.9.0.js and jquery-ui-1.10.0.custom.js)Adnah
How have you tried to integrate the code I posted? Did you add the faux span? Where did you add the new keydown event handler?Yemen
I was placing the .on function at the wrong place and which is why it didn't work. I placed correctly it after the last '});' just after the 'return false' removing the ';' and it works now. But there are some more things to be fixed with the code, the dropdown box keeps moving with the cursor move and the width of the dropdown box after the first word has to be dependent on the width of the largest word in the related dropdown and also the width or the dropdown box should contain within the width of the input box. To make it short, a code similar to the Google search box would be appropriate.Adnah

© 2022 - 2024 — McMap. All rights reserved.