develop

JQuery UI autocomplete as a kind of intellisense

I recently had to develop an email template system, where the user could create a new email template with a couple of placeholders to be replaced when the email is compiled and sent.

I recently had to develop an email template system, where the user could create a new email template with a couple of placeholders to be replaced when the email is compiled and sent. What I wanted was a normal textarea where the user could type out the email body, but when they pressed the $ key followed by a couple of characters, then the jQuery UI dialog should dropdown and show the various fields that are available and match with the characters entered.

To start of, lets create a form with the email controls:

<html>
<head>
</head>
<body>
    <form>
        <label for="toInput">To</label>
        <input type="text" id="toInput" />
        <label for="subjectInput">Subject</label>
        <input type="text" id="subjectInput" />
        <label for="bodyTextarea">Message Body</label>
        <textarea type="text" id="bodyTextarea" ></textarea>
    </form>
</body>
</html>

We can then add some styling:

form{
    color: #555;
    font-family: Impact, Charcoal, sans-serif;
    width:30em;
    margin:3em;
}
label {
    display: block;
}
textarea {
    width:100%;
    height:10em;
}
input {
    width:100%
}
textarea, input {
    color: #333;
    font-family: ‘Trebuchet MS’, Helvetica, sans-serif;
    padding:0.5em;
}

We’ll also need script references to jQuery, jQuery UI and jQuery Caret Utilities by Gary Haran and a reference to a jQuery UI stylesheet:

<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript" src="http://code.jquery.com/ui/1.8.18/jquery-ui.min.js"></script>
<script type="text/javascript" src="https://raw.github.com/garyharan/jQuery-caret-utilities/master/jquery.caret.js"></script>
<link rel="stylesheet" type="text/css" href="http://code.jquery.com/ui/1.8.18/themes/base/jquery-ui.css">

Lets create a namespace to contain our variables, object literals and methods.

var my = my || {};

Next, we’ll create some test data.

my.sampleData = {
    replaces : ['$name$', '$surname$', '$greeting$', '$age$', '$birthdate$', '$email$'],
    users : [{
        'name' : 'John',
        'surname':'Doe' ,
        'greeting':'Hello',
        'age':25,
        'birthdate':'14 June',
        'email':'[email protected]'
    }]
};

Next we’ll set two variables, the first will be a flag to indicate when we’re requesting a “manipulated” search and the second will be the regular expression defining the search term we want.

//a flag to indicate when we called the search manually
my.manualSearch = false;
//the regular expression used to determine the search string, a $ sign followed by 0-10 characters, followed by the end of input
my.reSearch = /\$[^\$]{0,10}$/i;

Next we’ll setup the autocomplete plugin on our controls and define a couple of callbacks/overrides. Manipulating the source allows us to do a custom search, in this case we want to ignore the $ sign and only search with the term that follows it. We might also want to display a sorted list of matches. We’ll implement the search callback in order to ignore the normal behaviour of searching on every keypress, but only when we find a match near the caret for our my.reSearch regular expression will we call the search manually. We’ll also change the select behaviour, since we don’t want the entire contents of the input control to be replaced, but only the $ sign and the few characters following it. Finally we’ll ignore the focus event’s behaviour.

$(function() { // on document ready
    //rounded corners to make it pretty
    $('input, textarea').addClass('ui-corner-all');
    //add the same autcomplete logic to all the inputs
    $('#bodyTextarea, #toInput, #subjectInput').autocomplete({
        source : function(req, res) { //manipulate the source 
            var re = $.ui.autocomplete.escapeRegex(req.term.substring(1) /* when searching, skip the $ sign */);
            var matcher = new RegExp( re, "i" );
            var results = $.grep( my.sampleData.replaces, function(item,index){
                return matcher.test(item);
            });
            //return a sorted list
            results.sort();
            //return the manipulated source
            res(results);
        }
       , search : function(event, ui) {
            //check if this method called the search
            if(my.manualSearch) {
                //let autocomplete handle it and clear the flag
                my.manualSearch = false;
                return true;
            }
            var val = $(this).val();
            var pos = $(this).getCaretPosition();
            var segment1 = val.substring(0,pos); //the part before the caret
            var q = '';
            if(my.reSearch.test(segment1)) {
                //change the searched query to the last $xxx part instead of everythin entered so far
                var matches = my.reSearch.exec(segment1);
                q = matches[matches.length-1];
                //set flag to indicate that the search was called from here
                my.manualSearch = true;
                //call search with the manipulated query
                $(this).autocomplete('search', q);
            }
            return false;
        }
        , select : function(event, ui) {
            var val = $(this).val();
            var pos = $(this).getCaretPosition();
            var segment1 = val.substring(0,pos); //the segment before the caret
            var segment2 = val.substring(pos); //the segment after the caret
            segment1 = segment1.replace(my.reSearch, ui.item.value); //replace the searched string with the selected match
            $(this).val(segment1 + segment2); //add the segment after the caret
            $(this).setCaretPosition(segment1.length); //set the caret position to follow the replace
            return false; //skip default behaviour
        }
        , focus : function() {
            return false; //skip default behaviour
        }
    });
});

The code is also available on jsfiddle.net to play with.