Writing Macros

   

The next and last level of customization is performed by using macros. Macros can, amongst other things

  • Add new menu items or new toolbar buttons

  • Control what happens at critical moments, such as when the document is saved

You can associate one macro file with each DTD. The macros file, event.mcr , is shown in Listing 3.6. We will examine it, one macro at a time, in the following sections.

Note that the macro file is an XML document, so you can use XMetaL to edit it. To create an empty macro file, choose File, New, Blank XML Document and then select macros.dtd .

This causes a script error. You should immediately save the file as event.mcr under the Macros directory below the XMetaL directory to avoid the script errors.

Tip

If a macro file exists for the current DTD, you can edit it by choosing Tools, Macros, selecting Open Document Macros, and clicking Run.


Listing 3.6 event.mcr
 <?xml version="1.0"?> <!DOCTYPE MACROS SYSTEM "macros.dtd"> <MACROS> <MACRO lang="JScript" name="Insert Email"><![CDATA[if(ActiveDocument.documentElement) {    var emails = ActiveDocument.getElementsByTagName("Email")    if(0 == emails.length)    {       var contacts = ActiveDocument.getElementsByTagName("Contact")       if(0 != contacts.length)       {          var phones = contacts(0).getElementsByTagName("Phone")          if(0 != phones.length)          {             Selection.SelectAfterNode(phones(phones.length - 1))             Selection.InsertWithTemplate("Email")          }          else          {             var names = contacts(0).getElementsByTagName("Name")             if(0 != names.length)             {                Selection.SelectAfterNode( names(names.length - 1))                Selection.InsertWithTemplate("Phone")                Selection.SelectAfterNode(contacts(0).lastChild)                Selection.InsertWithTemplate("Email")             }             else             {                Selection.SelectNodeContents(contacts(0))                Selection.InsertWithTemplate("Name")                Selection.SelectAfterNode(contacts(0).lastChild)                Selection.InsertWithTemplate("Phone")                Selection.SelectAfterNode(contacts(0).lastChild)                Selection.InsertWithTemplate("Email")             }          }       }    }  } ]]></MACRO> <MACRO lang="JScript" name="Italic"><![CDATA[if(ActiveDocument.documentElement) {    if(Selection.ContainerName == "Italic")       Selection.RemoveContainerTags()    else if(Selection.CanSurround("Italic"))       Selection.Surround("Italic") } ]]></MACRO> <MACRO lang="JScript" name="Bold"><![CDATA[if(ActiveDocument.documentElement) {    if(Selection.ContainerName == "Bold")       Selection.RemoveContainerTags()    else if(Selection.CanSurround("Bold"))       Selection.Surround("Bold") } ]]></MACRO> <MACRO lang="JScript" name="On_Document_Save"><![CDATA[if(ActiveDocument.documentElement) {    var isStart = true,        isEnd = true    var invalidFields = null    var starts = ActiveDocument.getElementsByTagName("Start")    if(0 != starts.length)    {       starts(0).normalize()       var startText = starts(0).firstChild       if(null != startText &&          3 == startText.nodeType)   // 3 == DOMText          isStart = !isNaN(Date.parse(startText.data))    }    var ends = ActiveDocument.getElementsByTagName("End")    if(0 != ends.length)    {       ends(0).normalize()       var endText = ends(0).firstChild       if(null != endText &&          3 == endText.nodeType)   // 3 == DOMText          isEnd = !isNaN(Date.parse(endText.data))    }    var msg = null    if(!isStart && !isEnd)       msg = "Both event dates are invalid.\ n You should fix them and save again."    else if(!isStart)       msg = "Event start date is invalid.\ n You should fix it and save again."    else if(!isEnd)       msg = "Event end date is invalid.\ n You should fix it and save again."    if(msg != null)       Application.Alert(msg,"Event Description Form") }]]></MACRO> <MACRO lang="JScript" graphics/ccc.gif name="On_Document_SaveAs"><![CDATA[Application.Run("On_Document_Save")]]></MACRO> <MACRO name="On_Update_UI" lang="JScript"><![CDATA[if(!ActiveDocument.documentElement     3 == ActiveDocument.ViewType) {    Application.DisableMacro("Insert Email")    Application.DisableMacro("Italic")    Application.DisableMacro("Bold") } else {    var emails = ActiveDocument.getElementsByTagName("Email")    if(0 != emails.length)       Application.DisableMacro("Insert Email")    var contacts = ActiveDocument.getElementsByTagName("Contact")    if(0 == contacts.length)       Application.DisableMacro("Insert Email")    if(!Selection.IsParentElement("Para"))    {       Application.DisableMacro("Italic")       Application.DisableMacro("Bold")    } } ]]></MACRO> </MACROS> 

Creating a Toolbar Button

First, you should review the Italic macro (the Bold macro is almost identical):

 <MACRO lang="JScript" name="Italic"><![CDATA[if(ActiveDocument.documentElement)    {    if(Selection.ContainerName == "Italic")          Selection.RemoveContainerTags()       else if(Selection.CanSurround("Italic"))          Selection.Surround("Italic")    }]]></MACRO> 

This macro inserts or removes the Italic element. Before running macros that will modify the document, it is good practice to test whether a document object is available. ActiveDocument is a special object that always points to the document in the active window.

The core of the macro is simple: It tests whether the cursor is within an Italic element, in which case the macro removes it. Otherwise, it attempts to insert an Italic element around the current selection.

The RemoveContainerTags() and Surround() methods modify the document. The CanSurround() method tests against the DTD. Our macro uses both to test against the DTD before inserting the element. For your DTD, CanSurround() returns true if the selection is within a Para element.

This macro implements the Italic command from word processors. You should add it to the toolbar. Make sure you have opened an empty event.dtd document and then select Tools, Macros to open the Macros dialog box. In the list, select the Italic macro and assign it a shortcut of Ctrl+I. XMetaL warns you that Ctrl+I conflicts with another macro, but ignore it.

Tip

When you edit macros in XMetaL, you can reload the macros with the Save and Refresh button on the macros toolbar.


Click Choose Image to open the Choose Toolbar Button Image dialog box. In the Formatting images, select the slanted I. Then close the macro box.

Choose View, Toolbars to open the Toolbars dialog box and click the New button. Enter event as the toolbar name. Immediately, an empty toolbar appears onscreen. Tab to the Buttons panel and select event Macros . In the list of macros, choose Italic. Finally, drag the button to the toolbar (see Figure 3.12). Repeat these steps for the Bold macro.

Figure 3.12. Editing the toolbar.

graphics/03fig12.gif

Creating an XML Element

Although you have already improved things, inserting the Email element is still difficult. Specifically, the user must be in the Phone field and press Enter. It's great if you know it, but almost impossible to find if you don't.

Add a button to the toolbar to insert the Email element. Because the button will be visible on the toolbar, it will be easier for the user.

This is implemented in the Insert Email macro:

 <MACRO lang="JScript" name="Insert Email"><![CDATA[if(ActiveDocument.documentElement)    {       var emails = ActiveDocument.getElementsByTagName("Email")       if(0 == emails.length)       {          var contacts = ActiveDocument.getElementsByTagName("Contact")          if(0 != contacts.length)          {             var phones = contacts(0).getElementsByTagName("Phone")             if(0 != phones.length)             {                Selection.SelectAfterNode(phones(phones.length - 1))                Selection.InsertWithTemplate("Email")             }             else             {                var names = contacts(0).getElementsByTagName("Name")                if(0 != names.length)                {                   Selection.SelectAfterNode( names(names.length - 1))                   Selection.InsertWithTemplate("Phone")                   Selection.SelectAfterNode(contacts(0).lastChild)                   Selection.InsertWithTemplate("Email")                }                else                {                   Selection.SelectNodeContents(contacts(0))                   Selection.InsertWithTemplate("Name")                   Selection.SelectAfterNode(contacts(0).lastChild)                   Selection.InsertWithTemplate("Phone")                   Selection.SelectAfterNode(contacts(0).lastChild)                   Selection.InsertWithTemplate("Email")                }             }          }       }     } ]]></MACRO> 

Similar to Italic, this macro creates a new XML element. However, it is more complex because the Email element must appear within the Contact element and more than one Email element can't exist.

The Insert Email macro starts by testing whether an Email element already exists. If none does, it tries to locate the Contact element. If no Contact elements exist, it stops. However, if it finds Contact , it tries to locate a Phone or Name element. If Phone or Name are missing, it inserts them before inserting Email .

Insert Email is more complex than the Italic macro because it must enforce the document structure. For example, it might have to create other elements ( Phone and Name ) before creating the Email element.

Note

The macro uses InsertWithTemplate() to create the elements. InsertWithTemplate() uses the template defined in the customization editor, so it will end up prompting the user through a dialog box.


Don't forget to create a button on the toolbar. You can use the envelope image in the Quick Tools list.

Improving the User Interface

The next macro is On_Update_UI. XMetaL executes it when it needs to update the user interface ”for example, when the user moves to a new element or switches from normal to plain text view.

This macro is responsible for selectively disabling those macros that no longer work. For example, if the user moves from a Para element to Location , the Italic and Bold macros must be disabled.

On_Update_UI disables all the macros if no document object is available. It also disables Insert Email if an email already is in the document or if no Contact element exists. Finally, it disables Italic and Bold unless the cursor is within a Para :

 <MACRO name="On_Update_UI" lang="JScript"><![CDATA[if(!ActiveDocument.documentElement graphics/ccc.gif 3 == ActiveDocument.ViewType)    {       Application.DisableMacro("Insert Email")       Application.DisableMacro("Italic")       Application.DisableMacro("Bold")    }    else    {       var emails = ActiveDocument.getElementsByTagName("Email")       if(0 != emails.length)          Application.DisableMacro("Insert Email")       var contacts = ActiveDocument.getElementsByTagName("Contact")       if(0 == contacts.length)          Application.DisableMacro("Insert Email")       if(!Selection.IsParentElement("Para"))       {          Application.DisableMacro("Italic")          Application.DisableMacro("Bold")       }    } ]]></MACRO> 

Validating the Form

The last two macros are On_Document_Save and On_Document_SaveAs. They perform additional validation before the document is saved. Indeed, although XMetaL enforces the structure of the document, the user can always enter incorrect information in the fields. The DTD offers much built-in validation, but it is not always powerful enough. You can develop additional validations using On_Document_Save and On_Document_SaveAs.

Specifically, the macro extracts the start and end dates from the document and checks that they are indeed dates. In case of errors, it warns the user through a dialog box (see Figure 3.13):

 <MACRO lang="JScript" graphics/ccc.gif name="On_Document_Save"><![CDATA[if(ActiveDocument.documentElement)    {       var isStart = true,           isEnd = true       var invalidFields = null       var starts = ActiveDocument.getElementsByTagName("Start")       if(0 != starts.length)       {          starts(0).normalize()          var startText = starts(0).firstChild          if(null != startText &&             3 == startText.nodeType)   // 3 == DOMText             isStart = !isNaN(Date.parse(startText.data))       }       var ends = ActiveDocument.getElementsByTagName("End")       if(0 != ends.length)       {          ends(0).normalize()          var endText = ends(0).firstChild          if(null != endText &&             3 == endText.nodeType)   // 3 == DOMText             isEnd = !isNaN(Date.parse(endText.data))       }       var msg = null       if(!isStart && !isEnd)          msg = "Both event dates are invalid.\ n You should fix them and save again."       else if(!isStart)          msg = "Event start date is invalid.\ n You should fix it and save again."       else if(!isEnd)          msg = "Event end date is invalid.\ n You should fix it and save again."       if(msg != null)          Application.Alert(msg,"Event Description Form")    } ]]></MACRO> 

Tip

On_Document_Save does not prevent the user from saving an incorrect document; it only warns her. In practice, this is a good compromise: Good reasons might exist for the user to temporarily enter an invalid date.

If you need to prevent the user from saving incorrect documents, use the File_Save and File_SaveAs macros.


Figure 3.13. Oops! The dates are not acceptable.

graphics/03fig13.gif

   


Applied XML Solutions
Applied XML Solutions
ISBN: 0672320541
EAN: 2147483647
Year: 1999
Pages: 142

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net