]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 80169: JavaScript-enhanced keyword editing - Patch by Teemu Mannermaa <wicked...
authorlpsolit%gmail.com <>
Mon, 14 May 2007 22:56:29 +0000 (22:56 +0000)
committerlpsolit%gmail.com <>
Mon, 14 May 2007 22:56:29 +0000 (22:56 +0000)
17 files changed:
buglist.cgi
enter_bug.cgi
js/keyword-chooser.js [new file with mode: 0644]
js/util.js
post_bug.cgi
process_bug.cgi
show_bug.cgi
skins/standard/global.css
template/en/default/bug/create/create.html.tmpl
template/en/default/bug/create/created.html.tmpl
template/en/default/bug/edit.html.tmpl
template/en/default/bug/keyword-chooser.html.tmpl [new file with mode: 0644]
template/en/default/bug/process/header.html.tmpl
template/en/default/bug/show.html.tmpl
template/en/default/filterexceptions.pl
template/en/default/list/edit-multiple.html.tmpl
template/en/default/list/list.html.tmpl

index 96011d3f700aaa37e414fa3924244059333b0677..338017fa14afa9586d52781575735b7be611f5a1 100755 (executable)
@@ -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;
index 3bd7e390c78e37ddfeca169bd5d3569e67cbcd37..c802c0096e74554ba63ee16c4fa431587c4bee57 100755 (executable)
@@ -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 (file)
index 0000000..afc90b4
--- /dev/null
@@ -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);
+}
index 9d2209093f742f6d935bfe32d4b5d7452ea68eda..293c89a5d4f8e9112e9bdd4d63a1d0b4fef9669f 100644 (file)
@@ -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;
+}
index 7d01ab62d6b1dffdd8f28537508b996f9cae4668..35a43feaa4d646c48b1738b6aac4b7f78053d55b 100755 (executable)
@@ -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) {
index 55e65d2a970c836bdd618df3509156a5ba53cdc0..8be6cb48f7796c91b72347b187108d892a32ddba 100755 (executable)
@@ -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();
index bc6faa8a58b85027aaf462babe9efe02f9a76fe8..6aa31456564dd6fef555e53d165f51e419aa7f81 100755 (executable)
@@ -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;
index 2449587499f4a542c2755ae6d1ada4e104d3cd63..2c26966a46caffc8acdd079a4761d060d52f3e39 100644 (file)
@@ -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;
+}
index af7285cacdb375110ec23b0f5fc19abac79982ea..883db1893d59763cdc209cd468164e12c3b0d6d2 100644 (file)
@@ -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") %]
 
index 17f057ca086dba9c3498b0f34fe10349efedc60f..dbdf24432b0eaa6ba6511400d5501ad38cba2eaa 100644 (file)
@@ -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 %]
index 215595e5a0011239c868f3089b08fc6219d33a90..619c594e182bda00b55994dfe506f4deb4eba902 100644 (file)
                     <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 %]
 
 
 </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                       #%]
 [%############################################################################%]
     [% 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 %]">
   [% 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 (file)
index 0000000..a3d5d1b
--- /dev/null
@@ -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&nbsp;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&nbsp;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>
index f15648c7d51b633c495fd62c4db002aada286f77..dd384ad515dbbca8d42500dd1978b83d6f3ba58e 100644 (file)
@@ -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" ]
+ %]
index c652773e84bda17b21ac0cc5d6cdf98fe932e574..326c7821f7502757988d352f1886951bce40258b 100644 (file)
@@ -40,6 +40,7 @@
                    "bz_component_$bug.component",
                    "bz_bug_$bug.bug_id"
                   ]
+    javascript_urls = [ "js/util.js", "js/keyword-chooser.js" ]
   %]
 [% END %]
 
index cc2e060ee62da268ac8066c624b97fad9b34e18d..ac579115b5d793aa3a446bc7a4aa16bb5343794c 100644 (file)
   '" colspan=\"$colspan\"" IF colspan',
   '" size=\"$size\"" IF size',
   '" maxlength=\"$maxlength\"" IF maxlength',
+  '" onfocus=\"$onfocus\"" IF onfocus',
   'flag.status',
 ],
 
index 667ad1b27ff4c9d7e2d892dffb865bd011247848..6684459954b6bc49108c762bf31068a7c3ea79ef 100644 (file)
         </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>
 
 </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'
index dfed2f0242ff41d4c3e3cd77b8b8672438107524..823ca2a105fb29740fc9b1e00e4344ad73b4a9d2 100644 (file)
@@ -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">