diff options
author | lpsolit%gmail.com <> | 2007-05-15 00:56:29 +0200 |
---|---|---|
committer | lpsolit%gmail.com <> | 2007-05-15 00:56:29 +0200 |
commit | d48ca3b1db102bb50b831ce0f9bbbf7117afedd8 (patch) | |
tree | e117c17bd33808abebd0c3a02a6613fbf51a998e | |
parent | 3941c84b967f6d35062c429728b252e26bcd080d (diff) | |
download | bugzilla-d48ca3b1db102bb50b831ce0f9bbbf7117afedd8.tar.gz bugzilla-d48ca3b1db102bb50b831ce0f9bbbf7117afedd8.tar.xz |
Bug 80169: JavaScript-enhanced keyword editing - Patch by Teemu Mannermaa <wicked@etlicon.fi> r=justdave a=LpSolit
-rwxr-xr-x | buglist.cgi | 1 | ||||
-rwxr-xr-x | enter_bug.cgi | 1 | ||||
-rw-r--r-- | js/keyword-chooser.js | 162 | ||||
-rw-r--r-- | js/util.js | 98 | ||||
-rwxr-xr-x | post_bug.cgi | 1 | ||||
-rwxr-xr-x | process_bug.cgi | 1 | ||||
-rwxr-xr-x | show_bug.cgi | 1 | ||||
-rw-r--r-- | skins/standard/global.css | 11 | ||||
-rw-r--r-- | template/en/default/bug/create/create.html.tmpl | 10 | ||||
-rw-r--r-- | template/en/default/bug/create/created.html.tmpl | 1 | ||||
-rw-r--r-- | template/en/default/bug/edit.html.tmpl | 13 | ||||
-rw-r--r-- | template/en/default/bug/keyword-chooser.html.tmpl | 91 | ||||
-rw-r--r-- | template/en/default/bug/process/header.html.tmpl | 4 | ||||
-rw-r--r-- | template/en/default/bug/show.html.tmpl | 1 | ||||
-rw-r--r-- | template/en/default/filterexceptions.pl | 1 | ||||
-rw-r--r-- | template/en/default/list/edit-multiple.html.tmpl | 9 | ||||
-rw-r--r-- | template/en/default/list/list.html.tmpl | 1 |
17 files changed, 401 insertions, 6 deletions
diff --git a/buglist.cgi b/buglist.cgi index 96011d3f7..338017fa1 100755 --- a/buglist.cgi +++ b/buglist.cgi @@ -1127,6 +1127,7 @@ $vars->{'currenttime'} = time(); # The following variables are used when the user is making changes to multiple bugs. if ($dotweak) { $vars->{'dotweak'} = 1; + $vars->{'valid_keywords'} = [map($_->name, Bugzilla::Keyword->get_all)]; $vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count(); $vars->{'products'} = Bugzilla->user->get_enterable_products; diff --git a/enter_bug.cgi b/enter_bug.cgi index 3bd7e390c..c802c0096 100755 --- a/enter_bug.cgi +++ b/enter_bug.cgi @@ -364,6 +364,7 @@ $vars->{'bug_severity'} = get_legal_field_values('bug_severity'); $vars->{'rep_platform'} = get_legal_field_values('rep_platform'); $vars->{'op_sys'} = get_legal_field_values('op_sys'); +$vars->{'valid_keywords'} = [map($_->name, Bugzilla::Keyword->get_all)]; $vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count(); $vars->{'assigned_to'} = formvalue('assigned_to'); diff --git a/js/keyword-chooser.js b/js/keyword-chooser.js new file mode 100644 index 000000000..afc90b491 --- /dev/null +++ b/js/keyword-chooser.js @@ -0,0 +1,162 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Keyword Chooser. + * + * The Initial Developer of the Original Code is America Online, Inc. + * Portions created by the Initial Developer are Copyright (C) 2004 + * Mozilla Foundation. All Rights Reserved. + * + * Contributor(s): + * Christopher A. Aillon <christopher@aillon.com> (Original Author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +function KeywordChooser(aParent, aChooser, aAvail, aChosen, aValidKeywords) +{ + // Initialization + this._parent = aParent; + this._chooser = aChooser; + this._available = aAvail; + this._chosen = aChosen; + this._validKeywords = aValidKeywords; + + this.setInitialStyles(); + + // Register us, our properties, and our events + this._parent.chooser = this; + this._chooser.chooserElement = this._parent; +} + +KeywordChooser.prototype = +{ + // chooses the selected items + choose: function() + { + this._swapSelected(this._available, this._chosen); + }, + + unchoose: function() + { + this._swapSelected(this._chosen, this._available); + }, + + positionChooser: function() + { + if (this._positioned) { + return; + } + + var elemY = bz_findPosY(this._parent); + var elemX = bz_findPosX(this._parent); + var elemH = this._parent.offsetHeight; + + this._chooser.style.left = elemX + "px"; + this._chooser.style.top = elemY + elemH + 1 + "px"; + + this._positioned = true; + }, + + setInitialStyles: function() + { + this._chooser.style.display = "none"; + this._chooser.style.position = "absolute"; + this._positioned = false; + }, + + open: function() + { + this._chooser.style.display = ""; + this._available.style.display = ""; + this._chosen.style.display = ""; + this._parent.disabled = true; + this.positionChooser(); + }, + + ok: function() + { + var len = this._chosen.options.length; + + var text = ""; + for (var i = 0; i < len; i++) { + text += this._chosen.options[i].text; + if (i != len - 1) { + text += ", "; + } + } + + this._parent.value = text; + this._parent.title = text; + + this.close(); + }, + + cancel: function() + { + var chosentext = this._parent.value; + var chosenArray = new Array(); + + if (chosentext != ""){ + chosenArray = chosentext.split(", "); + } + + var availArray = new Array(); + + for (var i = 0; i < this._validKeywords.length; i++) { + if (!bz_isValueInArray(chosenArray, this._validKeywords[i])) { + availArray[availArray.length] = this._validKeywords[i]; + } + } + + bz_populateSelectFromArray(this._available, availArray, false, true); + bz_populateSelectFromArray(this._chosen, chosenArray, false, true); + this.close(); + }, + + close: function() + { + this._chooser.style.display = "none"; + this._parent.disabled = false; + }, + + _swapSelected: function(aSource, aTarget) + { + var kNothingSelected = -1; + while (aSource.selectedIndex != kNothingSelected) { + var option = aSource.options[aSource.selectedIndex]; + aTarget.appendChild(option); + option.selected = false; + } + } +}; + +function InitializeKeywordChooser(aValidKeywords) +{ + var keywords = document.getElementById("keywords"); + var chooser = document.getElementById("keyword-chooser"); + var avail = document.getElementById("keyword-list"); + var chosen = document.getElementById("bug-keyword-list"); + var chooserObj = new KeywordChooser(keywords, chooser, avail, chosen, aValidKeywords); +} diff --git a/js/util.js b/js/util.js index 9d2209093..293c89a5d 100644 --- a/js/util.js +++ b/js/util.js @@ -20,6 +20,7 @@ * * Contributor(s): * Max Kanat-Alexander <mkanat@bugzilla.org> + * Christopher A. Aillon <christopher@aillon.com> * * ***** END LICENSE BLOCK ***** */ @@ -114,3 +115,100 @@ function bz_getFullWidth(fromObj) return scrollX; } + +/** + * Create wanted options in a select form control. + * + * @param aSelect Select form control to manipulate. + * @param aValue Value attribute of the new option element. + * @param aTextValue Value of a text node appended to the new option + * element. + * @param aOwnerDocument Owner document of the new option element. If not + * specified then "document" will be used. + * @return Created option element. + */ +function bz_createOptionInSelect(aSelect, aValue, aTextValue, aOwnerDocument) +{ + if (!aOwnerDocument) { + aOwnerDocument = document; + } + + var myOption = aOwnerDocument.createElement("option"); + myOption.setAttribute("value", aValue); + + var myTextNode = aOwnerDocument.createTextNode(aTextValue) + myOption.appendChild(myTextNode); + + aSelect.appendChild(myOption); + + return myOption; +} + +/** + * Clears all options from a select form control. + * + * @param aElm Select form control of which options to clear. + * @param aSkipFirst Boolean; true to skip (not clear) first option in the + * select and false to remove all options. + */ +function bz_clearOptions(aElm, aSkipFirst) +{ + var start = 0; + + // Skip the first element? (for 'Choose One' type foo) + if (aSkipFirst) { + start = 1; + } + + var length = aElm.options.length; + + for (var run = start; run < length; run++) { + aElm.removeChild(aElm.options[start]); + } +} + +/** + * Takes an array and moves all the values to an select. + * + * @param aSelect Select form control to populate. Will be cleared + * before array values are created in it. + * @param aArray Array with values to populate select with. + * @param aSkipFirst Boolean; true to skip (not touch) first option in the + * select and false to remove all options. + * @param aUseNameAsValue Boolean; true if name is used as value and false if + * not. + */ +function bz_populateSelectFromArray(aSelect, aArray, aSkipFirst, aUseNameAsValue) +{ + // Clear the field + bz_clearOptions(aSelect, aSkipFirst); + + for (var run = 0; run < aArray.length; run++) { + if (aUseNameAsValue) { + bz_createOptionInSelect(aSelect, aArray[run], aArray[run]); + } else { + bz_createOptionInSelect(aSelect, aArray[run][0], aArray[run][0]); + } + } +} + +/** + * Checks if a specified value is in the specified array. + * + * @param aArray Array to search for the value. + * @param aValue Value to search from the array. + * @return Boolean; true if value is found in the array and false if not. + */ +function bz_isValueInArray(aArray, aValue) +{ + var run = 0; + var len = aArray.length; + + for ( ; run < len; run++) { + if (aArray[run] == aValue) { + return true; + } + } + + return false; +} diff --git a/post_bug.cgi b/post_bug.cgi index 7d01ab62d..35a43feaa 100755 --- a/post_bug.cgi +++ b/post_bug.cgi @@ -255,6 +255,7 @@ if ($cgi->cookie("BUGLIST")) { @bug_list = split(/:/, $cgi->cookie("BUGLIST")); } $vars->{'bug_list'} = \@bug_list; +$vars->{'valid_keywords'} = [map($_->name, Bugzilla::Keyword->get_all)]; $vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count(); if ($token) { diff --git a/process_bug.cgi b/process_bug.cgi index 55e65d2a9..8be6cb48f 100755 --- a/process_bug.cgi +++ b/process_bug.cgi @@ -66,6 +66,7 @@ my $cgi = Bugzilla->cgi; my $dbh = Bugzilla->dbh; my $template = Bugzilla->template; local our $vars = {}; +$vars->{'valid_keywords'} = [map($_->name, Bugzilla::Keyword->get_all)]; $vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count(); my @editable_bug_fields = editable_bug_fields(); diff --git a/show_bug.cgi b/show_bug.cgi index bc6faa8a5..6aa314565 100755 --- a/show_bug.cgi +++ b/show_bug.cgi @@ -97,6 +97,7 @@ eval { $vars->{'bugs'} = \@bugs; $vars->{'marks'} = \%marks; +$vars->{'valid_keywords'} = [map($_->name, Bugzilla::Keyword->get_all)]; $vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count(); my @bugids = map {$_->bug_id} @bugs; diff --git a/skins/standard/global.css b/skins/standard/global.css index 244958749..2c26966a4 100644 --- a/skins/standard/global.css +++ b/skins/standard/global.css @@ -346,3 +346,14 @@ div.user_match { .field_value { vertical-align: top; } + +#keyword-chooser { + padding: 10px; + position: absolute; + z-index: 25; + top: 50px; + left: 50px; + border: 2px solid #404D6C; + -moz-border-radius: 5px; + background: white; +} diff --git a/template/en/default/bug/create/create.html.tmpl b/template/en/default/bug/create/create.html.tmpl index af7285cac..883db1893 100644 --- a/template/en/default/bug/create/create.html.tmpl +++ b/template/en/default/bug/create/create.html.tmpl @@ -31,7 +31,7 @@ [% PROCESS global/header.html.tmpl title = title style_urls = [ 'skins/standard/create_attachment.css' ] - javascript_urls = [ "js/attachment.js" ] + javascript_urls = [ "js/attachment.js", "js/util.js", "js/keyword-chooser.js" ] %] <script type="text/javascript"> @@ -486,7 +486,7 @@ function handleWantsAttachment(wants_attachment) { </strong> </td> <td colspan="3"> - <input name="keywords" size="60" value="[% keywords FILTER html %]"> (optional) + <input id="keywords" name="keywords" size="60" value="[% keywords FILTER html %]" onfocus="this.chooser.open();"> (optional) </td> </tr> [% END %] @@ -575,6 +575,12 @@ function handleWantsAttachment(wants_attachment) { <input type="hidden" name="form_name" value="enter_bug"> </form> +[% IF use_keywords %] + [% PROCESS "bug/keyword-chooser.html.tmpl" + sel_keywords = keywords.split(', ') + %] +[% END %] + [%# Links or content with more information about the bug being created. %] [% Hook.process("end") %] diff --git a/template/en/default/bug/create/created.html.tmpl b/template/en/default/bug/create/created.html.tmpl index 17f057ca0..dbdf24432 100644 --- a/template/en/default/bug/create/created.html.tmpl +++ b/template/en/default/bug/create/created.html.tmpl @@ -37,6 +37,7 @@ [% PROCESS global/header.html.tmpl title = "$terms.Bug $id Submitted" + javascript_urls = [ "js/util.js", "js/keyword-chooser.js" ] %] [% header_done = 1 %] diff --git a/template/en/default/bug/edit.html.tmpl b/template/en/default/bug/edit.html.tmpl index 215595e5a..619c594e1 100644 --- a/template/en/default/bug/edit.html.tmpl +++ b/template/en/default/bug/edit.html.tmpl @@ -204,7 +204,8 @@ <b><a href="describekeywords.cgi"><u>K</u>eywords</a></b></label>: </td> [% PROCESS input inputname => "keywords" size => 60 colspan => 2 - value => bug.keywords.join(', ') %] + value => bug.keywords.join(', ') + onfocus => "this.chooser.open()" %] </tr> [% END %] @@ -542,6 +543,12 @@ </form> +[% IF use_keywords %] + [% PROCESS "bug/keyword-chooser.html.tmpl" + sel_keywords = bug.keywords.split(', ') + %] +[% END %] + [%############################################################################%] [%# Block for the first table in the "Details" section #%] [%############################################################################%] @@ -826,7 +833,8 @@ [% IF bug.check_can_change_field(inputname, 0, 1) %] <input id="[% inputname %]" name="[% inputname %]" value="[% val FILTER html %]"[% " size=\"$size\"" IF size %] - [% " maxlength=\"$maxlength\"" IF maxlength %]> + [% " maxlength=\"$maxlength\"" IF maxlength %] + [% " onfocus=\"$onfocus\"" IF onfocus %]> [% ELSE %] <input type="hidden" name="[% inputname %]" id="[% inputname %]" value="[% val FILTER html %]"> @@ -843,6 +851,7 @@ [% colspan = 0 %] [% size = 0 %] [% value = undef %] + [% onfocus = undef %] [% END %] [%############################################################################%] diff --git a/template/en/default/bug/keyword-chooser.html.tmpl b/template/en/default/bug/keyword-chooser.html.tmpl new file mode 100644 index 000000000..a3d5d1b55 --- /dev/null +++ b/template/en/default/bug/keyword-chooser.html.tmpl @@ -0,0 +1,91 @@ +<!-- 1.0@bugzilla.org --> +[%# ***** BEGIN LICENSE BLOCK ***** + # Version: MPL 1.1/GPL 2.0/LGPL 2.1 + # + # The contents of this file are subject to the Mozilla Public License Version + # 1.1 (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.mozilla.org/MPL/ + # + # Software distributed under the License is distributed on an "AS IS" basis, + # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + # for the specific language governing rights and limitations under the + # License. + # + # The Original Code is Keyword Picker. + # + # The Initial Developer of the Original Code is America Online, Inc. + # Portions created by the Initial Developer are Copyright (C) 2004 + # Mozilla Foundation. All Rights Reserved. + # + # Contributor(s): + # Christopher A. Aillon <christopher@aillon.com> (Original Author) + # + # Alternatively, the contents of this file may be used under the terms of + # either the GNU General Public License Version 2 or later (the "GPL"), or + # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + # in which case the provisions of the GPL or the LGPL are applicable instead + # of those above. If you wish to allow use of your version of this file only + # under the terms of either the GPL or the LGPL, and not to allow others to + # use your version of this file under the terms of the MPL, indicate your + # decision by deleting the provisions above and replace them with the notice + # and other provisions required by the GPL or the LGPL. If you do not delete + # the provisions above, a recipient may use your version of this file under + # the terms of any one of the MPL, the GPL or the LGPL. + # + # ***** END LICENSE BLOCK ***** + #%] + +[%############################################################################%] +[%# Keyword Picker #%] +[%############################################################################%] +[%# #%] +[%# If you edit this file, you might also need to edit js/keyword-chooser.js #%] +[%# #%] +[%############################################################################%] + +<div id="keyword-chooser" style="display: none"> + <table> + <tr> + <td valign="top"> + Available Keywords:<br> + <select id="keyword-list" size="5" multiple> + [% FOREACH kwd = valid_keywords %] + [% UNLESS sel_keywords && lsearch(sel_keywords, kwd) != -1 %] + <option value="[% kwd FILTER html %]">[% kwd FILTER html %]</option> + [% END %] + [% END %] + </select> + </td> + <td valign="middle"> + <button onclick="document.getElementById('keyword-chooser').chooserElement.chooser.choose(); return false;">-></button><br> + <button onclick="document.getElementById('keyword-chooser').chooserElement.chooser.unchoose(); return false;"><-</button> + </td> + <td valign="top"> + Bug Keywords:<br> + <select id="bug-keyword-list" size="5" multiple> + [% FOREACH kwd = valid_keywords %] + [% IF sel_keywords && lsearch(sel_keywords, kwd) != -1 %] + <option value="[% kwd FILTER html %]">[% kwd FILTER html %]</option> + [% END %] + [% END %] + </select> + </td> + <td valign="middle"> + <button type="button" onclick="document.getElementById('keyword-chooser').chooserElement.chooser.ok(); return false">OK</button> + <br> + <button type="button" onclick="document.getElementById('keyword-chooser').chooserElement.chooser.cancel(); return false" style="margin-top:3px;">Cancel</button> + </td> + </tr> + </table> +</div> + +<script type="text/javascript"> + var validKeywords = new Array(); + + [% FOREACH kwd = valid_keywords %] + validKeywords[validKeywords.length] = "[% kwd FILTER html %]"; + [% END %] + + InitializeKeywordChooser(validKeywords); +</script> diff --git a/template/en/default/bug/process/header.html.tmpl b/template/en/default/bug/process/header.html.tmpl index f15648c7d..dd384ad51 100644 --- a/template/en/default/bug/process/header.html.tmpl +++ b/template/en/default/bug/process/header.html.tmpl @@ -42,4 +42,6 @@ [% title = "Change Votes" %] [% END %] -[% PROCESS global/header.html.tmpl %] +[% PROCESS global/header.html.tmpl + javascript_urls = [ "js/util.js", "js/keyword-chooser.js" ] + %] diff --git a/template/en/default/bug/show.html.tmpl b/template/en/default/bug/show.html.tmpl index c652773e8..326c7821f 100644 --- a/template/en/default/bug/show.html.tmpl +++ b/template/en/default/bug/show.html.tmpl @@ -40,6 +40,7 @@ "bz_component_$bug.component", "bz_bug_$bug.bug_id" ] + javascript_urls = [ "js/util.js", "js/keyword-chooser.js" ] %] [% END %] diff --git a/template/en/default/filterexceptions.pl b/template/en/default/filterexceptions.pl index cc2e060ee..ac579115b 100644 --- a/template/en/default/filterexceptions.pl +++ b/template/en/default/filterexceptions.pl @@ -315,6 +315,7 @@ '" colspan=\"$colspan\"" IF colspan', '" size=\"$size\"" IF size', '" maxlength=\"$maxlength\"" IF maxlength', + '" onfocus=\"$onfocus\"" IF onfocus', 'flag.status', ], diff --git a/template/en/default/list/edit-multiple.html.tmpl b/template/en/default/list/edit-multiple.html.tmpl index 667ad1b27..668445995 100644 --- a/template/en/default/list/edit-multiple.html.tmpl +++ b/template/en/default/list/edit-multiple.html.tmpl @@ -204,7 +204,8 @@ </label> </th> <td colspan="3"> - <input id="keywords" name="keywords" size="32"> + <input id="keywords" name="keywords" size="32" + onfocus = "this.chooser.open();"> <select name="keywordaction"> <option value="add">Add these keywords</option> <option value="delete">Delete these keywords</option> @@ -238,6 +239,12 @@ </table> +[% IF use_keywords %] + [% PROCESS "bug/keyword-chooser.html.tmpl" + sel_keywords = keywords.split(', ') + %] +[% END %] + <b><label for="comment">Additional Comments:</label></b><br> [% INCLUDE global/textarea.html.tmpl name = 'comment' diff --git a/template/en/default/list/list.html.tmpl b/template/en/default/list/list.html.tmpl index dfed2f024..823ca2a10 100644 --- a/template/en/default/list/list.html.tmpl +++ b/template/en/default/list/list.html.tmpl @@ -49,6 +49,7 @@ title = title style = style atomlink = "buglist.cgi?$urlquerypart&title=$title&ctype=atom" + javascript_urls = [ "js/util.js", "js/keyword-chooser.js" ] %] <div class="bz_query_head" align="center"> |