[aur-dev] [PATCH] Implemented typeahead suggest
--- web/html/home.php | 19 ++- web/html/index.php | 4 + web/html/js/bootstrap-typeahead.js | 311 +++++++++++++++++++++++++++++++++++++ web/html/suggest.php | 12 ++ web/lib/routing.inc.php | 1 + web/lib/suggestfunc.inc.php | 32 ++++ 6 files changed, 378 insertions(+), 1 deletion(-) create mode 100644 web/html/js/bootstrap-typeahead.js create mode 100644 web/html/suggest.php create mode 100644 web/lib/suggestfunc.inc.php diff --git a/web/html/home.php b/web/html/home.php index 4e489ba..2d6d786 100644 --- a/web/html/home.php +++ b/web/html/home.php @@ -95,7 +95,7 @@ $dbh = db_connect(); <fieldset> <label for="pkgsearch-field"><?= __('Package Search') ?>:</label> <input type="hidden" name="O" value="0" /> - <input type="text" name="K" size="30" value="<?php if (isset($_REQUEST["K"])) { print stripslashes(trim(htmlspecialchars($_REQUEST["K"], ENT_QUOTES))); } ?>" maxlength="35" /> + <input id="pkgsearch-field" type="text" name="K" size="30" value="<?php if (isset($_REQUEST["K"])) { print stripslashes(trim(htmlspecialchars($_REQUEST["K"], ENT_QUOTES))); } ?>" maxlength="35" /> </fieldset> </form> </div> @@ -107,5 +107,22 @@ $dbh = db_connect(); </div> </div> +<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script> +<script type="text/javascript" src="/js/bootstrap-typeahead.js"></script> +<script type="text/javascript"> +$(document).ready(function() { + $('#pkgsearch-field').typeahead({ + source: function(query, callback) { + $.getJSON('/suggest', {q: query}, function(data) { + callback(data); + }); + }, + matcher: function(item) { return true; }, + sorter: function(items) { return items; }, + menu: '<ul class="pkgsearch-typeahead"></ul>', + items: 20 + }).attr('autocomplete', 'off'); +}); +</script> <?php html_footer(AUR_VERSION); diff --git a/web/html/index.php b/web/html/index.php index a197d0b..c51f409 100644 --- a/web/html/index.php +++ b/web/html/index.php @@ -118,6 +118,10 @@ if (!empty($tokens[1]) && '/' . $tokens[1] == get_pkg_route()) { header("Content-Type: image/png"); include "./$path"; break; + case "/js/bootstrap-typeahead.js": + header("Content-Type: application/javascript"); + include "./$path"; + break; default: header("HTTP/1.0 404 Not Found"); include "./404.php"; diff --git a/web/html/js/bootstrap-typeahead.js b/web/html/js/bootstrap-typeahead.js new file mode 100644 index 0000000..2bb0355 --- /dev/null +++ b/web/html/js/bootstrap-typeahead.js @@ -0,0 +1,311 @@ +/* ============================================================= + * bootstrap-typeahead.js v2.2.1 + * http://twitter.github.com/bootstrap/javascript.html#typeahead + * ============================================================= + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + + +!function($){ + + "use strict"; // jshint ;_; + + + /* TYPEAHEAD PUBLIC CLASS DEFINITION + * ================================= */ + + var Typeahead = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, $.fn.typeahead.defaults, options) + this.matcher = this.options.matcher || this.matcher + this.sorter = this.options.sorter || this.sorter + this.highlighter = this.options.highlighter || this.highlighter + this.updater = this.options.updater || this.updater + this.$menu = $(this.options.menu).appendTo('body') + this.source = this.options.source + this.shown = false + this.listen() + } + + Typeahead.prototype = { + + constructor: Typeahead + + , select: function () { + var val = this.$menu.find('.active').attr('data-value') + this.$element + .val(this.updater(val)) + .change() + return this.hide() + } + + , updater: function (item) { + return item + } + + , show: function () { + var pos = $.extend({}, this.$element.offset(), { + height: this.$element[0].offsetHeight + }) + + this.$menu.css({ + top: pos.top + pos.height + , left: pos.left + }) + + this.$menu.show() + this.shown = true + return this + } + + , hide: function () { + this.$menu.hide() + this.shown = false + return this + } + + , lookup: function (event) { + var items + + this.query = this.$element.val() + + if (!this.query || this.query.length < this.options.minLength) { + return this.shown ? this.hide() : this + } + + items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source + + return items ? this.process(items) : this + } + + , process: function (items) { + var that = this + + items = $.grep(items, function (item) { + return that.matcher(item) + }) + + items = this.sorter(items) + + if (!items.length) { + return this.shown ? this.hide() : this + } + + return this.render(items.slice(0, this.options.items)).show() + } + + , matcher: function (item) { + return ~item.toLowerCase().indexOf(this.query.toLowerCase()) + } + + , sorter: function (items) { + var beginswith = [] + , caseSensitive = [] + , caseInsensitive = [] + , item + + while (item = items.shift()) { + if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item) + else if (~item.indexOf(this.query)) caseSensitive.push(item) + else caseInsensitive.push(item) + } + + return beginswith.concat(caseSensitive, caseInsensitive) + } + + , highlighter: function (item) { + var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&') + return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) { + return '<strong>' + match + '</strong>' + }) + } + + , render: function (items) { + var that = this + + items = $(items).map(function (i, item) { + i = $(that.options.item).attr('data-value', item) + i.find('a').html(that.highlighter(item)) + return i[0] + }) + + items.first().addClass('active') + this.$menu.html(items) + return this + } + + , next: function (event) { + var active = this.$menu.find('.active').removeClass('active') + , next = active.next() + + if (!next.length) { + next = $(this.$menu.find('li')[0]) + } + + next.addClass('active') + } + + , prev: function (event) { + var active = this.$menu.find('.active').removeClass('active') + , prev = active.prev() + + if (!prev.length) { + prev = this.$menu.find('li').last() + } + + prev.addClass('active') + } + + , listen: function () { + this.$element + .on('blur', $.proxy(this.blur, this)) + .on('keypress', $.proxy(this.keypress, this)) + .on('keyup', $.proxy(this.keyup, this)) + + if (this.eventSupported('keydown')) { + this.$element.on('keydown', $.proxy(this.keydown, this)) + } + + this.$menu + .on('click', $.proxy(this.click, this)) + .on('mouseenter', 'li', $.proxy(this.mouseenter, this)) + } + + , eventSupported: function(eventName) { + var isSupported = eventName in this.$element + if (!isSupported) { + this.$element.setAttribute(eventName, 'return;') + isSupported = typeof this.$element[eventName] === 'function' + } + return isSupported + } + + , move: function (e) { + if (!this.shown) return + + switch(e.keyCode) { + case 9: // tab + case 13: // enter + case 27: // escape + e.preventDefault() + break + + case 38: // up arrow + e.preventDefault() + this.prev() + break + + case 40: // down arrow + e.preventDefault() + this.next() + break + } + + e.stopPropagation() + } + + , keydown: function (e) { + this.suppressKeyPressRepeat = !~$.inArray(e.keyCode, [40,38,9,13,27]) + this.move(e) + } + + , keypress: function (e) { + if (this.suppressKeyPressRepeat) return + this.move(e) + } + + , keyup: function (e) { + switch(e.keyCode) { + case 40: // down arrow + case 38: // up arrow + case 16: // shift + case 17: // ctrl + case 18: // alt + break + + case 9: // tab + case 13: // enter + if (!this.shown) return + this.select() + break + + case 27: // escape + if (!this.shown) return + this.hide() + break + + default: + this.lookup() + } + + e.stopPropagation() + e.preventDefault() + } + + , blur: function (e) { + var that = this + setTimeout(function () { that.hide() }, 150) + } + + , click: function (e) { + e.stopPropagation() + e.preventDefault() + this.select() + this.$element.focus() + } + + , mouseenter: function (e) { + this.$menu.find('.active').removeClass('active') + $(e.currentTarget).addClass('active') + } + + } + + + /* TYPEAHEAD PLUGIN DEFINITION + * =========================== */ + + $.fn.typeahead = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('typeahead') + , options = typeof option == 'object' && option + if (!data) $this.data('typeahead', (data = new Typeahead(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.typeahead.defaults = { + source: [] + , items: 8 + , menu: '<ul class="typeahead dropdown-menu"></ul>' + , item: '<li><a></a></li>' + , minLength: 1 + } + + $.fn.typeahead.Constructor = Typeahead + + + /* TYPEAHEAD DATA-API + * ================== */ + + $(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) { + var $this = $(this) + if ($this.data('typeahead')) return + e.preventDefault() + $this.typeahead($this.data()) + }) + +}(window.jQuery); diff --git a/web/html/suggest.php b/web/html/suggest.php new file mode 100644 index 0000000..2696662 --- /dev/null +++ b/web/html/suggest.php @@ -0,0 +1,12 @@ +<?php +set_include_path(get_include_path() . PATH_SEPARATOR . '../lib'); +include_once('suggestfunc.inc.php'); + +if ( $_SERVER['REQUEST_METHOD'] != 'GET' ) { + header('HTTP/1.1 405 Method Not Allowed'); + exit(); +} + +if ( isset($_GET['q']) ) { + echo suggest_search($_GET['q']); +} diff --git a/web/lib/routing.inc.php b/web/lib/routing.inc.php index 206886c..ebe9dd3 100644 --- a/web/lib/routing.inc.php +++ b/web/lib/routing.inc.php @@ -13,6 +13,7 @@ $ROUTES = array( '/rpc' => 'rpc.php', '/rss' => 'rss.php', '/submit' => 'pkgsubmit.php', + '/suggest' => 'suggest.php', '/tu' => 'tu.php', '/addvote' => 'addvote.php', ); diff --git a/web/lib/suggestfunc.inc.php b/web/lib/suggestfunc.inc.php new file mode 100644 index 0000000..7519da9 --- /dev/null +++ b/web/lib/suggestfunc.inc.php @@ -0,0 +1,32 @@ +<?php +/** + * This file contains the typeahead suggest search function + **/ +include_once("aur.inc.php"); + +/** + * This function searches the package database + * @param string $search Contains search string + * @param \PDO $dbh Already established database connection + * @return string The JSON formatted response data + **/ +function suggest_search($search="", $dbh=NULL) { + if(!$dbh) { + $dbh = db_connect(); + } + + // get all package names that start with $search + $query = 'SELECT Name FROM Packages WHERE Name LIKE ' . + $dbh->quote(addcslashes($search, '%_') . '%') . + ' ORDER BY Name ASC LIMIT 20'; + + $result = $dbh->query($query); + $json_array = array(); + + if ($result) { + $json_array = $result->fetchAll(PDO::FETCH_COLUMN, 0); + } + + header('Content-Type: application/json'); + return json_encode($json_array); +} -- 1.8.0.2
On Fri, Dec 14, 2012 at 10:03 AM, Marcel Korpel <marcel.lists@gmail.com> wrote:
---
So after thinking about this some more, what are your thoughts on adding a method to aurjson.class.php and having the JavaScript call rpc.php for the JSON data instead of a new suggest.php file? It would keep all the JSON related code in the same place rather than even having to add a new html or lib file. Thoughts from anyone?
web/html/home.php | 19 ++- web/html/index.php | 4 + web/html/js/bootstrap-typeahead.js | 311 +++++++++++++++++++++++++++++++++++++ web/html/suggest.php | 12 ++ web/lib/routing.inc.php | 1 + web/lib/suggestfunc.inc.php | 32 ++++ 6 files changed, 378 insertions(+), 1 deletion(-) create mode 100644 web/html/js/bootstrap-typeahead.js create mode 100644 web/html/suggest.php create mode 100644 web/lib/suggestfunc.inc.php
diff --git a/web/html/home.php b/web/html/home.php index 4e489ba..2d6d786 100644 --- a/web/html/home.php +++ b/web/html/home.php @@ -95,7 +95,7 @@ $dbh = db_connect(); <fieldset> <label for="pkgsearch-field"><?= __('Package Search') ?>:</label> <input type="hidden" name="O" value="0" /> - <input type="text" name="K" size="30" value="<?php if (isset($_REQUEST["K"])) { print stripslashes(trim(htmlspecialchars($_REQUEST["K"], ENT_QUOTES))); } ?>" maxlength="35" /> + <input id="pkgsearch-field" type="text" name="K" size="30" value="<?php if (isset($_REQUEST["K"])) { print stripslashes(trim(htmlspecialchars($_REQUEST["K"], ENT_QUOTES))); } ?>" maxlength="35" /> </fieldset> </form> </div> @@ -107,5 +107,22 @@ $dbh = db_connect(); </div>
</div> +<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script> +<script type="text/javascript" src="/js/bootstrap-typeahead.js"></script> +<script type="text/javascript"> +$(document).ready(function() { + $('#pkgsearch-field').typeahead({ + source: function(query, callback) { + $.getJSON('/suggest', {q: query}, function(data) { + callback(data); + }); + }, + matcher: function(item) { return true; }, + sorter: function(items) { return items; }, + menu: '<ul class="pkgsearch-typeahead"></ul>', + items: 20 + }).attr('autocomplete', 'off'); +}); +</script> <?php html_footer(AUR_VERSION); diff --git a/web/html/index.php b/web/html/index.php index a197d0b..c51f409 100644 --- a/web/html/index.php +++ b/web/html/index.php @@ -118,6 +118,10 @@ if (!empty($tokens[1]) && '/' . $tokens[1] == get_pkg_route()) { header("Content-Type: image/png"); include "./$path"; break; + case "/js/bootstrap-typeahead.js": + header("Content-Type: application/javascript"); + include "./$path"; + break; default: header("HTTP/1.0 404 Not Found"); include "./404.php"; diff --git a/web/html/js/bootstrap-typeahead.js b/web/html/js/bootstrap-typeahead.js new file mode 100644 index 0000000..2bb0355 --- /dev/null +++ b/web/html/js/bootstrap-typeahead.js @@ -0,0 +1,311 @@ +/* ============================================================= + * bootstrap-typeahead.js v2.2.1 + * http://twitter.github.com/bootstrap/javascript.html#typeahead + * ============================================================= + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + + +!function($){ + + "use strict"; // jshint ;_; + + + /* TYPEAHEAD PUBLIC CLASS DEFINITION + * ================================= */ + + var Typeahead = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, $.fn.typeahead.defaults, options) + this.matcher = this.options.matcher || this.matcher + this.sorter = this.options.sorter || this.sorter + this.highlighter = this.options.highlighter || this.highlighter + this.updater = this.options.updater || this.updater + this.$menu = $(this.options.menu).appendTo('body') + this.source = this.options.source + this.shown = false + this.listen() + } + + Typeahead.prototype = { + + constructor: Typeahead + + , select: function () { + var val = this.$menu.find('.active').attr('data-value') + this.$element + .val(this.updater(val)) + .change() + return this.hide() + } + + , updater: function (item) { + return item + } + + , show: function () { + var pos = $.extend({}, this.$element.offset(), { + height: this.$element[0].offsetHeight + }) + + this.$menu.css({ + top: pos.top + pos.height + , left: pos.left + }) + + this.$menu.show() + this.shown = true + return this + } + + , hide: function () { + this.$menu.hide() + this.shown = false + return this + } + + , lookup: function (event) { + var items + + this.query = this.$element.val() + + if (!this.query || this.query.length < this.options.minLength) { + return this.shown ? this.hide() : this + } + + items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source + + return items ? this.process(items) : this + } + + , process: function (items) { + var that = this + + items = $.grep(items, function (item) { + return that.matcher(item) + }) + + items = this.sorter(items) + + if (!items.length) { + return this.shown ? this.hide() : this + } + + return this.render(items.slice(0, this.options.items)).show() + } + + , matcher: function (item) { + return ~item.toLowerCase().indexOf(this.query.toLowerCase()) + } + + , sorter: function (items) { + var beginswith = [] + , caseSensitive = [] + , caseInsensitive = [] + , item + + while (item = items.shift()) { + if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item) + else if (~item.indexOf(this.query)) caseSensitive.push(item) + else caseInsensitive.push(item) + } + + return beginswith.concat(caseSensitive, caseInsensitive) + } + + , highlighter: function (item) { + var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&') + return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) { + return '<strong>' + match + '</strong>' + }) + } + + , render: function (items) { + var that = this + + items = $(items).map(function (i, item) { + i = $(that.options.item).attr('data-value', item) + i.find('a').html(that.highlighter(item)) + return i[0] + }) + + items.first().addClass('active') + this.$menu.html(items) + return this + } + + , next: function (event) { + var active = this.$menu.find('.active').removeClass('active') + , next = active.next() + + if (!next.length) { + next = $(this.$menu.find('li')[0]) + } + + next.addClass('active') + } + + , prev: function (event) { + var active = this.$menu.find('.active').removeClass('active') + , prev = active.prev() + + if (!prev.length) { + prev = this.$menu.find('li').last() + } + + prev.addClass('active') + } + + , listen: function () { + this.$element + .on('blur', $.proxy(this.blur, this)) + .on('keypress', $.proxy(this.keypress, this)) + .on('keyup', $.proxy(this.keyup, this)) + + if (this.eventSupported('keydown')) { + this.$element.on('keydown', $.proxy(this.keydown, this)) + } + + this.$menu + .on('click', $.proxy(this.click, this)) + .on('mouseenter', 'li', $.proxy(this.mouseenter, this)) + } + + , eventSupported: function(eventName) { + var isSupported = eventName in this.$element + if (!isSupported) { + this.$element.setAttribute(eventName, 'return;') + isSupported = typeof this.$element[eventName] === 'function' + } + return isSupported + } + + , move: function (e) { + if (!this.shown) return + + switch(e.keyCode) { + case 9: // tab + case 13: // enter + case 27: // escape + e.preventDefault() + break + + case 38: // up arrow + e.preventDefault() + this.prev() + break + + case 40: // down arrow + e.preventDefault() + this.next() + break + } + + e.stopPropagation() + } + + , keydown: function (e) { + this.suppressKeyPressRepeat = !~$.inArray(e.keyCode, [40,38,9,13,27]) + this.move(e) + } + + , keypress: function (e) { + if (this.suppressKeyPressRepeat) return + this.move(e) + } + + , keyup: function (e) { + switch(e.keyCode) { + case 40: // down arrow + case 38: // up arrow + case 16: // shift + case 17: // ctrl + case 18: // alt + break + + case 9: // tab + case 13: // enter + if (!this.shown) return + this.select() + break + + case 27: // escape + if (!this.shown) return + this.hide() + break + + default: + this.lookup() + } + + e.stopPropagation() + e.preventDefault() + } + + , blur: function (e) { + var that = this + setTimeout(function () { that.hide() }, 150) + } + + , click: function (e) { + e.stopPropagation() + e.preventDefault() + this.select() + this.$element.focus() + } + + , mouseenter: function (e) { + this.$menu.find('.active').removeClass('active') + $(e.currentTarget).addClass('active') + } + + } + + + /* TYPEAHEAD PLUGIN DEFINITION + * =========================== */ + + $.fn.typeahead = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('typeahead') + , options = typeof option == 'object' && option + if (!data) $this.data('typeahead', (data = new Typeahead(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.typeahead.defaults = { + source: [] + , items: 8 + , menu: '<ul class="typeahead dropdown-menu"></ul>' + , item: '<li><a></a></li>' + , minLength: 1 + } + + $.fn.typeahead.Constructor = Typeahead + + + /* TYPEAHEAD DATA-API + * ================== */ + + $(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) { + var $this = $(this) + if ($this.data('typeahead')) return + e.preventDefault() + $this.typeahead($this.data()) + }) + +}(window.jQuery); diff --git a/web/html/suggest.php b/web/html/suggest.php new file mode 100644 index 0000000..2696662 --- /dev/null +++ b/web/html/suggest.php @@ -0,0 +1,12 @@ +<?php +set_include_path(get_include_path() . PATH_SEPARATOR . '../lib'); +include_once('suggestfunc.inc.php'); + +if ( $_SERVER['REQUEST_METHOD'] != 'GET' ) { + header('HTTP/1.1 405 Method Not Allowed'); + exit(); +} + +if ( isset($_GET['q']) ) { + echo suggest_search($_GET['q']); +} diff --git a/web/lib/routing.inc.php b/web/lib/routing.inc.php index 206886c..ebe9dd3 100644 --- a/web/lib/routing.inc.php +++ b/web/lib/routing.inc.php @@ -13,6 +13,7 @@ $ROUTES = array( '/rpc' => 'rpc.php', '/rss' => 'rss.php', '/submit' => 'pkgsubmit.php', + '/suggest' => 'suggest.php', '/tu' => 'tu.php', '/addvote' => 'addvote.php', ); diff --git a/web/lib/suggestfunc.inc.php b/web/lib/suggestfunc.inc.php new file mode 100644 index 0000000..7519da9 --- /dev/null +++ b/web/lib/suggestfunc.inc.php @@ -0,0 +1,32 @@ +<?php +/** + * This file contains the typeahead suggest search function + **/ +include_once("aur.inc.php"); + +/** + * This function searches the package database + * @param string $search Contains search string + * @param \PDO $dbh Already established database connection + * @return string The JSON formatted response data + **/ +function suggest_search($search="", $dbh=NULL) { + if(!$dbh) { + $dbh = db_connect(); + } + + // get all package names that start with $search + $query = 'SELECT Name FROM Packages WHERE Name LIKE ' . + $dbh->quote(addcslashes($search, '%_') . '%') . + ' ORDER BY Name ASC LIMIT 20'; + + $result = $dbh->query($query); + $json_array = array(); + + if ($result) { + $json_array = $result->fetchAll(PDO::FETCH_COLUMN, 0); + } + + header('Content-Type: application/json'); + return json_encode($json_array); +} -- 1.8.0.2
On Sun, Dec 16, 2012 at 7:05 PM, canyonknight <canyonknight@gmail.com> wrote:
So after thinking about this some more, what are your thoughts on adding a method to aurjson.class.php and having the JavaScript call rpc.php for the JSON data instead of a new suggest.php file?
Changing the function to a method in aurjson.class.php seems all right to me, it's a logical place to put that function in. BTW, why is aurjson.class.php the only OO PHP file in the AUR and the rest merely procedural? Not that I'm in favor of one or the other (and my OOP skills are limited to prototypal OOP in JavaScript). However, it would imply quite a change of aurjson.class.php, as many methods in it are specifically meant for the current RPC interface. E.g., 'search' is not designed for this specific suggest search and 'json_results' produces an object rather than an array. Or would you want to include a new public method called 'suggest_search'? Regards, Marcel
participants (2)
-
canyonknight
-
Marcel Korpel