Multi-Document Interface Script API

Author: Stephen W. Cote

Prelude

License

The latest release is version 2.5.1116.2001. The content and related software are made available for educational purposes only. It may not be distributed or used for public projects without prior consent. Licensing terms for non-commercial use will be made available upon request, and will stipulate that no fees need to be paid. Commercial use may carry licensing fees.

Acknowledgements

The project began in December 1998 and so far has been a solitary development effort.

Current Version

The MDI script version is based on a major and minor milestone, and the date. The current distribution is: 2.5.1116.2001. The latest version, not available for download, but described within this document, is: 2.6.0409.2002

SDK Availability

The entire MDI source, samples, and documents are available at: mdi.tar.gz

Please be sure to use the following copyright comment when implementing the script file.

<!--
MDI Script.
Copyright 2000 - 2003, All Rights Reserved.
Stephen W. Cote / www.whitefrost.com
-->

Browser Support

Supported browsers are Internet Explorer 5.01 and Mozilla/Netscape 6.

At present, the MDI code will only run on Internet Explorer 5.5 or later on Windows, or Mozilla/Netscape 6. There is a bug in IE 5.01 that causes the browser to crash when some DOM maneuvers are performed.

Forward

The objective was to create a highly customizable infrastructure for delivering production content to the end user through a Web browser. The end result is a set of script-based components that allow developers and Web Authors to easily create a robust user interface for displaying content.

This document describes one possible solution for managing content, and how that solution was implemented.

  1. Part I discusses the implementation of the MDI script, how it can be used, and how it can be extended.
  2. Part II explores how it was constructed, and the rationale behind its structure and conventions.
  3. Part III is a walkthrough of developing a rich table control as a cross browser component, and then as an MDI component. This section demonstrates the amount of development time that can be saved using the MDI framework.
  4. Part IV includes reference material for implementing the script.

Modification History

AuthorDateDescription
Stephen W. Cote04/16/2002Updated the content based on recent changes to the code.
Stephen W. Cote03/30/2002Corrected some formatting.

Part I: Implementation

Introduction

The model of the MDI framework is a script API exposed to a Web page. Dynamic content can be tailored to the developer's own scripts, which in turn may implement the script API. Other standard routines based on the API can operate whether the custom content or scripts do not make full use of the API features. Because it is component-based, the MDI framework only needs to implement the pieces a developer wants to use.

The MDI framework is an abstract layer hosted by a browser and that assists developers to separate the content and form. Content can be easily directed into the customizable infrastructure. Since the infrastructure is created as a generic content block, the style can be changed to meet the needs of the client. By combining well-formed content with this infrastructure, a single Web page can become the foundation for a complex Web application.

Live Examples

The following examples make use of the MDI framework and demonstrate many of its features.

Structure

The structure of a framework content block is represented using a window look-and-feel in the following diagram.

Diagram #4: Content Container

The MDI framework uses the above model to expose a set of foundation classes used to manage the overall look-and-feel of the content. The outermost group of content blocks are children of a content container. Each content block can be thought of as using a window paradigm, where each block has a title, a status, and a body. While one common type of behavior and look-and-feel is to have the semblance of a window, content blocks can be specifically configured to have any look-and-feel that is desired.

The following block layout represents a content block and some content. This uses a window-style look-and-feel, which happens to use almost all of the configurable style options. Keep in mind that any look-and-feel other than a window is possible.

Diagram #6: Content Container Detail

Whether the MDI APIs are used or not, the content does not need to be changed. The difference is that content contained by an MDI content block is manageable using the MDI features.

The foundation framework components are the CMDIStyleManager, CErrorFactory, and CEventHandler classes. These three script classes, along with the available components, are organized through the CMDI class and made available through the global MDI object. The core UI components are CMDIContainer, CMDIWindow and CMDILayoutManager. The following list describes these classes.

  • CMDIStyleManager: Responsible for loading and managing style data. The data is stored in an XML file, and is grouped into style names, where each group contains a map between the internal style name for a component, and the custom style name. The result is that switching the style group applies the entire set of styles to the component.
  • CErrorFactory: Handles internal messaging and error reporting.
  • CEventHandler: Provides an API to attach handlers to component events, and also manages some event requests, such as event capturing.
  • CMDIContainer: Represents a container for zero or more instances of CMDIWindow. This can either be a new, empty container, or be derived from existing content.
  • CMDIWindow: Represents a single content block. As the name implies, it is designed to operate as a child window to the parent container, though its appearance need not be that of a window.
  • CMDILayoutManager: Responsible for managing the layout behavior of CMDIContainer and CMDIWindow instances. The layout manager handles actions such as tiling iconified windows, grid layouts, minimizing and maximizing.

Behind the nitty gritty details of the MDI script are a few global helper functions that encapsulate several repetetive tasks, such as the loading of external XML files.

MDI Template

The easiest way to get started with the MDI framework is to begin with a basic template and an explanation of how it is initialized. This template will be referred to and used for other examples in Part I.

Figure #1

<HTML>
<HEAD>
<TITLE>Sample Template #1</TITLE>
<STYLE>
BODY {margin: 0px; overflow: hidden;}
</STYLE>
<!--
MDI Script.
Copyright 2000 - 2003, All Rights Reserved.
Stephen W. Cote / www.whitefrost.com
-->
<SCRIPT LANGUAGE="JavaScript1.3">
var sCode="mdi.js";
var sPath="";
/* Specify the composite MDI code. */
if(typeof(document.getElementsByTagName)!='undefined'){
   document.write(
      '<S' + 'CRIPT'
      + ' SRC="' + sPath + 'code/' + sCode
      + '"></S' + 'CRIPT>'
   );
}
</SCRIPT>
<SCRIPT>

var sPath="";
/* If the MDI global exists, initialize it. */
if(typeof(MDI)=="object"){
   MDI.init(
      sPath,
      sPath + "code/",
      sPath + "graphics/",
      sPath + "data/default-styleData.xml"
   );
}

window.onload=Init;
var MC,oW,LM;
function Init(){
   /*
      If the MDI global exists, call its postInit function.
      This checks to make sure everything is loaded.
   */
   if(typeof(MDI)=="object" && MDI.postInit()){
      /* Create a new container */
      MC=new MDI.CMC();

      /* 
         Initialize the container with the
	     default-container style.
      */
      MC.init("container1","default-container");

      /* Create a new layout manager */
      LM=new MDI.CML(MC,1);

      /*
         Create a new window with a name, a title, no content,
         the default-window style and the layout manager.
      */
      oW=MC.createWindow(
         "win1",
         "[ window ]",
         null,
         "default-window",
         LM
      );

      /*
         Set the content of the window.
         setInnerXHTML is a DOM helper routine
         available in the 2.6 release.
      */
      setInnerXHTML(
         oW.window.body,
         "[ body text ]"
      );
	  
      /* Set the status of the window. */
      oW.setStatus("[ status text ]");
   }
   else if(typeof(MDI) == 'object' && MDI.wait==true){
      /* Try again if loading synchronously */
      window.setTimeout(Init,100);
   }
   else{
      alert('Failed');
   }
}
</SCRIPT>
</HEAD>
<BODY>
</BODY>
</HTML>
	

In the above example, the MDI initialization sets the locations of the data and graphics, and the post initialization routine verifies the required components are loaded and available. If the post initialization is successful, a new container is created and initialized with the default-container style data. A new layout manager is created for the window, and the new container is specified as the parent container. The new window is created through the container, and is assigned the default-window style group. Finally, some content is added to the window, and a status message is set.

Note that two CSS attributes are set, or rather disabled, for the BODY element: margin and overflow. This was done to maximize screen realestate.

When the page loads, some DHTML should be visible with all the trappings of a window. The window can be moved, resized, minimized, maximized, and closed.

Style Data

Style information is located in an XML file and provides a mapping between the developer-specified style set, custom styles, and the internal component names. For example, the style data for the title in the previous example follows.

<mdi>
   <config>
      <!-- Contains all style sets for a window -->
      <!-- 
         The value refers to an element node
         within this document
      -->
      <stylenames name="default-window">
         <!-- The style name is internal, so don't change it. -->
         <stylename name="title" value="title" />
      </stylenames>   
   </config> 
   <element name="title">
      <style-data>
         <style name="cursor" value="hand" />
         <style name="height" value="15" />
         <style name="backgroundColor" value="#0000FF" />
         <style name="borderWidth" value="1" />
         <style name="borderStyle" value="outset" />
         <style name="borderColor" value="#C0C0C0" />
      </style-data>
   </element>
</mdi>
	

When the CMDI object is initialized, a new CMDIStyleSource is created and loads the specified style source. New CMDIStyleManager instances refer to the style source instance for style data.

Event and Layout Management

When a content block using a window look-and-feel is minimized, it snaps down to the bottom of the screen. When the window block receives the DOM onclick event for the button, it sets the appropriate style, and then fires an event through the global CEventHandler object, CEH. The layout manager listens for certain events, such as when the window is changing state. The layout manager checks to make sure the window is associated with that specific instance of the layout manager before taking any action. If the window is using a specific layout manager, that layout manager moves and resizes the window based on the event type. In this case, the window is minimized and tiled.

The layout manager provides a great deal of flexibility for managing content blocks. For example, when the window block is minimized, it appears to snap to the bottom of the screen. It might be preferable to animate the action, and that is the kind of action the layout manager performs.

In the previous template example (see Figure 1), add the following lines of code right after the layout manager is created.

   /* Enable layout animation */
   LM.setAnim(true);

   /* Animate at 40 pixels per frame */
   LM.setAnimSpeed(40);

   /* Delay 10 milliseconds between each frame */
   LM.setAnimDelay(10);

The layout manager is capable of controlling multiple windows in a grid layout. By specifying the dimension and location values, the layout manager manages when and where the window is moved and resized. Add the following lines of code to the previous template example (Figure 1).

   /* Create two new windows */
   var oW2=MC.createWindow(
      "win2",
      "[ template window 2 ]",
      null,
      "template-window",
      LM
   );
   var oW3=MC.createWindow(
      "win3",
      "[ template window 3 ]",
      null,
      "template-window",
      LM
   );

   /*
      Invoke the gridLayout method.
      The last param is the array of windows.
   */
   LM.gridLayout("80%",150,"center",20,10,0,[2,1,0]);

   /*
      Set options to force a refresh when
      items are moved or change state.
   */
   LM.setGridAutoAdjust(true);
   LM.setGridAutoRefresh(true);

   /* Use a static width when iconified */
   LM.setUseStaticTileWidth(true);

Content Navigation

The fundamentals of the MDI containers and content blocks were covered in the previous sections. Still remaining is the actual content.

Current Implementations

Given a fully-featured web site with toolbars, menus, and a lot of external data, there is more information in the UI than in the actual content, so it makes little sense to leave a page if the same overhead is to be incurred on each page thereafter. Web browsers will usually cache the style and script data loaded from external files. However, the browser would still need to reload and render the information. If just the HTML can be loaded, and everything that is needed to display that HTML is already in memory, then throughput is reduced, server load is lessened, and the user experience is made better. Certainly, these are debatable points.

So far, the solution has been centered around dynamic content, or content that is read from the server or a hidden buffer and added to the loaded page.

One popular mechanism for displaying dynamic content is to use an HTTP buffer, via a hidden FRAME or IFRAME, to load another HTML document. Still another popular approach is to use a script file. Yet another way is to use an object, such as a Java applet or Flash movie.

Ultimately, it comes down to assembling a string of HTML and using innerHTML, insertAdjacentHTML, or document.write to add the content to the page. This suddenly becomes a very limited and non-scalable approach.

XHTML Implementation

The most versatile and scalable approach is to use XHTML. That necessitates that all content is at least valid XHTML.

Modern browsers, such as Mozilla and IE 6, expose an API to load XML. At present, the onus is on the developer to copy the XML DOM into the HTML DOM, but the savings in time and space are enormous by not using the previously mentioned implementations.

Consider that if all content is based in XHTML, it is well-formed and for the most part available to down level browsers as HTML. From a single page, the MDI framework can be used to read the XHTML documents from the server and display that data in a rich environment. And the content is single-sourced, thus reducing the overhead of publishing to multiple formats in the form of buffers or hard coded scripts.

While data can be jammed into a window block, it makes more sense to expose the aforementioned flexibility to access the data source. The MDI framework addresses content navigation by including an optional CMDIContentNavigator component, and exposing a generic setInnerXHTML function. Using the content navigator, a content window can use the navigate method to load an XHTML document as XML. The XML DOM is then copied into the live HTML DOM; directly into the content window block.

The content navigator also manages a cache and history of data for a content container, and exposes an API for moving within the content cache. Data for content containers can be loaded by navigating to the data, whether it is raw HTML or text, or the results of a script function.

For example, you can enable the content navigator for the template (see Figure 1) by adding the following script.

   /*
      Enable content navigation.
      The params are for disabling caching,
      and not adding the title buttons.
   */
   oW.addNavigator(false, false);
   
   /* Navigate to an XHTML file */
   oW.navigate("xml://[/documents/index.html]");

When the content navigator was added, back and forward buttons were added to the title bar buttons. These can be used to navigate through the cache of content. Optionally, the cache can be disabled and the buttons not added. * Swapping content with raw HTML is just the beginning.

* See the below notes on recent implementation changes regarding raw HTML text and innerXHTML.

The content navigator supports the following protocols:

  • content: Navigates to raw HTML. Example: content://<B>Bold Text</B>
  • script: Navigates to the return value of a script function. Example: script://getTimeOfDay
  • http: Uses an IFRAME to display an HTTP address. Example: http://www.microsoft.com
  • xml: Navigates to an XML document, or the transformation of XML and XSLT documents. Example: xml://[xmlFile.xml], Example 2: xml://[xmlFile.xml,xslFile.xsl]

The xml: protocol will identify the structure of HTML documents. If the XML/XHTML file is in the standard HTML document format, then any child nodes in the body are copied into the content block, and the title is set accordingly. Note this is a significant change from the previous implementation, which used an HTML fragment and a custom data format.

Custom links can be used to instruct windows to navigate to specific resources. These special constructs are assigned style through the style manager, and are controlled by the content navigator. At the moment, the syntax is very rigid. The element must be a SPAN, it must include the static %cnav% value for the RID attribute, and it must be activated either by including the link in a previous content navigation, or invoking the activateLinks method for the content navigator assigned to specific window.

<SPAN
   TARGET = "[window index]"
   REF = "[protocol]"
   RID="%cnav%"
>
Link Label
</SPAN>

Notes:

  • History and caching are somewhat broken.
  • The use of innerHTML was weeded out in version 2.6 in favor of setInnerXHTML. This will change the structure of the protocols, so content: and script: are currently iffy.

Component Template

At the heart of the user interface portion of the MDI code are components, each derived from a template. This template structure is used for CMDIContainer and CMDIWindow, plus several extension components, such as CResultSet (rich select box), and CProgressBar (progress bar).

Figure #2

/*
   This is a template for an MDI component.
*/
function CTemplate(oParent){

   oParent=(oParent!=null && typeof(oParent)=="object")
      ? oParent : document.body;

   /* Set generic properties for a component */
   _implement_IComponent(this,oParent,"template");
	
   this.styleName="default-templateTemplate";
   this.styleNames={
      "templateStyle":null,
      "templateStyleToo":null
   };
   this.status={
      "name":"value
   };
   this._init();

   /*
      Use createHandler to register
      event handlers.  This is needed because an event handler
      in IE is anonymous, and we want it to be specific
      to this instance.
      createHandler as automatically created
      for all IComponent classes.
   */   
   this.createHandler("someevent");
}

/*
   The prototype property is mapped to "somotype".
   In the obfuscated version, prototype is then referenced as
   ".p".  This is a space saving technique.
*/
CTemplate.p=CTemplate."somotype";

CTemplate.prototype._init=function(){
   this.styleManager=new CMDIStyleManager(this);

   /* loadStyleNames is added when implementing IStyleHelper */
   var vRet=this.loadStyleNames(this.styleName);
}

/*
   If this template is going to use the style manager,
   it must implement IStyleHelper.
   
   The _implement_IFace function is just a wrapper for
   adding prototypes to the pseudo JavaScript class.
   Be sure to rename 'CMDITemplate'.
*/
_implement_IStyleHelper("CMDITemplate");


/* optional: used for handling events */
CTemplate.prototype._handle_someevent=function(vRef){
   /* handle event */
   var evt=_gevt(vRef);
   var oSrc=(typeof(vRef)!='undefined' && vRef.nodeType)
      ? vRef : _gevt_src(vRef);
}
	

To make the slide control, for example, the only two steps for adding the HTML are 1) make the elements, and 2) initialize that element in the style manager for a specific class. Of course, there is additional footwork to be done in adding in the events, but at this point, that can be done using regular DHTML.

   var oNode=document.createElement("DIV");
   this.container.appendChild(oNode);
   this.styleManager.init(oNode,this.styleNames["compSlider"]);

   var oImg=document.createElement("IMG");
   this.styleManager.init(oImg,this.styleNames["compSliderArrow"]);
   oImg.src=this.styleManager.getStyle(
      "backgroundImage",
      this.styleNames["compSliderArrowImg"]
   );
   oNode.appendChild(oImg);
	

[ top ]

Part II: Rationale and Construction

General Rationale

There are myriad issues that arise from this project. Why crush all the code into a single, obfuscated file? Why not use external CSS files? Why do any of this?

The original MDI code was a project created for an internal Microsoft Corp. Internet Explorer 5.0 behaviors contest. The behaviors were sadly misnamed, were very kludgy, and were ill suited for use as an application UI. Eventually, I desired a more usable and robust implementation of the original behaviors library created for the contest. The MDI Project is the result.

Using external Cascading Stylesheet files was a problem because I wanted more flexibility in being able to dynamically and arbitrarily assign sets of styles to one or more elements, without hard coding the class names. In addition, dynamically reassigning CSS classes is a performance hit. The XML source came about because I wanted to assign abstract names for components that could be reset to any number of different style groups, while remaining independent of like-components.

The earliest versions of these scripts required references to no less than nine JScript files. And, when combined, the size was horrendous. By combining each file into a template file (mdi.template.js), and then obfuscating the template, the source is compressed by forty percent. Using a single set of names to obfuscate reduces the chance of name collisions. Part of the compression/obfuscation was removing as much redundant script as possible, and that included all of the 'prototype' references. Aliasing "prototype" with "p" saved about 2K. If "function" and "this" could be aliased to two characters, another 7K could be saved. But, that doesn't seem likely.

Another reason for using a single file is I was using the code for multiple projects, and in some cases didn't want all of the extras, and in other cases wanted to include debugging information. It made more sense to build separate composite files from only the JavaScript sources I was going to use. Plus, it is a lot easier to work on multiple files, but deliver only one.

Construction

Take a good look at the component template (Figure 2). Much of the MDI Project has revolved around reducing waste and not duplicating code. So far, it has proven valuable in development of additional components and debugging. For example, to create the CResultSet component, I started with the template, created a couple new style sets in the XML Style Data, and from there only had to worry about adding the DHTML necessary to create the actual component. Most of the code in the component is a couple public APIs and handling events for swapping styles out. By using the component template, time and space were saved, and the component can use much of the internal MDI framework.

Development of the MDI API is halted. Refer to the Engine for Web Applications project for related work.

[ top ]

Part III: Component Development Walkthrough

Rich Table Control

[ document development currently halted ]

[ top ]

Part IV: API Reference

[ document development currently halted ]

[ top ]