Here is a collection of macros to help create templates.
Copy the macros from the file macros.txt into the hotmetal.mcr file found in the Macros folder of your HoTMetaL PRO application folder.
Some of the macros in macro.txt use standard macros such as On_Application_Open so be sure to check that you are not duplicating any of those. If you already have such macros in hotmetal.mcr already, just add the code into the appropriate macros instead of adding a new macro.
We first define some useful constants in On_Application_Open. By setting these variables here, we can be sure they are available for any macros that are run after HoTMetaL starts up.
<MACRO name="On_Application_Open" lang="JScript"><![CDATA[ // Some useful constants var viewWYSIWYG = 0; var viewTagsOn = 1; var viewSource = 2; ]]></MACRO>
A template is just an ordinary Web page that is stored in a special location, namely in folder underneath the Template folder in the HoTMetaL application folder. When the user chooses the Page From Template menu item from the File New... menu, a tabbed dialog is presented which groups templates of various kinds. The tabs of the dialog come from the names of the folders underneath the Template folder.
So to create a new template, it is just a matter of saving the current document into a folder underneath the Template folder. The following macro, Save As Template, shows how to create a Save As dialog using the DisplayFileDlg method of HMExtras.FileDlg. The dialog starts the user in the Template folder so they need merely double-click on the subfolder they want to save to and press OK.
This macro would be a good candidate to add to the File menu, as a menu customization.
<MACRO name="Save As Template" lang="JScript" id="90"><![CDATA[ var obj = new ActiveXObject("HMExtras.FileDlg"); var filter = "Web Page Template (*.htm, *.html)|*.htm;*.html|All Files (*.*)|*.*||"; if (obj.DisplayFileDlg(0, "File Save As", filter, Application.Path + "\\Template")) { ActiveDocument.SaveAs(obj.FullPathName, true); // save and put on recent file list } ]]></MACRO>
HoTMetaL has a replaceable text feature. Replaceable text (or just "replace text") is a block of text displayed with a gray background like this. When the user puts the cursor on it, the entire text block is selected and is immediately replaced by whatever is typed. Replace text is a handy way to prompt the user of a template for information to go in specific locations on the page.
Replace text is represented in the HTML code by surrounding the text with the processing instruction <?xm-replace_text ... ?>, like this:
<?xm-replace_text This text will be replaced when the user starts typing over it.?>
A convenient way to create replace text is to select the desired text in the document, then run a macro to surround it with the replace text processing instruction. That's what the following macro does. It is looks a little complicated because we have taken care of the case where the selected text already contains some replace text.
We have split off the functionality into a function called Do_MakeReplaceText. This makes it easier to exit in case of error, because a script has no way to return other than by running through to the end.
<MACRO name="MakeReplaceText" key="Ctrl+Alt+Z" lang="JScript" id="62" tooltip="Make Replace Text (Ctrl+Alt+Z)" desc="Makes current selection into replaceable text"><![CDATA[ function Do_MakeReplaceText() { // Check if we are in a view that works var view = Application.ActiveDocument.ViewType; if (view != viewWYSIWYG && view != viewTagsOn) { var msg = "Can't make replaceable text in this view."; msg += "\nPlease switch to Tags On or WYSIWYG view and try again."; Application.Alert(msg); return; } // Check if we have an extended selection if (Selection.IsInsertionPoint) { Application.Alert("Select some text and try again."); return; } // Handle replace text that is already there in the selection // This is not perfect: if there are other PI's it will fail. // But it should be plenty good enough for HTML. var txt = Selection.Text; var exp = new RegExp("<\\?xm-replace_text ", "g"); txt = txt.replace(exp, ""); exp.compile("\\?>", "g"); txt = txt.replace(exp, ""); Selection.InsertReplaceableText(txt); } Do_MakeReplaceText(); // Put it into a function for easier error handling ]]></MACRO>
If there are widely separated regions of replaceable text in a template, it can be convenient for your users to be able to move from one to the next with a simple keystroke. The following macros move the selection to the next or previous block of replaceable text. The common functionality of the two cases is embodied in the function MoveToReplaceable which is placed in On_Application_Open so it will be available to the other macros.
<MACRO name="On_Application_Open" lang="JScript"><![CDATA[ function MoveToReplaceable(dir) { var rng = ActiveDocument.Range; var foundCandidate; var found = false; do { foundCandidate = rng.Find.Execute("<.PROCINS>", "", "", false, false, false, dir, false); if (foundCandidate && (rng.Text.substring(0, 17) == "<?xm-replace_text")) { found = true; } } while (!found && foundCandidate); if (found) { rng.Select(); } else { Application.Beep(); } } ]]></MACRO> <MACRO name="Previous Replaceable" lang="JScript" key="Ctrl+UpArrow"><![CDATA[ MoveToReplaceable(false); ]]></MACRO> <MACRO name="Next Replaceable" lang="JScript" key="Ctrl+DnArrow"><![CDATA[ MoveToReplaceable(true); ]]></MACRO>
HoTMetaL lets you designate tags and their content as read-only. This is done to prevent users of templates, for example, from changing key parts that need to remain fixed.
This can be easily accomplished by using the macros below. Each section of the document that is surrounded by a DIV or SPAN tag with CLASS attribute equal to "SQ-ReadOnly" is set to readonly. The macro On_Document_Open_Complete is a good time to do this.
<MACRO name="On_Document_Open_Complete" lang="JScript"><![CDATA[ function MarkReadOnlySections() { // Mark special DIV and SPAN sections as read-only var rng = ActiveDocument.Range; var i; var nodeList = ActiveDocument.getElementsByTagName("DIV"); for (i = 0; i < nodeList.length; i++) { var node = nodeList.item(i); if (node.getAttribute("CLASS") == "SQ-ReadOnly") { rng.SelectBeforeNode(node); rng.MoveToElement("DIV"); rng.ReadOnlyContainer = true; } } nodeList = ActiveDocument.getElementsByTagName("SPAN"); for (i = 0; i < nodeList.length; i++) { var node = nodeList.item(i); if (node.getAttribute("CLASS") == "SQ-ReadOnly") { rng.SelectBeforeNode(node); rng.MoveToElement("SPAN"); rng.ReadOnlyContainer = true; } } } // Read and save last modified date of file var name = ActiveDocument.LocalFullName; if (Application.ReadableFileExists(name)) { // if document has never been saved, do nothing var fso = new ActiveXObject("Scripting.FileSystemObject"); var f = fso.GetFile(name); var mod = Date.parse(f.DateLastModified); var props = ActiveDocument.CustomDocumentProperties; props.Add("LastMod", mod); } // Mark sections as readonly if (ActiveDocument.ViewType == viewWYSIWYG || ActiveDocument.ViewType == viewTagsOn) { MarkReadOnlySections(); } ]]></MACRO>
It's a good idea to give some visual indication that a section is read-only. The following CSS style is one way to indicate it, in this case, by making the background blue:
*.SQ-ReadOnly { background-color: #CCFFFF; }
To create a readonly section, select it and apply the following macro:
<MACRO name="Make ReadOnly Section" lang="JScript" id="132"><![CDATA[ function Do_MakeReadOnlySection() { // Check if we are in a view that works var view = Application.ActiveDocument.ViewType; if (view != viewWYSIWYG && view != viewTagsOn) { var msg = "Can't make read-only section in this view."; msg += "\nPlease switch to Tags On or WYSIWYG view and try again."; Application.Alert(msg); return; } // Check if we have an extended selection if (Selection.IsInsertionPoint) { Application.Alert("Select some text and try again."); return; } if (Selection.CanSurround("DIV")) { Selection.Surround("DIV"); } else if (Selection.CanSurround("SPAN")) { Selection.Surround("SPAN"); } else { // impossible? Application.Alert("Could not surround with a DIV or a SPAN tag."); return; } Selection.ContainerAttribute("CLASS") = "SQ-ReadOnly"; Selection.ReadOnlyContainer = true; } if (ActiveDocument.ViewType == viewWYSIWYG || ActiveDocument.ViewType == viewTagsOn) { Do_MakeReadOnlySection(); } ]]></MACRO>
Finally, to make a readonly section unprotected again so you can write into it, put the selection inside it and run the follow macro (possibly multiple times, if there are nested readonly sections):
<MACRO name="Remove Readonly Section" lang="JScript" id="148"><![CDATA[ // Remove the closest surrounding "readonly section" tags function Do_RemoveReadonlySection() { var rngDIV = ActiveDocument.Range; var rngSPAN = rngDIV.Duplicate; // SPAN, then DIV if (rngSPAN.isParentElement("SPAN")) { rngSPAN.MoveToElement("SPAN", false); if (rngSPAN.ContainerAttribute("CLASS") == "SQ-ReadOnly") { rngSPAN.ReadOnlyContainer = false; rngSPAN.RemoveContainerTags(); return; } } if (rngDIV.isParentElement("DIV")) { rngDIV.MoveToElement("DIV", false); if (rngDIV.ContainerAttribute("CLASS") == "SQ-ReadOnly") { rngDIV.ReadOnlyContainer = false; rngDIV.RemoveContainerTags(); return; } } } if (ActiveDocument.ViewType == viewWYSIWYG || ActiveDocument.ViewType == viewTagsOn) { Do_RemoveReadonlySection(); } ]]></MACRO>