diff options
-rw-r--r-- | Bugzilla/Search.pm | 4 | ||||
-rw-r--r-- | js/instant-search.js | 201 | ||||
-rw-r--r-- | template/en/default/search/search-instant.html.tmpl | 83 | ||||
-rw-r--r-- | template/en/default/search/tabs.html.tmpl | 4 |
4 files changed, 289 insertions, 3 deletions
diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm index 0c9f84689..0c5b65acf 100644 --- a/Bugzilla/Search.pm +++ b/Bugzilla/Search.pm @@ -2797,8 +2797,8 @@ sub _changedby { sub IsValidQueryType { my ($queryType) = @_; - # BMO: Added google query type - if (grep { $_ eq $queryType } qw(specific advanced google)) { + # BMO: Added google and instant + if (grep { $_ eq $queryType } qw(specific advanced google instant)) { return 1; } return 0; diff --git a/js/instant-search.js b/js/instant-search.js new file mode 100644 index 000000000..f590c8ef3 --- /dev/null +++ b/js/instant-search.js @@ -0,0 +1,201 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This Source Code Form is "Incompatible With Secondary Licenses", as + * defined by the Mozilla Public License, v. 2.0. */ + +var Dom = YAHOO.util.Dom; +var Event = YAHOO.util.Event; + +Event.onDOMReady(function() { + YAHOO.bugzilla.instantSearch.onInit(); + if (YAHOO.bugzilla.instantSearch.getContent().length >= 4) { + YAHOO.bugzilla.instantSearch.doSearch(YAHOO.bugzilla.instantSearch.getContent()); + } else { + YAHOO.bugzilla.instantSearch.reset(); + } + Dom.get('content').focus(); +}); + +YAHOO.bugzilla.instantSearch = { + counter: 0, + dataTable: null, + dataTableColumns: null, + elContent: null, + elList: null, + currentSearchQuery: '', + currentSearchProduct: '', + + onInit: function() { + YAHOO.util.Connect.setDefaultPostHeader('text/plain; charset=UTF-8'); + + this.elContent = Dom.get('content'); + this.elList = Dom.get('results'); + + Event.addListener(this.elContent, 'keyup', this.onContentKeyUp); + Event.addListener(Dom.get('product'), 'change', this.onProductChange); + }, + + setLabels: function(labels) { + this.dataTableColumns = [ + { key: "id", label: labels.id, formatter: this.formatId }, + { key: "summary", label: labels.summary, formatter: "text" }, + { key: "component", label: labels.component, formatter: "text" }, + { key: "status", label: labels.status, formatter: this.formatStatus }, + ]; + }, + + initDataTable: function() { + var dataSource = new YAHOO.util.XHRDataSource("jsonrpc.cgi"); + dataSource.connTimeout = 15000; + dataSource.connMethodPost = true; + dataSource.connXhrMode = "cancelStaleRequests"; + dataSource.maxCacheEntries = 3; + dataSource.responseSchema = { + resultsList : "result.bugs", + metaFields : { error: "error", jsonRpcId: "id" } + }; + // DataSource can't understand a JSON-RPC error response, so + // we have to modify the result data if we get one. + dataSource.doBeforeParseData = + function(oRequest, oFullResponse, oCallback) { + if (oFullResponse.error) { + oFullResponse.result = {}; + oFullResponse.result.bugs = []; + if (console) + console.error("JSON-RPC error:", oFullResponse.error); + } + return oFullResponse; + }; + dataSource.subscribe('dataErrorEvent', + function() { + YAHOO.bugzilla.instantSearch.currentSearchQuery = ''; + } + ); + + this.dataTable = new YAHOO.widget.DataTable( + 'results', + this.dataTableColumns, + dataSource, + { + initialLoad: false, + MSG_EMPTY: 'No matching bugs found.', + MSG_ERROR: 'An error occurred while searching for bugs, please try again.' + } + ); + }, + + formatId: function(el, oRecord, oColumn, oData) { + el.innerHTML = '<a href="show_bug.cgi?id=' + oData + '" target="_blank">' + oData + '</a>'; + }, + + formatStatus: function(el, oRecord, oColumn, oData) { + var resolution = oRecord.getData('resolution'); + var bugStatus = display_value('bug_status', oData); + if (resolution) { + el.innerHTML = bugStatus + ' ' + display_value('resolution', resolution); + } else { + el.innerHTML = bugStatus; + } + }, + + reset: function() { + Dom.addClass(this.elList, 'hidden'); + this.elList.innerHTML = ''; + this.currentSearchQuery = ''; + this.currentSearchProduct = ''; + }, + + onContentKeyUp: function(e) { + clearTimeout(YAHOO.bugzilla.instantSearch.lastTimeout); + YAHOO.bugzilla.instantSearch.lastTimeout = setTimeout(function() { + YAHOO.bugzilla.instantSearch.doSearch(YAHOO.bugzilla.instantSearch.getContent()) }, + 600); + }, + + onProductChange: function(e) { + YAHOO.bugzilla.instantSearch.doSearch(YAHOO.bugzilla.instantSearch.getContent()); + }, + + doSearch: function(query) { + if (query.length < 4) + return; + + // don't query if we already have the results (or they are pending) + var product = Dom.get('product').value; + if (YAHOO.bugzilla.instantSearch.currentSearchQuery == query && + YAHOO.bugzilla.instantSearch.currentSearchProduct == product) + return; + YAHOO.bugzilla.instantSearch.currentSearchQuery = query; + YAHOO.bugzilla.instantSearch.currentSearchProduct = product; + + // initialise the datatable as late as possible + YAHOO.bugzilla.instantSearch.initDataTable(); + + try { + // run the search + Dom.removeClass(YAHOO.bugzilla.instantSearch.elList, 'hidden'); + + YAHOO.bugzilla.instantSearch.dataTable.showTableMessage( + 'Searching... ' + + '<img src="extensions/GuidedBugEntry/web/images/throbber.gif"' + + ' width="16" height="11">', + YAHOO.widget.DataTable.CLASS_LOADING + ); + var jsonObject = { + version: "1.1", + method: "Bug.possible_duplicates", + id: ++YAHOO.bugzilla.instantSearch.counter, + params: { + product: YAHOO.bugzilla.instantSearch.getProduct(), + summary: query, + limit: 20, + include_fields: [ "id", "summary", "status", "resolution", "component" ] + } + }; + + YAHOO.bugzilla.instantSearch.dataTable.getDataSource().sendRequest( + YAHOO.lang.JSON.stringify(jsonObject), + { + success: YAHOO.bugzilla.instantSearch.onSearchResults, + failure: YAHOO.bugzilla.instantSearch.onSearchResults, + scope: YAHOO.bugzilla.instantSearch.dataTable, + argument: YAHOO.bugzilla.instantSearch.dataTable.getState() + } + ); + + } catch(err) { + if (console) + console.error(err.message); + } + }, + + onSearchResults: function(sRequest, oResponse, oPayload) { + YAHOO.bugzilla.instantSearch.dataTable.onDataReturnInitializeTable(sRequest, oResponse, oPayload); + }, + + getContent: function() { + var content = YAHOO.lang.trim(this.elContent.value); + // work around chrome bug + if (content == YAHOO.bugzilla.instantSearch.elContent.getAttribute('placeholder')) { + return ''; + } else { + return content; + } + }, + + getProduct: function() { + var result = []; + var name = Dom.get('product').value; + result.push(name); + if (products[name] && products[name].related) { + for (var i = 0, n = products[name].related.length; i < n; i++) { + result.push(products[name].related[i]); + } + } + return result; + } + +}; + diff --git a/template/en/default/search/search-instant.html.tmpl b/template/en/default/search/search-instant.html.tmpl new file mode 100644 index 000000000..59cd8c551 --- /dev/null +++ b/template/en/default/search/search-instant.html.tmpl @@ -0,0 +1,83 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +[% PROCESS global/variables.none.tmpl %] + +[% PROCESS global/header.html.tmpl + title = "Instant Search" + javascript_urls = [ 'extensions/GuidedBugEntry/web/js/products.js', + 'js/instant-search.js', ] + yui = [ 'datatable', 'container' ] +%] + +[% default.product.push('Firefox') UNLESS default.product.size %] + +<script> +YAHOO.bugzilla.instantSearch.setLabels( { + id: "[% field_descs.bug_id FILTER js %]", + summary: "[% field_descs.short_desc FILTER js %]", + component: "[% field_descs.component FILTER js %]", + status: "[% field_descs.bug_status FILTER js %]", +}); +</script> + +[% WRAPPER search/tabs.html.tmpl %] + +<p> + This page provides instant results, however only the [% terms.bug %]'s summary + is searched. Products related to the selected product may also be searched. +</p> + +<table> + <tr> + <td align="right" valign="baseline"> + <b><label for="product">Product:</label></b> + </td> + <td> + <select name="product" id="product"> + [% IF Param('useclassification') %] + [% FOREACH c = classification %] + <optgroup label="[% c.name FILTER html %]"> + [% FOREACH p = user.get_selectable_products(c.id) %] + [% IF p.components.size %] + <option value="[% p.name FILTER html %]" + [% " selected" IF lsearch(default.product, p.name) != -1 %]> + [% p.name FILTER html %] + </option> + [% END %] + [% END %] + </optgroup> + [% END %] + [% ELSE %] + [% FOREACH p = product %] + <option value="[% p.name FILTER html %]" + [% " selected" IF lsearch(default.product, p.name) != -1 %]> + [% p.name FILTER html %] + </option> + [% END %] + [% END %] + </select> + </td> + </tr> + <tr> + <td align="right" valign="baseline"> + <b><label for="content">Words:</label></b> + </td> + <td> + <input id="content" spellcheck="true" size="60" + value="[% default.content.0 FILTER html %]"> + </td> + </tr> +</table> +<br> + +<div id="results"></div> + +[% END %] + +[% PROCESS global/footer.html.tmpl %] diff --git a/template/en/default/search/tabs.html.tmpl b/template/en/default/search/tabs.html.tmpl index 6a2f7f70a..26ad4f39b 100644 --- a/template/en/default/search/tabs.html.tmpl +++ b/template/en/default/search/tabs.html.tmpl @@ -24,7 +24,9 @@ #%] [% WRAPPER global/tabs.html.tmpl - tabs = [ { name => 'specific', label => "Simple Search", + tabs = [ { name => 'instant', label => "Instant Search", + link => "query.cgi?format=instant" }, + { name => 'specific', label => "Simple Search", link => "query.cgi?format=specific" }, { name => 'advanced', label => "Advanced Search", link => "query.cgi?format=advanced" }, |