Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added functionality to override default autoComplete item rendering via options #3

Closed
wants to merge 3 commits into from

Conversation

amcnea
Copy link

@amcnea amcnea commented Nov 21, 2014

Minified Version Updated
ReadMe Updated

With this change one no longer needs to pass in an array of strings into the suggest() function. This is still the default behavior, but now one may override the 'autocomplete-suggestion' item render to allow one to render any data item passed into the suggest() function.

This is handled by overriding the renderItem() function in the options variable. The renderItem() function takes 2 parameters 'item' and 'search'. Where item is an item in the suggest array and search is the search term used.

Below is a patch that will alter demo.html to use an array of objects instead of an array of strings. Each string can now be found in data[i].name instead of data[i]

diff --git a/demo.html b/demo.html
index ffa6cfe..6493632 100644
--- a/demo.html
+++ b/demo.html
@@ -239,11 +239,19 @@ $(<b>'input[name="q"]'</b>).autoComplete({
                 minChars: 1,
                 source: function(term, suggest){
                     term = term.toLowerCase();
-                    var choices = ['ActionScript', 'AppleScript', 'Asp', 'Assembly', 'BASIC', 'Batch', 'C', 'C++', 'CSS', 'Clojur
+                    var choicesArr = ['ActionScript', 'AppleScript', 'Asp', 'Assembly', 'BASIC', 'Batch', 'C', 'C++', 'CSS', 'Clo
+                        choices = [];
+
+                    for (i=0;i<choicesArr.length;i++)
+                        choices[i] = { name: choicesArr[i]};
                     var suggestions = [];
                     for (i=0;i<choices.length;i++)
-                        if (~choices[i].toLowerCase().indexOf(term)) suggestions.push(choices[i]);
+                        if (~choices[i].name.toLowerCase().indexOf(term)) suggestions.push(choices[i]);
                     suggest(suggestions);
+                },
+                renderItem: function (item, search) {
+                    var re = new RegExp("(" + search.split(' ').join('|') + ")", "gi");
+                    return '<div class="autocomplete-suggestion" data-val="' + item.name + '">' + item.name.replace(re, "<b>$1</b
                 }
             });
         });

@SimonSteinberger
Copy link

Sounds interesting. I'm just not sure how/where it could be used. Can you provide a short example or describe some use cases? I guess I do understand, but a small snippet can help a lot :-)

One of the main reasons for this autocompleter plugin is to provide a really minimalistic choice. So all options should also be useful for a significant part of users.

Thanks in any case for your contribution!

@amcnea
Copy link
Author

amcnea commented Nov 21, 2014

I am creating a directory list. So, you type in something and a list of people drop down. In the row object (div) I show the persons name, an image of the person (or default image), the status of the person in the system (away, idle, busy, etc.). As well as if the person is associated with any groups.

The list of people who appear in the list comes from an $.ajax request, but many of the properties such as their status and groups comes from other data source. With this I can combine multiple data sources into a single object and render complex row objects fairly easily.

   +-------+    John Doe
 * | image |    Group 01
   +-------+

Status indicator on left, then image, then name and group

@amcnea
Copy link
Author

amcnea commented Nov 21, 2014

Here is my implementation:

input.autoComplete({
                minChars: 1,
                source: function (term, suggest) {
                    $.ajax({
                        url: dateUrl,
                        data: 'term=' + term,
                        method: "GET",
                        dataType: "jsonp",
                        success: function (data, textStatus, jqXhr) {
                            suggest(data);
                        }
                    });
                },
                renderItem: function (item, search) {
                    var userInfo = userStore.get(item.user_id) || {},
                        tpl = {
                            status: userInfo.status || 'none',
                            photo: item.photo || defaultImagePath,
                            name: item.label || 'Unknown',
                            groups: userInfo.group || ''
                        };

                    return '<div class="autocomplete-suggestion" data-val="' + tpl.name + '">' +
                        '<div class="status status-' + tpl.status + '"></div>' +
                        '<div class="image"><img src="' + tpl.photo + '"></div>' +
                        '<div class="info">' +
                            '<div class="name">' + tpl.name + '</div>' +
                            '<div class="groups">' + tpl.groups + '</div>' +
                        '</div>' +
                    '</div>';
                }
            });

@SimonSteinberger
Copy link

That sounds really useful! We'll most likely include it. It'll probably take a few days, because I'm kept very busy right now with another project.

@amcnea
Copy link
Author

amcnea commented Nov 21, 2014

No problem. I also have a change today which I made where I added that.fixPosition() just before the dropdown is shows. When the input initializes (not the drop down) it has a 'display:none'. The width() value used at this time is invalid and the drop down appears in the wrong location (until you resize your window).

@SimonSteinberger
Copy link

There a timeout call after init, which should catch this problem:
setTimeout(that.fixPosition, 100);

Could you try to increase the delay value there (maybe to 200+ ms) and try again? Just to see if that's the issue ... I'd prefer not to include fixPosition() inside fixScroll(), just for preventing some overhead.

@amcnea
Copy link
Author

amcnea commented Nov 21, 2014

I think I was slightly incorrect earlier. Basically how the you get to the input on my screens is from the main panel:

  1. you open a sub-panel
  2. Click a button. And the input box expands outward to the right.

So, the width actually is 0 until the button is clicked.

I tried adding a resize callback to the input and it worked:

            setTimeout(that.fixPosition, 100);
            $(window).on('resize.autocomplete', that.fixPosition);
            that.on('resize.autocomplete', that.fixPosition);

@SimonSteinberger
Copy link

Hmm, interesting ... maybe your event is enough and the first two calls could be obsolete now. That would be great. I'll check that also. Or if you can confirm - that would also be great. Thanks :)

@amcnea
Copy link
Author

amcnea commented Nov 23, 2014

I ran some tests and the lines are needed or the demo.html fails. When resizing the window on demo.html the input resize doesn't fire off. I think this is because the width of the input is 100%, so the input is not registering changes.

My feeling on the subject is you probably want to calculate before each render. Since you are storying the inputDomObject in a variable and not looking it up off the DOM each time, your overhead isn't going to bad.

The reason I recommend this is because in order to be assured we have the correct data [width/top/left] for the dropdown. We must know 1) a valid point in time to gather accurate data, and 2) be informed of updates/changes to the data.

I order to do this via events we will need to attach to every event which could possibly alter the data for the dropdown and check if the data needs to be updated. So, this means the 'that.resize' event and 'window.resize' event are both needed as window can change the data for the input without causing the 'that.resize' to fire off. This should also be true for every parent DOM object between the input and the window objects. In addition to the .resize event for every DOM object between input and window, we must consider all other events that could fire off and change the data for the dropdown. So, in order to get full coverage then the number of event we must cover are:

(2+N) * T

2 for 'window' and 'that'
N for the number of DOM elements between 'window' and 'that'
T for the number of event Type to be covered.

So, what we currently have is the absolute minimal. Which is N = 0 and T = 1 (resize).

Honestly, this will probably cover most situation most of the time. But, the dropdown will have unexpected behavior in any edge cases where the DOM elements in-between are resized and the input has 100% width.

@SimonSteinberger
Copy link

I agree - it should be calculated whenever the dropdown is shown. Sorry for the rather slow process - I'm just kept extremely busy for a few days. I'll certainly include this in the next release, which will be soon.

@amcnea
Copy link
Author

amcnea commented Nov 25, 2014

If you want I can add the changes onto this branch as I have already made the change on my project version of the file.

@SimonSteinberger
Copy link

I've rewritten several parts based on your suggestions. Also included a public settings object and did some minor renaming for better readability of the code.

I couldn't find any bugs, so I hope it's working as expected :-)

Thanks for these great tips, amcnea! I think that's a very useful improvement that didn't come with significant overhead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants