From b004333eadb9a4db57592bb501b28edced708943 Mon Sep 17 00:00:00 2001 From: Marcel Korpel Date: Sun, 23 Dec 2012 21:23:45 +0000 Subject: Implemented typeahead suggest Use Twitter Bootstrap JavaScript framework for typeahead support. Add a new "suggest" JSON method, which returns the first 20 packages that match the beginning characters of a query. canyonknight: Link format change, commit message Signed-off-by: Lukas Fleischer --- web/html/home.php | 19 ++- web/html/index.php | 4 + web/html/js/bootstrap-typeahead.js | 311 +++++++++++++++++++++++++++++++++++++ web/lib/aurjson.class.php | 22 ++- 4 files changed, 354 insertions(+), 2 deletions(-) create mode 100644 web/html/js/bootstrap-typeahead.js diff --git a/web/html/home.php b/web/html/home.php index 4e489ba..0b51d55 100644 --- a/web/html/home.php +++ b/web/html/home.php @@ -95,7 +95,7 @@ $dbh = db_connect();
- " maxlength="35" /> + " maxlength="35" />
@@ -107,5 +107,22 @@ $dbh = db_connect(); + + + ' + match + '' + }) + } + + , 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: '' + , item: '
  • ' + , 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/lib/aurjson.class.php b/web/lib/aurjson.class.php index 949c34f..616b783 100644 --- a/web/lib/aurjson.class.php +++ b/web/lib/aurjson.class.php @@ -15,7 +15,7 @@ include_once("aur.inc.php"); class AurJSON { private $dbh = false; private static $exposed_methods = array( - 'search', 'info', 'multiinfo', 'msearch' + 'search', 'info', 'multiinfo', 'msearch', 'suggest' ); private static $fields = array( 'Packages.ID', 'Name', 'Version', 'CategoryID', 'Description', 'URL', @@ -276,5 +276,25 @@ class AurJSON { return $this->process_query('msearch', $where_condition); } + + /** + * Get all package names that start with $search. + * @param string $search Search string. + * @return string The JSON formatted response data. + **/ + private function suggest($search) { + $query = 'SELECT Name FROM Packages WHERE Name LIKE ' . + $this->dbh->quote(addcslashes($search, '%_') . '%') . + ' ORDER BY Name ASC LIMIT 20'; + + $result = $this->dbh->query($query); + $result_array = array(); + + if ($result) { + $result_array = $result->fetchAll(PDO::FETCH_COLUMN, 0); + } + + return json_encode($result_array); + } } -- cgit v1.2.3-70-g09d2