Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / XHTML

Bridge design pattern with JavaScript

4.76/5 (17 votes)
27 Jul 2007CPOL11 min read 1   549  
This article explains what the Bridge design pattern is, and presents a minimalistic cross-browser vector graphics (SVG + VML) library, written in JavaScript, built with the help of this pattern.

Foreword

Thanks to those of you who take the time to read through the article. If you feel like dropping off a vote (and particularly if it's a low one), please include a comment which mentions what the problem was. I've been getting mostly high votes for this article, apart from the odd 1's or 2's, and I'd really like to know what bothered those voters the most. Feedback is what drives improvement.

This article assumes you are familiar with object aspects of the JavaScript language. If not, it is recommended that you read the first half of Salvatore Vetro's article.

This article is organized as follows:

Problem

Graphic content is the thing every web developer deals with in every single page created. Problems begin when it goes a little further than placing an <IMG> tag. You may wish to build a web-based graphics editor, game, ad banner, interactive teaser, etc.

The first solution that comes to mind is Flash. You can satisfy almost any web graphics need with it, but disadvantages can be prohibitive. They are: need for investments in docs, tools, and programmers; deficient support for in-browser scripting; incompatibility with numerous web media standards, problems with Flash Player security; etc.

XML-based standards are the alternative. They offer perfect web integration (as XML does), are extensible, and do not require downloading large binary archives. The only drawback is that XML is 'still' an emerging technology, so you'll meet numerous incompatibility issues when dealing with XML grammars.

To start with web graphics, we'll need something simple like this:

JavaScript
<script type="text/javascript">

  var vgr = new VectorGraphics(...);

  vgr.MoveTo(100, 100);
  vgr.Rect(100, 100, "#0F0");

  vgr.MoveTo(350, 350);
  vgr.Circle(50, "#F0F");

  ...

</script>

As usual, there is more than one way to do the job. To be exact, the number of ways is the number of browsers, plug-ins, and standards (cross-multiplied with each other...) on the web. To satisfy the needs of all (or, at least, the majority of) your customers, you must pass all these ways... Do you want to meet the multiple-page-browser-and-platform-detection-stuff (with all those switches and ifs so easy to get lost in) once again? I don't.

So, before we move on with rectangles and circles, we'll have to answer two not-so-simple questions:

  • how do we build an object library which will take the best of two (three, four...) worlds, if choices are available?
  • how do we build an object library which is easy to extend and maintain?

The answer is:

Bridge pattern

Note: JavaScript is a prototype-based language, and doesn't support many features common to object-oriented languages. Where it makes sense, I'll make notes describing differences and sideways; they are designated as [JavaScript].

  • Motivation

    Consider the implementation of a cross-browser vector graphics library. Its main purpose is to allow us to use an (X)HTML page as a painting canvas, so we can draw various shapes: lines, rectangles, circles, etc. It also should enable us to write scripts that work on any browser that supports either SVG or VML standards. Using inheritance ([JavaScript]: prototype chaining), we could define an interface VectorGraphics, and classes VectorGraphicsImplSVG and VectorGraphicsImplVML that implement the given interface:

    Image 1

    This approach has two problems:

    1. It is inconvenient to extend the VectorGraphics abstraction to cover more specialized kinds of vector graphics API, or new platforms. Imagine a GeoGraphics subclass of VectorGraphics that specializes the vector graphics to display GIS data. To support GeoGraphics for both SVG and VML user agents, we have to implement two new classes, GeoGraphicsImplSVG and GeoGraphicsImplVML. Worse, we'll have to define two classes for every new XML-based graphics standard. Supporting a third API requires yet another new vector graphics subclass for every specific case of VectorGraphics. Every framework extension leads to the explosive growth of class/prototype hierarchy:

      Image 2

    2. Once the supporting programmer has GeoGraphicsImplSVG and GeoGraphicsImplVML, what class should he/she instantiate? It is clear that the VectorGraphics class itself must "decide" what class to present to the end-user. In other words, clients should be able to instantiate a VectorGraphics object without committing to a concrete implementation. Only the implementation classes should depend on the platform on which the script runs. Therefore, client code should instantiate VectorGraphics without mentioning specific graphics standards.

    The Bridge pattern solves the problem by putting the VectorGraphics abstraction and all its implementations in separate class/prototype hierarchies. There is one hierarchy for VectorGraphics interfaces (VectorGraphics, GeoGraphics, UIGraphics), and a separate hierarchy for platform-specific implementations, with VectorGraphicsImpl as its root (see details below). The VectorGraphicsImplSVG, for example, provides an implementation based on the SVG rendering subsystem.

    Image 3

    Such a relationship between VectorGraphics and VectorGraphicsImpl is called a bridge, because it bridges the interface and its implementation, letting them vary independently.

    (C++ gurus can say: "bridge pattern adds one 'level of indirection', just like a Proxy pattern".)

  • Applicability

    Use the Bridge pattern when:

    • you want to explicitly separate the interface and the implementation for a custom object. This allows you to choose (and even switch) implementors at run-time
    • both the abstractions and their implementations should be extensible (by either subclassing or prototype chaining). This prevents the uncontrolled growth of class hierarchy, and allows you to evade cumulative coding errors (changes on the implementor classes have no impact on the interface classes). That is why the Bridge pattern is great when used as a base of cross-platform frameworks
    • you want to localize platform-specific code, thus making your scripts easy to extend and maintain
    • you want to localize browser/feature/object detection code. (Note: you may or may not want to do it; the structure of the bridge pattern forces you to do it.)

  • Structure

    Image 4

  • Participants

    • Abstraction

      • defines the abstraction's interface
      • maintains a reference to an implementor object
    • RefinedAbstraction

      • extends the interface defined by abstraction
    • Implementor

      • defines the interface for implementation classes. This interface may or may not be the same as the abstraction's.

      Note: since JavaScript is a loosely-typed language with no notion of abstract classes, Implementor is uncommon to JavaScript implementations of the Bridge pattern. It is usual to have a set of ConcreteImplementors; you set or change the abstraction's implementor reference to one of the ConcreteImplementors during the setup of the bridge. All ConcreteImplementors must conform to a chosen interface, as if all concrete implementors were subclasses of an Implementor.

    • ConcreteImplementor
      • implements the Implementor interface and defines its concrete implementation.

  • Collaborations

    The abstraction forwards client requests to its Implementor object.

  • Sample implementation

    Note: this is not an example script; treat it as a fill-in-form for you to cut-and-paste to your script file, and start coding.

    JavaScript
    // Abstraction - the object you usually create
    //               in your "end-user" scripts:
    //
    // <SCRIPT type="text/javascript">
    
    //
    //    var abstr = new Abstraction();
    //    ...
    //
    // </SCRIPT>
    //
    function Abstraction()
    {
    // Implementation reference:
    
      this._impl = null;
    
    // Setup procedure:
    
      this._SetImplementation(this._EstablishImplementor(container));
    
      return this;
    }
    
    Abstraction.prototype = {
    
      _SetImplementation: function(implementor)
      {
        this._impl = null;
        if(implementor) this._impl = implementor;
      },
    
      // EstablishImplementor - function that creates
      // the Concrete Implementor and binds it to Abstraction.
      // This is the very method to place your
      // browser/feature/object detection code.
      _EstablishImplementor: function()
      {
        if(/*conditionOne*/)
          return new ImplementationOne();
    
        else if(/*conditionTwo*/)
          return new ImplementationTwo();
    
        // ...
    
        return null;
      },
    
      // Function "exported" by the Abstraction:
      FuncOne: function()
      {
        // Check if any implementor is bound and has the required method:
        if(_impl && _impl.FuncOne)
           _impl.FuncOne();     // Forward request to implementor
      }
    };
    
    // ...
    
    // This is the first in the set of concrete implementors:
    function ImplementationOne()
    {
    // ...
    }
    
    ImplementationOne.prototype = {
    
      // This "public" function is directly called by :
      FuncOne: function()
      {
        // ...
      },
    
    // ...
    }
    
    // This is the second implementor:
    function ImplementationTwo()
    {
    // ...
    }
    
    ImplementationTwo.prototype = {
    
      FuncOne: function()
      {
        // ...
      },
    
    // ...
    }

    Note 1: _EstablishImplementor in the sample above is an example of a Factory Method pattern. We use it to localize the knowledge of which helper class to instantiate.

    Note 2: to finish with patterns, I must say that the JavaScript language itself is built upon the Prototype pattern. Each object is derived from a prototype (prototypical instance); object creation is really the operation of cloning the prototype.

Case study: SVG vs. VML

Note: the situation with SVG/VML was explained (in short) earlier; this section provides an in-depth review of a problem and presents the solution to it.

  • Overview

    There are two well-known standards for web graphics: SVG and VML. Both provide:

    • XML-based language for describing two-dimensional graphics
    • language-neutral programming API (the Document Object Model)
    • seamless integration with other XML grammars (through XML namespaces).

    Differences:

    • SVG (Scalable Vector Graphics) - endorsed by W3C, this is truly an industry-wide initiative to bring vector graphics to the web:

      • extremely large specification that covers every single aspect of web graphics: shapes, text, filters, animation, audio/video, interactivity, scripting, etc.
      • modularized standard
      • full specification available
      • various implementations are available

      Problems are:

      • inline SVG is incompatible with HTML 4; all references recommend dealing with XHTML documents
      • browser support is still quite poor, even taking Mozilla (Firefox), Opera, and available plug-ins into account. Current development rates show that this situation is temporary

      Available through:

      • Mozilla Firefox browser - native support starting from version 1.5 (SVG support is incomplete as of September 2006);
      • Opera browser - native support (bundled plug-in) starting from version 9 (SVG support is incomplete and somewhat buggy as of September 2006);
      • MS Internet Explorer - through Adobe SVG Viewer;
      • any browser that supports Java technology - through the Batik applet.

    • VML (Vector Markup Language) - another (and, to be honest, the earliest - dates back to 1998) initiative, supported solely by Microsoft:

      • crippled (compared to SVG) functionality
      • loosely documented
      • it is not (and, probably, will not ever be) supported by the WWW community. Potentially obsolete technology

      However:

      • supported natively by Microsoft Internet Explorer since version 5.5 - the largest installed viewer base
      • can be integrated into any HTML document (as an XML island)
      • close integration with the Microsoft Office family of products

      Available through:

      • Microsoft Internet Explorer - starting from version 5.5.

      Note: while comparing the lists of contributors within SVG and VML specs, you may notice that these lists overlap. This may signify that Microsoft has, probably, given away VML development in favor of SVG. Nevertheless, VML is still worth looking, as even the newest version (7th, as of July 2007) of Microsoft IE retains native support for it (and doesn't natively support SVG).

    For browsers that do not support either of two, an alternative exists: High Performance JavaScript Graphics Library by Walter Zorn. It uses DIVs to draw shapes, so it doesn't rely on any standard.

  • Library interface

    Here is a high-level API for a VectorGraphics object. Not too extensive, but complete enough to fit the most common drawing needs.

    All functions listed below are "public", i.e., intended for use by the client, as opposed to "private" functions (with the preceding underscore), used internally by the library.

    JavaScript
    // VectorGraphics - the object you should create in your "end-user"
    // scripts.
    // 'Container' parameter defines an empty DIV or SPAN, which will serve
    //           as a painting canvas.
    //
    // <SCRIPT type="text/javascript">
    //
    //    var vg = new VectorGraphics(document.getElementById("container"));
    //    ...
    //
    // </SCRIPT>
    
    function VectorGraphics(container)
    { ... }
    
    VectorGraphics.prototype = {
    
      // MoveTo(x, y) - moves current painting position to a point
      //                with the coordinates of (x, y).
      MoveTo: function(x, y)
      { ... },
    
      // LineTo(x, y) - draws a line from current position
      //               (defined by a previous MoveTo call)
      //                to a point with the coordinates of (x, y).
      //                Line is drawn with current stroke color,
      //                line width is equal to current stroke width.
      LineTo: function(x, y)
      { ... },
    
      // Rect(x, y, width, height) - draws a rectangle. Upper-left
      //                corner is defined by the current position
      //               (probably set by a previous MoveTo call);
      //                lower-right corner is:
      //
      //                (currentX + width, currentY + height).
      //
      //                Rectangle is filled with current fill color,
      //                border is drawn with current stroke color,
      //                border width is equal to current stroke width.
      Rect: function(width, height)
      { ... },
    
      // Ellipse(rx, ry) - draws an ellipse; center
      //                is defined by the current position
      //               (probably set by a previous MoveTo call);
      //                radii are rx and ry. Ellipse is filled with
      //                current fill color, border is drawn with
      //                current stroke color, border width is equal
      //                to current stroke width.
      Ellipse: function(rx, ry)
      { ... },
    
      // Polygon(arrayofPoints) - draws a polygon. arrayofPoints
      //                has the following format:
      //
      //                [[x1, y1],[x2, y2], ... [xn, yn]]
      //
      //                Unlike all other functions, previous MoveTo call
      //                doesn't define the starting point. Polygon is
      //                filled with current fill color, border is drawn
      //                with current stroke color, border width is equal
      //                to current stroke width.
      Polygon: function(arrayofPoints)
      { ... },
    
      // Text(string) - draws a string at the current position
      //               (probably set by a previous MoveTo call);
      //                Text is drawn with the current font color,
      //                and at a current font size.
      Text: function(string)
      { ... },
    
      // SetFillColor(rgb) - sets the fill color,
      //                     in #xxx or #xxxxxx format.
      SetFillColor: function(rgb)
      { ... },
    
      // SetStrokeColor(rgb) - sets the stroke (line) color,
      //                     in #xxx or #xxxxxx format.
      SetStrokeColor: function(rgb)
      { ... },
    
      // SetStrokeWidth(rgb) - sets the stroke (line) width.
      SetStrokeWidth: function(rgb)
      { ... },
    
      // SetFont(fontNames, fontSize, fontColor, fontVariant) - sets
      //             the font family (fontNames), font color (fontColor),
      //             and font variant (fontVariant, not currently used).
      SetFont: function(fontNames, fontSize, fontColor, fontVariant)
      { ... }
    }
  • Concrete implementations

    VectorGraphics is accompanied by two implementors: VectorGraphicsImplSVG and VectorGraphicsImplVML. The implementor is chosen at run-time by the VectorGraphics.prototype._EstablishImplementor method, which is the place where browser detection code is localized. The function simply creates and returns the supported implementor:

    JavaScript
    // Checks for MSIE + Adobe SVG plugin installation:
    VectorGraphics.prototype = {
    
      _CheckASV: function()
      {
        var adobeplugin = false;
    
        if((navigator.userAgent.toLowerCase().indexOf("msie") != -1) &&
                                                     (!window.opera))
        {
          var obj = null;
          try
          {
            obj = new ActiveXObject("Adobe.SVGCtl");
            if(obj)
            {
              adobeplugin = true;
              obj = null;
            }
          }
          catch(e) {};
        }
    
        return adobeplugin;
      },
    
      _EstablishImplementor: function(container)
      {
        if((navigator.mimeTypes &&
                (navigator.mimeTypes["image/svg+xml"] ... ))
           ||
           // Firefox 1.5+ supports SVG natively:
           (navigator.userAgent.toLowerCase().indexOf("firefox")
                                      != -1) && !window.opera)
          return new VectorGraphicsImplSVG(container);
    
        else if(this._CheckASV())
          // MSIE + Adobe SVG plugin:
          return new VectorGraphicsImplSVG(container);
    
        // MS IE natively supports VML:
        if((navigator.userAgent.toLowerCase().indexOf("msie")
                                     != -1) && !window.opera)
          return new VectorGraphicsImplVML(container);
    
        return null;
      },
    
      ...
    }

    Note 1: a Batik applet detection code is under development.

    Note 2: please take a note that the Netscape browser has neither SVG nor VML support (as of July 2007).

  • SVG startup details

    1. For SVG to be displayed properly, the following conditions must be met:

      • the host document must be a valid XML document (e.g., valid XHTML page);
      • the SVG root element must be defined under the namespace of http://www.w3.org/2000/svg.

      The _SetupRoot procedure (this name is common to all implementors) performs all the startup tasks needed for an appropriate parser to function properly. For the SVG implementor, it is:

      JavaScript
      function VectorGraphicsImplSVG()
      {
        this._svgns = "http://www.w3.org/2000/svg";
        this._curX  = 0;
        this._curY  = 0;
        this._root  = null;
        this._cntr  = container;
        this._prep  = null;
      
        return this;
      }
      
      VectorGraphicsImplSVG.prototype = {
      
        // MSIE doesn't support createElementNS and
        // [get/set]AttributeNS, so we must take care:
        //
        _createElementNS: function(ns, name)
        {
          if(document.createElementNS)
            return document.createElementNS(ns, this._prep ?
                            (this._prep + ":" + name) : name)
          else
          {
            var elem = document.createElement(this._prep ?
                                (this._prep + ":" + name) : name);
            elem.setAttribute("xmlns", ns);
      
            return elem;
          }
        },
      
        _getAttributeNS: function(element, ns, name)
        {
          return (element.getAttributeNS ?
                  element.getAttributeNS(ns, name) :
                          element.getAttribute(name));
        },
      
        _setAttributeNS: function(element, ns, name, value)
        {
          if(element.setAttributeNS)
            element.setAttributeNS(ns, name, value);
          else
            element.setAttribute(name, value);
        },
      
        // SVG canvas setup:
        _SetupRoot: function()
        {
          if(this._root) return;
      
          if(VectorGraphics.prototype._CheckASV())
          {
            var obj = document.createElement("OBJECT");
            obj.setAttribute("id", "AdobeSVG");
            obj.setAttribute("classid",
                             "clsid:78156a80-c6a1-4bbf-8e6a-3cd390eeb4e2");
      
            document.getElementsByTagName("head")[0].appendChild(obj);
      
            document.namespaces.add("svg", this._svgns);
            document.namespaces("svg").doImport("#AdobeSVG");
      
            this._prep = "svg";
          }
      
          this._root = this._createElementNS(this._svgns, "svg");
          this._cntr.appendChild(this._root);
      
          this._setAttributeNS(this._root, null, "width",  "0");
          this._setAttributeNS(this._root, null, "height", "0");
        },
      
        ...
      
      }
    2. For the SVG code to work, all XML-aware browsers must treat downloaded pages as XHTML, not HTML. The problem is that Microsoft IE doesn't have an XHTML engine; given a document with the XML prologue, it displays just like XML - as a tree of nodes. The dilemma of constructing cross-browser XHTML, however, can be solved:

      • do not use any XML/XHTML prologues/DOCTYPEs;
      • do not send any document type info to the client (but: you can use server-side browser detection to send application/xhtml+xml to Mozilla/Opera, and text/html to Microsoft IE);
      • rely on file's extension; Mozilla and Opera use the XHTML engine if the page's extension is .xhtml, MSIE uses the HTML engine every time it doesn't meet the XML prologue.

  • VML startup details

    1. For VML to show up, the following conditions must be met:

      • the host browser must be Microsoft Internet Explorer;
      • the list of document namespaces must contain a reference to urn:schemas-microsoft-com:vml;
      • the host document must contain a stylesheet linked to the VML behavior: behavior: url(#default#VML);;
      • VML root element must be defined, with coord. sizes and origins specified.

      All these tasks are performed by the following _SetupRoot procedure:

      JavaScript
      function VectorGraphicsImplVML()
      {
        this._curX = 0;
        this._curY = 0;
        this._root = null;
        this._cntr = container;
      
        return this;
      }
      
      VectorGraphicsImplVML.prototype = {
      
        // VML canvas setup:
        _SetupRoot: function()
        {
          if(this._root) return;
      
          // Add VML style definition:
          var stl = document.createStyleSheet();
          stl.addRule("v\\:*", "behavior: url(#default#VML);");
      
          // Add xml namespace definition:
          document.namespaces.add("v",
                   "urn:schemas-microsoft-com:vml");
      
          var root = document.createElement("v:group");
          root.style.width  = this._cntr.offsetWidth;
          root.style.height = this._cntr.offsetHeight;
          root.coordorigin  = "0,0";
          root.coordsize    = this._cntr.offsetWidth + "," +
                              this._cntr.offsetHeight;
      
          this._root = this._cntr.appendChild(root)
        },
      
        ...
      
      }
    2. The solution for making XHTML work everywhere is described earlier. Just don't forget about well-formness and other XHTML syntax rules.

  • Usage

    Using of the library is simple as nothing:

    JavaScript
    <script type="text/javascript" src="svg+vml.js"></script>
    <script type="text/javascript">
    
       function Draw()
       {
         var vg = new VectorGraphics(document.getElementById("container"));
    
         vg.SetStrokeColor("black");
         vg.SetStrokeWidth(1);
    
         // rectangle #1:
         vg.MoveTo(100, 100);
         vg.SetFillColor("green");
         vg.Rect(100, 100);
    
         // inner squares:
         vg.MoveTo(105, 105);
         vg.SetFillColor("#ADFF2F");
         vg.Rect(10, 10);
         vg.MoveTo(120, 105);
         vg.SetFillColor("#7CFC00");
         vg.Rect(10, 10);
         vg.MoveTo(135, 105);
         vg.SetFillColor("#32CD32");
         vg.Rect(10, 10);
         vg.MoveTo(105, 120);
         vg.SetFillColor("#3CB371");
         vg.Rect(10, 10);
         vg.MoveTo(105, 135);
         vg.SetFillColor("#2E8B57");
         vg.Rect(10, 10);
    
         // green cross:
         vg.Polygon([[195,85],[205,85],[205,95],[215,95],[215,105],[205,105],
            [205,105],[205,115],[195,115],[195,105],[185,105],
            [185,95],[195,95]]);
    
         // supporter lines:
         vg.MoveTo(100, 205);
         vg.SetStrokeColor("#708090");
         vg.LineTo(200, 205);
         vg.LineTo(320, 320);
         vg.LineTo(543, 320);
    
         vg.SetStrokeColor("black");
    
         vg.SetFont("Arial Unicode MS", 16, "blue", "normal");
    
         // text:
         vg.MoveTo(321, 315);
         vg.Text("Some Not So Long Blue Text");
    
         // rectangle #2:
         vg.MoveTo(100, 240);
         vg.SetFillColor("#FF4500");
         vg.Rect(100, 100);
    
         vg.MoveTo(105, 245);
         vg.SetFillColor("#F08080");
         vg.Rect(10, 10);
         vg.MoveTo(120, 245);
         vg.SetFillColor("#CD5C5C");
         vg.Rect(10, 10);
         vg.MoveTo(135, 245);
         vg.SetFillColor("#FF0000");
         vg.Rect(10, 10);
         vg.MoveTo(105, 260);
         vg.SetFillColor("#B22222");
         vg.Rect(10, 10);
         vg.MoveTo(105, 275);
         vg.SetFillColor("#8B0000");
         vg.Rect(10, 10);
    
         // blood cross:
         vg.Polygon([[195,225],[205,225],[205,235],
                [215,235],[215,245],[205,245],
                [205,245],[205,255],[195,255],
                [195,245],[185,245],[185,235],[195,235]]);
    
         vg.MoveTo(100, 345);
         vg.SetStrokeColor("#90A0B0");
         vg.LineTo(200, 345);
         vg.LineTo(320, 345);
         vg.LineTo(535, 345);
    
         vg.MoveTo(321, 340);
         vg.Text("Another Not So Long Blue Text");
    
         vg.SetStrokeColor("black");
    
         // rectangle #3:
         vg.MoveTo(100, 400);
         vg.SetFillColor("#FFD700");
         vg.Rect(100, 100);
    
         vg.MoveTo(105, 405);
         vg.SetFillColor("#FFFF00");
         vg.Rect(10, 10);
         vg.MoveTo(120, 405);
         vg.SetFillColor("#FF8C00");
         vg.Rect(10, 10);
         vg.MoveTo(135, 405);
         vg.SetFillColor("#B8860B");
         vg.Rect(10, 10);
         vg.MoveTo(105, 420);
         vg.SetFillColor("#FFA500");
         vg.Rect(10, 10);
         vg.MoveTo(105, 435);
         vg.SetFillColor("#D2691E");
         vg.Rect(10, 10);
    
         // yellowish cross:
         vg.Polygon([[195,385],[205,385],[205,395],
                [215,395],[215,405],[205,405],
                [205,405],[205,415],[195,415],
                [195,405],[185,405],[185,395],[195,395]]);
    
         vg.MoveTo(100, 505);
         vg.SetStrokeColor("#B0C0D0");
         vg.LineTo(200, 505);
         vg.LineTo(320, 370);
         vg.LineTo(542, 370);
    
         vg.MoveTo(321, 365);
         vg.Text("Final Not So Long Blue Text");
    
         vg.SetStrokeColor("black");
    
         // circles:
         vg.MoveTo(585, 345);
         vg.SetFillColor("#DA70D6");
         vg.Ellipse(50, 100);
         vg.SetFillColor("#BA55D3");
         vg.Ellipse(40, 80);
         vg.SetFillColor("#9932CC");
         vg.Ellipse(30, 60);
         vg.SetFillColor("#8B008B");
         vg.Ellipse(20, 40);
         vg.SetFillColor("#800080");
         vg.Ellipse(10, 20);
       }
    </script>

    Image 5

  • Extending the library

    You may wish to extend the library to support any other API. How much will you have to change to be able to draw with PGML-enabled browsers? Very little:

    • Define a new VectorGraphicsImplPGML object and its methods:
      JavaScript
      function VectorGraphicsImplPGML(container)
      {
         this._curX = 0;
         this._curY = 0;
         this._root = null;
         this._cntr = container;
      
         ...
      }
      
      VectorGraphicsImplPGML.prototype = {
      
        LineTo: function(x, y, stroke_color)
        {
          if(!this._root) this._SetupRoot();
      
          ...
        },
      
        Rect: function(width, height, fill_color)
        {
          if(!this._root) this._SetupRoot();
      
          ...
        },
      
         ...
      }
    • Add a new condition path within VectorGraphics.prototype._EstablishImplementor:
      JavaScript
      _EstablishImplementor: function(container)
      {
        // Browsers/plugins that support SVG:
        if((navigator.mimeTypes &&
           (navigator.mimeTypes["image/svg+xml"] ...)))
      
          return new VectorGraphicsImplSVG(container);
      
        // Browsers/plugins that support PGML:
        else if(navigator.mimeTypes &&
      
                   navigator.mimeTypes["image/pgml+xml"])
      
          return new VectorGraphicsImplPGML(container);
      
        else
        {
          ...
        }
      
        ...
      }

After-word

I'd like to hear from you. Feel free to e-mail me if you have any questions and/or suggestions.

Links

Recommended reading

  • "Design Patterns: Elements of Reusable Object-Oriented Software" by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides
    <nobr>Addison-Wesley, 1995. <nobr>ISBN 0-201-63361-2
  • "Professional JavaScript for Web Developers" by Nicholas C. Zakas <nobr>Wiley Publishing Inc., 2005. <nobr>ISBN 0-7645-7908-8

Web

History

  • September 20th, 2006 - first release
  • October 2nd, 2006 - article revised, code updated with Adobe SVG plug-in support
  • November 16th, 2006 - article updated
  • December 20th, 2006 - source code revised, article updated
  • July 26th, 2007 - source code revised, article updated

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)