From ff495901c0b6786cea01e09f3a972a976beda939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?"J.=20Rene=CC=81e=20Beach"?= Date: Fri, 28 Sep 2012 16:44:40 -0400 Subject: [PATCH] Issue #1781422 by Boriss, jessebeach: Implement the auto-complete menu jumpbar in the responsive core toolbar update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: J. ReneĢe Beach --- core/modules/toolbar/css/toolbar.base.css | 2 +- core/modules/toolbar/js/toolbar.js | 126 +++++++++++++++++++++++++++-- 2 files changed, 122 insertions(+), 6 deletions(-) diff --git a/core/modules/toolbar/css/toolbar.base.css b/core/modules/toolbar/css/toolbar.base.css index 018e71d..5a4134e 100644 --- a/core/modules/toolbar/css/toolbar.base.css +++ b/core/modules/toolbar/css/toolbar.base.css @@ -24,7 +24,7 @@ body.toolbar { text-align: left; /* LTR */ -webkit-text-size-adjust: none; -webkit-touch-callout: none; - -webkit-user-select: none; + /*-webkit-user-select: none;*/ vertical-align: baseline; } #toolbar { diff --git a/core/modules/toolbar/js/toolbar.js b/core/modules/toolbar/js/toolbar.js index 94afb90..0be514f 100644 --- a/core/modules/toolbar/js/toolbar.js +++ b/core/modules/toolbar/js/toolbar.js @@ -16,7 +16,9 @@ Drupal.behaviors.toolbar = { if ($toolbar.length) { var $bar = $toolbar.find('.toolbar-bar'); var $tray = $toolbar.find('.toolbar-tray'); + var $menu = $tray.find('.toolbar-menu > .menu'); var $trigger = $toolbar.find('.toggle-tray'); + var $filter = $tray.find('.form-type-search input'); // Instanstiate the bar. if ($bar.length) { ToolBar.bar = new ToolBar($bar); @@ -25,18 +27,21 @@ Drupal.behaviors.toolbar = { if ($tray.length && $trigger.length) { ToolBar.tray = new TraySlider($tray, $trigger); } + if ($filter.length) { + ToolBar.filter = new ToolbarFilter($filter, $menu); + } } } }; /** - * Store references to the ToolBar and TraySlider objects in the ToolBar object. + * Store references to the ToolBar components. * - * These references will be available in Drupal.ToolBar.bar and - * Drupal.ToolBar.tray. + * These references will be available in Drupal.ToolBar. */ $.extend(ToolBar, { bar: null, - tray: null + tray: null, + filter: null }); /** * A toolbar is an administration action button container. @@ -119,7 +124,7 @@ function TraySlider ($tray, $trigger) { // Offset value vas changed by a third party script. 'offsettopchange.toolbar': $.proxy(this, 'displace') }); -}; +} /** * Extend the prototype of the TraySlider class. */ @@ -318,6 +323,117 @@ $.extend(TraySlider.prototype, { } }); +/** + * Apply the search bar functionality. + */ +function ToolbarFilter($filter, $menu) { + this.$filter = $filter; + this.$menu = $menu; + // Target toolbar + this.$toolbar = $filter.closest('#toolbar'); + // Initialize the current search needle. + this.needle = this.$filter.val(); + // Cache of all links that can be matched in the menu. + this.$links; + // Minimum search needle length. + this.needleMinLength = 2; + // Attach the search input event handler. + this.$filter.on('keyup.toolbarfilter search.toolbarfilter', $.proxy(this, 'keyupHandler')); + // Append the results container. + this.$results = $('
') + .insertAfter(this.$filter) + // Hide the result list after a link has been clicked, useful for overlay. + .on('click.toolbarfilter', 'li', $.proxy(this, 'resultsClickHandler')); +} + + +/** + * Extend the prototype of the ToolbarFilter class. + */ +$.extend(ToolbarFilter.prototype, { + /** + * Executes the search upon user input. + */ + keyupHandler: function (event) { + var value = this.$filter.val(); + var matches, $html; + // Only proceed if the search needle has changed. + if (value !== this.needle) { + this.needle = value; + // Initialize the cache of menu links upon first search. + if (!this.$links && this.needle.length >= this.needleMinLength) { + this.$links = this.buildSearchIndex(this.$menu.find('li a')); + } + // Empty results container when deleting search text. + if (this.needle.length < this.needleMinLength) { + this.$results.empty(); + } + // Only search if the needle is long enough. + if (this.needle.length >= this.needleMinLength && this.$links) { + matches = this.findMatches(this.needle, this.$links); + // Build the list in a detached DOM node. + $html = this.buildResultsList(matches); + // Display results. + this.$results.empty().append($html); + } + } + }, + /** + * Builds the search index. + */ + buildSearchIndex: function ($links) { + return $links + .map(function () { + var text = (this.textContent || this.innerText); + // Skip menu entries that do not contain any text (e.g., the icon). + if (typeof text === 'undefined') { + return; + } + return { + text: text, + textMatch: text.toLowerCase(), + element: this + }; + }); + }, + /** + * Searches the index for a given needle and returns matching entries. + */ + findMatches: function (needle, links) { + var needleMatch = needle.toLowerCase(); + // Select matching links from the cache. + return $.grep(links, function (link) { + return link.textMatch.indexOf(needleMatch) !== -1; + }); + }, + /** + * Builds the search result list in a detached DOM node. + */ + buildResultsList: function (matches) { + var $html = $('