Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(342)

Side by Side Diff: appengine/monorail/static/js/tracker/tracker-editing.js

Issue 1868553004: Open Source Monorail (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Rebase Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /* Copyright 2016 The Chromium Authors. All Rights Reserved.
2 *
3 * Use of this source code is governed by a BSD-style
4 * license that can be found in the LICENSE file or at
5 * https://developers.google.com/open-source/licenses/bsd
6 */
7
8 /**
9 * This file contains JS functions that support various issue editing
10 * features of Monorail. These editing features include: selecting
11 * issues on the issue list page, adding attachments, expanding and
12 * collapsing the issue editing form, and starring issues.
13 *
14 * Browser compatability: IE6, IE7, FF1.0+, Safari.
15 */
16
17
18 /**
19 * Here are some string constants that are used repeatedly in the code.
20 */
21 var TKR_SELECTED_CLASS = 'selected';
22 var TKR_UNDEF_CLASS = 'undef';
23 var TKR_NOVEL_CLASS = 'novel';
24 var TKR_EXCL_CONFICT_CLASS = 'exclconflict';
25 var TKR_QUESTION_MARK_CLASS = 'questionmark';
26 var TKR_ATTACHPROMPT_ID = 'attachprompt';
27 var TKR_ATTACHAFILE_ID = 'attachafile';
28 var TKR_ATTACHMAXSIZE_ID = 'attachmaxsize';
29 var TKR_CURRENT_TEMPLATE_INDEX_ID = 'current_template_index';
30 var TKR_PROMPT_MEMBERS_ONLY_CHECKBOX_ID = 'members_only_checkbox';
31 var TKR_PROMPT_SUMMARY_EDITOR_ID = 'summary_editor';
32 var TKR_PROMPT_SUMMARY_MUST_BE_EDITED_CHECKBOX_ID =
33 'summary_must_be_edited_checkbox';
34 var TKR_PROMPT_CONTENT_EDITOR_ID = 'content_editor';
35 var TKR_PROMPT_STATUS_EDITOR_ID = 'status_editor';
36 var TKR_PROMPT_OWNER_EDITOR_ID = 'owner_editor';
37 var TKR_PROMPT_ADMIN_NAMES_EDITOR_ID = 'admin_names_editor';
38 var TKR_OWNER_DEFAULTS_TO_MEMBER_CHECKBOX_ID =
39 'owner_defaults_to_member_checkbox';
40 var TKR_OWNER_DEFAULTS_TO_MEMBER_AREA_ID =
41 'owner_defaults_to_member_area';
42 var TKR_COMPONENT_REQUIRED_CHECKBOX_ID =
43 'component_required_checkbox';
44 var TKR_PROMPT_COMPONENTS_EDITOR_ID = 'components_editor';
45 var TKR_FIELD_EDITOR_ID_PREFIX = 'custom_';
46 var TKR_PROMPT_LABELS_EDITOR_ID_PREFIX = 'label';
47 var TKR_CONFIRMAREA_ID = 'confirmarea';
48 var TKR_DISCARD_YOUR_CHANGES = 'Discard your changes?';
49 // Note, users cannot enter '<'.
50 var TKR_DELETED_PROMPT_NAME = '<DELETED>';
51 // Display warning if labels contain the following prefixes.
52 // The following list is the same as tracker_constants.RESERVED_PREFIXES except
53 // for the 'hotlist' prefix. 'hostlist' will be added when it comes a full
54 // feature and when projects that use 'Hostlist-*' labels are transitioned off.
55 var TKR_LABEL_RESERVED_PREFIXES = [
56 'id', 'project', 'reporter', 'summary', 'status', 'owner', 'cc',
57 'attachments', 'attachment', 'component', 'opened', 'closed',
58 'modified', 'is', 'has', 'blockedon', 'blocking', 'blocked', 'mergedinto',
59 'stars', 'starredby', 'description', 'comment', 'commentby', 'label',
60 'rank', 'explicit_status', 'derived_status', 'explicit_owner',
61 'derived_owner', 'explicit_cc', 'derived_cc', 'explicit_label',
62 'derived_label', 'last_comment_by', 'exact_component',
63 'explicit_component', 'derived_component']
64
65 /**
66 * Select all the issues on the issue list page.
67 */
68 function TKR_selectAllIssues() {
69 TKR_selectIssues(true);
70 }
71
72
73 /**
74 * Function to deselect all the issues on the issue list page.
75 */
76 function TKR_selectNoneIssues() {
77 TKR_selectIssues(false);
78 }
79
80
81 /**
82 * Function to select or deselect all the issues on the issue list page.
83 * @param {boolean} checked True means select issues, False means deselect.
84 */
85 function TKR_selectIssues(checked) {
86 var table = $('resultstable');
87 for (var r = 0; r < table.rows.length; ++r) {
88 var row = table.rows[r];
89 var firstCell = row.cells[0];
90 if (firstCell.tagName == 'TD') {
91 for (var e = 0; e < firstCell.childNodes.length; ++e) {
92 var element = firstCell.childNodes[e];
93 if (element.tagName == 'INPUT' && element.type == 'checkbox') {
94 element.checked = checked ? 'checked' : '';
95 if (checked) {
96 row.classList.add(TKR_SELECTED_CLASS);
97 } else {
98 row.classList.remove(TKR_SELECTED_CLASS);
99 }
100 }
101 }
102 }
103 }
104 }
105
106
107 /**
108 * The ID number to append to the next dynamically created file upload field.
109 */
110 var TKR_nextFileID = 1;
111
112
113 /**
114 * Function to dynamically create a new attachment upload field add
115 * insert it into the page DOM.
116 * @param {string} id The id of the parent HTML element.
117 */
118 function TKR_addAttachmentFields(id) {
119 if (TKR_nextFileID >= 16) {
120 return;
121 }
122 var el = $(id);
123 el.style.marginTop = '4px';
124 var div = document.createElement('div');
125 var id = 'file' + TKR_nextFileID;
126 var label = TKR_createChild(div, 'label', null, null, 'Attach file:');
127 label.setAttribute('for', id);
128 var input = TKR_createChild(
129 div, 'input', null, id, null, "width:auto;margin-left:17px");
130 input.setAttribute('type', 'file');
131 input.name = id;
132 var removeLink = TKR_createChild(
133 div, 'a', null, null, 'Remove', "font-size:x-small");
134 removeLink.href = '#';
135 removeLink.addEventListener('click', function(event) {
136 console.log(arguments);
137 var target = event.target;
138 $('attachafile').focus();
139 target.parentNode.parentNode.removeChild(target.parentNode);
140 event.preventDefault();
141 });
142 el.appendChild(div);
143 el.querySelector('input').focus();
144 ++TKR_nextFileID;
145 if (TKR_nextFileID < 16) {
146 $(TKR_ATTACHAFILE_ID).innerText = 'Attach another file';
147 } else {
148 $(TKR_ATTACHPROMPT_ID).style.display = 'none';
149 }
150 $(TKR_ATTACHMAXSIZE_ID).style.display = '';
151 }
152
153
154 /**
155 * Function to display the form so that the user can update an issue.
156 */
157 function TKR_openIssueUpdateForm() {
158 TKR_showHidden($('makechangesarea'));
159 TKR_goToAnchor('makechanges');
160 TKR_forceProperTableWidth();
161 window.setTimeout(
162 function () { document.getElementById('addCommentTextArea').focus(); },
163 100);
164 }
165
166
167 /**
168 * The index of the template that is currently selected for editing
169 * on the administration page for issues.
170 */
171 var TKR_currentTemplateIndex = 0;
172
173
174 /**
175 * Array of field IDs that are defined in the current project, set by call to se tFieldIDs().
176 */
177 var TKR_fieldIDs = [];
178
179
180 function TKR_setFieldIDs(fieldIDs) {
181 TKR_fieldIDs = fieldIDs;
182 }
183
184
185 /**
186 * This function displays the appropriate template text in a text field.
187 * It is called after the user has selected one template to view/edit.
188 * @param {Element} widget The list widget containing the list of templates.
189 */
190 function TKR_selectTemplate(widget) {
191 TKR_showHidden($('edit_panel'));
192 TKR_currentTemplateIndex = widget.value;
193 $(TKR_CURRENT_TEMPLATE_INDEX_ID).value = TKR_currentTemplateIndex;
194
195 var content_editor = $(TKR_PROMPT_CONTENT_EDITOR_ID);
196 TKR_makeDefined(content_editor);
197
198 var can_edit = $('can_edit_' + TKR_currentTemplateIndex).value == 'yes';
199 var disabled = can_edit ? '' : 'disabled';
200
201 $(TKR_PROMPT_MEMBERS_ONLY_CHECKBOX_ID).disabled = disabled;
202 $(TKR_PROMPT_MEMBERS_ONLY_CHECKBOX_ID).checked = $(
203 'members_only_' + TKR_currentTemplateIndex).value == 'yes';
204 $(TKR_PROMPT_SUMMARY_EDITOR_ID).disabled = disabled;
205 $(TKR_PROMPT_SUMMARY_EDITOR_ID).value = $(
206 'summary_' + TKR_currentTemplateIndex).value;
207 $(TKR_PROMPT_SUMMARY_MUST_BE_EDITED_CHECKBOX_ID).disabled = disabled;
208 $(TKR_PROMPT_SUMMARY_MUST_BE_EDITED_CHECKBOX_ID).checked = $(
209 'summary_must_be_edited_' + TKR_currentTemplateIndex).value == 'yes';
210 content_editor.disabled = disabled;
211 content_editor.value = $('content_' + TKR_currentTemplateIndex).value;
212 $(TKR_PROMPT_STATUS_EDITOR_ID).disabled = disabled;
213 $(TKR_PROMPT_STATUS_EDITOR_ID).value = $(
214 'status_' + TKR_currentTemplateIndex).value;
215 $(TKR_PROMPT_OWNER_EDITOR_ID).disabled = disabled;
216 $(TKR_PROMPT_OWNER_EDITOR_ID).value = $(
217 'owner_' + TKR_currentTemplateIndex).value;
218 $(TKR_OWNER_DEFAULTS_TO_MEMBER_CHECKBOX_ID).disabled = disabled;
219 $(TKR_OWNER_DEFAULTS_TO_MEMBER_CHECKBOX_ID).checked = $(
220 'owner_defaults_to_member_' + TKR_currentTemplateIndex).value == 'yes';
221 $(TKR_COMPONENT_REQUIRED_CHECKBOX_ID).disabled = disabled;
222 $(TKR_COMPONENT_REQUIRED_CHECKBOX_ID).checked = $(
223 'component_required_' + TKR_currentTemplateIndex).value == 'yes';
224 $(TKR_OWNER_DEFAULTS_TO_MEMBER_AREA_ID).disabled = disabled;
225 $(TKR_OWNER_DEFAULTS_TO_MEMBER_AREA_ID).style.display =
226 $(TKR_PROMPT_OWNER_EDITOR_ID).value ? 'none' : '';
227 $(TKR_PROMPT_COMPONENTS_EDITOR_ID).disabled = disabled;
228 $(TKR_PROMPT_COMPONENTS_EDITOR_ID).value = $(
229 'components_' + TKR_currentTemplateIndex).value;
230
231 // Blank out all custom field editors first, then fill them in during the next loop.
232 for (var i = 0; i < TKR_fieldIDs.length; i++) {
233 var fieldEditor = $(TKR_FIELD_EDITOR_ID_PREFIX + TKR_fieldIDs[i]);
234 var holder = $('field_value_' + TKR_currentTemplateIndex + '_' + TKR_fieldID s[i]);
235 if (fieldEditor) {
236 fieldEditor.disabled = disabled;
237 fieldEditor.value = holder ? holder.value : '';
238 }
239 }
240
241 var i = 0;
242 while ($(TKR_PROMPT_LABELS_EDITOR_ID_PREFIX + i)) {
243 $(TKR_PROMPT_LABELS_EDITOR_ID_PREFIX + i).disabled = disabled;
244 $(TKR_PROMPT_LABELS_EDITOR_ID_PREFIX + i).value =
245 $('label_' + TKR_currentTemplateIndex + '_' + i).value;
246 i++;
247 }
248
249 $(TKR_PROMPT_ADMIN_NAMES_EDITOR_ID).disabled = disabled;
250 $(TKR_PROMPT_ADMIN_NAMES_EDITOR_ID).value = $(
251 'admin_names_' + TKR_currentTemplateIndex).value;
252
253 var numNonDeletedTemplates = 0;
254 for (var i = 0; i < TKR_templateNames.length; i++) {
255 if (TKR_templateNames[i] != TKR_DELETED_PROMPT_NAME) {
256 numNonDeletedTemplates++;
257 }
258 }
259 if ($('delbtn')) {
260 if (numNonDeletedTemplates > 1) {
261 $('delbtn').disabled='';
262 }
263 else { // Don't allow the last template to be deleted.
264 $('delbtn').disabled='disabled';
265 }
266 }
267 }
268
269
270 var TKR_templateNames = []; // Exported in tracker-onload.js
271
272
273 /**
274 * Create a new issue template and add the needed form fields to the DOM.
275 */
276 function TKR_newTemplate() {
277 var newIndex = TKR_templateNames.length;
278 var templateName = prompt('Name of new template?', '');
279 templateName = templateName.replace(
280 /[&<>"]/g, '' // " help emacs highlighing
281 );
282 if (!templateName) return;
283
284 for (var i = 0; i < TKR_templateNames.length; i++) {
285 if (templateName == TKR_templateNames[i]) {
286 alert('Please choose a unique name.')
287 return;
288 }
289 }
290
291 TKR_addTemplateHiddenFields(newIndex, templateName);
292 TKR_templateNames.push(templateName);
293
294 var templateOption = TKR_createChild(
295 $('template_menu'), 'option', null, null, templateName);
296 templateOption.value = newIndex;
297 templateOption.selected = 'selected';
298
299 var developerOption = TKR_createChild(
300 $('default_template_for_developers'), 'option', null, null, templateName);
301 developerOption.value = templateName;
302
303 var userOption = TKR_createChild(
304 $('default_template_for_users'), 'option', null, null, templateName);
305 userOption.value = templateName;
306
307 TKR_selectTemplate($('template_menu'));
308 }
309
310
311 /**
312 * Private function to append HTML for new hidden form fields
313 * for a new issue template to the issue admin form.
314 */
315 function TKR_addTemplateHiddenFields(templateIndex, templateName) {
316 var parentEl = $('adminTemplates');
317 TKR_appendHiddenField(
318 parentEl, 'template_id_' + templateIndex, 'template_id_' + templateIndex, '0');
319 TKR_appendHiddenField(parentEl, 'name_' + templateIndex,
320 'name_' + templateIndex, templateName);
321 TKR_appendHiddenField(parentEl, 'members_only_' + templateIndex);
322 TKR_appendHiddenField(parentEl, 'summary_' + templateIndex);
323 TKR_appendHiddenField(parentEl, 'summary_must_be_edited_' + templateIndex);
324 TKR_appendHiddenField(parentEl, 'content_' + templateIndex);
325 TKR_appendHiddenField(parentEl, 'status_' + templateIndex);
326 TKR_appendHiddenField(parentEl, 'owner_' + templateIndex);
327 TKR_appendHiddenField(
328 parentEl, 'owner_defaults_to_member_' + templateIndex,
329 'owner_defaults_to_member_' + templateIndex, 'yes');
330 TKR_appendHiddenField(parentEl, 'component_required_' + templateIndex);
331 TKR_appendHiddenField(parentEl, 'components_' + templateIndex);
332 TKR_appendHiddenField(parentEl, 'members_only_' + templateIndex);
333
334 var i = 0;
335 while ($('label_0_' + i)) {
336 TKR_appendHiddenField(parentEl, 'label_' + templateIndex,
337 'label_' + templateIndex + '_' + i);
338 i++;
339 }
340
341 for (var i = 0; i < TKR_fieldIDs.length; i++) {
342 var fieldId = 'field_value_' + templateIndex + '_' + TKR_fieldIDs[i];
343 TKR_appendHiddenField(parentEl, fieldId, fieldId);
344 }
345
346 TKR_appendHiddenField(parentEl, 'admin_names_' + templateIndex);
347 TKR_appendHiddenField(
348 parentEl, 'can_edit_' + templateIndex, 'can_edit_' + templateIndex,
349 'yes');
350 }
351
352
353 /**
354 * Utility function to append string parts for one hidden field
355 * to the given array.
356 */
357 function TKR_appendHiddenField(parentEl, name, opt_id, opt_value) {
358 var input = TKR_createChild(parentEl, 'input', null, opt_id || name);
359 input.setAttribute('type', 'hidden');
360 input.name = name;
361 input.value = opt_value || '';
362 }
363
364
365 /**
366 * Delete the currently selected issue template, and mark its hidden
367 * form field as deleted so that they will be ignored when submitted.
368 */
369 function TKR_deleteTemplate() {
370 // Mark the current template name as deleted.
371 TKR_templateNames.splice(
372 TKR_currentTemplateIndex, 1, TKR_DELETED_PROMPT_NAME);
373 $('name_' + TKR_currentTemplateIndex).value = TKR_DELETED_PROMPT_NAME;
374 _toggleHidden($('edit_panel'));
375 $('delbtn').disabled = 'disabled';
376 TKR_rebuildTemplateMenu();
377 TKR_rebuildDefaultTemplateMenu('default_template_for_developers');
378 TKR_rebuildDefaultTemplateMenu('default_template_for_users');
379 }
380
381 /**
382 * Utility function to rebuild the template menu on the issue admin page.
383 */
384 function TKR_rebuildTemplateMenu() {
385 var parentEl = $('template_menu');
386 while (parentEl.childNodes.length)
387 parentEl.removeChild(parentEl.childNodes[0]);
388 for (var i = 0; i < TKR_templateNames.length; i++) {
389 if (TKR_templateNames[i] != TKR_DELETED_PROMPT_NAME) {
390 var option = TKR_createChild(
391 parentEl, 'option', null, null, TKR_templateNames[i]);
392 option.value = i;
393 }
394 }
395 }
396
397
398 /**
399 * Utility function to rebuild a default template drop-down.
400 */
401 function TKR_rebuildDefaultTemplateMenu(menuID) {
402 var defaultTemplateName = $(menuID).value;
403 var parentEl = $(menuID);
404 while (parentEl.childNodes.length)
405 parentEl.removeChild(parentEl.childNodes[0]);
406 for (var i = 0; i < TKR_templateNames.length; i++) {
407 if (TKR_templateNames[i] != TKR_DELETED_PROMPT_NAME) {
408 var option = TKR_createChild(
409 parentEl, 'option', null, null, TKR_templateNames[i]);
410 option.values = TKR_templateNames[i];
411 if (defaultTemplateName == TKR_templateNames[i]) {
412 option.setAttribute('selected', 'selected');
413 }
414 }
415 }
416 }
417
418
419 /**
420 * Change the issue template to the specified one.
421 * TODO(jrobbins): move to an AJAX implementation that would not reload page.
422 *
423 * @param {string} projectName The name of the current project.
424 * @param {string} templateName The name of the template to switch to.
425 */
426 function TKR_switchTemplate(projectName, templateName) {
427 var ok = true;
428 if (TKR_isDirty) {
429 ok = confirm('Switching to a different template will lose the text you enter ed.');
430 }
431 if (ok) {
432 window.location = '/p/' + projectName +
433 '/issues/entry?template=' + templateName;
434 }
435 }
436
437 /**
438 * Function to remove a CSS class and initial tip from a text widget.
439 * Some text fields or text areas display gray textual tips to help the user
440 * make use of those widgets. When the user focuses on the field, the tip
441 * disappears and is made ready for user input (in the normal text color).
442 * @param {Element} el The form field that had the gray text tip.
443 */
444 function TKR_makeDefined(el) {
445 if (el.classList.contains(TKR_UNDEF_CLASS)) {
446 el.classList.remove(TKR_UNDEF_CLASS);
447 el.value = '';
448 }
449 }
450
451
452 /**
453 * Save the contents of the visible issue template text area into a hidden
454 * text field for later submission.
455 * Called when the user has edited the text of a issue template.
456 */
457 function TKR_saveTemplate() {
458 if (TKR_currentTemplateIndex) {
459 $('members_only_' + TKR_currentTemplateIndex).value =
460 $(TKR_PROMPT_MEMBERS_ONLY_CHECKBOX_ID).checked ? 'yes' : '';
461 $('summary_' + TKR_currentTemplateIndex).value =
462 $(TKR_PROMPT_SUMMARY_EDITOR_ID).value;
463 $('summary_must_be_edited_' + TKR_currentTemplateIndex).value =
464 $(TKR_PROMPT_SUMMARY_MUST_BE_EDITED_CHECKBOX_ID).checked ? 'yes' : '';
465 $('content_' + TKR_currentTemplateIndex).value =
466 $(TKR_PROMPT_CONTENT_EDITOR_ID).value;
467 $('status_' + TKR_currentTemplateIndex).value =
468 $(TKR_PROMPT_STATUS_EDITOR_ID).value;
469 $('owner_' + TKR_currentTemplateIndex).value =
470 $(TKR_PROMPT_OWNER_EDITOR_ID).value;
471 $('owner_defaults_to_member_' + TKR_currentTemplateIndex).value =
472 $(TKR_OWNER_DEFAULTS_TO_MEMBER_CHECKBOX_ID).checked ? 'yes' : '';
473 $('component_required_' + TKR_currentTemplateIndex).value =
474 $(TKR_COMPONENT_REQUIRED_CHECKBOX_ID).checked ? 'yes' : '';
475 $('components_' + TKR_currentTemplateIndex).value =
476 $(TKR_PROMPT_COMPONENTS_EDITOR_ID).value;
477 $(TKR_OWNER_DEFAULTS_TO_MEMBER_AREA_ID).style.display =
478 $(TKR_PROMPT_OWNER_EDITOR_ID).value ? 'none' : '';
479
480 for (var i = 0; i < TKR_fieldIDs.length; i++) {
481 var fieldID = TKR_fieldIDs[i];
482 var fieldEditor = $(TKR_FIELD_EDITOR_ID_PREFIX + fieldID);
483 if (fieldEditor) {
484 _saveFieldValue(fieldID, fieldEditor.value);
485 }
486 }
487
488 var i = 0;
489 while ($('label_' + TKR_currentTemplateIndex + '_' + i)) {
490 $('label_' + TKR_currentTemplateIndex + '_' + i).value =
491 $(TKR_PROMPT_LABELS_EDITOR_ID_PREFIX + i).value;
492 i++;
493 }
494
495 $('admin_names_' + TKR_currentTemplateIndex).value =
496 $(TKR_PROMPT_ADMIN_NAMES_EDITOR_ID).value;
497 }
498 }
499
500
501 function _saveFieldValue(fieldID, val) {
502 var fieldValId = 'field_value_' + TKR_currentTemplateIndex + '_' + fieldID;
503 $(fieldValId).value = val;
504 }
505
506
507 /**
508 * This flag indicates that the user had made some edit to some form
509 * field on the page. Only a few specific pages actually update and use
510 * this flag.
511 */
512 var TKR_isDirty = false;
513
514
515 /**
516 * This function is called when the user edits a form field on a page that
517 * should offer the user the option to discard his/her edits.
518 */
519 function TKR_dirty() {
520 TKR_isDirty = true;
521 }
522
523
524 /**
525 * The user has clicked the 'Discard' button on the issue update form.
526 * If the form has been edited, ask if he/she is sure about discarding
527 * before then navigating to the given URL. This can go up to some
528 * other page, or reload the current page with a fresh form.
529 * @param {string} nextUrl The page to show after discarding.
530 */
531 function TKR_confirmDiscardUpdate(nextUrl) {
532 if (!TKR_isDirty || confirm(TKR_DISCARD_YOUR_CHANGES)) {
533 document.location = nextUrl;
534 }
535 }
536
537
538 /**
539 * The user has clicked the 'Discard' button on the issue entry form.
540 * If the form has been edited, this function asks if he/she is sure about
541 * discarding before doing it.
542 * @param {Element} discardButton The 'Discard' button.
543 */
544 function TKR_confirmDiscardEntry(discardButton) {
545 if (!TKR_isDirty || confirm(TKR_DISCARD_YOUR_CHANGES)) {
546 TKR_go('list');
547 }
548 }
549
550
551 /**
552 * Normally, we show 2 rows of label editing fields when updating an issue.
553 * However, if the issue has more than that many labels already, we make sure to
554 * show them all.
555 */
556 function TKR_exposeExistingLabelFields() {
557 if ($('label3').value ||
558 $('label4').value ||
559 $('label5').value) {
560 if ($('addrow1')) {
561 _showID('LF_row2');
562 _hideID('addrow1');
563 }
564 }
565 if ($('label6').value ||
566 $('label7').value ||
567 $('label8').value) {
568 _showID('LF_row3');
569 _hideID('addrow2');
570 }
571 if ($('label9').value ||
572 $('label10').value ||
573 $('label11').value) {
574 _showID('LF_row4');
575 _hideID('addrow3');
576 }
577 if ($('label12').value ||
578 $('label13').value ||
579 $('label14').value) {
580 _showID('LF_row5');
581 _hideID('addrow4');
582 }
583 if ($('label15').value ||
584 $('label16').value ||
585 $('label17').value) {
586 _showID('LF_row6');
587 _hideID('addrow5');
588 }
589 if ($('label18').value ||
590 $('label19').value ||
591 $('label20').value) {
592 _showID('LF_row7');
593 _hideID('addrow6');
594 }
595 if ($('label21').value ||
596 $('label22').value ||
597 $('label23').value) {
598 _showID('LF_row8');
599 _hideID('addrow7');
600 }
601 }
602
603
604 /**
605 * Flag to indicate when the user has not yet caused any input events.
606 * We use this to clear the placeholder in the new issue summary field
607 * exactly once.
608 */
609 var TKR_firstEvent = true;
610
611
612 /**
613 * This is called in response to almost any user input event on the
614 * issue entry page. If the placeholder in the new issue sumary field has
615 * not yet been cleared, then this function clears it.
616 */
617 function TKR_clearOnFirstEvent() {
618 if (TKR_firstEvent) {
619 TKR_firstEvent = false;
620 $('summary').value = TKR_keepJustSummaryPrefixes($('summary').value);
621 }
622 }
623
624 /**
625 * Clear the summary, except for any prefixes of the form "[bracketed text]"
626 * or "keyword:". If there were any, add a trailing space. This is useful
627 * to people who like to encode issue classification info in the summary line.
628 */
629 function TKR_keepJustSummaryPrefixes(s) {
630 var matches = s.match(/^(\[[^\]]+\])+|^(\S+:\s*)+/);
631 if (matches == null) {
632 return '';
633 }
634
635 var prefix = matches[0];
636 if (prefix.substr(prefix.length - 1) != ' ') {
637 prefix += ' ';
638 }
639 return prefix;
640 }
641
642 /**
643 * An array of label <input>s that start with reserved prefixes.
644 */
645 var TKR_labelsWithReservedPrefixes = [];
646
647 /**
648 * An array of label <input>s that are equal to reserved words.
649 */
650 var TKR_labelsConflictingWithReserved = [];
651
652 /**
653 * An array of novel issue status values entered by the user on the
654 * current page. 'Novel' means that they are not well known and are
655 * likely to be typos. Note that this list will always have zero or
656 * one element, but a list is used for consistency with the list of
657 * novel labels.
658 */
659 var TKR_novelStatuses = [];
660
661 /**
662 * An array of novel issue label values entered by the user on the
663 * current page. 'Novel' means that they are not well known and are
664 * likely to be typos.
665 */
666 var TKR_novelLabels = [];
667
668 /**
669 * A boolean that indicates whether the entered owner value is valid or not.
670 */
671 var TKR_invalidOwner = false;
672
673 /**
674 * The user has changed the issue status text field. This function
675 * checks whether it is a well-known status value. If not, highlight it
676 * as a potential typo.
677 * @param {Element} textField The issue status text field.
678 * @returns Always returns true to indicate that the browser should
679 * continue to process the user input event normally.
680 */
681 function TKR_confirmNovelStatus(textField) {
682 var v = textField.value.trim().toLowerCase();
683 var isNovel = (v !== '');
684 var wellKnown = TKR_statusWords;
685 for (var i = 0; i < wellKnown.length && isNovel; ++i) {
686 var wk = wellKnown[i];
687 if (v == wk.toLowerCase()) {
688 isNovel = false;
689 }
690 }
691 if (isNovel) {
692 if (TKR_novelStatuses.indexOf(textField) == -1) {
693 TKR_novelStatuses.push(textField);
694 }
695 textField.classList.add(TKR_NOVEL_CLASS);
696 } else {
697 if (TKR_novelStatuses.indexOf(textField) != -1) {
698 TKR_novelStatuses.splice(TKR_novelStatuses.indexOf(textField), 1);
699 }
700 textField.classList.remove(TKR_NOVEL_CLASS);
701 }
702 TKR_updateConfirmBeforeSubmit();
703 return true;
704 }
705
706
707 /**
708 * The user has changed a issue label text field. This function checks
709 * whether it is a well-known label value. If not, highlight it as a
710 * potential typo.
711 * @param {Element} textField An issue label text field.
712 * @returns Always returns true to indicate that the browser should
713 * continue to process the user input event normally.
714 *
715 * TODO(jrobbins): code duplication with function above.
716 */
717 function TKR_confirmNovelLabel(textField) {
718 var v = textField.value.trim().toLowerCase();
719 if (v.search('-') == 0) {
720 v = v.substr(1);
721 }
722 var isNovel = (v !== '');
723 if (v.indexOf('?') > -1) {
724 isNovel = false; // We don't count labels that the user must edit anyway.
725 }
726 var wellKnown = TKR_labelWords;
727 for (var i = 0; i < wellKnown.length && isNovel; ++i) {
728 var wk = wellKnown[i];
729 if (v == wk.toLowerCase()) {
730 isNovel = false;
731 }
732 }
733
734 var containsReservedPrefix = false;
735 var textFieldWarningDisplayed = TKR_labelsWithReservedPrefixes.indexOf(textFie ld) != -1;
736 for (var i = 0; i < TKR_LABEL_RESERVED_PREFIXES.length; ++i) {
737 if (v.startsWith(TKR_LABEL_RESERVED_PREFIXES[i] + '-')) {
738 if (!textFieldWarningDisplayed) {
739 TKR_labelsWithReservedPrefixes.push(textField);
740 }
741 containsReservedPrefix = true;
742 break;
743 }
744 }
745 if (!containsReservedPrefix && textFieldWarningDisplayed) {
746 TKR_labelsWithReservedPrefixes.splice(
747 TKR_labelsWithReservedPrefixes.indexOf(textField), 1);
748 }
749
750 var conflictsWithReserved = false;
751 var textFieldWarningDisplayed =
752 TKR_labelsConflictingWithReserved.indexOf(textField) != -1;
753 for (var i = 0; i < TKR_LABEL_RESERVED_PREFIXES.length; ++i) {
754 if (v == TKR_LABEL_RESERVED_PREFIXES[i]) {
755 if (!textFieldWarningDisplayed) {
756 TKR_labelsConflictingWithReserved.push(textField);
757 }
758 conflictsWithReserved = true;
759 break;
760 }
761 }
762 if (!conflictsWithReserved && textFieldWarningDisplayed) {
763 TKR_labelsConflictingWithReserved.splice(
764 TKR_labelsConflictingWithReserved.indexOf(textField), 1);
765 }
766
767 if (isNovel) {
768 if (TKR_novelLabels.indexOf(textField) == -1) {
769 TKR_novelLabels.push(textField);
770 }
771 textField.classList.add(TKR_NOVEL_CLASS);
772 } else {
773 if (TKR_novelLabels.indexOf(textField) != -1) {
774 TKR_novelLabels.splice(TKR_novelLabels.indexOf(textField), 1);
775 }
776 textField.classList.remove(TKR_NOVEL_CLASS);
777 }
778 TKR_updateConfirmBeforeSubmit();
779 return true;
780 }
781
782 /**
783 * Dictionary { prefix:[textField,...], ...} for all the prefixes of any
784 * text that has been entered into any label field. This is used to find
785 * duplicate labels and multiple labels that share an single exclusive
786 * prefix (e.g., Priority).
787 */
788 var TKR_usedPrefixes = {};
789
790 /**
791 * This is a prefix to the HTML ids of each label editing field.
792 * It varied by page, so it is set in the HTML page. Needed to initialize
793 * our validation across label input text fields.
794 */
795 var TKR_labelFieldIDPrefix = '';
796
797 /**
798 * Initialize the set of all used labels on forms that allow users to
799 * enter issue labels. Some labels are supplied in the HTML page
800 * itself, and we do not want to offer duplicates of those.
801 */
802 function TKR_prepLabelAC() {
803 var i = 0;
804 while ($('label'+i)) {
805 TKR_validateLabel($('label'+i));
806 i++;
807 }
808 }
809
810 /**
811 * Reads the owner field and determines if the current value is a valid member.
812 */
813 function TKR_prepOwnerField(validOwners) {
814 if ($('owneredit')) {
815 currentOwner = $('owneredit').value;
816 if (currentOwner == "") {
817 // Empty owner field is not an invalid owner.
818 invalidOwner = false;
819 return;
820 }
821 invalidOwner = true;
822 for (var i = 0; i < validOwners.length; i++) {
823 var owner = validOwners[i].name;
824 if (currentOwner == owner) {
825 invalidOwner = false;
826 break;
827 }
828 }
829 TKR_invalidOwner = invalidOwner;
830 }
831 }
832
833 /**
834 * Keep track of which label prefixes have been used so that
835 * we can not offer the same label twice and so that we can highlight
836 * multiple labels that share an exclusive prefix.
837 */
838 function TKR_updateUsedPrefixes(textField) {
839 if (textField.oldPrefix != undefined) {
840 DeleteArrayElement(TKR_usedPrefixes[textField.oldPrefix], textField);
841 }
842
843 var prefix = textField.value.split('-')[0].toLowerCase();
844 if (TKR_usedPrefixes[prefix] == undefined) {
845 TKR_usedPrefixes[prefix] = [textField];
846 }
847 else {
848 TKR_usedPrefixes[prefix].push(textField);
849 }
850 textField.oldPrefix = prefix;
851 }
852
853 /**
854 * Go through all the label entry fields in our prefix-oriented
855 * data structure and highlight any that are part of a conflict
856 * (multiple labels with the same exclusive prefix). Unhighlight
857 * any label text entry fields that are not in conflict. And, display
858 * a warning message to encourage the user to correct the conflict.
859 */
860 function TKR_highlightExclusiveLabelPrefixConflicts() {
861 var conflicts = [];
862 for (var prefix in TKR_usedPrefixes) {
863 var textFields = TKR_usedPrefixes[prefix];
864 if (textFields == undefined || textFields.length == 0) {
865 delete TKR_usedPrefixes[prefix];
866 }
867 else if (textFields.length > 1 &&
868 FindInArray(TKR_exclPrefixes, prefix) != -1) {
869 conflicts.push(prefix);
870 for (var i = 0; i < textFields.length; i++) {
871 var tf = textFields[i];
872 tf.classList.add(TKR_EXCL_CONFICT_CLASS);
873 }
874 } else {
875 for (var i = 0; i < textFields.length; i++) {
876 var tf = textFields[i];
877 tf.classList.remove(TKR_EXCL_CONFICT_CLASS);
878 }
879 }
880 }
881 if (conflicts.length > 0) {
882 var severity = TKR_restrict_to_known ? 'Error' : 'Warning';
883 var confirm_area = $(TKR_CONFIRMAREA_ID);
884 if (confirm_area) {
885 $('confirmmsg').innerText = (severity +
886 ': Multiple values for: ' + conflicts.join(', '));
887 confirm_area.className = TKR_EXCL_CONFICT_CLASS;
888 confirm_area.style.display = '';
889 }
890 }
891 }
892
893 /**
894 * Keeps track of any label text fields that have a value that
895 * is bad enough to prevent submission of the form. When this
896 * list is non-empty, the submit button gets disabled.
897 */
898 var TKR_labelsBlockingSubmit = [];
899
900 /**
901 * Look for any "?" characters in the label and, if found,
902 * make the label text red, prevent form submission, and
903 * display on-page help to tell the user to edit those labels.
904 * @param {Element} textField An issue label text field.
905 */
906 function TKR_highlightQuestionMarks(textField) {
907 var tfIndex = TKR_labelsBlockingSubmit.indexOf(textField);
908 if (textField.value.indexOf('?') > -1 && tfIndex == -1) {
909 TKR_labelsBlockingSubmit.push(textField);
910 textField.classList.add(TKR_QUESTION_MARK_CLASS);
911 } else if (textField.value.indexOf('?') == -1 && tfIndex > -1) {
912 TKR_labelsBlockingSubmit.splice(tfIndex, 1);
913 textField.classList.remove(TKR_QUESTION_MARK_CLASS);
914 }
915
916 var block_submit_msg = $('blocksubmitmsg');
917 if (block_submit_msg) {
918 if (TKR_labelsBlockingSubmit.length > 0) {
919 block_submit_msg.innerText = 'You must edit labels that contain "?".';
920 } else {
921 block_submit_msg.innerText = '';
922 }
923 }
924 }
925
926 /**
927 * The user has edited a label. Display a warning if the label is
928 * not a well known label, or if there are multiple labels that
929 * share an exclusive prefix.
930 * @param {Element} textField An issue label text field.
931 */
932 function TKR_validateLabel(textField) {
933 if (textField == undefined) return;
934 TKR_confirmNovelLabel(textField);
935 TKR_updateUsedPrefixes(textField);
936 TKR_highlightExclusiveLabelPrefixConflicts();
937 TKR_highlightQuestionMarks(textField);
938 }
939
940 // TODO(jrobbins): what about typos in owner and cc list?
941
942 /**
943 * If there are any novel status or label values, we display a message
944 * that explains that to the user so that they can catch any typos before
945 * submitting them. If the project is restricting input to only the
946 * well-known statuses and labels, then show these as an error instead.
947 * In that case, on-page JS will prevent submission.
948 */
949 function TKR_updateConfirmBeforeSubmit() {
950 var severity = TKR_restrict_to_known ? 'Error' : 'Note';
951 var novelWord = TKR_restrict_to_known ? 'undefined' : 'uncommon';
952 var msg = '';
953 var labels = TKR_novelLabels.map(function(item) {
954 return item.value;
955 });
956 if (TKR_novelStatuses.length > 0 && TKR_novelLabels.length > 0) {
957 msg = severity + ': You are using an ' + novelWord + ' status and ' + novelW ord + ' label(s): ' + labels.join(', ') + '.'; // TODO: i18n
958 }
959 else if (TKR_novelStatuses.length > 0) {
960 msg = severity + ': You are using an ' + novelWord + ' status value.';
961 }
962 else if (TKR_novelLabels.length > 0) {
963 msg = severity + ': You are using ' + novelWord + ' label(s): ' + labels.joi n(', ') + '.';
964 }
965
966 for (var i = 0; i < TKR_labelsWithReservedPrefixes.length; ++i) {
967 msg += "\nNote: The label " + TKR_labelsWithReservedPrefixes[i].value +
968 " starts with a reserved word. This is not recommended."
969 }
970 for (var i = 0; i < TKR_labelsConflictingWithReserved.length; ++i) {
971 msg += "\nNote: The label " + TKR_labelsConflictingWithReserved[i].value +
972 " conflicts with a reserved word. This is not recommended."
973 }
974 // Display the owner is no longer a member note only if an owner error is not
975 // already shown on the page.
976 if (TKR_invalidOwner && !$('ownererror')) {
977 msg += "\nNote: Current owner is no longer a project member."
978 }
979
980 var confirm_area = $(TKR_CONFIRMAREA_ID);
981 if (confirm_area) {
982 $('confirmmsg').innerText = msg;
983 if (msg != '') {
984 confirm_area.className = TKR_NOVEL_CLASS;
985 confirm_area.style.display = '';
986 } else {
987 confirm_area.style.display = 'none';
988 }
989 }
990 }
991
992
993 /**
994 * The user has selected a command from the 'Actions...' menu
995 * on the issue list. This function checks the selected value and carry
996 * out the requested action.
997 * @param {Element} actionsMenu The 'Actions...' <select> form element.
998 */
999 function TKR_handleListActions(actionsMenu) {
1000 switch (actionsMenu.value) {
1001 case 'bulk':
1002 TKR_HandleBulkEdit();
1003 break;
1004 case 'colspec':
1005 TKR_closeAllPopups(actionsMenu);
1006 _showID('columnspec');
1007 break;
1008 case 'flagspam':
1009 TKR_flagSpam(true);
1010 break;
1011 case 'unflagspam':
1012 TKR_flagSpam(false);
1013 break;
1014 }
1015 actionsMenu.value = 'moreactions';
1016 }
1017
1018
1019 function TKR_handleDetailActions() {
1020 var moreActions = $('more_actions');
1021
1022 if (moreActions.value == 'delete') {
1023 $('copy_issue_form_fragment').style.display = 'none';
1024 $('move_issue_form_fragment').style.display = 'none';
1025 var ok = confirm(
1026 'Normally, you should just close issues by setting their status ' +
1027 'to a closed value.\n' +
1028 'Are you sure you want to delete this issue?');
1029 if (ok) {
1030 $('delete_form').submit();
1031 return;
1032 }
1033 }
1034
1035 if (moreActions.value == 'move') {
1036 $('move_issue_form_fragment').style.display = '';
1037 $('copy_issue_form_fragment').style.display = 'none';
1038 return;
1039 }
1040 if (moreActions.value == 'copy') {
1041 $('copy_issue_form_fragment').style.display = '';
1042 $('move_issue_form_fragment').style.display = 'none';
1043 return;
1044 }
1045
1046 // If no action was taken, reset the dropdown to the 'More actions...' item.
1047 moreActions.value = '0';
1048 }
1049
1050 /**
1051 * The user has selected the "Flag as spam..." menu item.
1052 */
1053 function TKR_flagSpam(isSpam) {
1054 var selectedLocalIDs = [];
1055 for (var i = 0; i < issueRefs.length; i++) {
1056 var checkbox = document.getElementById('cb_' + issueRefs[i]['id']);
1057 if (checkbox && checkbox.checked) {
1058 selectedLocalIDs.push(issueRefs[i]['id']);
1059 }
1060 }
1061 if (selectedLocalIDs.length > 0) {
1062 if (!confirm((isSpam ? 'Flag' : 'Un-flag') +
1063 ' all selected issues as spam?')) {
1064 return;
1065 }
1066 var selectedLocalIDString = selectedLocalIDs.join(',');
1067 $('bulk_spam_ids').value = selectedLocalIDString;
1068 $('bulk_spam_value').value = isSpam;
1069
1070 var loading = $('bulk-action-loading');
1071 loading.style.visibility = 'visible';
1072
1073 var form = $('bulkspam');
1074 form.submit();
1075 }
1076 else {
1077 alert('Please select some issues to flag as spam');
1078 }
1079 }
1080
1081 /**
1082 * The user has selected the "Bulk Edit..." menu item. Go to a page that
1083 * offers the ability to edit all selected issues.
1084 */
1085 // TODO(jrobbins): cross-project bulk edit
1086 function TKR_HandleBulkEdit() {
1087 var selectedLocalIDs = [];
1088 for (var i = 0; i < issueRefs.length; i++) {
1089 var checkbox = document.getElementById('cb_' + issueRefs[i]['id']);
1090 if (checkbox && checkbox.checked) {
1091 selectedLocalIDs.push(issueRefs[i]['id']);
1092 }
1093 }
1094 if (selectedLocalIDs.length > 0) {
1095 var selectedLocalIDString = selectedLocalIDs.join(',');
1096 var url = 'bulkedit?ids=' + selectedLocalIDString;
1097 TKR_go(url + _ctxArgs);
1098 }
1099 else {
1100 alert('Please select some issues to edit');
1101 }
1102 }
1103
1104
1105 /**
1106 * Array of original labels on the served page, so that we can notice
1107 * when the used submits a form that has any Restrict-* labels removed.
1108 */
1109 var TKR_allOrigLabels = [];
1110
1111
1112 /**
1113 * Prevent users from easily entering "+1" comments.
1114 */
1115 function TKR_checkPlusOne() {
1116 var c = $('addCommentTextArea').value;
1117 var instructions = (
1118 '\nPlease use the star icon instead.\n' +
1119 'Stars show your interest without annoying other users.');
1120 if (new RegExp('^\\s*[-+]+[0-9]+\\s*.{0,30}$', 'm').test(c) &&
1121 c.length < 150) {
1122 alert('This looks like a "+1" comment.' + instructions);
1123 return false;
1124 }
1125 if (new RegExp('^\\s*me too.{0,30}$', 'i').test(c)) {
1126 alert('This looks like a "me too" comment.' + instructions);
1127 return false;
1128 }
1129 return true;
1130 }
1131
1132
1133 /**
1134 * If the user removes Restrict-* labels, ask them if they are sure.
1135 */
1136 function TKR_checkUnrestrict(prevent_restriction_removal) {
1137 var removedRestrictions = [];
1138
1139 for (var i = 0; i < TKR_allOrigLabels.length; ++i) {
1140 var origLabel = TKR_allOrigLabels[i];
1141 if (origLabel.indexOf('Restrict-') == 0) {
1142 var found = false;
1143 var j = 0;
1144 while ($('label' + j)) {
1145 var newLabel = $('label' + j).value;
1146 if (newLabel == origLabel) {
1147 found = true;
1148 break;
1149 }
1150 j++;
1151 }
1152 if (!found) {
1153 removedRestrictions.push(origLabel);
1154 }
1155 }
1156 }
1157
1158 if (removedRestrictions.length == 0) {
1159 return true;
1160 }
1161
1162 if (prevent_restriction_removal) {
1163 var msg = 'You may not remove restriction labels.';
1164 alert(msg);
1165 return false;
1166 }
1167
1168 var instructions = (
1169 'You are removing these restrictions:\n ' +
1170 removedRestrictions.join('\n ') +
1171 '\nThis may allow more people to access this issue.' +
1172 '\nAre you sure?');
1173 return confirm(instructions);
1174 }
1175
1176
1177 /**
1178 * Add a column to a list view by updating the colspec form element and
1179 * submiting an invisible <form> to load a new page that includes the column.
1180 * @param {string} colname The name of the column to start showing.
1181 */
1182 function TKR_addColumn(colname) {
1183 var colspec = TKR_getColspecElement();
1184 colspec.value = colspec.value + ' ' + colname;
1185 $('colspecform').submit();
1186 }
1187
1188
1189 /**
1190 * Allow members to shift-click to select multiple issues. This keeps
1191 * track of the last row that the user clicked a checkbox on.
1192 */
1193 var TKR_lastSelectedRow = undefined;
1194
1195
1196 /**
1197 * Return true if an event had the shift-key pressed.
1198 * @param {Event} evt The mouse click event.
1199 */
1200 function TKR_hasShiftKey(evt) {
1201 evt = (evt) ? evt : (window.event) ? window.event : '';
1202 if (evt) {
1203 if (evt.modifiers) {
1204 return evt.modifiers & Event.SHIFT_MASK;
1205 } else {
1206 return evt.shiftKey;
1207 }
1208 }
1209 return false;
1210 }
1211
1212
1213 /**
1214 * Select one row: check the checkbox and use highlight color.
1215 * @param {Element} row the row containing the checkbox that the user clicked.
1216 * @param {boolean} checked True if the user checked the box.
1217 */
1218 function TKR_rangeSelectRow(row, checked) {
1219 if (!row) {
1220 return;
1221 }
1222 if (checked) {
1223 row.classList.add('selected');
1224 } else {
1225 row.classList.remove('selected');
1226 }
1227
1228 var td = row.firstChild;
1229 while (td && td.tagName != 'TD') {
1230 td = td.nextSibling;
1231 }
1232 if (!td) {
1233 return;
1234 }
1235
1236 var checkbox = td.firstChild;
1237 while (checkbox && checkbox.tagName != 'INPUT') {
1238 checkbox = checkbox.nextSibling;
1239 }
1240 if (!checkbox) {
1241 return;
1242 }
1243
1244 checkbox.checked = checked;
1245 }
1246
1247
1248 /**
1249 * If the user shift-clicked a checkbox, (un)select a range.
1250 * @param {Event} evt The mouse click event.
1251 * @param {Element} el The checkbox that was clicked.
1252 */
1253 function TKR_checkRangeSelect(evt, el) {
1254 var clicked_row = el.parentNode.parentNode.rowIndex;
1255 if (clicked_row == TKR_lastSelectedRow) {
1256 return;
1257 }
1258 if (TKR_hasShiftKey(evt) && TKR_lastSelectedRow != undefined) {
1259 var results_table = $('resultstable');
1260 var delta = (clicked_row > TKR_lastSelectedRow) ? 1 : -1;
1261 for (var i = TKR_lastSelectedRow; i != clicked_row; i += delta) {
1262 TKR_rangeSelectRow(results_table.rows[i], el.checked);
1263 }
1264 }
1265 TKR_lastSelectedRow = clicked_row;
1266 }
1267
1268
1269 /**
1270 * Make a link to a given issue that includes context parameters that allow
1271 * the user to see the same list columns, sorting, query, and pagination state
1272 * if he/she ever navigates up to the list again.
1273 * @param {{issue_url: string}} issueRef The dict with info about an issue,
1274 * including a url to the issue detail page.
1275 */
1276 function TKR_makeIssueLink(issueRef) {
1277 return '/p/' + issueRef['project_name'] + '/issues/detail?id=' + issueRef['id' ] + _ctxArgs;
1278 }
1279
1280
1281 /**
1282 * Hide or show a list column in the case where we already have the
1283 * data for that column on the page.
1284 * @param {number} colIndex index of the column that is being shown or hidden.
1285 */
1286 function TKR_toggleColumnUpdate(colIndex) {
1287 var shownCols = TKR_getColspecElement().value.split(' ');
1288 var filteredCols = [];
1289 for (var i=0; i< shownCols.length; i++) {
1290 if (_allColumnNames[colIndex] != shownCols[i].toLowerCase()) {
1291 filteredCols.push(shownCols[i]);
1292 }
1293 }
1294
1295 TKR_getColspecElement().value = filteredCols.join(' ');
1296 TKR_getSearchColspecElement().value = filteredCols.join(' ');
1297 TKR_toggleColumn('hide_col_' + colIndex);
1298 }
1299
1300
1301 /**
1302 * Convert a column into a groupby clause by removing it from the column spec
1303 * and adding it to the groupby spec, then reloading the page.
1304 * @param {number} colIndex index of the column that is being shown or hidden.
1305 */
1306 function TKR_addGroupBy(colIndex) {
1307 var colName = _allColumnNames[colIndex];
1308 var shownCols = TKR_getColspecElement().value.split(' ');
1309 var filteredCols = [];
1310 for (var i=0; i < shownCols.length; i++) {
1311 if (shownCols[i] && colName != shownCols[i].toLowerCase()) {
1312 filteredCols.push(shownCols[i]);
1313 }
1314 }
1315
1316 TKR_getColspecElement().value = filteredCols.join(' ');
1317 TKR_getSearchColspecElement().value = filteredCols.join(' ');
1318
1319 var groupSpec = $('groupbyspec');
1320 var shownGroupings = groupSpec.value.split(' ');
1321 var filteredGroupings = [];
1322 for (i=0; i < shownGroupings.length; i++) {
1323 if (shownGroupings[i] && colName != shownGroupings[i].toLowerCase()) {
1324 filteredGroupings.push(shownGroupings[i]);
1325 }
1326 }
1327 filteredGroupings.push(colName);
1328 groupSpec.value = filteredGroupings.join(' ');
1329 $('colspecform').submit();
1330 }
1331
1332
1333 /**
1334 * Add a multi-valued custom field editing widget.
1335 */
1336 function TKR_addMultiFieldValueWidget(
1337 el, field_id, field_type, opt_validate_1, opt_validate_2) {
1338 var widget = document.createElement('INPUT');
1339 widget.name = 'custom_' + field_id;
1340 if (field_type == 'str') {
1341 widget.size = 90;
1342 }
1343 if (field_type == 'user') {
1344 widget.style = 'width:12em';
1345 }
1346 if (field_type == 'int') {
1347 widget.style.textAlign = 'right';
1348 widget.style.width = '12em';
1349 widget.type = 'number';
1350 widget.min = opt_validate_1;
1351 widget.max = opt_validate_2;
1352 }
1353 el.parentNode.insertBefore(widget, el);
1354
1355 var del_button = document.createElement('U');
1356 del_button.onclick = function(event) {
1357 _removeMultiFieldValueWidget(event.target);
1358 };
1359 del_button.innerText = 'X';
1360 el.parentNode.insertBefore(del_button, el);
1361 }
1362
1363
1364 function TKR_removeMultiFieldValueWidget(el) {
1365 var target = el.previousSibling;
1366 while (target && target.tagName != 'INPUT') {
1367 target = target.previousSibling;
1368 }
1369 if (target) {
1370 el.parentNode.removeChild(target);
1371 }
1372 el.parentNode.removeChild(el); // the X itself
1373 }
1374
1375
1376 /**
1377 * Trim trailing commas and spaces off <INPUT type="email" multiple> fields
1378 * before submitting the form.
1379 */
1380 function TKR_trimCommas() {
1381 var ccField = $('memberccedit');
1382 if (ccField) {
1383 ccField.value = ccField.value.replace(/,\s*$/, '');
1384 }
1385 ccField = $('memberenter');
1386 if (ccField) {
1387 ccField.value = ccField.value.replace(/,\s*$/, '');
1388 }
1389 }
OLDNEW
« no previous file with comments | « appengine/monorail/static/js/tracker/tracker-display.js ('k') | appengine/monorail/static/js/tracker/tracker-fields.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698