Tag Archives: jquery

jQuery UI Autocomplete Widget with Perl and MySQL

As a follow up to the jQuery UI Autocomplete Widget with PHP and MySQL post, I did one with Perl as the backend.

The jQuery UI folks have released an autocomplete widget that is pretty slick. This example uses Perl as the backend.
autocomplete
This example will use US states and territories to populate the autocomplete. It will also demonstrate how to fill other fields with data returned from the database. This data can be used to fill a visible text box or a hidden form field. It also demonstrates the basic autocomplete functionality which may be fine for some applications.

Of course, you will need the jQuery core file, the jQuery UI core file, and the jQuery UI style sheet of choice. The style sheet comes from the themes available in the jQuery UI website and can be downloaded with the core file or you can link to the latest versions of both the core files and the css:

<link rel="stylesheet" type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1/themes/redmond/jquery-ui.css">

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.min.js"></script>

The HTML is straight forward and stripped down for the example:

<form  method="post">
<fieldset>
<legend>jQuery UI Autocomplete Example - PHP Backend</legend>
<p>Start typing the name of a state or territory of the United States</p>
<p class="ui-widget"><label for="state">State (abbreviation in separate field): </label>
	<input type="text" id="state"  name="state" /> <input readonly="readonly" type="text" id="abbrev" name="abbrev" maxlength="2" size="2"/></p>
    <input type="hidden" id="state_id" name="state_id" />
<p><input type="submit" name="submit" value="Submit" /></p>
</fieldset>
</form>

As a bonus, we dump out the form values to see what we have right underneath the form itself:

$("#autocompleteForm").submit(function(){
    $("#submitted").html("State: " + $("#state").val() + "<br />State Abbreviation: " + $("#abbrev").val() + "<br />State ID: " + $("#state_id").val());
    return false;
});

And the jQuery on the page is equally brief:

$(function() {

            $('#abbrev').val("");

            $("#state").autocomplete({
                source: "states.pl",
                minLength: 2,
                select: function(event, ui) {
                    $('#state_id').val(ui.item.id);
                    $('#abbrev').val(ui.item.abbrev);
                }
            });

        });

The minLength for autocomplete to return results is set to 2 to prevent too many rows from being returned.

The jquery autocomplete will append the text typed into the autocomplete field as the URL parameter ‘term.’ This URL parameter is used to query the database.

From the jquery documentation:

The request parameter “term” gets added to that URL.

The Perl page return the data after a few steps:

  1. It queries the database
  2. Loops an array of the query results adding each row to a return string
  3. Outputs the string as JSON data

The states.pl file returns the id field, the state field as ‘value’, and the abbrev field. These values are placed in the appropriate text boxes by the autocomplete jQuery function. And, of course, you will have to make your own connection to your MySQL database before running the query.

#!/usr/local/bin/perl

# PERL MODULES WE WILL BE USING
use CGI;
use DBI;
use DBD::mysql;
use JSON;

# HTTP HEADER
print "Content-type: application/json; charset=iso-8859-1\n\n";

# CONFIG VARIABLES
my $platform = "mysql";
my $database = "YOUR_DB_NAME";
my $host = "localhost";
my $port = "3306";
my $tablename = "YOUR_TABLE_NAME";
my $user = "YOUR_USER_NAME";
my $pw = "YOUR_DB_PW";
my $cgi = CGI->new();
my $term = $cgi->param('term');

# DATA SOURCE NAME
$dsn = "dbi:mysql:$database:localhost:3306";
# PERL DBI CONNECT
$connect = DBI->connect($dsn, $user, $pw);

# PREPARE THE QUERY
$query_handle = $connect->prepare(qq{select id, trim(both char(13) from state) AS value, abbrev FROM states where state like ?;});

# EXECUTE THE QUERY
$query_handle->execute('%'.$term.'%');

# LOOP THROUGH RESULTS
while ( my $row = $query_handle->fetchrow_hashref ){
    push @query_output, $row;
}
# CLOSE THE DATABASE CONNECTION
$connect->disconnect();

# JSON OUTPUT
print JSON::to_json(\@query_output);

Very important information below. Please read and understand before expecting the autocomplete to work properly.

The local data can be a simple Array of Strings, or it contains Objects for each item in the array, with either a label or value property or both. The label property is displayed in the suggestion menu. The value will be inserted into the input element after the user selected something from the menu. If just one property is specified, it will be used for both, eg. if you provide only value-properties, the value will also be used as the label.”

Demo

jQuery UI Autocomplete: Search from Beginning of String

The default functionality of the jQuery UI autocomplete will match text input against any part of a string in a searched data set. If you want to exclusively search from the beginning, you have to make some adjustments.
autocomplete screen shot

Remote Datasource

If you are using a remote datasource that queries a database, you can adjust your SQL statement to match the term at the beginning.

mysql_query("SELECT * FROM states where state like '" . mysql_real_escape_string($_GET['term']) . "%'")

If you cannot adjust the SQL for the remote datasource, you can still search from the start of the string. Be aware that, with this method, you may be returning a large dataset and that could have performance issues.

Using the U.S. states autocomplete as an example (xml source example below), a match can be made from the beginning of the string with a remote datasource like this:

$("#state").autocomplete({
	source: function(req, response) {
	   $.ajax({
		url: "states.php",
		dataType: "json",
		success: function( data ) {
			var re = $.ui.autocomplete.escapeRegex(req.term);
            var matcher = new RegExp( "^" + re, "i" );
			response($.grep(data, function(item){return matcher.test(item.value);}) );
			}
	    });
         },
      minLength: 2,
      select: function(event, ui) {
       $('#state_id').val(ui.item.id);
       $('#abbrev').val(ui.item.abbrev);
       }
});

Several things are happening:

  1. The data from the source is retrieved via an ajax call
  2. The term is escaped for any symbols that may cause a regular expression to be evaluated
  3. The matcher is given a new regular expression that instructs it to begin the search at the start of the string (^) and to be case insensitive (i)
  4. It then calls matcher.test to match the term against the value of the data item (this could be label depending on how your data is returned)

Remember, you need to return either a value or a label in the data item or the autocomplete will not work. Read the autocomplete documentation if you are not familiar with those fields:

The local data can be a simple Array of Strings, or it contains Objects for each item in the array, with either a label or value property or both. The label property is displayed in the suggestion menu. The value will be inserted into the input element after the user selected something from the menu. If just one property is specified, it will be used for both, eg. if you provide only value-properties, the value will also be used as the label.

Remote Datasource Autocomplete Demo

Array Datasource

If the datasource is an array of values or objects, the following method can be applied:

var states = [{"id":"1","label":"Armed Forces Americas (except Canada)","abbrev":"AA"},{"id":"2","label":"Armed Forces Africa, Canada, Europe, Middle East","abbrev":"AE"},{"id":"5","label":"Armed Forces Pacific","abbrev":"AP"},{"id":"9","label":"California","abbrev":"CA"},{"id":"10","label":"Colorado","abbrev":"CO"},{"id":"14","label":"Florida","abbrev":"FL"},{"id":"16","label":"Georgia","abbrev":"GA"},{"id":"33","label":"Northern Mariana Islands","abbrev":"MP"},{"id":"36","label":"North Carolina","abbrev":"NC"},{"id":"37","label":"North Dakota","abbrev":"ND"},{"id":"43","label":"New York","abbrev":"NY"},{"id":"46","label":"Oregon","abbrev":"OR"}];

$("#state").autocomplete({
	source: function(req, response) {
	    var re = $.ui.autocomplete.escapeRegex(req.term);
	    var matcher = new RegExp( "^" + re, "i" );
	    response($.grep(states, function(item){return matcher.test(item.label); }) );
	    },
      minLength: 2,
      select: function(event, ui) {
         $('#state_id').val(ui.item.id);
         $('#abbrev').val(ui.item.abbrev);
     }
});

Similar to the remote datasource example except this does not need the ajax call. The states array is simply pulled into the response and the rest is handled the same.

Array Datasource Autocomplete Demo

XML Source Example

jQuery Autocomplete with HTML in Dropdown Selection Menu

With jQuery 1.8 and prior, you could include some simple HTML in the selection menu and it would render. With post 1.8 converting the results as strings, the HTML failed to render.

Workaround

The source page (states.php) for the jQuery is coded as such:

try {
  $conn = new PDO("mysql:host=$dbhost;dbname=$dbname", $dbuser, $dbpass);
}
catch(PDOException $e) {
    echo $e->getMessage();
}

$return_arr = array();

if ($conn)
{
	$ac_term = "%".$_GET['term']."%";
	$query = "SELECT * FROM states where state like :term";
	$result = $conn->prepare($query);
	$result->bindValue(":term",$ac_term);
	$result->execute(); 

	/* Retrieve and store in array the results of the query.*/
	while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
		$row_array['id'] = $row['id'];
		$row_array['value'] = trim($row['state'],"\r");
		$row_array['label'] = "<img src='me.jpg' height='20'>" . trim($row['state'],"\r");
		$row_array['abbrev'] = $row['abbrev'];

        array_push($return_arr,$row_array);
    }

}
/* Free connection resources. */
$conn = null;
/* Toss back results as json encoded array. */
echo json_encode($return_arr);

The example simply concatenates on an image file. For the purpose of demonstration, the image is the same for all results.

Since the latest versions of jQuery are now replacing the ‘<' and '>‘ with (ampersand)lt; and (ampersand)gt; respectively, the results need to be looped and corrected. This can be done in the ‘open’ event of the autocomplete. For more information on the ‘open’ event, see the jQuery UI documentation.

The latest version of jquery causes the item altered in the open event to be too wide. The override of the renderItem function fixes this.

$("#state").autocomplete({
				source: "states_image.php",
				minLength: 2,
				select: function(event, ui) {
					$('#state_id').val(ui.item.id);
					$('#abbrev').val(ui.item.abbrev);
				}
			});

			$["ui"]["autocomplete"].prototype["_renderItem"] = function( ul, item) {
				return $( "<li></li>" )
  				.data( "item.autocomplete", item )
  				.append( $( "<a></a>" ).html( item.label ) )
  				.appendTo( ul );
			};

So you get something that looks like this:

Demo

Usual recommended jQuery and CF reading:

Pop-up Survey with ASP.NET and jQuery Dialog

Same pop-up survey as in the Pop-up Survey with jQuery UI Dialog post except with ASP.NET (VB) this time.
pop-up survey

Pop-up Behavior

  1. Pop-up survey opens when page loads. Pop-ups on window.unload or window close are being blocked by most browsers due to abuse and overuse.
  2. Cookie is set when survey submitted or user opts out (“No, thanks” click).
  3. Closing dialog will not set cookie

All survey data is stored in a database and jQuery .post is used to shuttle the information back and forth.

Code for the survey dialog:

<div id="survey" title="Pop-Up Survey">
	<p id="surveyDenied"><a href="#">No, thanks</a></p>
		<p>Pop-up survey that cookies browser on completion or on opt-out. Short 411 demo survey.</p>
		<form id="popup_survey" name="popup_survey" method="post">
        <p><strong>Pink or blue?</strong><br />
		<input id="pink" type="radio" name="radio_color" value="pink"  />Pink<br />
        <input id="blue" type="radio" name="radio_color" value="blue"  />Blue</p>
        <p><strong>Soccer or futbol?</strong><br />
		<input id="soccer" type="radio" name="radio_sport" value="soccer"  />Soccer<br />
        <input id="futbol" type="radio" name="radio_sport" value="futbol"  />Futbol</p>
        </form>
	<div id="error_message"></div>
</div>

It’s just a couple of radio buttons and a place for an error message. The submit is handled by the dialog button.

Here is the survey dialog code:

$(function(){
			$('#survey').dialog({
				bgiframe: true,
				autoOpen: false,
				modal: true,
				width: 500,
				resizable: false,
				buttons: {
					Submit: function(){
						if($("input[name='radio_color']:checked").val() !== undefined && $("input[name='radio_sport']:checked").val() !== undefined){
							setCookie('POPsurvey','POPsurvey',30);
							$.post("process_survey.aspx", $("#popup_survey").serialize(),
							function(data){
								if(data.db_check == 'fail'){
									$("#error_message").html("<p>Database not available. Please try again.</p>");
								} else {
									$("div.pink").css("width",data.perPink);
									$(".perPink").html(data.perPink + "% (" + data.totalPink + ")");

									$("div.blue").css("width",data.perBlue);
									$(".perBlue").html(data.perBlue + "% (" + data.totalBlue + ")");

									$("div.soccer").css("width",data.perSoccer);
									$(".perSoccer").html(data.perSoccer + "% (" + data.totalSoccer + ")");

									$("div.futbol").css("width",data.perFutbol);
									$(".perFutbol").html(data.perFutbol + "% (" + data.totalFutbol + ")");

									$(".totalRes").html(data.totalRes);

									$('#survey').dialog('close');
									$('#survey_thanks').dialog('open');
								}
								}, "json");
						}else{
							$("#error_message").html("<p>Please answer all questions.</p>");
						}
					}
				}
			});
		});

Lots of stuff but it does several things. First, on submit, it checks that the user answered both questions. Then it sets the cookie. And, finally, it sends the form data off via post and puts the response in elements on the page that are part of the “Thank you” dialog. Speaking of which…

The “Thank you” dialog code is thus:

<div id="survey_thanks" title="Pop-Up Survey - Thank You!">
    <p>Thank you for taking the time to answer our survey. Your input will help us improve the site.</p>
    <p>Responses: <span class="totalRes"></span></p>

    <div class="progress-container">
        pink <span class="perPink"></span>
        <div class="pink"></div>
        blue <span class="perBlue"></span>
        <div class="blue"></div>
    </div>

    <div class="progress-container">
        soccer <span class="perSoccer"></span>
        <div class="soccer"></div>
        futbol <span class="perFutbol"></span>
        <div class="futbol"></div>
    </div>
</div>
$(function(){
			$('#survey_thanks').dialog({
				bgiframe: true,
				autoOpen: false,
				modal: true,
				width: 500,
				resizable: false,
				buttons: {
					Close: function(){
						$(this).dialog('close');
						}
					}
			});
		});

Processing the Survey

The process_survey page adds the answers to the database and brings back the totals that are calculated by a view.

<%@ Page Language="VB" Debug="true" %>
<%@ Import Namespace="System.Web.Script.Serialization" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Math" %>

<script runat="server">
    Dim serializer As JavaScriptSerializer

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
        serializer = New JavaScriptSerializer()
        Response.Write(JSONData(Request("radio_color").ToString,Request("radio_sport").ToString))
    End Sub

    Public Class Survey
		Public dbcheck As String
        Public totalPink As Integer
		Public perPink As Integer
        Public totalBlue As Integer
		Public perBlue As Integer
        Public totalSoccer As Integer
		Public perSoccer As Integer
		Public totalFutbol As Integer
		Public perFutbol As Integer
		Public totalRes As Integer
    End Class

    Private Function JSONData(ByVal color As String, ByVal sport As String) As String
		Dim srvy As New Survey()

		If color.Length and sport.Length then

			Dim mySql As String
			Dim insertSql As String
			Dim insertCommand As SqlCommand
			Dim objConn As New SqlConnection("Server=YOUR_SERVER_HERE;Database=YOUR_DB_HERE;User ID=YOUR_ID_HERE;Password=YOUR_PW_HERE")
			insertSql = "INSERT INTO survey (color,sport) VALUES ('"
			insertSql += color & "', '"
			insertSql += sport & "')"
			objConn.Open()
			insertCommand = New SqlCommand(insertSql,objConn)

			insertCommand.ExecuteNonQuery()

			Dim myds As New DataSet("Survey")
			mySql = "SELECT * FROM v_totals"

			Dim adapter As New SqlClient.SqlDataAdapter(mySql, objConn)
			Dim dr As DataRow

			adapter.Fill(myds, "Survey")

			dr = myds.Tables(0).Rows(0)

				srvy.dbcheck = "OK"
				srvy.totalPink = dr("Pink")
				srvy.perPink = Round(dr("Pink")/(dr("Pink")+dr("Blue")),2)*100
				srvy.totalBlue = dr("Blue")
				srvy.perBlue = Round(dr("Blue")/(dr("Pink")+dr("Blue")),2)*100
				srvy.totalSoccer = dr("Soccer")
				srvy.perSoccer = Round(dr("Soccer")/(dr("Soccer")+dr("Futbol")),2)*100
				srvy.totalFutbol = dr("Futbol")
				srvy.perFutbol = Round(dr("Futbol")/(dr("Soccer")+dr("Futbol")),2)*100
				srvy.totalRes = dr("Total")

			objConn.Close()

		Else
			 srvy.dbcheck = "fail"
        End If

		Return serializer.Serialize(srvy)
    End Function
</script>    

The totals for the return data are stored in a view to prevent separate and multiple calls to the database. The table and the view can be created in SQL Server as such:

CREATE TABLE [dbo].[survey](
	[id] [smallint] IDENTITY(1,1) NOT NULL,
	[color] [varchar](4) NOT NULL,
	[sport] [varchar](6) NOT NULL,
 CONSTRAINT [PK_survey] PRIMARY KEY CLUSTERED
(
	[id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

CREATE VIEW [dbo].[v_totals]
AS
SELECT     TOP (1)
                          (SELECT     COUNT(color) AS Expr1
                            FROM          dbo.survey
                            WHERE      (color = 'pink')) AS Pink,
                          (SELECT     COUNT(color) AS Expr2
                            FROM          dbo.survey AS survey_5
                            WHERE      (color = 'blue')) AS Blue,
                          (SELECT     COUNT(sport) AS Expr3
                            FROM          dbo.survey AS survey_4
                            WHERE      (sport = 'futbol')) AS Futbol,
                          (SELECT     COUNT(sport) AS Expr3
                            FROM          dbo.survey AS survey_3
                            WHERE      (sport = 'soccer')) AS Soccer,
                          (SELECT     COUNT(id) AS Expr4
                            FROM          dbo.survey AS survey_2) AS Total
FROM         dbo.survey AS survey_1

Cookies

The code to handle setting, checking, and deleting cookies is standard javascript:

function setCookie(c_name,value,expiredays)
{
	var exdate=new Date();
	exdate.setDate(exdate.getDate()+expiredays);
	document.cookie=c_name+ "=" +escape(value)+((expiredays==null) ? "" : ";expires="+exdate.toGMTString());
} 

function getCookie(c_name)
{
	if (document.cookie.length>0)
	  {
	  c_start=document.cookie.indexOf(c_name + "=");
	  if (c_start!=-1)
		{
		c_start=c_start + c_name.length+1;
		c_end=document.cookie.indexOf(";",c_start);
		if (c_end==-1) c_end=document.cookie.length;
		return unescape(document.cookie.substring(c_start,c_end));
		}
	  }
	return "";
}

function checkCookie(c_name)
{
	cookie_value=getCookie(c_name);
	if (cookie_value=="") {
		$('#survey').dialog('open');
	}
}

function deleteCookie(c_name) {
	document.cookie = c_name +'=; expires=Thu, 01-Jan-70 00:00:01 GMT;';
}

The style for the “Thank you” graph:

div.progress-container {
  border: 1px solid #ccc;
  width: 150px;
  padding: 1px;
  margin-bottom: 5px;
  background: white;
}

div.progress-container > div {
  height: 12px;
  margin-bottom: 2px;
}
div.progress-container > div.pink {
  background-color:#CC6699;
}
div.progress-container > div.blue {
  background-color: #3366CC;
}
div.progress-container > div.soccer {
  background-color: #006633;
}
div.progress-container > div.futbol {
  background-color: #663333;
}

You’ll notice in the .post function that the width of the bar graph is set with the percent of responses for each answer that comes back from the database.

Note: The download zip does not include a web.config. You will need to create your own.

Download zip of all files

Output Javascript or Jquery Variables to Firebug Console

Console.log() is a great way to see what your javascript and jquery variables have in them. Debugging made easier. Just add it to the javascript/jquery on the page you want to debug and them check the console in Firebug to see the output. It has been around for a while, but sometimes you just don’t know about these gems.

For example, if you want to see the data that comes back from a jquery post you would do this:

$.post("process_survey.aspx",
     $("#popup_survey").serialize(),
     function(data){
          console.log(data);
     }, "json");

The console in Firebug will look like this:
Firebog console 1
And, when you click on “more”, you get this:
Firebog console 2
Firebug has some documentation on console.log for this must-have feature.

Build Internet did a more comprehensive article on Javascript/jQuery console debugging that is a must read.