/* 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/. */ /* import-globals-from ../../composer/content/editorUtilities.js */ /* import-globals-from EdDialogCommon.js */ // Global variables var hasValue; var oldValue; var insertNew; var itemArray; var theTree; var treeSelection; var selectElement; var currentItem = null; var selectedOption = null; var selectedOptionCount = 0; document.addEventListener("dialogaccept", onAccept); document.addEventListener("dialogcancel", onCancel); // Utility functions function getParentIndex(index) { switch (itemArray[index].level) { case 0: return -1; case 1: return 0; } // eslint-disable-next-line curly while (itemArray[--index].level > 1); return index; } function UpdateSelectMultiple() { if (selectedOptionCount > 1) { gDialog.selectMultiple.checked = true; gDialog.selectMultiple.disabled = true; } else { gDialog.selectMultiple.disabled = false; } } /* wrapper objects: * readonly attribute Node element; // DOM node (select/optgroup/option) * readonly attribute int level; // tree depth * readonly attribute boolean container; // can contain options * string getCellText(string col); // tree view helper * string cycleCell(int currentIndex); // tree view helper * void onFocus(); // load data into deck * void onBlur(); // save data from deck * boolean canDestroy(boolean prompt); // NB prompt not used * void destroy(); // post remove callback * void moveUp(); * boolean canMoveDown(); * void moveDown(); * void appendOption(newElement, currentIndex); */ // OPTION element wrapper object // Create a wrapper for the given element at the given level function optionObject(option, level) { // select an added option (when loading from document) if (option.hasAttribute("selected")) { selectedOptionCount++; } this.level = level; this.element = option; } optionObject.prototype.container = false; optionObject.prototype.getCellText = function(column) { if (column.id == "SelectSelCol") { return ""; } if (column.id == "SelectValCol" && this.element.hasAttribute("value")) { return this.element.getAttribute("value"); } return this.element.text; }; optionObject.prototype.cycleCell = function(index) { if (this.element.hasAttribute("selected")) { this.element.removeAttribute("selected"); selectedOptionCount--; selectedOption = null; } else { // Different handling for multiselect lists if (gDialog.selectMultiple.checked || !selectedOption) { selectedOptionCount++; } else if (selectedOption) { selectedOption.removeAttribute("selected"); let column = theTree.columns.SelectSelCol; theTree.invalidateColumn(column); selectedOption = null; } this.element.setAttribute("selected", ""); selectedOption = this.element; let column = theTree.columns.SelectSelCol; theTree.invalidateCell(index, column); } if (currentItem == this) { // Also update the deck gDialog.optionSelected.setAttribute( "checked", this.element.hasAttribute("selected") ); } UpdateSelectMultiple(); }; optionObject.prototype.onFocus = function() { gDialog.optionText.value = this.element.text; hasValue = this.element.hasAttribute("value"); oldValue = this.element.value; gDialog.optionHasValue.checked = hasValue; gDialog.optionValue.value = hasValue ? this.element.value : this.element.text; gDialog.optionSelected.checked = this.element.hasAttribute("selected"); gDialog.optionDisabled.checked = this.element.hasAttribute("disabled"); gDialog.selectDeck.setAttribute("selectedIndex", "2"); }; optionObject.prototype.onBlur = function() { this.element.text = gDialog.optionText.value; if (gDialog.optionHasValue.checked) { this.element.value = gDialog.optionValue.value; } else { this.element.removeAttribute("value"); } if (gDialog.optionSelected.checked) { this.element.setAttribute("selected", ""); } else { this.element.removeAttribute("selected"); } if (gDialog.optionDisabled.checked) { this.element.setAttribute("disabled", ""); } else { this.element.removeAttribute("disabled"); } }; optionObject.prototype.canDestroy = function(prompt) { return true; /* return !prompt || ConfirmWithTitle(GetString("DeleteOption"), GetString("DeleteOptionMsg"), GetString("DeleteOption"));*/ }; optionObject.prototype.destroy = function() { // Deselect a removed option if (this.element.hasAttribute("selected")) { selectedOptionCount--; selectedOption = null; UpdateSelectMultiple(); } }; /* 4 cases: * a) optgroup -> optgroup * ... ... * option option * b) optgroup -> option * option optgroup * ... ... * c) option * option * d) option * option */ optionObject.prototype.moveUp = function() { var index = treeSelection.currentIndex; if ( itemArray[index].level < itemArray[index - 1].level + itemArray[index - 1].container ) { // we need to repaint the tree's lines theTree.invalidateRange(getParentIndex(index), index); // a) option is just after an optgroup, so it becomes the last child itemArray[index].level = 2; theTree.view.selectionChanged(); } else { // otherwise new option level is now the same as the previous item itemArray[index].level = itemArray[index - 1].level; // swap the option with the previous item itemArray.splice(index, 0, itemArray.splice(--index, 1)[0]); } selectTreeIndex(index, true); }; optionObject.prototype.canMoveDown = function() { // move down is not allowed on the last option if its level is 1 return this.level > 1 || itemArray.length - treeSelection.currentIndex > 1; }; optionObject.prototype.moveDown = function() { var index = treeSelection.currentIndex; if ( index + 1 == itemArray.length || itemArray[index].level > itemArray[index + 1].level ) { // we need to repaint the tree's lines theTree.invalidateRange(getParentIndex(index), index); // a) option is last child of an optgroup, so it moves just after itemArray[index].level = 1; theTree.view.selectionChanged(); } else { // level increases if the option was preceding an optgroup itemArray[index].level += itemArray[index + 1].container; // swap the option with the next item itemArray.splice(index, 0, itemArray.splice(++index, 1)[0]); } selectTreeIndex(index, true); }; optionObject.prototype.appendOption = function(child, parent) { // special case quick check if (this.level == 1) { return gDialog.appendOption(child, 0); } // append the option to the parent element parent = getParentIndex(parent); return itemArray[parent].appendOption(child, parent); }; // OPTGROUP element wrapper object function optgroupObject(optgroup) { this.element = optgroup; } optgroupObject.prototype.level = 1; optgroupObject.prototype.container = true; optgroupObject.prototype.getCellText = function(column) { return column.id == "SelectTextCol" ? this.element.label : ""; }; optgroupObject.prototype.cycleCell = function(index) {}; optgroupObject.prototype.onFocus = function() { gDialog.optgroupLabel.value = this.element.label; gDialog.optgroupDisabled.checked = this.element.disabled; gDialog.selectDeck.setAttribute("selectedIndex", "1"); }; optgroupObject.prototype.onBlur = function() { this.element.label = gDialog.optgroupLabel.value; this.element.disabled = gDialog.optgroupDisabled.checked; }; optgroupObject.prototype.canDestroy = function(prompt) { // Only removing empty option groups for now return ( gDialog.nextChild(treeSelection.currentIndex) - treeSelection.currentIndex == 1 ); /* && (!prompt || ConfirmWithTitle(GetString("DeleteOptGroup"), GetString("DeleteOptGroupMsg"), GetString("DeleteOptGroup"))); */ }; optgroupObject.prototype.destroy = function() {}; optgroupObject.prototype.moveUp = function() { // Find the index of the previous and next elements at the same level var index = treeSelection.currentIndex; var i = index; // eslint-disable-next-line curly while (itemArray[--index].level > 1); var j = gDialog.nextChild(i); // Cut out the element, cut the array in two, then join together var movedItems = itemArray.splice(i, j - i); var endItems = itemArray.splice(index); itemArray = itemArray.concat(movedItems).concat(endItems); // Repaint the lot theTree.invalidateRange(index, j); selectTreeIndex(index, true); }; optgroupObject.prototype.canMoveDown = function() { return gDialog.lastChild() > treeSelection.currentIndex; }; optgroupObject.prototype.moveDown = function() { // Find the index of the next two elements at the same level var index = treeSelection.currentIndex; var i = gDialog.nextChild(index); var j = gDialog.nextChild(i); // Cut out the element, cut the array in two, then join together var movedItems = itemArray.splice(i, j - 1); var endItems = itemArray.splice(index); itemArray = itemArray.concat(movedItems).concat(endItems); // Repaint the lot theTree.invalidateRange(index, j); index += j - i; selectTreeIndex(index, true); }; optgroupObject.prototype.appendOption = function(child, parent) { var index = gDialog.nextChild(parent); // XXX need to repaint the lines, tree won't do this var primaryCol = theTree.columns.getPrimaryColumn(); theTree.invalidateCell(index - 1, primaryCol); // insert the wrapped object as the last child itemArray.splice(index, 0, new optionObject(child, 2)); theTree.rowCountChanged(index, 1); selectTreeIndex(index, false); }; // dialog initialization code function Startup() { var editor = GetCurrentEditor(); if (!editor) { dump("Failed to get active editor!\n"); window.close(); return; } // Get a single selected select element const kTagName = "select"; try { selectElement = editor.getSelectedElement(kTagName); } catch (e) {} if (selectElement) { // We found an element and don't need to insert one insertNew = false; } else { insertNew = true; // We don't have an element selected, // so create one with default attributes try { selectElement = editor.createElementWithDefaults(kTagName); } catch (e) {} if (!selectElement) { dump("Failed to get selected element or create a new one!\n"); window.close(); return; } } // SELECT element wrapper object gDialog = { // useful elements accept: document.documentElement.getButton("accept"), selectDeck: document.getElementById("SelectDeck"), selectName: document.getElementById("SelectName"), selectSize: document.getElementById("SelectSize"), selectMultiple: document.getElementById("SelectMultiple"), selectDisabled: document.getElementById("SelectDisabled"), selectTabIndex: document.getElementById("SelectTabIndex"), optgroupLabel: document.getElementById("OptGroupLabel"), optgroupDisabled: document.getElementById("OptGroupDisabled"), optionText: document.getElementById("OptionText"), optionHasValue: document.getElementById("OptionHasValue"), optionValue: document.getElementById("OptionValue"), optionSelected: document.getElementById("OptionSelected"), optionDisabled: document.getElementById("OptionDisabled"), removeButton: document.getElementById("RemoveButton"), previousButton: document.getElementById("PreviousButton"), nextButton: document.getElementById("NextButton"), tree: document.getElementById("SelectTree"), // wrapper methods (except MoveUp and MoveDown) element: selectElement.cloneNode(false), level: 0, container: true, getCellText(column) { return column.id == "SelectTextCol" ? this.element.getAttribute("name") : ""; }, cycleCell(index) {}, onFocus() { gDialog.selectName.value = this.element.getAttribute("name"); gDialog.selectSize.value = this.element.getAttribute("size"); gDialog.selectMultiple.checked = this.element.hasAttribute("multiple"); gDialog.selectDisabled.checked = this.element.hasAttribute("disabled"); gDialog.selectTabIndex.value = this.element.getAttribute("tabindex"); this.selectDeck.setAttribute("selectedIndex", "0"); onNameInput(); }, onBlur() { this.element.setAttribute("name", gDialog.selectName.value); if (gDialog.selectSize.value) { this.element.setAttribute("size", gDialog.selectSize.value); } else { this.element.removeAttribute("size"); } if (gDialog.selectMultiple.checked) { this.element.setAttribute("multiple", ""); } else { this.element.removeAttribute("multiple"); } if (gDialog.selectDisabled.checked) { this.element.setAttribute("disabled", ""); } else { this.element.removeAttribute("disabled"); } if (gDialog.selectTabIndex.value) { this.element.setAttribute("tabindex", gDialog.selectTabIndex.value); } else { this.element.removeAttribute("tabindex"); } }, appendOption(child, parent) { var index = itemArray.length; // XXX need to repaint the lines, tree won't do this theTree.invalidateRange(this.lastChild(), index); // append the wrapped object itemArray.push(new optionObject(child, 1)); theTree.rowCountChanged(index, 1); selectTreeIndex(index, false); }, canDestroy(prompt) { return false; }, canMoveDown() { return false; }, // helper methods // Find the index of the next immediate child of the select nextChild(index) { // eslint-disable-next-line curly while (++index < itemArray.length && itemArray[index].level > 1); return index; }, // Find the index of the last immediate child of the select lastChild() { var index = itemArray.length; // eslint-disable-next-line curly while (itemArray[--index].level > 1); return index; }, }; // Start with the