Moderator |
|
Bij wijze van oefening heb ik zelf geprobeerd een autocomplete dropdown te maken met behulp van jQuery (gebruikt o.a. AJAX) in combinatie met PHP. Er zullen vast legio out-of-the-box oplossingen zijn, maar dit wiel is van mij . De vraag is nu, wat kan hier nog aan veranderd/verbeterd worden? Ik heb zo snel geen manier gevonden om de (eenvoudige) toetsenbordnavigatie zo te maken dat deze een muisbesturing simuleert, weet ook niet of dit e.e.a. makkelijker maakt. Om dit script uit te kunnen voeren heb je PHP-ondersteuning nodig (voor het afhandelen van de AJAX-call).
In mijn opzet was mijn voornaamste doel (naast het opfrissen van mijn kennis) het zoveel mogelijk beperken van (overbodige) AJAX-calls. Daarnaast ging het mij om de herbruikbaarheid en flexibiliteit: je kunt meerdere autocomplete-velden in eenzelfde formulier stoppen, dit zal werken. Ook mag de AJAX-URL querystring-variabelen (bijvoorbeeld voor authenticatie in het script etc.) bevatten, hier wordt de zoekstring-variabele via jQuery netjes aan vast geplakt.
Ik denk dat alles redelijk voor zich spreekt; motivaties voor andere manier van aanpakken zijn welkom.
voorbeeld van gebruik (jquery.dropdown.htm)
<!--
// @todo simulate mouse instead of keyboard?
-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="jquery.dropdown.js" type="text/javascript"></script>
<link href="jquery.dropdown.css" rel="stylesheet" type="text/css" />
</head>
<body>
<input type="text" id="inputtest" class="autocomplete" value="" /><br />
<div id="boxtest" class="autocomplete-box"></div>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p> </p>
<input type="text" id="inputtest2" class="autocomplete" value="" /><br />
<div id="boxtest2" class="autocomplete-box"></div>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<script type="text/javascript">
//<![CDATA[
$().ready(function() {
var box = new autocompleteBox();
box.init({
'inputId': 'inputtest',
'boxId': 'boxtest',
'ajaxURL': 'jquery.dropdown.php',
'ajaxQueryStringVariable': 'q' // try "z" (obviously will not return any results)
});
var box2 = new autocompleteBox();
box2.init({'inputId': 'inputtest2', 'boxId': 'boxtest2', 'ajaxURL': 'jquery.dropdown.php'});
});
//]]>
</script>
</body>
</html>
<!-- // @todo simulate mouse instead of keyboard? --> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script> <script src="jquery.dropdown.js" type="text/javascript"></script> <link href="jquery.dropdown.css" rel="stylesheet" type="text/css" /> <input type="text" id="inputtest" class="autocomplete" value="" /><br /> <div id="boxtest" class="autocomplete-box"></div> <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. </p> <input type="text" id="inputtest2" class="autocomplete" value="" /><br /> <div id="boxtest2" class="autocomplete-box"></div> <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. </p> <script type="text/javascript"> //<![CDATA[ $().ready(function() { var box = new autocompleteBox(); box.init({ 'inputId': 'inputtest', 'boxId': 'boxtest', 'ajaxURL': 'jquery.dropdown.php', 'ajaxQueryStringVariable': 'q' // try "z" (obviously will not return any results) }); var box2 = new autocompleteBox(); box2.init({'inputId': 'inputtest2', 'boxId': 'boxtest2', 'ajaxURL': 'jquery.dropdown.php'}); }); //]]>
De CSS (jquery.dropdown.css)
@CHARSET "UTF-8";
input.autocomplete { width: 195px; height: 25px; line-height: 25px; border: 1px solid #000000; margin: 0; padding: 0 0 0 5px; }
div.autocomplete-box { position: absolute; z-index: 1; width: 200px; border-width: 0px 1px 1px 1px; border-style: solid; border-color: #ff0000; background-color: #ffffff; }
div.autocomplete-box-row { display: block; width: 200px; height: 25px; line-height: 25px; overflow: hidden; }
div.autocomplete-box-row a { display: block; width: 100%; height: 100%; padding-left: 5px; }
/* let jQuery handle :hover state as well, to prevent "sticky" hover */
div.autocomplete-box-row a.hover { background-color: #ff0000; }
@CHARSET "UTF-8"; input.autocomplete { width: 195px; height: 25px; line-height: 25px; border: 1px solid #000000; margin: 0; padding: 0 0 0 5px; } div.autocomplete-box { position: absolute; z-index: 1; width: 200px; border-width: 0px 1px 1px 1px; border-style: solid; border-color: #ff0000; background-color: #ffffff; } div.autocomplete-box-row { display: block; width: 200px; height: 25px; line-height: 25px; overflow: hidden; } div.autocomplete-box-row a { display: block; width: 100%; height: 100%; padding-left: 5px; } /* let jQuery handle :hover state as well, to prevent "sticky" hover */ div.autocomplete-box-row a.hover { background-color: #ff0000; }
De jabbascript (jquery.dropdown.js)
function autocompleteBox() {
this.visible = false;
this.timer = false;
this.searchString = ''; // to keep track if the searchstring has changed (only then calling updateBox is useful)
this.selectedIndex = -1; // index number of the manually selected result
this.ajaxURL = '';
this.ajaxQueryStringVariable = 'q';
this.$box = false; // quick reference
this.$input = false; // quick reference
this.init = function(options) {
this.$box = $('#'+options.boxId);
this.$input = $('#'+options.inputId);
this.ajaxURL = options.ajaxURL;
if (options.ajaxQueryStringVariable) {
this.ajaxQueryStringVariable = options.ajaxQueryStringVariable;
}
this.hideBox();
// add event listener to inputfield
var that = this;
that.$input.keyup(function(e) {
// apparently, we were still typing, so clear timer from the previous keystroke
// this will prevent updateBox being called from the (any) previous keystroke
clearTimeout(that.timer);
// special key actions, for cycling through the box
// no need to return (it also seems to break jQuery?), as the search string is checked for changes
// only useful if there are any results in the box (if there is anything to cycle through)
if ($.inArray(e.keyCode, ['38', '40', '13']) && that.$box.find('a').length > 0) {
that.cycleBox(e.keyCode);
}
// only call updateBox when the search string changed and the string has a minimal of 2 chars
if (that.searchString != that.$input.val() && that.$input.val().length > 1) {
that.timer = setTimeout(function() {
that.updateBox();
}, 400); // you might want to play around with this timeout, 400ms seems a decent delay
}
});
}; // init
// load results into the result box
this.updateBox = function() {
this.searchString = this.$input.val(); // update search string
var that = this;
var data = {};
data[that.ajaxQueryStringVariable] = that.$input.val(); // to avoid having to use eval() for dynamic variable names
$.ajax({
'url': that.ajaxURL,
'data': data,
'dataType': 'json',
'success': function(data) {
if (data.length) {
var html = '';
for (i in data) {
// @todo make this snippet dynamic (option? template of some kind?)
html += '<div class="autocomplete-box-row">\
<a href="javascript:void(0)">'+data[i]+'</a>\
</div>';
}
// add HTML to box and thus to DOM
that.$box.html(html);
// add listeners (works after HTML is added to box, but not before?
// I though you could do something funky with $(html), then add everything to DOM? guess not...)
they = that.$box.find('a');
// this is for mouse navigation, we have a separate routine (cycleBox) for handling keyboard navigation
they.click(function(e) {
e.preventDefault();
// update the search string with the selected link
// not necessary to prevent extra AJAX calls, because mouseclicks (probably) don't trigger keyUp events
that.searchString = $(this).text();
// put the text of the clicked link in the input field and hide the box
that.$input.val($(this).text());
that.hideBox();
});
// add hover effect to links, this is done to have a uniform "hover" behaviour for
// both mouse and keyboard navigation
they.hover(function() {
// we switched to mouse navigation, so clear selected index
that.selectedIndex = -1;
they.removeClass('hover');
$(this).addClass('hover');
}, function() {
$(this).removeClass('hover');
});
if (!that.visible) {
that.showBox();
}
} else {
// no results (anymore), hide box
that.hideBox();
}
} // success
});
}
// separate function for all the animation etc. for showing the resultbox
this.showBox = function() {
this.visible = true;
this.$box.show(); // animation, slideDown works too
} // showBox
// separate function for all the animation etc. for hiding the resultbox
this.hideBox = function() {
this.visible = false;
// cleanup
this.$box.html(''); // clear inner HTML
// do NOT clear the searchstring as this might trigger another AJAX call
this.selectedIndex = -1; // clear selected index (if any)
this.$box.hide(); // animation, slideUp works too
} // hideBox
// function for tabbing through the results with keyboard (we don't actually use tab though)
this.cycleBox = function(keyCode) {
$links = this.$box.find('a');
if (keyCode == 40) { // down
// init?
if (this.selectedIndex == -1) {
this.selectedIndex = 0;
} else {
this.selectedIndex = (this.selectedIndex + 1) % $links.length;
}
$links.removeClass('hover');
activeItem = $links[this.selectedIndex]; // not a jQuery object?
$(activeItem).addClass('hover');
}
if (keyCode == 38) { // up
if (this.selectedIndex == -1 || this.selectedIndex == 0) {
// init?
this.selectedIndex = $links.length - 1;
} else {
this.selectedIndex = this.selectedIndex - 1;
}
$links.removeClass('hover');
activeItem = $links[this.selectedIndex]; // not a jQuery object?
$(activeItem).addClass('hover');
}
if (keyCode == 13) { // enter
// was anything selected?
if (this.selectedIndex > -1) {
activeItem = $links[this.selectedIndex]; // not a jQuery object?
// update the search string with the selected item, this will prevent extra AJAX calls
this.searchString = $(activeItem).text();
// put the text of the active link in the input field and hide the box
this.$input.val($(activeItem).text());
this.hideBox();
}
}
} // cycleBox
}; // autocompleteBox
function autocompleteBox() { this.visible = false; this.timer = false; this.searchString = ''; // to keep track if the searchstring has changed (only then calling updateBox is useful) this.selectedIndex = -1; // index number of the manually selected result this.ajaxURL = ''; this.ajaxQueryStringVariable = 'q'; this.$box = false; // quick reference this.$input = false; // quick reference this.init = function(options) { this.$box = $('#'+options.boxId); this.$input = $('#'+options.inputId); this.ajaxURL = options.ajaxURL; if (options.ajaxQueryStringVariable) { this.ajaxQueryStringVariable = options.ajaxQueryStringVariable; } this.hideBox(); // add event listener to inputfield var that = this; that.$input.keyup(function(e) { // apparently, we were still typing, so clear timer from the previous keystroke // this will prevent updateBox being called from the (any) previous keystroke clearTimeout(that.timer); // special key actions, for cycling through the box // no need to return (it also seems to break jQuery?), as the search string is checked for changes // only useful if there are any results in the box (if there is anything to cycle through) if ($.inArray(e.keyCode, ['38', '40', '13']) && that.$box.find('a').length > 0) { that.cycleBox(e.keyCode); } // only call updateBox when the search string changed and the string has a minimal of 2 chars if (that.searchString != that.$input.val() && that.$input.val().length > 1) { that.timer = setTimeout(function() { that.updateBox(); }, 400); // you might want to play around with this timeout, 400ms seems a decent delay } }); }; // init // load results into the result box this.updateBox = function() { this.searchString = this.$input.val(); // update search string var that = this; var data = {}; data[that.ajaxQueryStringVariable] = that.$input.val(); // to avoid having to use eval() for dynamic variable names $.ajax({ 'url': that.ajaxURL, 'data': data, 'dataType': 'json', 'success': function(data) { if (data.length) { var html = ''; for (i in data) { // @todo make this snippet dynamic (option? template of some kind?) html += '<div class="autocomplete-box-row">\ <a href="javascript:void(0)">'+data[i]+'</a>\ </div>'; } // add HTML to box and thus to DOM that.$box.html(html); // add listeners (works after HTML is added to box, but not before? // I though you could do something funky with $(html), then add everything to DOM? guess not...) they = that.$box.find('a'); // this is for mouse navigation, we have a separate routine (cycleBox) for handling keyboard navigation they.click(function(e) { e.preventDefault(); // update the search string with the selected link // not necessary to prevent extra AJAX calls, because mouseclicks (probably) don't trigger keyUp events that.searchString = $(this).text(); // put the text of the clicked link in the input field and hide the box that.$input.val($(this).text()); that.hideBox(); }); // add hover effect to links, this is done to have a uniform "hover" behaviour for // both mouse and keyboard navigation they.hover(function() { // we switched to mouse navigation, so clear selected index that.selectedIndex = -1; they.removeClass('hover'); $(this).addClass('hover'); }, function() { $(this).removeClass('hover'); }); if (!that.visible) { that.showBox(); } } else { // no results (anymore), hide box that.hideBox(); } } // success }); } // separate function for all the animation etc. for showing the resultbox this.showBox = function() { this.visible = true; this.$box.show(); // animation, slideDown works too } // showBox // separate function for all the animation etc. for hiding the resultbox this.hideBox = function() { this.visible = false; // cleanup this.$box.html(''); // clear inner HTML // do NOT clear the searchstring as this might trigger another AJAX call this.selectedIndex = -1; // clear selected index (if any) this.$box.hide(); // animation, slideUp works too } // hideBox // function for tabbing through the results with keyboard (we don't actually use tab though) this.cycleBox = function(keyCode) { $links = this.$box.find('a'); if (keyCode == 40) { // down // init? if (this.selectedIndex == -1) { this.selectedIndex = 0; } else { this.selectedIndex = (this.selectedIndex + 1) % $links.length; } $links.removeClass('hover'); activeItem = $links[this.selectedIndex]; // not a jQuery object? $(activeItem).addClass('hover'); } if (keyCode == 38) { // up if (this.selectedIndex == -1 || this.selectedIndex == 0) { // init? this.selectedIndex = $links.length - 1; } else { this.selectedIndex = this.selectedIndex - 1; } $links.removeClass('hover'); activeItem = $links[this.selectedIndex]; // not a jQuery object? $(activeItem).addClass('hover'); } if (keyCode == 13) { // enter // was anything selected? if (this.selectedIndex > -1) { activeItem = $links[this.selectedIndex]; // not a jQuery object? // update the search string with the selected item, this will prevent extra AJAX calls this.searchString = $(activeItem).text(); // put the text of the active link in the input field and hide the box this.$input.val($(activeItem).text()); this.hideBox(); } } } // cycleBox }; // autocompleteBox
PHP file die database simuleert met enkele voorbeeldwoorden die je kunt uitproberen voor de autocomplete (jquery.dropdown.php):
<?php
$q = !empty($_GET['q']) ? $_GET['q'] : '';
$matches = array();
if (strlen($q)) {
$words = array(
'test',
'testtwee',
'test met spatie',
'blaat',
'whatever',
'iets anders',
'anders',
);
// case insensitive substring match
foreach ($words as $word) {
// from the start of a word
// if (strtolower(substr($word, 0, strlen($q))) == strtolower(urldecode($q))) {
// within any part of the word
if (stripos($word, urldecode($q)) !== false) {
$matches[] = htmlspecialchars($word, ENT_QUOTES); // should be safe, but escape them anyway
}
}
}
echo json_encode($matches);
?>
<?php $q = !empty($_GET['q']) ? $_GET['q'] : ''; 'test', 'testtwee', 'test met spatie', 'blaat', 'whatever', 'iets anders', 'anders', ); // case insensitive substring match foreach ($words as $word) { // from the start of a word // if (strtolower(substr($word, 0, strlen($q))) == strtolower(urldecode($q))) { // within any part of the word if (stripos ($word, urldecode($q)) !== false) { $matches[] = htmlspecialchars($word, ENT_QUOTES ); // should be safe, but escape them anyway } } } echo json_encode ($matches); ?>
Have fun .
|