You are here: Editor project page > Writing Property Dialogs for Composer
Writing Property Dialogs for Mozilla Composer
by Charles Manske (cmanske@netscape.com)Introduction
This document describes the rules to follow when writing a property dialog in XUL, in such a manner so that the "Advanced Edit" feature will work. The Advanced Edit button in property dialogs brings up a dialog that allows editing any attribute, including JavaScript and inline style. I will use the Named Anchor Properties dialog as an example, as it is a very simple property dialog.
XUL for a Typical Property Dialog:
The contents of EdNamedAnchorProps.xul (extra
comments are in blue):
<?xml version="1.0"?>
<!-- [NPL license message goes here] -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
CSS needed for dialog layout is in this file:
<?xml-stylesheet href="chrome://editor/skin/EditorDialog.css"
type="text/css"?>
This overlay file contains the OK, Cancel buttons:
<?xul-overlay href="chrome://global/content/dialogOverlay.xul"?>
<?xul-overlay href="chrome://editor/content/EdDialogOverlay.xul"?>
All strings that need to be translated (localized)
must be put here:
<!DOCTYPE window SYSTEM "chrome://editor/locale/EdNamedAnchorProperties.dtd">
<xul:window class="dialog" title="&windowTitle.label;"
xmlns:xul ="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns="http://www.w3.org/1999/xhtml"
onload = "Startup()"
align="vertical">
<!-- Methods common to all editor dialogs -->
The JS for onAdvancedEdit buttons as well as
many utility methods are here:
<script language="JavaScript" src="chrome://editor/content/EdDialogCommon.js">
</script>
The JS for this particular dialog:
<script language="JavaScript" src="chrome://editor/content/EdNamedAnchorProps.js">
</script>
The JS for the OK, Cancel buttons:
<script language="JavaScript" src="chrome://global/content/dialogOverlay.js"
/>
This XUL for this specific element:
<label class="spacedtext" for="nameInput"> &anchorNameEditField.label; </label> <input type="text" id="nameInput" size="30" maxlength="255"/> <!-- from EdDialogOverlay --> <xul:box id="advancedEditButton"/> <!-- from global dialogOverlay --> <xul:box id="okCancelButtons"/> </xul:window>
Here are the contents of EdNamedAnchorProps.dtd, showing the syntax
for the entity strings:
<!ENTITY windowTitle.label "Named Anchor Properties">
<!ENTITY anchorNameEditField.label "Anchor Name:">
Here is the EdDialogOverlay.xul file:
<?xml version="1.0"?> <!DOCTYPE window SYSTEM "chrome://editor/locale/EdDialogOverlay.dtd"> <overlay id="EdDialogOverlay" xmlns:html="http://www.w3.org/1999/xhtml" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> <box id="advancedEditButton" align="vertical"> <box align="horizontal" style="margin-top: 0.2em"> <titledbutton class="hspaced" id="AdvancedEdit" onclick="onAdvancedEdit()" value="&AdvancedEditButton.label;"/> </box> <html:div><html:hr width="100%"/></html:div> </box> </overlay>
Java Script for a Property Dialog:
Each property dialog that will use AdvancedEdit must have the following methods, which perform these actions:
- Startup() [this may be called by another name -- it
is the value of the onload attribute on the XUL Window]
- Do EditorShell initialization.
- Set variables for controls that we use in the dialog
- Get the element we will edit from the document or create a new one to insert.
- Copy this element to the global "globalElement" for use by the Advanced Edit dialog
- Initialize the dialog widgets from this globalElement
- InitDialog()
- Fill dialog widgets with attribute values from globalElement
- ValidateData()
- Get the values from each widget and validate them for things like:
- Is a required field empty?
Use the helper ShowInputErrorMessage(GetString("errorName")) to display an error message. Note that you must use GetString() to get the message, which you must define in the Editor.properties file. - Is a number field within the allowable range?
Use ValidateNumberString(value, min, max) to test for a range. It will display an error message and return false if there was an error, or true if it was valid.
- Is a required field empty?
- For each attribute, if value is valid, set that attribute on the globalElement.
- If all attributes are valid, return true, else return false.
- Get the values from each widget and validate them for things like:
- onOK()
- Call ValidateData() and if result is true:
- Call editorShell.CloneAttributes() to copy all values set on the globalElement to the element we are editing (already in the document) or the new one we created for inserting.
- If we are inserting a new element, call the appropriate editorShell method, usually InsertElement().
- Return true if the data was valid and everything went OK.
Note that onCancel() is supplied by EdDialogCommon.js and it simply closes the dialog window.
Here is the example EdNamedAnchor.js:
var insertNew = true;
var tagName = "anchor";
var anchorElement = null;
var nameInput;
// dialog initialization code
function Startup()
{
if (!InitEditorShell())
return;
Set the method for the OK and Cancel
buttons supplied in the globalOverlay file:
doSetOKCancel(onOK, null);
Get the widgets that we will use often:
nameInput = document.getElementById("nameInput"); dump("tagName = "+tagName+"\n"); // Get a single selected element of the desired type anchorElement = editorShell.GetSelectedElement(tagName); if (anchorElement) { // We found an element and don't need to insert one insertNew = false; dump("Found existing anchor\n"); } else { insertNew = true; // We don't have an element selected, // so create one with default attributes dump("Element not selected - calling createElementWithDefaults\n"); anchorElement = editorShell.CreateElementWithDefaults(tagName); // Use the current selection as suggested name var name = GetSelectionAsText(); // Get 40 characters of the selected text and don't add "..." name = TruncateStringAtWordEnd(name, 40, false); // Replace whitespace with "_" name = ReplaceWhitespace(name, "_"); //Be sure the name is unique to the document if (AnchorNameExists(name)) name += "_" anchorElement.setAttribute("name",name); } if(!anchorElement) { dump("Failed to get selected element or create a new one!\n"); window.close(); } // Make a copy to use for AdvancedEdit globalElement = anchorElement.cloneNode(false); InitDialog(); nameInput.focus(); } function InitDialog() { nameInput.value = globalElement.getAttribute("name"); } function AnchorNameExists(name) { anchorList = editorShell.editorDocument.anchors; // getElementsByTagName("A"); if (anchorList) { dump("We have an anchor list\n"); for (i=0; i < anchorList.length; i++) { dump("Anchor name: "+anchorList[i].name+"\n"); if (anchorList[i].name == name) return true; } } return false; } // Get and validate data from widgets. // Set attributes on globalElement so they can be accessed by AdvancedEdit() function ValidateData() { var name = TrimString(nameInput.value); if (name.length == 0) { ShowInputErrorMessage(GetString("MissingAnchorNameError")); nameInput.focus(); return false; } else { // Replace spaces with "_" else it causes trouble in URL parsing name = ReplaceWhitespace(name, "_"); if (AnchorNameExists(name)) { ShowInputErrorMessage("\""+name+"\" "+GetString("DuplicateAnchorNameError")); nameInput.focus(); return false; } globalElement.setAttribute("name",name); } return true; }
function onOK() { if (ValidateData()) { // Copy attributes to element we are changing or inserting editorShell.CloneAttributes(anchorElement, globalElement); if (insertNew) { // Don't delete selected text when inserting editorShell.InsertElement(anchorElement, false); } return true; } return false; }
The Advanced Edit Dialog
If the above is supplied correctly, the onAdvancedEdit() method for the button supplied by the EdDialogOverlay.xul file can use the globalElement as the element to edit and call the appropriate methods when finished. It will try to immediately exit the "parent" dialog, but it may not succeed if there is a validation error. Note that we don't validate attributes enterred in the AdvancedEdit dialog itself that are not shared with the parent dialog (or at least we don't plan to for the initial version.) We assume "advanced" users know what they are doing!
Here is the onAdvancedEdit method from EdDialogCommon.js:
function onAdvancedEdit() { // First validate data from widgets in the "simpler" property dialog if (ValidateData()) { // Set true if OK is clicked in the Advanced Edit dialog window.AdvancedEditOK = false; // Open the AdvancedEdit dialog, passing in the element to be edited // (the copy named "globalElement") window.openDialog("chrome://editor/content/EdAdvancedEdit.xul", "AdvancedEdit", "chrome,close,titlebar,modal", "", globalElement); if (window.AdvancedEditOK) { dump("Advanced Dialog closed with OK\n"); // Copy edited attributes to the dialog widgets: // to setup for validation InitDialog(); // Try to just close the parent dialog as well, // but this will do validation first if (onOK()) { // I'm not sure why, but calling onOK() from JS doesn't trigger closing // automatically as it does when you click on the OK button! window.close(); } } // Should we Cancel the parent dialog if user Cancels in AdvancedEdit? } }
The design and implementation of the Advanced Edit dialog
is being done by our good mozilla friend Ben Goodger (rgoodger@ihug.co.nz
).
We will supply details about this dialog as his work progresses.