From 23ccdf38469f803adee2d799a7b4669d7d04834b Mon Sep 17 00:00:00 2001 From: =?utf8?q?St=C3=A9phane=20Jacob?= Date: Fri, 30 Sep 2011 16:29:59 +0200 Subject: [PATCH] Switches autocomplete to jQuery UI. MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Jacob --- ChangeLog | 3 + Makefile | 7 +- classes/direnum.php | 56 ++-- htdocs/css/base.css | 48 ++- htdocs/css/keynote.css | 4 +- htdocs/javascript/jquery.autocomplete.js | 559 ------------------------------- htdocs/javascript/search.js | 82 ++--- modules/search.php | 126 ++++--- upgrade/1.1.4/07_autocomplete.sql | 3 + 9 files changed, 165 insertions(+), 723 deletions(-) delete mode 100644 htdocs/javascript/jquery.autocomplete.js create mode 100644 upgrade/1.1.4/07_autocomplete.sql diff --git a/ChangeLog b/ChangeLog index 9f47e10..e0e3e30 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,6 +6,9 @@ New: * Newsletter: - Displays number of subscribers without any valid redirection -JAC + * Search: + - Switches autocomplete to jQuery UI -JAC + Bug/Wish: * Carnet: diff --git a/Makefile b/Makefile index 5b9c699..6a8a175 100644 --- a/Makefile +++ b/Makefile @@ -174,7 +174,7 @@ JQUERY_PLUGINS=color form JQUERY_PLUGINS_PATHES=$(addprefix htdocs/javascript/jquery.,$(addsuffix .js,$(JQUERY_PLUGINS))) JQUERY_UI_VERSION=1.8.10 -JQUERY_UI=core widget tabs datepicker +JQUERY_UI=core widget tabs datepicker autocomplete position JQUERY_UI_PATHES=$(addprefix htdocs/javascript/jquery.ui.,$(addsuffix .js,$(JQUERY_UI))) JQUERY_TMPL_VERSION=vBeta1.0.0 @@ -183,12 +183,9 @@ JQUERY_TMPL_PATH=htdocs/javascript/jquery.tmpl.js JSTREE_VERSION=1.0rc2 JSTREE_PATH=htdocs/javascript/jquery.jstree.js -# TODO: jquery.autocomplete.js should rather be downloaded from an official source. The issue -# is that the version we use is not available anymore on the Internet, and the latest version -# we could use is not backward compatible with our current code. jquery: htdocs/javascript/jquery.xorg.js htdocs/javascript/jquery.ui.xorg.js $(JSTREE_PATH) -htdocs/javascript/jquery.xorg.js: htdocs/javascript/jquery.js $(JQUERY_PLUGINS_PATHES) $(JQUERY_TMPL_PATH) htdocs/javascript/jquery.autocomplete.js +htdocs/javascript/jquery.xorg.js: htdocs/javascript/jquery.js $(JQUERY_PLUGINS_PATHES) $(JQUERY_TMPL_PATH) cat $^ > $@ htdocs/javascript/jquery.ui.xorg.js: $(JQUERY_UI_PATHES) htdocs/javascript/jquery.ui.datepicker-fr.js diff --git a/classes/direnum.php b/classes/direnum.php index 9d9a163..f911799 100644 --- a/classes/direnum.php +++ b/classes/direnum.php @@ -128,7 +128,7 @@ class DirEnum if ($obj->capabilities & DirEnumeration::HAS_AUTOCOMP) { return call_user_func(array($obj, 'getAutoComplete'), $text); } else { - return PlIteratorUtils::fromArray(array()); + return array(); } } @@ -293,15 +293,15 @@ abstract class DirEnumeration $where .= '(' . implode(' OR ', $tests) . ')'; - return XDB::iterator('SELECT ' . $this->valfield . ' AS field' - . ($this->ac_distinct ? (', COUNT(DISTINCT ' . $this->ac_unique . ') AS nb') : '') - . ($this->ac_withid ? (', ' . $this->idfield . ' AS id') : '') . ' - FROM ' . $this->from . ' - ' . $this->ac_join . ' - WHERE ' . $where . ' - GROUP BY ' . $this->valfield . ' - ORDER BY ' . ($this->ac_distinct ? 'nb DESC' : $this->valfield) . ' - LIMIT ' . self::AUTOCOMPLETE_LIMIT); + return XDB::fetchAllAssoc('SELECT ' . $this->valfield . ' AS field' + . ($this->ac_distinct ? (', COUNT(DISTINCT ' . $this->ac_unique . ') AS nb') : '') + . ($this->ac_withid ? (', ' . $this->idfield . ' AS id') : '') . ' + FROM ' . $this->from . ' + ' . $this->ac_join . ' + WHERE ' . $where . ' + GROUP BY ' . $this->valfield . ' + ORDER BY ' . ($this->ac_distinct ? 'nb DESC' : $this->valfield) . ' + LIMIT ' . self::AUTOCOMPLETE_LIMIT); } // }}} @@ -421,15 +421,15 @@ abstract class DE_WithSuboption extends DirEnumeration $where .= '(' . implode(' OR ', $tests) . ')'; - return XDB::iterator('SELECT ' . $this->valfield . ' AS field' - . ($this->ac_distinct ? (', COUNT(DISTINCT ' . $this->ac_unique . ') AS nb') : '') - . ($this->ac_withid ? (', ' . $this->idfield . ' AS id') : '') . ' - FROM ' . $this->from . ' - ' . $this->ac_join . ' - WHERE ' . $where . ' - GROUP BY ' . $this->valfield . ' - ORDER BY ' . ($this->ac_distinct ? 'nb DESC' : $this->valfield) . ' - LIMIT ' . self::AUTOCOMPLETE_LIMIT); + return XDB::fetchAllAssoc('SELECT ' . $this->valfield . ' AS field' + . ($this->ac_distinct ? (', COUNT(DISTINCT ' . $this->ac_unique . ') AS nb') : '') + . ($this->ac_withid ? (', ' . $this->idfield . ' AS id') : '') . ' + FROM ' . $this->from . ' + ' . $this->ac_join . ' + WHERE ' . $where . ' + GROUP BY ' . $this->valfield . ' + ORDER BY ' . ($this->ac_distinct ? 'nb DESC' : $this->valfield) . ' + LIMIT ' . self::AUTOCOMPLETE_LIMIT); } } // }}} @@ -663,17 +663,17 @@ class DE_JobTerms extends DirEnumeration { $tokens = JobTerms::tokenize($text.'%'); if (count($tokens) == 0) { - return PlIteratorUtils::fromArray(array()); + return array(); } $token_join = JobTerms::token_join_query($tokens, 'e'); - return XDB::iterator('SELECT e.jtid AS id, e.full_name AS field, COUNT(DISTINCT p.pid) AS nb - FROM profile_job_term_enum AS e - INNER JOIN profile_job_term_relation AS r ON (r.jtid_1 = e.jtid) - INNER JOIN profile_job_term AS p ON (r.jtid_2 = p.jtid) - '.$token_join.' - GROUP BY e.jtid - ORDER BY nb DESC, field - LIMIT ' . self::AUTOCOMPLETE_LIMIT); + return XDB::fetchAllAssoc('SELECT e.jtid AS id, e.full_name AS field, COUNT(DISTINCT p.pid) AS nb + FROM profile_job_term_enum AS e + INNER JOIN profile_job_term_relation AS r ON (r.jtid_1 = e.jtid) + INNER JOIN profile_job_term AS p ON (r.jtid_2 = p.jtid) + '.$token_join.' + GROUP BY e.jtid + ORDER BY nb DESC, field + LIMIT ' . self::AUTOCOMPLETE_LIMIT); } // }}} } diff --git a/htdocs/css/base.css b/htdocs/css/base.css index d13d2f6..ed74318 100644 --- a/htdocs/css/base.css +++ b/htdocs/css/base.css @@ -18,39 +18,33 @@ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ***************************************************************************/ -.ac_results { - background-color: window; - border: 1px solid; - overflow: hidden; - padding: 0px; +.ui-autocomplete { + position: absolute; + cursor: pointer; } -.ac_results ul { - padding:0px; - margin:0px; - width:100%; +.ui-menu { + list-style: none; + padding: 0px; + margin: 0; + display: block; + border: 1px solid black; + background: white; + color: black; } - -.ac_results li { - display:block; - padding: 2px; - cursor:pointer; - font-size: 90%; +.ui-menu .ui-menu-item { + margin: 0; + padding: 0; + width: 100%; } - -.ac_results iframe { - display:none;/*sorry for IE5*/ - display/**/:block;/*sorry for IE5*/ - position:absolute; - top:0; - left:0; - z-index:-1; - filter:mask(); - width:3000px; - height:3000px; +.ui-menu .ui-menu-item a { + text-decoration: none; + display: block; + padding: .2em .4em; } -.ac_over { +.ui-menu .ui-menu-item a.ui-state-hover, +.ui-menu .ui-menu-item a.ui-state-active { background: highlight; color: highlighttext; } diff --git a/htdocs/css/keynote.css b/htdocs/css/keynote.css index f0dd75a..ac7e1b2 100644 --- a/htdocs/css/keynote.css +++ b/htdocs/css/keynote.css @@ -748,8 +748,8 @@ div#content { font-size: 85%; } -.ac_results { - color: black; +.ui-menu { + color: black; } /* vim: set et ts=4 sts=4 sw=4: */ diff --git a/htdocs/javascript/jquery.autocomplete.js b/htdocs/javascript/jquery.autocomplete.js deleted file mode 100644 index 4e211a1..0000000 --- a/htdocs/javascript/jquery.autocomplete.js +++ /dev/null @@ -1,559 +0,0 @@ -/* - * jQuery autocomplete plugin - * Version 2.0.0 (2008-03-22) - * @requires jQuery v1.1.1+ - * - * Dual licensed under the MIT and GPL licenses: - * http://www.opensource.org/licenses/mit-license.php - * http://www.gnu.org/licenses/gpl.html - * - * Dylan Verheul - * http://www.dyve.net/jquery - * - */ -(function($) { - /** - * The autocompleter object - */ - $.autocomplete = function(input, options) { - - // Create a link to self - var me = this; - // Create jQuery object for input element - var $input = $(input).attr("autocomplete", "off"); - // Apply inputClass if necessary - if (options.inputClass) { - $input.addClass(options.inputClass); - } - - // Create results - var results = document.createElement("div"); - - // Create jQuery object for results - // var $results = $(results); - var $results = $(results).hide().addClass(options.resultsClass).css("position", "absolute"); - if( options.width > 0 ) { - $results.css("width", options.width); - } - - // Add to body element - $("body").append(results); - - input.autocompleter = me; - - var timeout = null; - var prev = ""; - var active = -1; - var cache = {}; - var keyb = false; - var hasFocus = false; - var lastKeyPressCode = null; - var mouseDownOnSelect = false; - var hidingResults = false; - - // flush cache - function flushCache(){ - cache = {}; - cache.data = {}; - cache.length = 0; - }; - - // flush cache - flushCache(); - - // if there is a data array supplied - if( options.data != null ){ - var sFirstChar = "", stMatchSets = {}, row = []; - - // no url was specified, we need to adjust the cache length to make sure it fits the local data store - if (typeof options.url != "string") { - options.cacheLength = 1; - } - - // loop through the array and create a lookup structure - for( var i=0; i < options.data.length; i++ ){ - // if row is a string, make an array otherwise just reference the array - row = ((typeof options.data[i] == "string") ? [options.data[i]] : options.data[i]); - - // if the length is zero, don't add to list - if( row[0].length > 0 ){ - // get the first character - sFirstChar = row[0].substring(0, 1).toLowerCase(); - // if no lookup array for this character exists, look it up now - if( !stMatchSets[sFirstChar] ) stMatchSets[sFirstChar] = []; - // if the match is a string - stMatchSets[sFirstChar].push(row); - } - } - - // add the data items to the cache - for (var k in stMatchSets) { - // increase the cache size - options.cacheLength++; - // add to the cache - addToCache(k, stMatchSets[k]); - } - } - - $input - .keydown(function(e) { - // track last key pressed - lastKeyPressCode = e.keyCode; - switch(e.keyCode) { - case 38: // up - e.preventDefault(); - moveSelect(-1); - break; - case 40: // down - e.preventDefault(); - moveSelect(1); - break; - case 9: // tab - case 13: // return - if( selectCurrent() ){ - // make sure to blur off the current field - $input.blur(); - e.preventDefault(); - // give focus with a small timeout (weird behaviour in FF) - setTimeout(function() { $input.focus() }, 10); - } - break; - default: - active = -1; - if (timeout) clearTimeout(timeout); - timeout = setTimeout(onChange, options.delay); - break; - } - }) - .focus(function(){ - // track whether the field has focus, we shouldn't process any results if the field no longer has focus - hasFocus = true; - }) - .blur(function() { - // track whether the field has focus - hasFocus = false; - if (!mouseDownOnSelect) { - hideResults(); - } - }); - - hideResultsNow(); - - function onChange() { - // ignore if the following keys are pressed: [del] [shift] [capslock] - if( lastKeyPressCode == 46 || (lastKeyPressCode > 8 && lastKeyPressCode < 32) ) return $results.hide(); - var v = $input.val(); - if (v == prev) return; - prev = v; - if (v.length >= options.minChars) { - $input.addClass(options.loadingClass); - requestData(v); - } else { - $input.removeClass(options.loadingClass); - $results.hide(); - } - }; - - function moveSelect(step) { - - var lis = $("li", results); - if (!lis) return; - - active += step; - - if (active < 0) { - active = 0; - } else if (active >= lis.size()) { - active = lis.size() - 1; - } - - lis.removeClass("ac_over"); - - $(lis[active]).addClass("ac_over"); - - // Weird behaviour in IE - // if (lis[active] && lis[active].scrollIntoView) { - // lis[active].scrollIntoView(false); - // } - - }; - - function selectCurrent() { - var li = $("li.ac_over", results)[0]; - if (!li) { - var $li = $("li", results); - if (options.selectOnly) { - if ($li.length == 1) li = $li[0]; - } else if (options.selectFirst) { - li = $li[0]; - } - } - if (li) { - selectItem(li); - return true; - } else { - return false; - } - }; - - function selectItem(li) { - if (!li) { - li = document.createElement("li"); - li.extra = []; - li.selectValue = ""; - } - var v = $.trim(li.selectValue ? li.selectValue : li.innerHTML); - input.lastSelected = v; - prev = v; - $results.html(""); - $input.val(v); - hideResultsNow(); - if (options.onItemSelect) { - setTimeout(function() { options.onItemSelect(li) }, 1); - } - }; - - // selects a portion of the input string - function createSelection(start, end){ - // get a reference to the input element - var field = $input.get(0); - if( field.createTextRange ){ - var selRange = field.createTextRange(); - selRange.collapse(true); - selRange.moveStart("character", start); - selRange.moveEnd("character", end); - selRange.select(); - } else if( field.setSelectionRange ){ - field.setSelectionRange(start, end); - } else { - if( field.selectionStart ){ - field.selectionStart = start; - field.selectionEnd = end; - } - } - field.focus(); - }; - - // fills in the input box w/the first match (assumed to be the best match) - function autoFill(sValue){ - // if the last user key pressed was backspace, don't autofill - if( lastKeyPressCode != 8 ){ - // fill in the value (keep the case the user has typed) - $input.val($input.val() + sValue.substring(prev.length)); - // select the portion of the value not typed by the user (so the next character will erase) - createSelection(prev.length, sValue.length); - } - }; - - function showResults() { - // get the position of the input field right now (in case the DOM is shifted) - var pos = findPos(input); - // either use the specified width, or autocalculate based on form element - var iWidth = (options.width > 0) ? options.width : $input.width(); - // reposition - $results.css({ - width: parseInt(iWidth) + "px", - top: (pos.y + input.offsetHeight) + "px", - left: pos.x + "px" - }).show(); - }; - - function hideResults() { - if (timeout) clearTimeout(timeout); - timeout = setTimeout(hideResultsNow, 200); - }; - - function hideResultsNow() { - if (hidingResults) { - return; - } - hidingResults = true; - - if (timeout) { - clearTimeout(timeout); - } - - var v = $input.removeClass(options.loadingClass).val(); - - if ($results.is(":visible")) { - $results.hide(); - } - - if (options.mustMatch) { - if (!input.lastSelected || input.lastSelected != v) { - selectItem(null); - } - } - - hidingResults = false; - }; - - function receiveData(q, data) { - if (data) { - $input.removeClass(options.loadingClass); - results.innerHTML = ""; - - // if the field no longer has focus or if there are no matches, do not display the drop down - if( !hasFocus || data.length == 0 ) return hideResultsNow(); - - if ($.browser.msie) { - // we put a styled iframe behind the calendar so HTML SELECT elements don't show through - $results.append(document.createElement('iframe')); - } - results.appendChild(dataToDom(data)); - // autofill in the complete box w/the first match as long as the user hasn't entered in more data - if( options.autoFill && ($input.val().toLowerCase() == q.toLowerCase()) ) autoFill(data[0][0]); - showResults(); - } else { - hideResultsNow(); - } - }; - - function parseData(data) { - if (!data) return null; - var parsed = []; - var rows = data.split(options.lineSeparator); - for (var i=0; i < rows.length; i++) { - var row = $.trim(rows[i]); - if (row) { - parsed[parsed.length] = row.split(options.cellSeparator); - } - } - return parsed; - }; - - function dataToDom(data) { - var ul = document.createElement("ul"); - var num = data.length; - - // limited results to a max number - if( (options.maxItemsToShow > 0) && (options.maxItemsToShow < num) ) num = options.maxItemsToShow; - - for (var i=0; i < num; i++) { - var row = data[i]; - if (!row) continue; - var li = document.createElement("li"); - if (options.formatItem) { - li.innerHTML = options.formatItem(row, i, num); - li.selectValue = row[0]; - } else { - li.innerHTML = row[0]; - li.selectValue = row[0]; - } - var extra = null; - if (row.length > 1) { - extra = []; - for (var j=1; j < row.length; j++) { - extra[extra.length] = row[j]; - } - } - li.extra = extra; - ul.appendChild(li); - - $(li).hover( - function() { - var $this = $(this); - active = $("li", ul).removeClass("ac_over").index($this[0]); - $this.addClass("ac_over"); - }, - function() { - $(this).removeClass("ac_over"); - } - ).click(function(e) { - e.preventDefault(); - e.stopPropagation(); - selectItem(this) - }); - - } - $(ul).mousedown(function() { - mouseDownOnSelect = true; - }).mouseup(function() { - mouseDownOnSelect = false; - }); - return ul; - }; - - function requestData(q) { - if (!options.matchCase) q = q.toLowerCase(); - var data = options.cacheLength ? loadFromCache(q) : null; - // recieve the cached data - if (data) { - receiveData(q, data); - // if an AJAX url has been supplied, try loading the data now - } else if( (typeof options.url == "string") && (options.url.length > 0) ){ - $.get(makeUrl(q), function(data) { - data = parseData(data); - addToCache(q, data); - receiveData(q, data); - }); - // if there's been no data found, remove the loading class - } else { - $input.removeClass(options.loadingClass); - } - }; - - function makeUrl(q) { - var sep = options.url.indexOf('?') == -1 ? '?' : '&'; - var url = options.url + sep + "q=" + encodeURI(q); - for (var i in options.extraParams) { - url += "&" + i + "=" + encodeURI(options.extraParams[i]); - } - return url; - }; - - function loadFromCache(q) { - if (!q) return null; - if (cache.data[q]) return cache.data[q]; - if (options.matchSubset) { - for (var i = q.length - 1; i >= options.minChars; i--) { - var qs = q.substr(0, i); - var c = cache.data[qs]; - if (c) { - var csub = []; - for (var j = 0; j < c.length; j++) { - var x = c[j]; - var x0 = x[0]; - if (matchSubset(x0, q)) { - csub[csub.length] = x; - } - } - return csub; - } - } - } - return null; - }; - - function matchSubset(s, sub) { - if (!options.matchCase) s = s.toLowerCase(); - var i = s.indexOf(sub); - if (i == -1) return false; - return i == 0 || options.matchContains; - }; - - this.flushCache = function() { - flushCache(); - }; - - this.setExtraParams = function(p) { - options.extraParams = p; - }; - - this.findValue = function(){ - var q = $input.val(); - - if (!options.matchCase) q = q.toLowerCase(); - var data = options.cacheLength ? loadFromCache(q) : null; - if (data) { - findValueCallback(q, data); - } else if( (typeof options.url == "string") && (options.url.length > 0) ){ - $.get(makeUrl(q), function(data) { - data = parseData(data) - addToCache(q, data); - findValueCallback(q, data); - }); - } else { - // no matches - findValueCallback(q, null); - } - } - - function findValueCallback(q, data){ - if (data) $input.removeClass(options.loadingClass); - - var num = (data) ? data.length : 0; - var li = null; - - for (var i=0; i < num; i++) { - var row = data[i]; - - if( row[0].toLowerCase() == q.toLowerCase() ){ - li = document.createElement("li"); - if (options.formatItem) { - li.innerHTML = options.formatItem(row, i, num); - li.selectValue = row[0]; - } else { - li.innerHTML = row[0]; - li.selectValue = row[0]; - } - var extra = null; - if( row.length > 1 ){ - extra = []; - for (var j=1; j < row.length; j++) { - extra[extra.length] = row[j]; - } - } - li.extra = extra; - } - } - - if( options.onFindValue ) setTimeout(function() { options.onFindValue(li) }, 1); - } - - function addToCache(q, data) { - if (!data || !q || !options.cacheLength) return; - if (!cache.length || cache.length > options.cacheLength) { - flushCache(); - cache.length++; - } else if (!cache[q]) { - cache.length++; - } - cache.data[q] = data; - }; - - function findPos(obj) { - var curleft = obj.offsetLeft || 0; - var curtop = obj.offsetTop || 0; - while (obj = obj.offsetParent) { - curleft += obj.offsetLeft - curtop += obj.offsetTop - } - return {x:curleft,y:curtop}; - } - } - - /** - * The autocomplete plugin itself - */ - $.fn.autocomplete = function(url, options, data) { - - // Make sure options exists - options = options || {}; - // Set url as option - options.url = url; - // set some bulk local data - options.data = ((typeof data == "object") && (data.constructor == Array)) ? data : null; - - // Set default values for required options (set global defaults in $.fn.autocomplete.defaults) - options = $.extend({ - inputClass: "ac_input", - resultsClass: "ac_results", - lineSeparator: "\n", - cellSeparator: "|", - minChars: 1, - delay: 400, - matchCase: 0, - matchSubset: 1, - matchContains: 0, - cacheLength: 1, - mustMatch: 0, - extraParams: {}, - loadingClass: "ac_loading", - selectFirst: false, - selectOnly: false, - maxItemsToShow: -1, - autoFill: false, - width: 0 - }, $.fn.autocomplete.defaults, options); - - options.width = parseInt(options.width, 10); - - return this.each(function() { - var input = this; - new $.autocomplete(input, options); - }); - - } - -})(jQuery); diff --git a/htdocs/javascript/search.js b/htdocs/javascript/search.js index 29ba340..e61617c 100644 --- a/htdocs/javascript/search.js +++ b/htdocs/javascript/search.js @@ -28,13 +28,16 @@ function load_advanced_search(request) { $('.autocomplete_target').hide(); $('.autocomplete').show().each(function() { - $(this).autocomplete(baseurl + 'autocomplete/' + this.name, { - selectOnly: 1, - formatItem: make_format_autocomplete(this), - field: this.name, - onItemSelect: select_autocomplete(this.name), - matchSubset: 0, - width: $(this).width() + $(this).autocomplete({ + source: baseurl + 'autocomplete/' + this.name, + select: function(event, ui) { + select_autocomplete(this.name, ui.item.id); + }, + change: function(event, ui) { + if (ui.item != null && ui.item.field != null) { + $(this).val(ui.item.field); + } + } }); }); @@ -148,25 +151,6 @@ function cleanForm(f, targeturl) // }}} // {{{ Autocomplete related functions. -// display an autocomplete row : blabla (nb of found matches) -function make_format_autocomplete(block) -{ - return function(row) { - regexp = new RegExp('(' + RegExp.escape(block.value) + ')', 'i'); - name = row[0].htmlEntities().replace(regexp, '$1<\/strong>'); - - if (row[1] === '-1') { - return '…'; - } - if (row[1] === '-2') { - return 'aucun camarade trouvé pour '+row[0].htmlEntities()+'<\/em>'; - } - - mate = (row[1] > 1) ? 'camarades' : 'camarade'; - return name + '  -  ' + row[1].htmlEntities() + ' ' + mate + '<\/em>'; - }; -} - function cancel_autocomplete(field, realfield) { $(".autocomplete[name='" + field + "']").removeClass('hidden_valid').val('').focus(); @@ -177,46 +161,42 @@ function cancel_autocomplete(field, realfield) } // when choosing autocomplete from list, must validate -function select_autocomplete(name) +function select_autocomplete(name, id) { var field_name = name.replace(/_text$/, ''); // just display field as valid if field is not a text field for a list if (field_name == name) { - return function(i) { - $(".autocomplete[name='" + name + "']").addClass('hidden_valid'); - } + $(".autocomplete[name='" + name + "']").addClass('hidden_valid'); + return; } // When changing country, locality or school, open next address component. if (field_name == 'country' || field_name == 'locality' || field_name == 'school') { - return function(i) { - if (i.extra[0] < 0) { - cancel_autocomplete(name, field_name); - i.extra[1] = ''; - } - - if (field_name == 'school') { - changeSchool(i.extra[1], ''); - } else { - changeAddressComponents(field_name, i.extra[1]); - } - - $(".autocomplete_target[name='" + field_name + "']").attr('value', i.extra[1]); - $(".autocomplete[name='" + name + "']").addClass('hidden_valid'); + if (id < 0) { + cancel_autocomplete(name, field_name); + id = ''; } - } - // change field in list and display text field as valid - return function(i) { - if (i.extra[0] < 0) { - cancel_autocomplete(this.field, field_name); - return; + if (field_name == 'school') { + changeSchool(id, ''); + } else { + changeAddressComponents(field_name, id); } - $(".autocomplete_target[name='" + field_name + "']").attr('value', i.extra[1]); + $(".autocomplete_target[name='" + field_name + "']").attr('value', id); $(".autocomplete[name='" + name + "']").addClass('hidden_valid'); + return; } + + // change field in list and display text field as valid + if (id < 0) { + cancel_autocomplete(this.field, field_name); + return; + } + + $(".autocomplete_target[name='" + field_name + "']").attr('value', id); + $(".autocomplete[name='" + name + "']").addClass('hidden_valid'); } // }}} diff --git a/modules/search.php b/modules/search.php index 0504a77..7c2ed2e 100644 --- a/modules/search.php +++ b/modules/search.php @@ -149,6 +149,7 @@ class SearchModule extends PLModule function handler_advanced($page, $model = null, $byletter = null) { global $globals; + $page->addJsLink('jquery.ui.xorg.js'); $page->addJsLink('search.js'); $page->assign('advanced',1); @@ -212,6 +213,11 @@ class SearchModule extends PLModule $page->assign('public_directory',0); } + private function format_autocomplete(array $item) + { + return $item['field'] . ' (' . $item['nb'] . ' camarade' . ($item['nb'] > 1 ? 's' : '') . ')'; + } + function handler_autocomplete($page, $type = null) { // Autocompletion : according to type required, return @@ -227,66 +233,84 @@ class SearchModule extends PLModule array('', '\\\\\1', '.*'), - Env::s('q')); - if (!$q) exit(); + Get::t('term')); + if (!$q) { + exit(); + } - // try to look in cached results + // Try to look in cached results. + $cached = false; $cache = XDB::query('SELECT result FROM search_autocomplete - WHERE name = {?} AND - query = {?} AND - generated > NOW() - INTERVAL 1 DAY', + WHERE name = {?} AND query = {?} AND generated > NOW() - INTERVAL 1 DAY', $type, $q); - if ($res = $cache->fetchOneCell()) { - echo $res; - die(); - } - - $enums = array( - 'binet_text' => DirEnum::BINETS, - 'groupex_text' => DirEnum::GROUPESX, - 'section_text' => DirEnum::SECTIONS, - 'networking_type_text' => DirEnum::NETWORKS, - 'locality_text' => DirEnum::LOCALITIES, - 'country_text' => DirEnum::COUNTRIES, - 'entreprise' => DirEnum::COMPANIES, - 'jobterm_text' => DirEnum::JOBTERMS, - 'description' => DirEnum::JOBDESCRIPTION, - 'nationalite_text' => DirEnum::NATIONALITIES, - 'school_text' => DirEnum::EDUSCHOOLS, - ); - if (!array_key_exists($type, $enums)) { - exit(); - } - - $enum = $enums[$type]; - $list = DirEnum::getAutoComplete($enum, $q); - $nbResults = 0; - $res = ""; - while ($result = $list->next()) { - $nbResults++; - if ($nbResults == 11) { - $res .= $q."|-1\n"; - } else { - $res .= $result['field'].'|'; - if (isset($result['nb'])) { - $res .= $result['nb']; - } - if (isset($result['id'])) { - $res .= '|'.$result['id']; + if ($cache->numRows() > 0) { + $cached = true; + $data = explode("\n", $cache->fetchOneCell()); + $list = array(); + foreach ($data as $line) { + if ($line != '') { + $aux = explode("\t", $line); + $item = array( + 'field' => $aux[0], + 'nb' => $aux[1], + 'id' => $aux[2] + ); + $item['value'] = $this->format_autocomplete($item); + array_push($list, $item); } - $res .= "\n"; + } + } else { + $enums = array( + 'binet_text' => DirEnum::BINETS, + 'groupex_text' => DirEnum::GROUPESX, + 'section_text' => DirEnum::SECTIONS, + 'networking_type_text' => DirEnum::NETWORKS, + 'locality_text' => DirEnum::LOCALITIES, + 'country_text' => DirEnum::COUNTRIES, + 'entreprise' => DirEnum::COMPANIES, + 'jobterm_text' => DirEnum::JOBTERMS, + 'description' => DirEnum::JOBDESCRIPTION, + 'nationalite_text' => DirEnum::NATIONALITIES, + 'school_text' => DirEnum::EDUSCHOOLS, + ); + if (!array_key_exists($type, $enums)) { + exit(); + } + + $list = DirEnum::getAutoComplete($enums[$type], $q); + $to_cache = ''; + foreach ($list as &$item) { + $to_cache .= $item['field'] . "\t" . $item['nb'] . "\t" . $item['id'] . "\n"; + $item['value'] = $this->format_autocomplete($item); } } - if ($nbResults == 0) { - $res = $q."|-2\n"; + + $count = count($list); + if ($count == DirEnumeration::AUTOCOMPLETE_LIMIT) { + $list[] = array( + 'value' => '…', + 'field' => '', + 'nb' => 0, + 'id' => -1 + ); + } elseif ($count == 0) { + $list[] = array( + 'value' => 'Aucun camarade trouvé pour ' . $q . '.', + 'field' => '', + 'nb' => 0, + 'id' => -1 + ); + } + + if (!$cached) { + XDB::query('INSERT INTO search_autocomplete (name, query, result, generated) + VALUES ({?}, {?}, {?}, NOW()) + ON DUPLICATE KEY UPDATE result = VALUES(result), generated = VALUES(generated)', + $type, $q, $to_cache); } - XDB::query('INSERT INTO search_autocomplete (name, query, result, generated) - VALUES ({?}, {?}, {?}, NOW()) - ON DUPLICATE KEY UPDATE result = VALUES(result), generated = VALUES(generated)', - $type, $q, $res); - echo $res; + echo json_encode($list); exit(); } diff --git a/upgrade/1.1.4/07_autocomplete.sql b/upgrade/1.1.4/07_autocomplete.sql new file mode 100644 index 0000000..f760b19 --- /dev/null +++ b/upgrade/1.1.4/07_autocomplete.sql @@ -0,0 +1,3 @@ +DELETE FROM search_autocomplete; + +-- vim:set syntax=mysql: -- 2.1.4