summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiroslav2011-12-25 20:09:57 +0100
committerMiroslav2011-12-25 20:09:57 +0100
commitc19a0802c75716d0b1a9e974cbff92f3ef5c1a94 (patch)
tree9614a4c5981b5ec6d9c820156fbcde251a863352
parentebc172c92e391453b09bafabed7512181fbc1c4d (diff)
Discover some new features with this 2.0 version!7.x-2.0
- Use a standalone jquery plugin for autocompletion providing more settings, such as coloration, highlighting, caching, etc... - Forms to autocomplete are individually configurable. - Advanced users can add their own forms to autocomplete whatever they want! Fields to autocomplete are therefore targeted using jquery selectors. - More types of suggestions to display are available: - words (any word in your site) - node titles - comment titles (if optional module comment enabled) - taxonomies (if optional module taxonomy enabled) - Each type of suggestion is prefixable (to indicate your user what the suggestion stand for) - Advanced users can define their own suggestions providing an SQL query.
-rw-r--r--README.txt48
-rw-r--r--css/help.pngbin0 -> 1222 bytes
-rw-r--r--css/jquery.autocomplete.css74
-rw-r--r--css/throbber.gifbin0 -> 1336 bytes
-rw-r--r--js/jquery.autocomplete.js847
-rw-r--r--search_autocomplete.admin.inc156
-rw-r--r--search_autocomplete.form.add.inc113
-rw-r--r--search_autocomplete.form.configure.inc277
-rw-r--r--search_autocomplete.form.delete.inc77
-rw-r--r--search_autocomplete.form.treelist.inc236
-rw-r--r--search_autocomplete.info6
-rw-r--r--search_autocomplete.install300
-rw-r--r--search_autocomplete.module249
-rw-r--r--search_autocomplete.suggestion.configure.inc225
-rw-r--r--search_autocomplete.suggestion.delete.inc70
-rw-r--r--translations/search_autocomplete.pot133
16 files changed, 2479 insertions, 332 deletions
diff --git a/README.txt b/README.txt
index 606031f..f451cad 100644
--- a/README.txt
+++ b/README.txt
@@ -1,18 +1,16 @@
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*
-* Welcome to Search Autocomplete v7.x-1.0 !
+* Welcome to Search Autocomplete v7.x-2.0 !
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*
- !! This module has just changed maintainer. Let's give it a second life !!
-
***
* Search Autocomplete
* Enables autocomplete functionality on search fields.
***
@authors
- D7 version by Miroslav (Dominique CLAUSE) <http://www.axiomcafe.fr/contact>
-
+ Miroslav (Dominique CLAUSE) <http://www.axiomcafe.fr/contact>
+
Sponsored by:
www.axiomcafe.fr
@@ -22,32 +20,46 @@
Place the entirety of this directory in sites/all/modules/search_autocomplete
or in the equivalent directory of your Drupal installation.
-Navigate to administer >> modules. Enable Search Autocomplete.
+Navigate to administer >> build >> modules. Enable Search Autocomplete.
+
+If you're having trouble installing this module, please ensure that your tar
+program is not flattening the directory tree, truncating filenames or losing
+files.
+
+---------------------------------------------------------------------------------
+-- 2. Updating Search Autocomplete:
+
+Delete every files in the module directory sites/all/modules/search_autocomplete
+or in the equivalent directory of your Drupal installation.
+
+Place all files from this 2.x version in this sites/all/modules/search_autocomplete
+or equivalent folder.
+
+Run the update script. Make sure to run the udpate functions.
If you're having trouble installing this module, please ensure that your tar
program is not flattening the directory tree, truncating filenames or losing
files.
---------------------------------------------------------------------------------
--- 2. Setting Search Autocomplete
+-- 3. Setting Search Autocomplete
-Navigate to admin/config/user-interface/search_autocomplete
+Navigate to /admin/config/search/search_autocomplete
-The configuration options are quite easy to understand. However, a documentation
-will soon be released for your convenience.
+The configuration options are not as quite easy to understand as it was in previous
+version. Please consider reading the documentation available at:
+http://projects.axiomcafe.fr
---------------------------------------------------------------------------------
--- 3. Translating Search Autocomplete
+-- 4. Translating Search Autocomplete
-Translations are available -as for every module- on "localize" platform at
-http://localize.drupal.org/translate/projects/search_autocomplete
+Please visit the module translation page to download translation:
+http://localize.drupal.org/translate/downloads?project=search_autocomplete.
-A .pot translation file is also given for your convenience to help in translating
-the module.
-Please refer to section 4 (Helping) for typo, grammar or langage issues.
+Please refer to section 5 (Helping) for typo, grammar or langage issues.
---------------------------------------------------------------------------------
--- 4. Helping and complaining about Search Autocomplete
+-- 5. Helping and complaining on Search Autocomplete
To help this module live, please post your issues, ideas and comments at:
http://drupal.org/node/add/project-issue/search_autocomplete
@@ -55,4 +67,4 @@ and view issues at:
http://drupal.org/project/issues/search_autocomplete?categories=All
-The new maintainer: Miroslav (Dominique CLAUSE) \ No newline at end of file
+The new maintener: Miroslav \ No newline at end of file
diff --git a/css/help.png b/css/help.png
new file mode 100644
index 0000000..86b6407
--- /dev/null
+++ b/css/help.png
Binary files differ
diff --git a/css/jquery.autocomplete.css b/css/jquery.autocomplete.css
new file mode 100644
index 0000000..117a786
--- /dev/null
+++ b/css/jquery.autocomplete.css
@@ -0,0 +1,74 @@
+.ac_input {
+ background-image: url("throbber.gif");
+ background-position: 100% 4px;
+ background-repeat: no-repeat;
+}
+.ac_loading {
+ background-image: url("throbber.gif");
+ background-position: 100% -16px;
+ background-repeat: no-repeat;
+}
+
+.ac_results {
+ padding: 0px;
+ border: 1px solid black;
+ background-color: white;
+ overflow: hidden;
+ z-index: 99999;
+}
+
+.ac_results ul {
+ width: 100%;
+ list-style-position: outside;
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.ac_results li {
+ background-image: none;
+ margin: 0px;
+ padding: 2px 5px;
+ cursor: default;
+ display: block;
+ /*
+ if width will be 100% horizontal scrollbar will apear
+ when scroll mode will be used
+ */
+ /*width: 100%;*/
+ font: menu;
+ font-size: 12px;
+ /*
+ it is very important, if line-height not setted or setted
+ in relative units scroll will be broken in firefox
+ */
+ line-height: 16px;
+ overflow: hidden;
+}
+
+.ac_odd {
+ background-color: #eee;
+}
+
+.ac_over {
+ background-color: #0A246A;
+ color: white;
+}
+
+/* HELP PANEL CSS */
+div.help {
+ background-image: url("help.png");
+}
+div.help {
+ background-color: #F7F7F7 !important;
+ border: 1px solid #E7E7E7 !important;
+}
+div.help {
+ background-color: #FFFFFF;
+ background-position: 10px 50%;
+ background-repeat: no-repeat;
+ border: 1px solid;
+ font-weight: normal;
+ margin: 10px 0;
+ padding: 15px 10px 15px 50px;
+}
diff --git a/css/throbber.gif b/css/throbber.gif
new file mode 100644
index 0000000..4352e64
--- /dev/null
+++ b/css/throbber.gif
Binary files differ
diff --git a/js/jquery.autocomplete.js b/js/jquery.autocomplete.js
new file mode 100644
index 0000000..4a3811d
--- /dev/null
+++ b/js/jquery.autocomplete.js
@@ -0,0 +1,847 @@
+/*
+ * jQuery Autocomplete plugin 1.2.2
+ *
+ * Copyright (c) 2009 Jörn Zaefferer
+ *
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * With small modifications by Alfonso Gómez-Arzola.
+ * See changelog for details.
+ *
+ */
+(function ($) {
+ $(document).ready(function() {
+ $.each(Drupal.settings.search_autocomplete, function(key, value) {
+ var obj = 'Drupal.settings.search_autocomplete.' + key;
+ $(eval(obj + '.selector') + ' input:first').autocomplete(
+ eval(obj + '.url'), {
+ dataType: "json",
+ cacheLength : 20,
+ matchContains: true,
+ minChars: eval(obj + '.minChars'),
+ selectFirst: false,
+ max: eval(obj + '.max_sug'),
+ }).result(function () {
+ $(this).get(0).form.submit();
+ }).focus();
+ });
+ });
+}(jQuery)) ;
+
+;(function($) {
+
+$.fn.extend({
+ autocomplete: function(urlOrData, options) {
+ var isUrl = typeof urlOrData == "string";
+ options = $.extend({}, $.Autocompleter.defaults, {
+ url: isUrl ? urlOrData : null,
+ data: isUrl ? null : urlOrData,
+ delay: isUrl ? $.Autocompleter.defaults.delay : 10,
+ max: options && !options.scroll ? 10 : 150
+ }, options);
+
+ // if highlight is set to false, replace it with a do-nothing function
+ options.highlight = options.highlight || function(value) { return value; };
+
+ // if the formatMatch option is not specified, then use formatItem for backwards compatibility
+ options.formatMatch = options.formatMatch || options.formatItem;
+
+ return this.each(function() {
+ new $.Autocompleter(this, options);
+ });
+ },
+ result: function(handler) {
+ return this.bind("result", handler);
+ },
+ search: function(handler) {
+ return this.trigger("search", [handler]);
+ },
+ flushCache: function() {
+ return this.trigger("flushCache");
+ },
+ setOptions: function(options){
+ return this.trigger("setOptions", [options]);
+ },
+ unautocomplete: function() {
+ return this.trigger("unautocomplete");
+ }
+});
+
+$.Autocompleter = function(input, options) {
+
+ var KEY = {
+ UP: 38,
+ DOWN: 40,
+ DEL: 46,
+ TAB: 9,
+ RETURN: 13,
+ ESC: 27,
+ COMMA: 188,
+ PAGEUP: 33,
+ PAGEDOWN: 34,
+ BACKSPACE: 8
+ };
+
+ var globalFailure = null;
+ if(options.failure != null && typeof options.failure == "function") {
+ globalFailure = options.failure;
+ }
+
+ // Create $ object for input element
+ var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
+
+ var timeout;
+ var previousValue = "";
+ var cache = $.Autocompleter.Cache(options);
+ var hasFocus = 0;
+ var lastKeyPressCode;
+ var config = {
+ mouseDownOnSelect: false
+ };
+ var select = $.Autocompleter.Select(options, input, selectCurrent, config);
+
+ var blockSubmit;
+
+ // prevent form submit in opera when selecting with return key
+ $.browser.opera && $(input.form).bind("submit.autocomplete", function() {
+ if (blockSubmit) {
+ blockSubmit = false;
+ return false;
+ }
+ });
+
+ // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
+ $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
+ // a keypress means the input has focus
+ // avoids issue where input had focus before the autocomplete was applied
+ hasFocus = 1;
+ // track last key pressed
+ lastKeyPressCode = event.keyCode;
+ switch(event.keyCode) {
+
+ case KEY.UP:
+ if ( select.visible() ) {
+ event.preventDefault();
+ select.prev();
+ } else {
+ onChange(0, true);
+ }
+ break;
+
+ case KEY.DOWN:
+ if ( select.visible() ) {
+ event.preventDefault();
+ select.next();
+ } else {
+ onChange(0, true);
+ }
+ break;
+
+ case KEY.PAGEUP:
+ if ( select.visible() ) {
+ event.preventDefault();
+ select.pageUp();
+ } else {
+ onChange(0, true);
+ }
+ break;
+
+ case KEY.PAGEDOWN:
+ if ( select.visible() ) {
+ event.preventDefault();
+ select.pageDown();
+ } else {
+ onChange(0, true);
+ }
+ break;
+
+ // matches also semicolon
+ case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
+ case KEY.TAB:
+ case KEY.RETURN:
+ if( selectCurrent() ) {
+ // stop default to prevent a form submit, Opera needs special handling
+ event.preventDefault();
+ blockSubmit = true;
+ return false;
+ }
+ break;
+
+ case KEY.ESC:
+ select.hide();
+ break;
+
+ default:
+ 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++;
+ }).blur(function() {
+ hasFocus = 0;
+ if (!config.mouseDownOnSelect) {
+ hideResults();
+ }
+ }).click(function() {
+ // show select when clicking in a focused field
+ // but if clickFire is true, don't require field
+ // to be focused to begin with; just show select
+ if( options.clickFire ) {
+ if ( !select.visible() ) {
+ onChange(0, true);
+ }
+ } else {
+ if ( hasFocus++ > 1 && !select.visible() ) {
+ onChange(0, true);
+ }
+ }
+ }).bind("search", function() {
+ // TODO why not just specifying both arguments?
+ var fn = (arguments.length > 1) ? arguments[1] : null;
+ function findValueCallback(q, data) {
+ var result;
+ if( data && data.length ) {
+ for (var i=0; i < data.length; i++) {
+ if( data[i].result.toLowerCase() == q.toLowerCase() ) {
+ result = data[i];
+ break;
+ }
+ }
+ }
+ if( typeof fn == "function" ) fn(result);
+ else $input.trigger("result", result && [result.data, result.value]);
+ }
+ $.each(trimWords($input.val()), function(i, value) {
+ request(value, findValueCallback, findValueCallback);
+ });
+ }).bind("flushCache", function() {
+ cache.flush();
+ }).bind("setOptions", function() {
+ $.extend(true, options, arguments[1]);
+ // if we've updated the data, repopulate
+ if ( "data" in arguments[1] )
+ cache.populate();
+ }).bind("unautocomplete", function() {
+ select.unbind();
+ $input.unbind();
+ $(input.form).unbind(".autocomplete");
+ });
+
+
+ function selectCurrent() {
+ var selected = select.selected();
+ if( !selected )
+ return false;
+
+ var v = selected.result;
+ previousValue = v;
+
+ if ( options.multiple ) {
+ var words = trimWords($input.val());
+ if ( words.length > 1 ) {
+ var seperator = options.multipleSeparator.length;
+ var cursorAt = $(input).selection().start;
+ var wordAt, progress = 0;
+ $.each(words, function(i, word) {
+ progress += word.length;
+ if (cursorAt <= progress) {
+ wordAt = i;
+ return false;
+ }
+ progress += seperator;
+ });
+ words[wordAt] = v;
+ // TODO this should set the cursor to the right position, but it gets overriden somewhere
+ //$.Autocompleter.Selection(input, progress + seperator, progress + seperator);
+ v = words.join( options.multipleSeparator );
+ }
+ v += options.multipleSeparator;
+ }
+
+ $input.val(v);
+ hideResultsNow();
+ $input.trigger("result", [selected.data, selected.value]);
+ return true;
+ }
+
+ function onChange(crap, skipPrevCheck) {
+ if( lastKeyPressCode == KEY.DEL ) {
+ select.hide();
+ return;
+ }
+
+ var currentValue = $input.val();
+
+ if ( !skipPrevCheck && currentValue == previousValue )
+ return;
+
+ previousValue = currentValue;
+
+ currentValue = lastWord(currentValue);
+ if ( currentValue.length >= options.minChars) {
+ $input.addClass(options.loadingClass);
+ if (!options.matchCase)
+ currentValue = currentValue.toLowerCase();
+ request(currentValue, receiveData, hideResultsNow);
+ } else {
+ stopLoading();
+ select.hide();
+ }
+ };
+
+ function trimWords(value) {
+ if (!value)
+ return [""];
+ if (!options.multiple)
+ return [$.trim(value)];
+ return $.map(value.split(options.multipleSeparator), function(word) {
+ return $.trim(value).length ? $.trim(word) : null;
+ });
+ }
+
+ function lastWord(value) {
+ if ( !options.multiple )
+ return value;
+ var words = trimWords(value);
+ if (words.length == 1)
+ return words[0];
+ var cursorAt = $(input).selection().start;
+ if (cursorAt == value.length) {
+ words = trimWords(value)
+ } else {
+ words = trimWords(value.replace(value.substring(cursorAt), ""));
+ }
+ return words[words.length - 1];
+ }
+
+ // fills in the input box w/the first match (assumed to be the best match)
+ // q: the term entered
+ // sValue: the first matching result
+ function autoFill(q, sValue){
+ // autofill in the complete box w/the first match as long as the user hasn't entered in more data
+ // if the last user key pressed was backspace, don't autofill
+ if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
+ // fill in the value (keep the case the user has typed)
+ $input.val($input.val() + sValue.substring(lastWord(previousValue).length));
+ // select the portion of the value not typed by the user (so the next character will erase)
+ $(input).selection(previousValue.length, previousValue.length + sValue.length);
+ }
+ };
+
+ function hideResults() {
+ clearTimeout(timeout);
+ timeout = setTimeout(hideResultsNow, 200);
+ };
+
+ function hideResultsNow() {
+ var wasVisible = select.visible();
+ select.hide();
+ clearTimeout(timeout);
+ stopLoading();
+ if (options.mustMatch) {
+ // call search and run callback
+ $input.search(
+ function (result){
+ // if no value found, clear the input box
+ if( !result ) {
+ if (options.multiple) {
+ var words = trimWords($input.val()).slice(0, -1);
+ $input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
+ }
+ else {
+ $input.val( "" );
+ $input.trigger("result", null);
+ }
+ }
+ }
+ );
+ }
+ };
+
+ function receiveData(q, data) {
+ if ( data && data.length && hasFocus ) {
+ stopLoading();
+ select.display(data, q);
+ autoFill(q, data[0].value);
+ select.show();
+ } else {
+ hideResultsNow();
+ }
+ };
+
+ function request(term, success, failure) {
+ if (!options.matchCase)
+ term = term.toLowerCase();
+ var data = cache.load(term);
+ // recieve the cached data
+ if (data && data.length) {
+ success(term, data);
+ // if an AJAX url has been supplied, try loading the data now
+ } else if( (typeof options.url == "string") && (options.url.length > 0) ){
+
+ $.ajax({
+ // try to leverage ajaxQueue plugin to abort previous requests
+ mode: "abort",
+ // limit abortion to this input
+ port: "autocomplete" + input.name,
+ dataType: options.dataType,
+ url: options.url + "/" + term,
+ success: function(data) {
+ var parsed = options.parse && options.parse(data) || parse(data);
+ cache.add(term, parsed);
+ success(term, parsed);
+ }
+ });
+ } else {
+ // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
+ select.emptyList();
+ if(globalFailure != null) {
+ globalFailure();
+ }
+ else {
+ failure(term);
+ }
+ }
+ };
+
+ function parse(data) {
+ var parsed = [];
+ $.each(data, function(key, value) {
+ var row = [];
+ row[0] = key;
+
+
+ parsed[parsed.length] = {
+ data: row,
+ value: value,
+ result: options.formatResult && options.formatResult(row, value) || value
+ };
+ });
+
+ return parsed;
+ };
+
+ function stopLoading() {
+ $input.removeClass(options.loadingClass);
+ };
+
+};
+
+$.Autocompleter.defaults = {
+ inputClass: "ac_input",
+ resultsClass: "ac_results",
+ loadingClass: "ac_loading",
+ minChars: 1,
+ delay: 400,
+ matchCase: false,
+ matchSubset: true,
+ matchContains: false,
+ cacheLength: 100,
+ max: 1000,
+ mustMatch: false,
+ extraParams: {},
+ selectFirst: true,
+ formatItem: function(row) { return row[0]; },
+ formatMatch: null,
+ autoFill: false,
+ width: 0,
+ multiple: false,
+ multipleSeparator: " ",
+ inputFocus: true,
+ clickFire: false,
+ highlight: function(value, term) {
+ return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
+ },
+ scroll: true,
+ scrollHeight: 180,
+ scrollJumpPosition: true
+};
+
+$.Autocompleter.Cache = function(options) {
+
+ var data = {};
+ var length = 0;
+
+ function matchSubset(s, sub) {
+ if (!options.matchCase)
+ s = s.toLowerCase();
+ var i = s.indexOf(sub);
+ if (options.matchContains == "word"){
+ i = s.toLowerCase().search("\\b" + sub.toLowerCase());
+ }
+ if (i == -1) return false;
+ return i == 0 || options.matchContains;
+ };
+
+ function add(q, value) {
+ if (length > options.cacheLength){
+ flush();
+ }
+ if (!data[q]){
+ length++;
+ }
+ data[q] = value;
+ }
+
+ function populate(){
+ if( !options.data ) return false;
+ // track the matches
+ var stMatchSets = {},
+ nullData = 0;
+
+ // no url was specified, we need to adjust the cache length to make sure it fits the local data store
+ if( !options.url ) options.cacheLength = 1;
+
+ // track all options for minChars = 0
+ stMatchSets[""] = [];
+
+ // loop through the array and create a lookup structure
+ for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
+ var rawValue = options.data[i];
+ // if rawValue is a string, make an array otherwise just reference the array
+ rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
+
+ var value = options.formatMatch(rawValue, i+1, options.data.length);
+ if ( value === false )
+ continue;
+
+ var firstChar = value.charAt(0).toLowerCase();
+ // if no lookup array for this character exists, look it up now
+ if( !stMatchSets[firstChar] )
+ stMatchSets[firstChar] = [];
+
+ // if the match is a string
+ var row = {
+ value: value,
+ data: rawValue,
+ result: options.formatResult && options.formatResult(rawValue) || value
+ };
+
+ // push the current match into the set list
+ stMatchSets[firstChar].push(row);
+
+ // keep track of minChars zero items
+ if ( nullData++ < options.max ) {
+ stMatchSets[""].push(row);
+ }
+ };
+
+ // add the data items to the cache
+ $.each(stMatchSets, function(i, value) {
+ // increase the cache size
+ options.cacheLength++;
+ // add to the cache
+ add(i, value);
+ });
+ }
+
+ // populate any existing data
+ setTimeout(populate, 25);
+
+ function flush(){
+ data = {};
+ length = 0;
+ }
+
+ return {
+ flush: flush,
+ add: add,
+ populate: populate,
+ load: function(q) {
+ if (!options.cacheLength || !length)
+ return null;
+ /*
+ * if dealing w/local data and matchContains than we must make sure
+ * to loop through all the data collections looking for matches
+ */
+ if( !options.url && options.matchContains ){
+ // track all matches
+ var csub = [];
+ // loop through all the data grids for matches
+ for( var k in data ){
+ // don't search through the stMatchSets[""] (minChars: 0) cache
+ // this prevents duplicates
+ if( k.length > 0 ){
+ var c = data[k];
+ $.each(c, function(i, x) {
+ // if we've got a match, add it to the array
+ if (matchSubset(x.value, q)) {
+ csub.push(x);
+ }
+ });
+ }
+ }
+ return csub;
+ } else
+ // if the exact item exists, use it
+ if (data[q]){
+ return data[q];
+ } else
+ if (options.matchSubset) {
+ for (var i = q.length - 1; i >= options.minChars; i--) {
+ var c = data[q.substr(0, i)];
+ if (c) {
+ var csub = [];
+ $.each(c, function(i, x) {
+ if (matchSubset(x.value, q)) {
+ csub[csub.length] = x;
+ }
+ });
+ return csub;
+ }
+ }
+ }
+ return null;
+ }
+ };
+};
+
+$.Autocompleter.Select = function (options, input, select, config) {
+ var CLASSES = {
+ ACTIVE: "ac_over"
+ };
+
+ var listItems,
+ active = -1,
+ data,
+ term = "",
+ needsInit = true,
+ element,
+ list;
+
+ // Create results
+ function init() {
+ if (!needsInit)
+ return;
+ element = $("<div/>")
+ .hide()
+ .addClass(options.resultsClass)
+ .css("position", "absolute")
+ .appendTo(document.body)
+ /*.hover(function() {
+ // Browsers except FF do not fire mouseup event on scrollbars, resulting in mouseDownOnSelect remaining true, and results list not always hiding.
+ if($(this).is(":visible")) {
+ input.focus();
+ }
+ config.mouseDownOnSelect = false;
+ })*/;
+ list = $("<ul/>").appendTo(element).mouseover( function(event) {
+ if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
+ active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
+ $(target(event)).addClass(CLASSES.ACTIVE);
+ }
+ }).click(function(event) {
+ $(target(event)).addClass(CLASSES.ACTIVE);
+ select();
+ if( options.inputFocus )
+ input.focus();
+ return false;
+ }).mousedown(function() {
+ config.mouseDownOnSelect = true;
+ }).mouseup(function() {
+ config.mouseDownOnSelect = false;
+ });
+
+ if( options.width > 0 )
+ element.css("width", options.width);
+
+ needsInit = false;
+ }
+
+ function target(event) {
+ var element = event.target;
+ while(element && element.tagName != "LI")
+ element = element.parentNode;
+ // more fun with IE, sometimes event.target is empty, just ignore it then
+ if(!element)
+ return [];
+ return element;
+ }
+
+ function moveSelect(step) {
+ listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
+ movePosition(step);
+ var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
+ if(options.scroll) {
+ var offset = 0;
+ listItems.slice(0, active).each(function() {
+ offset += this.offsetHeight;
+ });
+ if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
+ list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
+ } else if(offset < list.scrollTop()) {
+ list.scrollTop(offset);
+ }
+ }
+ };
+
+ function movePosition(step) {
+ if (options.scrollJumpPosition || (!options.scrollJumpPosition && !((step < 0 && active == 0) || (step > 0 && active == listItems.size() - 1)) )) {
+ active += step;
+ if (active < 0) {
+ active = listItems.size() - 1;
+ } else if (active >= listItems.size()) {
+ active = 0;
+ }
+ }
+ }
+
+
+ function limitNumberOfItems(available) {
+ return options.max && options.max < available
+ ? options.max
+ : available;
+ }
+
+ function fillList() {
+ list.empty();
+ var max = limitNumberOfItems(data.length);
+ for (var i=0; i < max; i++) {
+ if (!data[i])
+ continue;
+ var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
+ if ( formatted === false )
+ continue;
+ var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
+ $.data(li, "ac_data", data[i]);
+ }
+ listItems = list.find("li");
+ if ( options.selectFirst ) {
+ listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
+ active = 0;
+ }
+ // apply bgiframe if available
+ if ( $.fn.bgiframe )
+ list.bgiframe();
+ }
+
+ return {
+ display: function(d, q) {
+ init();
+ data = d;
+ term = q;
+ fillList();
+ },
+ next: function() {
+ moveSelect(1);
+ },
+ prev: function() {
+ moveSelect(-1);
+ },
+ pageUp: function() {
+ if (active != 0 && active - 8 < 0) {
+ moveSelect( -active );
+ } else {
+ moveSelect(-8);
+ }
+ },
+ pageDown: function() {
+ if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
+ moveSelect( listItems.size() - 1 - active );
+ } else {
+ moveSelect(8);
+ }
+ },
+ hide: function() {
+ element && element.hide();
+ listItems && listItems.removeClass(CLASSES.ACTIVE);
+ active = -1;
+ },
+ visible : function() {
+ return element && element.is(":visible");
+ },
+ current: function() {
+ return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
+ },
+ show: function() {
+ var offset = $(input).offset();
+ element.css({
+ width: typeof options.width == "string" || options.offsetWidth > 0 ? options.offsetWidth : $(input).innerWidth(),
+ top: offset.top + input.clientHeight,
+ left: offset.left
+ }).show();
+ if(options.scroll) {
+ list.scrollTop(0);
+ list.css({
+ maxHeight: options.scrollHeight,
+ overflow: 'auto'
+ });
+
+ if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
+ var listHeight = 0;
+ listItems.each(function() {
+ listHeight += this.offsetHeight;
+ });
+ var scrollbarsVisible = listHeight > options.scrollHeight;
+ list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
+ if (!scrollbarsVisible) {
+ // IE doesn't recalculate width when scrollbar disappears
+ listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
+ }
+ }
+
+ }
+ },
+ selected: function() {
+ var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
+ return selected && selected.length && $.data(selected[0], "ac_data");
+ },
+ emptyList: function (){
+ list && list.empty();
+ },
+ unbind: function() {
+ element && element.remove();
+ }
+ };
+};
+
+$.fn.selection = function(start, end) {
+ if (start !== undefined) {
+ return this.each(function() {
+ if( this.createTextRange ){
+ var selRange = this.createTextRange();
+ if (end === undefined || start == end) {
+ selRange.move("character", start);
+ selRange.select();
+ } else {
+ selRange.collapse(true);
+ selRange.moveStart("character", start);
+ selRange.moveEnd("character", end);
+ selRange.select();
+ }
+ } else if( this.setSelectionRange ){
+ this.setSelectionRange(start, end);
+ } else if( this.selectionStart ){
+ this.selectionStart = start;
+ this.selectionEnd = end;
+ }
+ });
+ }
+ var field = this[0];
+ if ( field.createTextRange ) {
+ var range = document.selection.createRange(),
+ orig = field.value,
+ teststring = "<->",
+ textLength = range.text.length;
+ range.text = teststring;
+ var caretAt = field.value.indexOf(teststring);
+ field.value = orig;
+ this.selection(caretAt, caretAt + textLength);
+ return {
+ start: caretAt,
+ end: caretAt + textLength
+ }
+ } else if( field.selectionStart !== undefined ){
+ return {
+ start: field.selectionStart,
+ end: field.selectionEnd
+ }
+ }
+};
+
+})(jQuery); \ No newline at end of file
diff --git a/search_autocomplete.admin.inc b/search_autocomplete.admin.inc
new file mode 100644
index 0000000..10f6585
--- /dev/null
+++ b/search_autocomplete.admin.inc
@@ -0,0 +1,156 @@
+<?php
+
+/**
+ * @file
+ * Search Autocomplete
+ * Sets the admin part of the module: permissions, hooks, callbacks, etc...
+ *
+ * @authors
+ * Miroslav Talenberg (Dominique CLAUSE) <http://www.axiomcafe.fr/contact>
+ *
+ * Sponsored by:
+ * www.axiomcafe.fr
+ */
+
+
+/**
+ * Implementation of hook_permission().
+ * Valid permissions for this module
+ * @return
+ * An array of valid permissions for the autocomplete module
+ */
+function search_autocomplete_permission() {
+ return array(
+ 'administer Search Autocomplete' => array(
+ 'title' => t('Administer Search Autocomplete'),
+ 'description' => t('Access administration panel for autocompletion settings.'),
+ ),
+ 'use Search Autocomplete' => array(
+ 'title' => t('Use Search Autocomplete'),
+ 'description' => t('Allow usage of autocompletion on forms.'),
+ ),
+ );
+} // function search_autocomplete_permissions()
+
+/**
+ * Implementation of hook_theme().
+ * Define the function to render our forms
+ */
+function search_autocomplete_theme($existing, $type, $theme, $path) {
+ return array(
+ 'search_autocomplete_treelist_form' => array( // register theme function for draggable treelist search forms
+ 'render element' => 'form',
+ ),
+ 'search_autocomplete_form_configuration_fieldset' => array( // register theme function for suggestions list
+ 'render element' => 'form',
+ ),
+ );
+} // function search_autocomplete_theme($existing, $type, $theme, $path)
+
+/**
+ * Implementation of hook_menu().
+ * Create an administration page to access admin form
+ */
+function search_autocomplete_menu() {
+
+ // create the admin setting page: list of forms
+ $items['admin/config/search/search_autocomplete'] = array(
+ 'title' => 'Search Autocomplete settings',
+ 'description' => 'Choose settings and suggestion items for autocompletion in input forms.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('search_autocomplete_treelist_form'),
+ 'access arguments' => array(t('administer Search Autocomplete')),
+ 'access callback' => TRUE,
+ 'file' => 'search_autocomplete.form.treelist.inc',
+ );
+ $items['admin/config/search/search_autocomplete/treelist'] = array(
+ 'title' => 'Search Autocomplete',
+ 'access arguments' => array(t('administer Search Autocomplete')),
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -1,
+ );
+ // create an admin setting page: add a new form
+ $items['admin/config/search/search_autocomplete/add'] = array(
+ 'title' => 'Add a form',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('search_autocomplete_form_add'),
+ 'access arguments' => array(t('administer Search Autocomplete')),
+ 'file' => 'search_autocomplete.form.add.inc',
+ 'type' => MENU_LOCAL_TASK,
+ );
+ // create an admin setting page: configure a form
+ $items['admin/config/search/search_autocomplete/%/configure'] = array(
+ 'title' => 'Search Autocomplete - Configuration',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('search_autocomplete_form_configure'),
+ 'access arguments' => array(t('administer Search Autocomplete')),
+ 'file' => 'search_autocomplete.form.configure.inc',
+ );
+ // create an admin setting page: delete a form
+ $items['admin/config/search/search_autocomplete/%/delete/%'] = array(
+ 'title' => 'Search Autocomplete - Delete',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('search_autocomplete_form_delete', 4, 6),
+ 'access arguments' => array(t('administer Search Autocomplete')),
+ 'file' => 'search_autocomplete.form.delete.inc',
+ );
+ // create an admin setting page: add a new suggestion
+ $items['admin/config/search/search_autocomplete/new'] = array(
+ 'title' => 'Add a suggestion type',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('search_autocomplete_suggestion_new'),
+ 'access arguments' => array(t('administer Search Autocomplete')),
+ 'file' => 'search_autocomplete.suggestion.configure.inc',
+ 'type' => MENU_LOCAL_TASK,
+ );
+ // create the admin setting page: configure a suggestion
+ $items['admin/config/search/search_autocomplete/suggestion/%/edit'] = array(
+ 'title' => 'Search Autocomplete - Configuration',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('search_autocomplete_suggestion_configure'),
+ 'access arguments' => array(t('administer Search Autocomplete')),
+ 'file' => 'search_autocomplete.suggestion.configure.inc',
+ );
+ // create the admin setting page: delete a suggestion
+ $items['admin/config/search/search_autocomplete/suggestion/%/delete'] = array(
+ 'title' => 'Search Autocomplete - Delete',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('search_autocomplete_suggestion_delete'),
+ 'access arguments' => array(t('administer Search Autocomplete')),
+ 'file' => 'search_autocomplete.suggestion.delete.inc',
+ );
+ // create a callback page for JSON suggestions
+ $items['search_autocomplete/%/autocomplete'] = array(
+ 'page callback' => 'search_autocomplete_autocomplete',
+ 'access arguments' => array('use Search Autocomplete'),
+ 'type' => MENU_CALLBACK,
+ );
+ return $items;
+} // function search_autocomplete_menu()
+
+/**
+ * Implementation of hook_help().
+ */
+function search_autocomplete_help($path, $arg) {
+ $help = '';
+ switch ($path) {
+ case 'admin/modules#description':
+ $help = t('Allows the users with the right permission to use advanced autocompletion features on forms.');
+ break;
+ case 'admin/config/search/search_autocomplete':
+ $help = '<div>' . t('Search Autocomplete helps you to enhance your search forms with autocompleted suggestions.') . '</div>';
+ $help .= '<div>' . t('Use this form to activate the forms you want to autocomplete.') . '</div>';
+ $help .= '<div>' . t('You may want to add new possible form to autocomplete. To do so, please use the tab <a href="search_autocomplete/add">Add a form</a>.') . '</div>';
+ $help .= '<div>' . t('You may also want to add a new possible suggestion. To do so, please use the tab <a href="search_autocomplete/new">Add a suggestion</a>.') . '</div>';
+ break;
+ case 'admin/config/search/search_autocomplete/add':
+ $help = '<div>' . t('This page allows you to add a new form to be autocompleted with the Search Autocomplete module.') . '</div>';
+ $help .= '<div>' . t('This is an advanced feature configuration. Use it only if you know what you are doing and after reading <a href="http://projects.axiomcafe.fr/search-autocomplete">documentation</a>. If you wish help, please ask for a new default form to be added in the next release of Search Autocomplete module.') . '</div>';
+ break;
+ case 'admin/config/search/search_autocomplete/%/configure':
+ $help = '<div>' . t('You are currently configuring the options to autocomplete a form.') . '</div>';
+ $help .= '<div>' . t('Every children forms will be modified as well.') . '</div>';
+ break;
+ }
+ return $help;
+}
diff --git a/search_autocomplete.form.add.inc b/search_autocomplete.form.add.inc
new file mode 100644
index 0000000..4fc2110
--- /dev/null
+++ b/search_autocomplete.form.add.inc
@@ -0,0 +1,113 @@
+<?php
+
+/**
+ * @file
+ * Search Autocomplete
+ * Add a new form to Search Autocomplete form list.
+ *
+ * @authors
+ * Miroslav Talenberg (Dominique CLAUSE) <http://www.axiomcafe.fr/contact>
+ *
+ * Sponsored by:
+ * www.axiomcafe.fr
+ */
+
+/**
+ * MENU CALLBACK:
+ * Define the page to add a form.
+ * @return A rendered form
+ */
+function search_autocomplete_form_add() {
+ $form = array();
+ /* ------------------------------------------------------------------ */
+ $form['title'] = array(
+ '#title' => t('Title'),
+ '#description' => 'Please enter a title for this form',
+ '#type' => 'textfield',
+ '#default_value' => '',
+ '#maxlength' => 255,
+ '#required' => TRUE,
+ );
+ $descr = t('Enter a valid query selector for the form. This should be an id or a class surrounding the input box.') . '<br/>' . t('Do not include input. In case of a doubt, refer to the <a href="http://projects.axiomcafe.fr/search-autocomplete">documentation</a>');
+ $form['selector'] = array(
+ '#title' => t('Selector'),
+ '#description' => $descr,
+ '#type' => 'textfield',
+ '#default_value' => '',
+ '#maxlength' => 255,
+ '#required' => TRUE,
+ );
+ // submit buton
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ );
+ return $form;
+}
+
+// -------------------------------------------------------------------------------------
+/**
+ * Implements hook_submit().
+ * Save the new form in database
+ */
+function search_autocomplete_form_add_submit($form, &$form_state) {
+ $ok_query = TRUE; // so far so good!
+
+ // Update the database with the new values
+ $what = '';
+ $sids = '';
+ $weights = '';
+
+ // Get the form values
+ $values = $form_state['values'];
+
+ // Check if aready existing records
+ $result = db_select('search_autocomplete_forms', 'f')
+ ->fields('f')
+ ->condition('title', $values['title'])
+ ->condition('selector', $values['selector'])
+ ->execute()
+ ->fetchAll();
+
+ foreach ($result as $obj) {
+ drupal_set_message(t("The title or the selector already exists. Please choose another one."), 'error');
+ return;
+ }
+
+ // Insert the new form in database
+ $fid = db_insert('search_autocomplete_forms')
+ ->fields(array(
+ 'title' => $values['title'],
+ 'selector' => $values['selector']
+ ))
+ ->execute();
+
+ // Connect default suggestions to this form
+ $result = db_select('search_autocomplete_suggestions', 's')
+ ->fields('s')
+ ->condition('sug_fid', 0)
+ ->execute()
+ ->fetchAllAssoc('sid');
+
+ foreach ($result as $match) {
+ db_insert('search_autocomplete_suggestions')
+ ->fields(array(
+ 'sid' => $match->sid,
+ 'sug_fid' => $fid,
+ 'sug_enabled' => $match->sug_enabled,
+ 'sug_prefix' => $match->sug_prefix,
+ 'sug_title' => $match->sug_title,
+ 'sug_name' => $match->sug_name,
+ 'sug_dependencies' => $match->sug_dependencies,
+ 'sug_weight' => $match->sug_weight,
+ 'sug_query' => $match->sug_query
+ ))
+ ->execute();
+ }
+
+ // redirect to configuration page
+ $form_state['redirect'] = 'admin/config/search/search_autocomplete/' . $fid . '/configure';
+
+ // Give a return to the user
+ $ok_query ? drupal_set_message(t('The form has been created successfully !') . '<br/>' . t('Please check its configuration.')) : drupal_set_message(t("An error has occured while creating the form. Please, double check your settings!"), 'error');
+} \ No newline at end of file
diff --git a/search_autocomplete.form.configure.inc b/search_autocomplete.form.configure.inc
new file mode 100644
index 0000000..3cc588e
--- /dev/null
+++ b/search_autocomplete.form.configure.inc
@@ -0,0 +1,277 @@
+<?php
+
+/**
+ * @file
+ * Search Autocomplete
+ * Helper functions to retrive suggestions from database
+ *
+ * @authors
+ * Miroslav Talenberg (Dominique CLAUSE) <http://www.axiomcafe.fr/contact>
+ *
+ * Sponsored by:
+ * www.axiomcafe.fr
+ */
+
+/**
+ * MENU CALLBACK:
+ * Define the form to configure the suggestions.
+ * @return A rendered form
+ */
+function search_autocomplete_form_configure($form, &$form_state) {
+ $base = "admin/config/search/search_autocomplete"; // base URL for this module configurations
+ // get data from database
+ $fid = arg(4);
+
+ $result = db_select('search_autocomplete_forms', 'f')
+ ->fields('f')
+ ->condition('fid', $fid)
+ ->execute()
+ ->fetchAllAssoc('fid');
+
+ foreach ($result as $item) {
+ $form['fid'] = array(
+ '#type' => 'hidden',
+ '#value' => $fid,
+ );
+ // ------------------------------------------------------------------
+ // HOW - How to display Search Autocomplete suggestions
+ $form['search_autocomplete_how'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('HOW - How to display Search Autocomplete suggestions?'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE
+ );
+ // Number of characters before suggestions
+ $trigger = array();
+ for ($i=1; $i<20; $i++) {
+ $trigger[$i] = $i . ' ' . t('characters');
+ }
+ $form['search_autocomplete_how']['min_char'] = array(
+ '#type' => 'select',
+ '#title' => t('Minimum keyword size that uncouple autocomplete search'),
+ '#default_value' => $item->min_char,
+ '#options' => $trigger,
+ '#multiple' => FALSE,
+ '#required' => TRUE,
+ );
+ // Number of suggestions to display
+ $limit = array();
+ for ($i=1;$i<50;$i++) {
+ $limit[$i] = $i . ' ' . t('results');
+ }
+ $form['search_autocomplete_how']['max_sug'] = array(
+ '#type' => 'select',
+ '#title' => t('Limit of the autocomplete search result'),
+ '#default_value' => $item->max_sug,
+ '#options' => $limit,
+ '#multiple' => FALSE,
+ '#required' => TRUE
+ );
+
+ // ------------------------------------------------------------------
+ // WHAT - What to display in Search Autocomplete suggestions
+ $form['search_autocomplete_what'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('WHAT - What to display in Search Autocomplete suggestions?'),
+ '#description' => t('Choose which data should be added to autocompletion suggestions.'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#theme' => 'search_autocomplete_form_configuration_fieldset'
+ );
+ $form['search_autocomplete_what']['#tree'] = TRUE;
+
+ // Built possible suggestions
+ $result = db_select('search_autocomplete_suggestions', 's')
+ ->fields('s')
+ ->condition('sug_fid', $fid)
+ ->orderBy('sug_weight')
+ ->execute()
+ ->fetchAllAssoc('sid');
+
+ foreach ($result as $suggestion) {
+ // if the module has no dependencies, or if the dependencies are enabled: activate the suggestion
+ if (drupal_strlen($suggestion->sug_dependencies) == 0 || module_exists($suggestion->sug_dependencies)) {
+ $activate = TRUE;
+ $title = $suggestion->sug_title;
+ } // else, do not activate the suggestion and has the dependencie reason
+ else {
+ $activate = FALSE;
+ $title = $suggestion->sug_title . ' ' . t('(require @module module)', array('@module' => $suggestion->sug_dependencies));
+ }
+ $sid = $suggestion->sid;
+
+ $form['search_autocomplete_what'][$sid]['sid'] = array(
+ '#type' => 'hidden',
+ '#value' => $sid,
+ '#disabled' => !$activate
+ );
+ $form['search_autocomplete_what'][$sid]['sug_title'] = array(
+ '#type' => 'item',
+ '#title' => $title,
+ '#disabled' => !$activate
+ );
+ $form['search_autocomplete_what'][$sid]['sug_enabled'] = array(
+ '#type' => 'checkbox',
+ '#return_value' => 1,
+ '#default_value' => $suggestion->sug_enabled,
+ '#disabled' => !$activate
+ );
+ $form['search_autocomplete_what'][$sid]['sug_prefix'] = array( // -> sug_prefix
+ '#type' => 'textfield',
+ '#default_value' => $suggestion->sug_prefix,
+ '#maxlength' => 255,
+ '#size' => 35,
+ '#disabled' => !$activate
+ );
+ $form['search_autocomplete_what'][$sid]['sug_weight'] = array( // -> weight of the item in hierarchy
+ '#type' => 'weight',
+ '#default_value' => $suggestion->sug_weight,
+ '#disabled' => !$activate
+ );
+ $form['search_autocomplete_what'][$sid]['sug_edit'] = array( // -> weight of the item in hierarchy
+ '#type' => 'item',
+ '#title' => l(t('edit'), "$base/suggestion/" . $suggestion->sid . "/edit")
+ );
+ }
+
+ // ------------------------------------------------------------------
+ // ADVANCED - Advanced options
+ $form['search_autocomplete_advanced'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('ADVANCED - Advanced options'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE
+ );
+ $form['search_autocomplete_advanced']['selector'] = array(
+ '#type' => 'textfield',
+ '#title' => t('ID selector for this form'),
+ '#description' => t('Please change only if you know what you do, read <a href="http://projects.axiomcafe.fr/search-autocomplete">documentation</a> first.'),
+ '#default_value' => $item->selector,
+ '#maxlength' => 255,
+ '#size' => 35
+ );
+ // Add button validation
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save configuration')
+ );
+ }
+ return $form;
+}
+
+// -------------------------------------------------------------------------------------
+/**
+ * Implementation of hook_submit().
+ * Save the changes in the database
+ */
+function search_autocomplete_form_configure_submit($form, &$form_state) {
+ $ok_query = TRUE; // so far so good!
+ //Update the database with the new values
+ $what = '';
+ $sids = '';
+ $weights = '';
+
+ // ###
+ // UPDATE THE FORM
+ // -> update form
+ $values = $form_state['values'];
+ db_update('search_autocomplete_forms')
+ ->fields(array(
+ 'min_char' => $values['min_char'],
+ 'max_sug' => $values['max_sug'],
+ 'selector' => $values['selector']
+ ))
+ ->condition('fid', $values['fid'])
+ ->execute();
+ // -> update each suggestions
+ foreach ($form_state['input']['search_autocomplete_what'] as $key => $item) {
+ drupal_write_record('search_autocomplete_suggestions', $values['search_autocomplete_what'][$key], 'sid');
+ }
+ // ###
+ // UPDATE CHILD LIST BUT NOT THE ADVANCED OPTIONS
+ $fids = _search_autocomplete_get_all_children($values['fid']);
+ // update the settings for this form + every children form
+ foreach ($fids as $fid) {
+ // -> update form
+ db_update('search_autocomplete_forms')
+ ->fields(array(
+ 'min_char' => $values['min_char'],
+ 'max_sug' => $values['max_sug']
+ ))
+ ->condition('fid', $fid)
+ ->execute();
+ // -> update each suggestions
+ foreach ($form_state['input']['search_autocomplete_what'] as $key => $item) {
+ drupal_write_record('search_autocomplete_suggestions', $values['search_autocomplete_what'][$key], 'sid');
+ }
+ }
+ // ###
+
+ $form_state['redirect'] = 'admin/config/search/search_autocomplete';
+ $ok_query ? drupal_set_message(t("Configuration success !")) : drupal_set_message(t("An error has occured while saving the settings. Please, double check your settings!"), 'error');
+}
+
+
+// -------------------------------------------------------------------------------------
+/**
+ * CALLBACK:
+ * Theme function for this treelist form
+ */
+function theme_search_autocomplete_form_configuration_fieldset($variables) {
+ $form = $variables['form'];
+ $header = array('Title', 'Enabled', 'Prefix of the item in suggestions', 'Weight', 'Operation');
+
+ $rows = array();
+ // for each elements to anchor in the form
+ foreach (element_children($form) as $key) {
+ $element = &$form[$key];
+ $element['sug_weight']['#attributes']['class'] = array('weight-group');
+
+ $rows[] = array(
+ 'data' => array(
+ drupal_render($element['sug_title']),
+ drupal_render($element['sug_enabled']),
+ drupal_render($element['sug_prefix']),
+ drupal_render($element['sug_weight']),
+ drupal_render($element['sug_edit'])
+ ),
+ 'class' => array('draggable'),
+ );
+ }
+ // Themize the table and render the form
+ $output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'draggable-table')));
+ $output .= drupal_render($form['submit']);
+ $output .= drupal_render_children($form);
+
+ drupal_add_tabledrag('draggable-table', 'order', 'sibling', 'weight-group');
+
+ return $output;
+} // function theme_search_autocomplete_form_configuration()
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+//// HELPER FUNCTIONS ////
+
+// -------------------------------------------------------------------------------------
+/**
+ * Helper function: get the array of fids every of his children of the caller but not
+ * caller fid.
+ */
+function _search_autocomplete_get_all_children($fid, &$items = array(), $depth = 0) {
+ if ($depth)
+ $items[] = $fid;
+
+ //$result = db_query('SELECT * FROM {search_autocomplete_forms} WHERE parent_fid=:parent_fid', array(':parent_fid' => $fid));
+ $result = db_select('search_autocomplete_forms', 'f')
+ ->fields('f')
+ ->condition('parent_fid', $fid)
+ ->execute()
+ ->fetchAllAssoc('fid');
+
+ foreach ($result as $item) {
+ $depth++;
+ _search_autocomplete_get_all_children($item->fid, $items, $depth);
+ }
+ return $items;
+} \ No newline at end of file
diff --git a/search_autocomplete.form.delete.inc b/search_autocomplete.form.delete.inc
new file mode 100644
index 0000000..594284e
--- /dev/null
+++ b/search_autocomplete.form.delete.inc
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * @file
+ * Search Autocomplete
+ * Delete a form from Search Autocomplete form list.
+ *
+ * @authors
+ * Miroslav Talenberg (Dominique CLAUSE) <http://www.axiomcafe.fr/contact>
+ *
+ * Sponsored by:
+ * www.axiomcafe.fr
+ */
+
+/**
+ * Return the filter delete form.
+ */
+function search_autocomplete_form_delete($form, &$form_state, $parent_fid, $fid) {
+
+ if (!$fid) {
+ drupal_set_message(
+ t('The form has not been found, or the menu callback received a wrong parameter.'),
+ 'error'
+ );
+ watchdog(
+ 'search_autocomplete',
+ 'The form has not been found, or the menu callback received a wrong parameter.',
+ NULL,
+ WATCHDOG_ERROR
+ );
+
+ return $form;
+ }
+
+ $form['parent_fid'] = array(
+ '#type' => 'hidden',
+ '#value' => $parent_fid,
+ );
+ $form['fid'] = array(
+ '#type' => 'hidden',
+ '#value' => $fid,
+ );
+
+ return confirm_form(
+ $form,
+ t('Are you sure you want to delete this form?'),
+ 'admin/config/search/search_autocomplete',
+ NULL,
+ t('Delete'),
+ t('Cancel')
+ );
+}
+
+/**
+ * Submission callback for the filter delete form.
+ */
+function search_autocomplete_form_delete_submit($form, &$form_state) {
+
+ $ok = TRUE;
+ $values = $form_state['values'];
+ $fid = $values['fid'];
+ $parent_fid = $values['parent_fid'];
+
+ db_update('search_autocomplete_forms')
+ ->fields(array(
+ 'parent_fid' => $parent_fid,
+ ))
+ ->condition('parent_fid', $fid)
+ ->execute();
+ db_query('DELETE FROM {search_autocomplete_forms} WHERE fid = :fid', array(':fid' => $fid));
+ db_query('DELETE FROM {search_autocomplete_suggestions} WHERE sug_fid = :sug_fid', array(':sug_fid' => $fid));
+
+ // Give a return to the user
+ drupal_set_message(t("The form has been successfully deleted !"));
+
+ $form_state['redirect'] = 'admin/config/search/search_autocomplete';
+} \ No newline at end of file
diff --git a/search_autocomplete.form.treelist.inc b/search_autocomplete.form.treelist.inc
new file mode 100644
index 0000000..578cb31
--- /dev/null
+++ b/search_autocomplete.form.treelist.inc
@@ -0,0 +1,236 @@
+<?php
+
+/**
+ * @file
+ * Search Autocomplete
+ * Display the list of form to autocomplete and themis it as a draggable table.
+ *
+ * @authors
+ * Miroslav Talenberg (Dominique CLAUSE) <http://www.axiomcafe.fr/contact>
+ *
+ * Sponsored by:
+ * www.axiomcafe.fr
+ */
+
+//-------------------------------------------------------------------------------------
+/**
+ * Menu Callback: create the form to list the searchforms
+ * @return the form
+ */
+function search_autocomplete_treelist_form($form, &$form_state) {
+ $base = "admin/config/search/search_autocomplete"; // base URL for this module configurations
+
+ $data = _search_autocomplete_get_items(); // get all forms ordered as a tree;
+ $form['my_items'] = array();
+ $form['my_items']['#tree'] = TRUE;
+
+ // for each items to render in the form
+ foreach ($data as $values) {
+ $fid = $values->fid;
+ $title = $values->title;
+ $weight = $values->weight;
+ $enabled = $values->enabled;
+ $parent_fid = $values->parent_fid;
+
+ $form['my_items'][$fid] = array( // element for this item
+ 'title' => array( // -> human readeable title
+ '#type' => 'item',
+ '#title' => check_plain($title),
+ ),
+ 'enabled' => array( // -> defines if the autocompletion is enabled for this item
+ '#type' => 'checkbox',
+ '#default_value' => $enabled,
+ ),
+ 'operations' => array(
+ 'configure' => array(
+ '#type' => 'item',
+ '#title' => filter_xss(l(t('configure'), "$base/$fid/configure")),
+ ),
+ 'delete' => array(
+ '#type' => 'item',
+ '#title' => filter_xss(l(t('delete'), "$base/$parent_fid/delete/$fid")),
+ ),
+ ),
+ 'weight' => array( // -> weight of the item in hierarchy
+ '#type' => 'weight',
+ '#delta' => count($data),
+ '#default_value' => $weight,
+ ),
+ 'fid' => array( // -> the individual id if the item
+ '#type' => 'hidden',
+ '#value' => $fid,
+ ),
+ 'parent_fid' => array( // -> id of the parent item in hierarchy
+ '#type' => 'textfield',
+ '#default_value' => $parent_fid
+ ),
+ '#depth' => $values->depth, // -> depth of the item
+ );
+ }
+
+ // submit buton
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ );
+ return $form;
+} // function search_autocomplete_treelist_form()
+
+//-------------------------------------------------------------------------------------
+/**
+ * Implementation of hook_submit().
+ * Save the changes in the database
+ */
+function search_autocomplete_treelist_form_submit($form, &$form_state) {
+ //Update the database with the new values
+ foreach ($form_state['values']['my_items'] as $item) {
+ db_update('search_autocomplete_forms')
+ ->fields(array(
+ 'weight' => $item['weight'],
+ 'parent_fid' => $item['parent_fid'],
+ 'enabled' => $item['enabled'],
+ ))
+ ->condition('fid', $item['fid'])
+ ->execute();
+ }
+ drupal_set_message(t('Configuration success'));
+} //function search_autocomplete_treelist_form_submit()
+
+/**
+ * This function transforms the choices into a string.
+ */
+function array2str($myarray, &$output = '', $parentkey = "---") {
+ foreach ($myarray as $key => $value) {
+ if (is_array($value)) {
+ $parentkey .= "---";
+ array2str($value, $output, $parentkey);
+ $parentkey = substr($parentkey, 0, -3);
+ }
+ else {
+ $output .= $parentkey . "[" . $key . "] => " . $value . "<br/>";
+ }
+ }
+ return $output;
+}
+//-------------------------------------------------------------------------------------
+/**
+ * CALLBACK:
+ * Theme function for this treelist form
+ */
+function theme_search_autocomplete_treelist_form($variables) {
+ $form = $variables['form'];
+
+ drupal_add_tabledrag('my-draggable-table', 'order', 'sibling', 'weight-group');
+ drupal_add_tabledrag('my-draggable-table', 'match', 'parent', 'parent-group', 'parent-group', 'id-group');
+
+ $rows = array();
+ // for each elements to anchor in the form
+ foreach (element_children($form['my_items']) as $key) {
+
+ $element = &$form['my_items'][$key];
+ $element['weight']['#attributes']['class'] = array('weight-group');
+ $element['fid']['#attributes']['class'] = array('id-group');
+ $element['parent_fid']['#attributes']['class'] = array('parent-group');
+
+ $rows[] = array(
+ 'data' => array(
+ theme('indentation', array('size' => $element['#depth'])) . drupal_render($element['title']),
+ drupal_render($element['enabled']),
+ drupal_render($element['weight']) .
+ drupal_render($element['fid']) .
+ drupal_render($element['parent_fid']),
+ drupal_render($element['operations']['configure']),
+ drupal_render($element['operations']['delete']),
+ ),
+ 'class' => array('draggable'),
+ );
+ }
+ // create table headers
+ $header = array(t('Form nam'), t('Enabled'), t('Weight'), array(
+ 'data' => t('Operations'),
+ 'colspan' => 2,
+ ));
+ // Themize the table and render the form
+ $output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'my-draggable-table')));
+ $output .= drupal_render($form['submit']);
+ $output .= drupal_render_children($form);
+
+ return $output;
+} //function theme_search_autocomplete_treelist_form()
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+//// HELPER FUNCTIONS ////
+
+//-------------------------------------------------------------------------------------
+/**
+ * Helper function: get the forms from database and render them hierarchically
+ * return the items sorted
+ */
+function _search_autocomplete_get_items() {
+ $items = array();
+ // get data in database and fetch it
+ $result = db_select('search_autocomplete_forms', 'f')
+ ->fields('f')
+ ->orderBy('weight') //ORDER BY created
+ ->execute()
+ ->fetchAllAssoc('fid');
+
+ // order the list
+ return _search_autocomplete_get_ordered_list(0, $result);
+} //function _search_autocomplete_get_items()
+
+//-------------------------------------------------------------------------------------
+/**
+ * HELPER:
+ * Returns a tree list of all items in the $items array that are children
+ * of the supplied parent, ordered appropriately
+ * @return the ordered tree
+ */
+function _search_autocomplete_get_ordered_list($parent, $items, $depth = 0) {
+
+ $remnant = array(); $children = array();
+ // Insert direct children in $children
+ // and remaining in $remnant
+ foreach ($items as $item) {
+ if ($item->parent_fid == $parent) {
+ $item->depth = $depth;
+ $children[] = $item;
+ }
+ else
+ $remnant[] = $item;
+ }
+
+ // Sort the direct children by weight
+ usort($children, '_search_autocomplete_sort_by_weight');
+
+ $ancestors = array();
+
+ foreach ($children as $child) {
+ // Order the child ancestors iteratively
+ $child_children = _search_autocomplete_get_ordered_list($child->fid, $remnant, $depth + 1);
+ // Push the results into the main array below the child
+ $ancestors[] = $child;
+ // Merge it if needed
+ if (count($child_children)) {
+ $ancestors = array_merge($ancestors, $child_children);
+ }
+ }
+ return $ancestors;
+}
+
+//-------------------------------------------------------------------------------------
+/**
+ * HELPER:
+ * Usort function for sorting arrays by weight
+ * @return 1: if ($a < $b),
+ * 0 if equal,
+ * -1 otherwise
+ */
+function _search_autocomplete_sort_by_weight($a, $b) {
+ if ($a->weight == $b->weight)
+ return 0;
+ return ($a->weight < $b->weight) ? -1 : 1;
+}
+
diff --git a/search_autocomplete.info b/search_autocomplete.info
index b87f9c9..683081e 100644
--- a/search_autocomplete.info
+++ b/search_autocomplete.info
@@ -1,7 +1,7 @@
name = Search Autocomplete
description = Provides aucompletion for Drupal search forms.
core = 7.x
-package = User interface
+package = Search
+version = 2.0
dependencies[] = search
-files[] = search_autocomplete.module
-configure = admin/config/user-interface/search_autocomplete \ No newline at end of file
+configure = admin/config/search/search_autocomplete \ No newline at end of file
diff --git a/search_autocomplete.install b/search_autocomplete.install
new file mode 100644
index 0000000..e1e69cb
--- /dev/null
+++ b/search_autocomplete.install
@@ -0,0 +1,300 @@
+<?php
+
+/**
+ * @file
+ * This file is used to install/update/delete the module tables in database
+ *
+ * @authors
+ * Miroslav Talenberg (Dominique CLAUSE) <http://www.axiomcafe.fr/contact>
+ *
+ * Sponsored by:
+ * www.axiomcafe.fr
+ */
+
+// -----------------------------------------------------------------------------------------------
+/**
+ * Implementation of hook_schema().
+ * Set the schema of database
+ * @return the schema for of the table to create
+ */
+function search_autocomplete_schema() {
+ // schema for search_autocomplete database
+ $schema['search_autocomplete_forms'] = array(
+ 'description' => t('Store the forms to autocomplete using Search Autocomplete.'),
+ 'fields' => array(
+ 'fid' => array(
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ 'title' => array(
+ 'description' => 'Human readable name for the form',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'selector' => array(
+ 'description' => 'Reference id selector of the the form in drupal',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'weight' => array(
+ 'description' => 'Form weight in table',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'enabled' => array(
+ 'description' => 'Define if autocomplete is activated or not',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'parent_fid' => array(
+ 'description' => 'Define if the from follows the configuration of another one',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'min_char' => array(
+ 'description' => 'Minimum of character before triggering suggestions',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 3,
+ ),
+ 'max_sug' => array(
+ 'description' => 'Maximum number of suggestions',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 15,
+ ),
+ ),
+ 'primary key' => array('fid'),
+ );
+
+ // schema for search_autocomplete database
+ $schema['search_autocomplete_suggestions'] = array(
+ 'description' => t('Store the suggestions for this form.'),
+ 'fields' => array(
+ 'sid' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'sug_fid' => array(
+ 'description' => 'Form fid the Suggestion belongs to',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'sug_enabled' => array(
+ 'description' => 'Define if suggestion is activated or not',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'sug_prefix' => array(
+ 'description' => 'Human readable prefix in suggestion',
+ 'type' => 'varchar',
+ 'length' => 15,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'sug_title' => array(
+ 'description' => 'Human readable title for the suggestion',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'sug_name' => array(
+ 'description' => 'Reference name of the the suggestion in drupal',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'sug_dependencies' => array(
+ 'description' => 'Name of the module (if such) which the suggestion depends on',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'sug_weight' => array(
+ 'description' => 'Suggestion weight in table',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'sug_query' => array(
+ 'description' => 'The database query for this suggestion',
+ 'type' => 'varchar',
+ 'length' => 512,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ ),
+ );
+
+
+ return $schema;
+} // function search_autocomplete_schema
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Implementation of hook_install().
+ */
+function search_autocomplete_install() {
+
+ $limit = variable_get('search_autocomplete_limit', 15);
+ $trigger = variable_get('search_autocomplete_trigger', 3);
+ $enabled = 1;
+
+ // ----------------
+ // declare insertion statement
+ $insert = db_insert('search_autocomplete_forms')
+ ->fields(array('title', 'selector', 'weight', 'enabled', 'min_char', 'max_sug'));
+ $insert->values(array(
+ 'title' => st('Search page - Node Tab') . " (search/node/%)",
+ 'selector' => '#search-form[action="/search/node"]',
+ 'weight' => 0,
+ 'enabled' => $enabled,
+ 'min_char' => $trigger,
+ 'max_sug' => $limit
+ ));
+ $insert->values(array(
+ 'title' => st('Search page - User Tab') . " (search/user/%)",
+ 'selector' => '#search-form[action="/search/user"]',
+ 'weight' => 1,
+ 'enabled' => $enabled,
+ 'min_char' => $trigger,
+ 'max_sug' => $limit
+ ));
+ $insert->values(array(
+ 'title' => st('Search Block'),
+ 'selector' => "#search-block-form",
+ 'weight' => 0,
+ 'enabled' => $enabled,
+ 'min_char' => $trigger,
+ 'max_sug' => $limit
+ ));
+ $insert->execute();
+
+ // declare insertion statement
+ $insert = db_insert('search_autocomplete_suggestions')
+ ->fields(array(
+ 'sid',
+ 'sug_fid',
+ 'sug_enabled',
+ 'sug_prefix',
+ 'sug_title',
+ 'sug_name',
+ 'sug_dependencies',
+ 'sug_weight',
+ 'sug_query')
+ );
+ // for earch possible default form
+ for ($form = 0; $form <= 3; $form++) {
+ // values for user title default form
+ $insert->values(array(
+ 'sid' => 1,
+ 'sug_fid' => $form,
+ 'sug_enabled' => 1,
+ 'sug_prefix' => "node:",
+ 'sug_title' => "Add node titles",
+ 'sug_name' => "node_title",
+ 'sug_dependencies' => "",
+ 'sug_weight' => 1,
+ 'sug_query' => "SELECT n.title FROM {node} n WHERE n.status = 1 AND LOWER(n.title) LIKE LOWER('%%%s%%')"
+ ));
+ // values for username default form
+ $insert->values(array(
+ 'sid' => 2,
+ 'sug_fid' => $form,
+ 'sug_enabled' => 1,
+ 'sug_prefix' => "user:",
+ 'sug_title' => "Add usernames",
+ 'sug_name' => "username",
+ 'sug_dependencies' => "",
+ 'sug_weight' => 2,
+ 'sug_query' => "SELECT u.name FROM {users} u WHERE u.status = 1 AND LOWER(u.name) LIKE LOWER('%%%s%%')"
+ ));
+ // values for taxonomies default form
+ $insert->values(array(
+ 'sid' => 3,
+ 'sug_fid' => $form,
+ 'sug_enabled' => 1,
+ 'sug_prefix' => "taxo:",
+ 'sug_title' => "Add taxonomies",
+ 'sug_name' => "taxo_title",
+ 'sug_dependencies' => "taxonomy",
+ 'sug_weight' => 3,
+ 'sug_query' => "SELECT t.name FROM {taxonomy_term_data} t WHERE LOWER(t.name) LIKE LOWER('%%%s%%')"
+ ));
+ // values for comment title default form
+ $insert->values(array(
+ 'sid' => 4,
+ 'sug_fid' => $form,
+ 'sug_enabled' => 1,
+ 'sug_prefix' => "comment:",
+ 'sug_title' => "Add comment titles",
+ 'sug_name' => "comment_title",
+ 'sug_dependencies' => "comment",
+ 'sug_weight' => 4,
+ 'sug_query' => "SELECT c.subject FROM {comment} c WHERE c.subject LIKE LOWER('%%%s%%')"
+ ));
+ // values for comment title default form
+ $insert->values(array(
+ 'sid' => 5,
+ 'sug_fid' => $form,
+ 'sug_enabled' => 1,
+ 'sug_prefix' => "",
+ 'sug_title' => "Add all possible words (any word appearing on your website)",
+ 'sug_name' => "word_title",
+ 'sug_dependencies' => "search",
+ 'sug_weight' => 5,
+ 'sug_query' => "SELECT DISTINCT s.word FROM {search_index} s, {node} n WHERE s.type = 'node' AND n.nid = s.sid AND n.status = 1 AND LOWER(s.word) LIKE LOWER('%%%s%%')"
+ ));
+ $insert->execute();
+ }
+
+ drupal_set_message(st('Search Autocomplete is now correctly installed!'));
+
+} // function search_autocomplete_install
+
+// -----------------------------------------------------------------------------------------------
+/**
+ * Implementation of hook_update_N().
+ * Get ready from version 1.x to 2.x
+ */
+function search_autocomplete_update_6200() {
+ // if tables does'not already exists: run install
+ $ret = array();
+ $ok_result = TRUE; // so far so good
+
+ $results = drupal_install_schema('search_autocomplete'); //Install the database specified in 'function search_autocomplete_schema'
+ foreach ($results as $result) { // Check eventual errors that could have occured
+ if (!$result->success)
+ drupal_set_message(st('An error has occured during table creation, please retry. If the problem persist please post an issue and report the code: #err_code:400 @query', $result['query']), 'error');
+ }
+
+ if (db_table_exists('search_autocomplete_forms') && db_table_exists('search_autocomplete_suggestions')) {
+ search_autocomplete_install();
+ }
+
+ variable_del('search_autocomplete_forms');
+ variable_del('search_autocomplete_test');
+ variable_del('search_autocomplete_limit');
+ variable_del('search_autocomplete_trigger');
+ variable_del('search_autocomplete_method');
+
+ // Rebuild system table contents.
+ system_rebuild_module_data();
+ system_rebuild_theme_data();
+
+ return $ret;
+} \ No newline at end of file
diff --git a/search_autocomplete.module b/search_autocomplete.module
index fa2e673..1ca5feb 100644
--- a/search_autocomplete.module
+++ b/search_autocomplete.module
@@ -6,199 +6,92 @@
* Enables autocomplete functionality on search fields.
*
* @authors
- * D7 port by Miroslav (Dominique CLAUSE), <http://www.axiomcafe.fr/contact>
+ * Miroslav Talenberg (Dominique CLAUSE) <http://www.axiomcafe.fr/contact>
*
* Sponsored by:
* www.axiomcafe.fr
*/
-/**
- * Implements hook_permission().
- * Valid permissions for this module
- * @return An array of valid permissions for the autocomplete module
- */
-function search_autocomplete_permission() {
- return array(
- 'administer Search Autocomplete' => array(
- 'title' => t('Administer Search Autocomplete'),
- 'description' => t('Access administration panel for autocompletion settings.'),
- ),
- 'search with autocomplete' => array(
- 'title' => t('Use Search Autocomplete'),
- 'description' => t('Allow usage of autocompletion on forms.'),
- ),
- );
- } // function search_autocomplete_permissions()
-
-/**
- * Implements hook_menu().
- * Create an administration page to access admin form
- */
-function search_autocomplete_menu() {
- $items = array();
- $items['admin/config/user-interface/search_autocomplete'] = array(
- 'title' => 'Search Autocomplete',
- 'description' => 'Choose settings and suggestion items for autocompletion',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('search_autocomplete_admin_settings'),
- 'access arguments' => array(t('administer Search Autocomplete')),
- 'type' => MENU_NORMAL_ITEM,
- );
- $items['search_autocomplete/autocomplete'] = array(
- 'page callback' => 'search_autocomplete_autocomplete',
- 'access arguments' => array('search with autocomplete'),
- 'type' => MENU_CALLBACK,
- );
- return $items;
-} // function search_autocomplete_menu()
+include_once('search_autocomplete.admin.inc');
/**
- * Form function, called by drupal_get_form()
- * in current_posts_menu().
+ * Menu callback; autocomplete handler.
+ * Creates suggestions for autocompletion according to settings
+ * @param string the characters entered in the search boxs
*/
-function search_autocomplete_admin_settings($form, &$form_state) {
- $form = array();
-
- $form['search_autocomplete_forms'] = array(
- '#type' => 'checkboxes',
- '#title' => t('Enable autocomplete on'),
- '#description' => t('Select the search forms that should have autocomplete enabled.'),
- '#options' => array(
- 'search_form' => t('Search page (node tab only)'),
- 'search_block_form' => t('Search block'),
- ),
- '#default_value' => variable_get('search_autocomplete_forms', array('search_form', 'search_block_form')),
- );
-
- $form['search_autocomplete_setting'] = array(
- '#type' => 'fieldset',
- '#title' => t('Autocomplete settings'),
- '#collapsible' => TRUE,
- '#collapsed' => FALSE
- );
- $form['search_autocomplete_setting']['search_autocomplete_method'] = array(
- '#type' => 'radios',
- '#title' => t('Choose a search method'),
- '#default_value' => variable_get('search_autocomplete_method', 1),
- '#options' => array(
- '1' => t('Natural sort (no sort)'),
- '2' => t('Sort keyword alphabetically'),
- '3' => t('Sort by keyword\'s score'),
- '4' => t('Sort by keyword\'s relevance (slow)'),
- ),
- '#description' => t("Caution: Some request method can be very slow !"),
- '#autocomplete_path' => 'search_autocomplete/autocomplete',
- '#required' => TRUE,
- );
- $trigger = array();
- for ($i=1;$i<20;$i++) {
- $trigger[$i] = $i . ' ' . t('characters');
- }
- $form['search_autocomplete_setting']['search_autocomplete_trigger'] = array(
- '#type' => 'select',
- '#title' => t('Minimum keyword size that uncouple autocomplete search'),
- '#default_value' => variable_get('search_autocomplete_trigger', variable_get('minimum_word_size', 3)),
- '#options' => $trigger,
- '#multiple' => FALSE,
- '#required' => TRUE,
- );
- $limit = array();
- for ($i=1;$i<50;$i++) {
- $limit[$i] = $i . ' ' . t('results');
+function search_autocomplete_autocomplete($string = '') {
+ $matches = array();
+ $word_items = array();
+ $node_items = array();
+ $user_items = array();
+ $taxo_items = array();
+ $comment_items = array();
+ static $max_sug = 0;
+
+ $fid = arg(1); // get the form calling
+ $words = explode(' ', $string); // explode the words entered in the input string
+ $word = $words[0]; // get the first word entered in the search box
+
+ $result = db_query('SELECT min_char FROM {search_autocomplete_forms} f WHERE f.fid = :fid', array(':fid' => $fid));
+ foreach ($result as $match) {
+ if (drupal_strlen($word) < $match->min_char) { // Check if there if enough letter to start autocompletion.
+ drupal_json_output($matches); // no matches, return it empty
+ return; // if there is not enough letters, stop here
+ }
}
- $form['search_autocomplete_setting']['search_autocomplete_limit'] = array(
- '#type' => 'select',
- '#title' => t('Limit of the autocomplete search result'),
- '#default_value' => variable_get('search_autocomplete_limit', 15),
- '#options' => $limit,
- '#multiple' => FALSE,
- '#required' => TRUE,
- );
-
- $form['search_autocomplete_setting_test'] = array(
- '#type' => 'fieldset',
- '#title' => t('Test field'),
- '#description' => t('In this field, you can check the appearence of an autocomplete search field.'),
- '#collapsible' => TRUE,
- '#collapsed' => TRUE
- );
- $form['search_autocomplete_setting_test']['search_autocomplete_test'] = array(
- '#type' => 'textfield',
- '#title' => t('Search autocomplete test field'),
- '#size' => 40,
- '#maxlength' => 40,
- '#default_value' => '',
- '#description' => t("Enter a keyword and wait for the result."),
- '#autocomplete_path' => 'search_autocomplete/autocomplete',
- '#required' => FALSE,
- );
-
- return system_settings_form($form);
-}
-
-/**
- * Implements hook_form_alter().
- * Changes the various search forms
- */
-function search_autocomplete_form_alter(&$form, &$form_state, $form_id) {
-
- if (user_access('search with autocomplete')) {
-
- $enabled_forms = variable_get('search_autocomplete_forms', array());
-
- if (isset($enabled_forms[$form_id])) {
- // Search block and search box have similar form structure.
- if ($form_id == 'search_block_form') {
- $form[$form_id]['#autocomplete_path'] = 'search_autocomplete/autocomplete';
- }
-
- // Search page; only add autocomplete to node search.
- if ($form_id == 'search_form' && $form['module']['#value'] == 'node') {
- $form['basic']['keys']['#autocomplete_path'] = 'search_autocomplete/autocomplete';
+
+ // get every suggestion types associated with the form being autocompleted
+ $result = db_query('SELECT * FROM {search_autocomplete_suggestions} s WHERE s.sug_fid = :fid AND s.sug_enabled = :sug_enabled', array(':fid' => $fid, 'sug_enabled' => 1));
+ foreach ($result as $item) { // while there is suggestion types to analyse:
+ //if ($query != NULL) {
+ $prefix = $item->sug_prefix; // get the prefix for this suggestion type
+ $query = sprintf($item->sug_query, $word); // get the SQL query for this suggestion type
+ $result = db_query($query); // get all the suggestions of this suggestion type
+ foreach ($result as $obj) {
+ $sug_elem = array_shift(array_values(get_object_vars($obj)));
+ $sug = html_entity_decode(check_plain($sug_elem), ENT_QUOTES);
+ $sug_index = trim($prefix) . ' ' . $sug;
+ $matches[trim($sug_index)] = trim($sug); // add the suggestion to be returned
}
- }
+ //}
}
-}
+ drupal_json_output($matches); // Return matches.
+} // search_autocomplete_autocomplete()
/**
- * Menu callback; autocomplete handler.
- * Creates suggestions for autocompletion according to settings
+ * HOOK OF INIT:
+ * add autocomplete.js on everypage
*/
-function search_autocomplete_autocomplete() {
-
- $args = arg();
- // Allow Drupal to parse the search query.
- $query=explode(" ", $args[2]);
-
- $matches = array();
+function search_autocomplete_init() {
+ global $base_url;
- // Check to see if we've got something to work with.
- if ($query !== NULL && drupal_strlen($query[0]) >= (variable_get('search_autocomplete_trigger', variable_get('minimum_word_size', 3))) ) {
-
- // Perform search using the chosen sort method.
- $limit = variable_get('search_autocomplete_limit', 10);
- $word = $query[0];
- switch (variable_get('search_autocomplete_sort', 'none')) {
- case 'none':
- $result = db_query_range("SELECT DISTINCT s.word FROM {search_index} s, {node} n WHERE s.type = 'node' AND n.nid = s.sid AND n.status = 1 AND LOWER(s.word) LIKE LOWER(:word)", 0, $limit, array(':word' => $word . '%'));
- break;
- case 'alphabetical':
- $result = db_query_range("SELECT DISTINCT i.word FROM {search_index} i, {node} n WHERE i.type = 'node' AND n.nid = i.sid AND n.status = 1 AND LOWER(i.word) LIKE LOWER(:word) ORDER BY i.word ASC", 0, $limit, array(':word' => $word . '%'));
- break;
- case 'score':
- $result = db_query_range("SELECT i.word FROM {search_index} i, {node} n WHERE i.type = 'node' AND n.nid = i.sid AND n.status = 1 AND LOWER(i.word) LIKE LOWER(:word) GROUP BY i.word ORDER BY SUM(i.score) DESC", 0, $limit, array(':word' => $word . '%'));
- break;
- case 'relevance':
- $result = db_query_range("SELECT i.word FROM {search_index} AS i INNER JOIN {search_total} AS t ON i.word = t.word INNER JOIN n AS n ON n.nid = i.sid WHERE i.type = 'node' AND n.status = 1 AND LOWER(i.word) LIKE LOWER(:word) GROUP BY i.word ORDER BY SUM(i.score * t.count) DESC", 0, $limit, array(':word' => $word . '%'));
- break;
- }
-
- // Build an array of returned matches.
- foreach ($result as $match) {
- $matches[$match->word] = check_plain($match->word);
+ // checkout if user have authorization to access the autocompleted form
+ if (user_access('use Search Autocomplete')) {
+ // init:
+ $settings = array();
+
+ // checkout if the db exists (it should)
+ if (db_table_exists('search_autocomplete_forms')) {
+ // get every form to autocomplete
+ $result = db_query('SELECT * FROM {search_autocomplete_forms} WHERE enabled=1');
+ // build the setting array to transfert to JS
+ foreach ($result as $match) {
+ $form_id = 'form' . $match->fid;
+ drupal_add_js(array('search_autocomplete' => array(
+ $form_id => array(
+ 'selector' => $match->selector,
+ 'minChars' => $match->min_char,
+ 'max_sug' => $match->max_sug,
+ 'url' => $base_url . '/search_autocomplete/' . $match->fid . '/autocomplete',
+ 'fid' => $match->fid
+ ))), 'setting');
+ }
+ // If there is some results: need to include the css and js....
+ if ($result) {
+ drupal_add_css(drupal_get_path('module', 'search_autocomplete') . '/css/jquery.autocomplete.css');
+ drupal_add_js(drupal_get_path('module', 'search_autocomplete') . '/js/jquery.autocomplete.js');
+ }
}
}
- // Return matches.
- drupal_json_output($matches);
-} \ No newline at end of file
+} // search_autocomplete_init()
diff --git a/search_autocomplete.suggestion.configure.inc b/search_autocomplete.suggestion.configure.inc
new file mode 100644
index 0000000..f02b037
--- /dev/null
+++ b/search_autocomplete.suggestion.configure.inc
@@ -0,0 +1,225 @@
+<?php
+
+/**
+ * @file
+ * Search Autocomplete
+ * Create a new suggestion to Search Autocomplete suggestion list.
+ *
+ * @authors
+ * Miroslav Talenberg (Dominique CLAUSE) <http://www.axiomcafe.fr/contact>
+ *
+ * Sponsored by:
+ * www.axiomcafe.fr
+ */
+
+/**
+ * MENU CALLBACK:
+ * Define the page to create a new suggestion.
+ * @return A rendered form
+ */
+function search_autocomplete_suggestion_new($form, &$form_state) {
+ $form = search_autocomplete_suggestion_configure($form, &$form_state, 0);
+ return $form;
+}
+
+/**
+ * MENU CALLBACK:
+ * Define the page to configure a suggestion.
+ * @return A rendered form
+ */
+function search_autocomplete_suggestion_configure($form, &$form_state, $sid = -1) {
+ // if no sid provided: get it
+ if ($sid == -1)
+ $sid = arg(5);
+
+ // default values:
+ $title = '';
+ $dependancies = '';
+ $prefix = '';
+ $query = '';
+
+ // if we got a sid: retrieve it from database for edition
+ if ($sid > 0) {
+ $result = db_select('search_autocomplete_suggestions', 's')
+ ->fields('s')
+ ->condition('sid', $sid)
+ ->condition('sug_fid', 0)
+ ->execute()
+ ->fetchAllAssoc('sid');
+ foreach ($result as $match) {
+ $title = $match->sug_title;
+ $dependancies = $match->sug_dependencies;
+ $prefix = $match->sug_prefix;
+ $query = $match->sug_query;
+ }
+ }
+
+ // define the form:
+ $form = array();
+ /* ------------------------------------------------------------------ */
+ $from['sid'] = array(
+ '#type' => 'hidden',
+ '#value' => $sid,
+ );
+ $form['title'] = array(
+ '#title' => t('Title'),
+ '#description' => 'Please enter a title for this suggestion',
+ '#type' => 'textfield',
+ '#default_value' => $title,
+ '#maxlength' => 255,
+ '#required' => TRUE,
+ );
+ $descr = t('Enter the list of machine name modules which your suggestion depend on (items should be separated with comma ",").') . '<br/>' .
+ t('For example, if your suggestion adds taxonomy names, it depends on taxonomy module. Therefore, you should enter: <i>taxonomy</i>.');
+ $form['dependancies'] = array(
+ '#title' => t('Dependency module'),
+ '#description' => $descr,
+ '#type' => 'textfield',
+ '#default_value' => $dependancies,
+ '#maxlength' => 255,
+ '#required' => FALSE,
+ );
+ $descr = t('This prefix will be added to any suggestion of that type during autocompletion.');
+ $form['prefix'] = array(
+ '#title' => t('Suggestion prefix'),
+ '#description' => $descr,
+ '#type' => 'textfield',
+ '#default_value' => $prefix,
+ '#maxlength' => 255,
+ '#required' => FALSE,
+ );
+ $descr = t('The query to perform to retrieve suggestions. If you are not sure what to do, please look at examples in <a href="http://projects.axiomcafe.fr/search-autocomplete">the documentation</a> and/or ask for help');
+ $form['query'] = array(
+ '#title' => t('Query performed to get suggestion'),
+ '#description' => $descr,
+ '#type' => 'textfield',
+ '#default_value' => $query,
+ '#size' => 105,
+ '#maxlength' => 255,
+ '#required' => TRUE,
+ );
+ // submit buton
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ '#name' => 'submit',
+ );
+ if ($sid > 0) { // no need to delete a form that has not been created
+ // delete buton
+ $form['delete'] = array(
+ '#type' => 'submit',
+ '#value' => t('Delete'),
+ '#name' => 'delete',
+ );
+ }
+ // cancel buton
+ $form['cancel'] = array(
+ '#type' => 'submit',
+ '#value' => t('Cancel'),
+ '#name' => 'cancel',
+ );
+ return $form;
+}
+
+//-------------------------------------------------------------------------------------
+
+function search_autocomplete_suggestion_configure_submit($form, &$form_state) {
+ $values = $form_state['values'];
+ $sid = arg(5);
+ $ok_query = TRUE;
+
+ // if cancel button has send the form: cancel any configuration
+ if ($form_state['clicked_button']['#name'] == 'cancel') {
+ drupal_set_message(t('No suggestion has been created'), 'info');
+ $form_state['redirect'] = 'admin/config/search/search_autocomplete';
+ return;
+ }
+ // if delete button has send the form: delete the form
+ if ($form_state['clicked_button']['#name'] == 'delete') {
+ $form_state['redirect'] = 'admin/config/search/search_autocomplete/suggestion/' . $sid . '/delete';
+ return;
+ }
+
+ // change common values (title, dependencies,...)
+ db_update('search_autocomplete_suggestions')
+ ->fields(array(
+ 'sug_title' => $values['title'],
+ 'sug_dependencies' => $values['dependancies'],
+ 'sug_query' => $values['query']
+ ))
+ ->condition('sid', $sid)
+ ->execute();
+ db_update('search_autocomplete_suggestions')
+ ->fields(array(
+ 'sug_prefix' => $values['prefix']
+ ))
+ ->condition('sid', $sid)
+ ->condition('sug_fid', 0)
+ ->execute();
+
+ // redirect to configuration page
+ $form_state['redirect'] = 'admin/config/search/search_autocomplete';
+
+ // Give a return to the user
+ $ok_query ? drupal_set_message(t('The suggestion has been updated successfully !')) : drupal_set_message(t("An error has occured while updating the suggestion. Please, double check your settings!"), 'error');
+
+}
+/**
+ * Implementation of hook_submit().
+ * Save the new form in database
+ */
+function search_autocomplete_suggestion_new_submit($form, &$form_state) {
+ $ok_query = TRUE; // so far so good
+ // if cancel the creation:
+ if ($form_state['clicked_button']['#name'] == 'cancel') {
+ drupal_set_message(t('No suggestion has been created'), 'info');
+ $form_state['redirect'] = 'admin/config/search/search_autocomplete';
+ return;
+ }
+
+ $values = $form_state['values'];
+
+ // get a new possible sid
+ $result = db_select('search_autocomplete_suggestions', 's')
+ ->fields('s', array('sid'))
+ ->orderBy('sid', 'DESC')
+ ->execute()
+ ->fetchAllAssoc('sid');
+
+ $newsid = key($result) + 1;
+ // insert the default entry:
+ $ok_query &= db_insert('search_autocomplete_suggestions')
+ ->fields(array(
+ 'sid' => $newsid,
+ 'sug_fid' => 0,
+ 'sug_title' => $values['title'],
+ 'sug_dependencies' => $values['dependancies'],
+ 'sug_prefix' => $values['prefix'],
+ 'sug_query' => $values['query']
+ ))
+ ->execute();
+
+ // duplicate the suggestion for as many form as needed:
+ $result = db_select('search_autocomplete_forms', 'f')
+ ->fields('f', array('fid'))
+ ->execute()
+ ->fetchAllAssoc('fid');
+ foreach ($result as $match) {
+ db_insert('search_autocomplete_suggestions')
+ ->fields(array(
+ 'sid' => $newsid,
+ 'sug_fid' => $match->fid,
+ 'sug_title' => $values['title'],
+ 'sug_dependencies' => $values['dependancies'],
+ 'sug_prefix' => $values['prefix'],
+ 'sug_query' => $values['query'],
+ ))
+ ->execute();
+ }
+
+ // redirect to configuration page
+ $form_state['redirect'] = 'admin/config/search/search_autocomplete';
+
+ // Give a return to the user
+ drupal_set_message(t('The suggestion has been created successfully !'));
+} \ No newline at end of file
diff --git a/search_autocomplete.suggestion.delete.inc b/search_autocomplete.suggestion.delete.inc
new file mode 100644
index 0000000..77c6bd7
--- /dev/null
+++ b/search_autocomplete.suggestion.delete.inc
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * @file
+ * Search Autocomplete
+ * Delete a form from Search Autocomplete form list.
+ *
+ * @authors
+ * Miroslav Talenberg (Dominique CLAUSE) <http://www.axiomcafe.fr/contact>
+ *
+ * Sponsored by:
+ * www.axiomcafe.fr
+ */
+
+/**
+ * Return the filter delete form.
+ */
+function search_autocomplete_suggestion_delete($form_state) {
+ $form = array();
+
+ // get data from database
+ $sid = arg(5);
+
+ if (!$sid && !is_int($sid)) {
+ drupal_set_message(
+ t('The suggestion has not been found, or the menu callback received a wrong parameter.'),
+ 'error'
+ );
+ watchdog(
+ 'search_autocomplete',
+ 'The suggestion has not been found, or the menu callback received a wrong parameter.',
+ NULL,
+ WATCHDOG_ERROR
+ );
+
+ return $form;
+ }
+
+ $form['sid'] = array(
+ '#type' => 'hidden',
+ '#value' => $sid,
+ );
+
+ return confirm_form(
+ $form,
+ t('Are you sure you want to delete this suggestion?'),
+ 'admin/config/search/search_autocomplete',
+ NULL,
+ t('Delete'),
+ t('Cancel')
+ );
+}
+
+/**
+ * Submission callback for the filter delete form.
+ */
+function search_autocomplete_suggestion_delete_submit($form, &$form_state) {
+ $ok = TRUE;
+ $values = $form_state['values'];
+ $sid = $values['sid'];
+
+ $ok .= db_delete('search_autocomplete_suggestions')
+ ->condition('sid', $sid)
+ ->execute();
+
+ // Give a return to the user
+ $ok ? drupal_set_message(t("The suggestion has been successfully deleted !")) : drupal_set_message(t("An error has occured while deleting the suggestion!"), 'error');
+
+ $form_state['redirect'] = 'admin/config/search/search_autocomplete';
+} \ No newline at end of file
diff --git a/translations/search_autocomplete.pot b/translations/search_autocomplete.pot
deleted file mode 100644
index 8b218ef..0000000
--- a/translations/search_autocomplete.pot
+++ /dev/null
@@ -1,133 +0,0 @@
-# $Id$
-#
-# LANGUAGE translation of Drupal (general)
-# Copyright YEAR NAME <EMAIL@ADDRESS>
-# Generated from files:
-# search_autocomplete.module: n/a
-# search_autocomplete.info: n/a
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PROJECT VERSION\n"
-"POT-Creation-Date: 2011-07-31 16:15+0100\n"
-"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n"
-"Last-Translator: NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <EMAIL@ADDRESS>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
-
-#: search_autocomplete.module:23
-msgid "Administer Search Autocomplete"
-msgstr ""
-
-#: search_autocomplete.module:24
-msgid "Access administration panel for autocompletion settings."
-msgstr ""
-
-#: search_autocomplete.module:27
-msgid "Use Search Autocomplete"
-msgstr ""
-
-#: search_autocomplete.module:28
-msgid "Allow usage of autocompletion on forms."
-msgstr ""
-
-#: search_autocomplete.module:45
-msgid "administer Search Autocomplete"
-msgstr ""
-
-#: search_autocomplete.module:65
-msgid "Enable autocomplete on"
-msgstr ""
-
-#: search_autocomplete.module:66
-msgid "Select the search forms that should have autocomplete enabled."
-msgstr ""
-
-#: search_autocomplete.module:68
-msgid "Search page (node tab only)"
-msgstr ""
-
-#: search_autocomplete.module:69
-msgid "Search block"
-msgstr ""
-
-#: search_autocomplete.module:76
-msgid "Autocomplete settings"
-msgstr ""
-
-#: search_autocomplete.module:82
-msgid "Choose a search method"
-msgstr ""
-
-#: search_autocomplete.module:85
-msgid "Natural sort (no sort)"
-msgstr ""
-
-#: search_autocomplete.module:86
-msgid "Sort keyword alphabetically"
-msgstr ""
-
-#: search_autocomplete.module:87
-msgid "Sort by keyword's score"
-msgstr ""
-
-#: search_autocomplete.module:88
-msgid "Sort by keyword's relevance (slow)"
-msgstr ""
-
-#: search_autocomplete.module:90
-msgid "Caution: Some request method can be very slow !"
-msgstr ""
-
-#: search_autocomplete.module:96
-msgid "characters"
-msgstr ""
-
-#: search_autocomplete.module:100
-msgid "Minimum keyword size that uncouple autocomplete search"
-msgstr ""
-
-#: search_autocomplete.module:108
-msgid "results"
-msgstr ""
-
-#: search_autocomplete.module:112
-msgid "Limit of the autocomplete search result"
-msgstr ""
-
-#: search_autocomplete.module:121
-msgid "Test field"
-msgstr ""
-
-#: search_autocomplete.module:122
-msgid "In this field, you can check the appearence of an autocomplete search field."
-msgstr ""
-
-#: search_autocomplete.module:128
-msgid "Search autocomplete test field"
-msgstr ""
-
-#: search_autocomplete.module:132
-msgid "Enter a keyword and wait for the result."
-msgstr ""
-
-#: search_autocomplete.module:41 search_autocomplete.info:0
-msgid "Search Autocomplete"
-msgstr ""
-
-#: search_autocomplete.module:42
-msgid "Choose settings and suggestion items for autocompletion"
-msgstr ""
-
-#: search_autocomplete.info:0
-msgid "Provides aucompletion for Drupal search forms."
-msgstr ""
-
-#: search_autocomplete.info:0
-msgid "User interface"
-msgstr ""
-