You are currently viewing a snapshot of www.mozilla.org taken on April 21, 2008. Most of this content is highly out of date (some pages haven't been updated since the project began in 1998) and exists for historical purposes only. If there are any pages on this archive site that you think should be added back to www.mozilla.org, please file a bug.



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:

  1. Startup() [this may be called by another name -- it is the value of the onload attribute on the XUL Window]
    1. Do EditorShell initialization.
    2. Set variables for controls that we use in the dialog
    3. Get the element we will edit from the document or create a new one to insert.
    4. Copy this element to the global "globalElement" for use by the Advanced Edit dialog
    5. Initialize the dialog widgets from this globalElement
  2. InitDialog()
    1. Fill dialog widgets with attribute values from globalElement
  3. ValidateData()
    1. 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.
    2. For each attribute, if value is valid, set that attribute on the globalElement.
    3. If all attributes are valid, return true, else return false.
  4. onOK()
    1. Call ValidateData() and if result is true:
    2. 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.
    3. If we are inserting a new element, call the appropriate editorShell method, usually InsertElement().
    4. 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.