[OpenLayers-Dev] [VML] Cloning VML nodes

RICHARD Didier didier.richard at ign.fr
Wed Aug 4 09:25:20 EDT 2010


Hi devs,

I am developing a client side print control. Basically, the idea is to
clone the map's div into a new window.

Everything works (FF, Chrome, Safari, Opera --almost there is still CSS
issues--) except for IE and for VML nodes ...

Digging the web, I found that to get access to VML nodes' attributes (all
of them not only the casual attributes), it is required to remove the node
from the document. This has been done and all attributes for all shapes
are accessed.


Here is the code of the importNode() function, I am using :


    if (!window.Node || !Node.ELEMENT_NODE) {
        Node= {
            ELEMENT_NODE: 1,
            ATTRIBUTE_NODE: 2,
            TEXT_NODE: 3,
            CDATA_SECTION_NODE: 4,
            ENTITY_REFERENCE_NODE: 5,
            ENTITY_NODE: 6,
            PROCESSING_INSTRUCTION_NODE: 7,
            COMMENT_NODE: 8,
            DOCUMENT_NODE: 9,
            DOCUMENT_TYPE_NODE: 10,
            DOCUMENT_FRAGMENT_NODE: 11,
            NOTATION_NODE: 12
        };
    };

    /**
     * APIFunction: importNode
     * Clone a node that belongs to a different document than the target
     * document. Only ELEMENT_NODE, TEXT_NODE, CDATA_SECTION_NODE and
     * COMMENT_NODE are supported.
     *
     * Parameters:
     * doc - {DOMElement} the target document.
     * externalNode - {DOMElement} the node to clone.
     * deepCopy - {Boolean} indicator of cloning inner nodes and attributes.
     *
     * Returns:
     * {DOMElement} the cloned node or null if not possible.
     */
    OpenLayers.Element.importNode= function(doc, externalNode, deepCopy) {
        if (doc.importNode) {
            // FF, Opera, Safari, Chrome :
            return doc.importNode(externalNode, deepCopy);
        }
        // IE
        var clonedNode= null, vmlNode= null, detached= false;
        // FIXME: should be a constant of OpenLayers.Renderer.VML :
        var shapes= ['shape','rect', 'oval', 'fill', 'stroke',
'imagedata', 'group','textbox'];
        switch (externalNode.nodeType) {
        case Node.ELEMENT_NODE:
            // FIXME: VML nodes are not properly copied ...
            if (deepCopy && externalNode.tagName &&
OpenLayers.Util.indexOf(shapes,externalNode.tagName)!=-1) {
                //OpenLayers.Console.log('name=['+externalNode.tagName+']
urn=['+externalNode.tagUrn+']:');
                //OpenLayers.Console.log('=====('+externalNode.attributes.length+')('+(externalNode.childNodes
|| []).length+')');
                // VML node: detach it from the source document to get
access all attributes :
                if (externalNode.__detached!==true) {//not yet detached
                    //OpenLayers.Console.log('===== detaching');
                    vmlNode=
externalNode.document.createElement('div');//dummy
node
                    vmlNode.id= 'dummy_'+externalNode.id;
                    externalNode.parentNode.insertBefore(vmlNode,externalNode);
                    externalNode.parentNode.removeChild(externalNode);
                    externalNode.__detached= true;
                }
                detached= true;
                //OpenLayers.Console.log('=====('+externalNode.attributes.length+')('+(externalNode.childNodes
|| []).length+')');
            }

            clonedNode= doc.createElement(externalNode.nodeName);
            /* does the node have any attributes to add? */
            if (externalNode.attributes &&
externalNode.attributes.length>0) {
                for (var i= 0, il= externalNode.attributes.length; i<il;
i++) {
                    var att= externalNode.attributes[i];
                    if (detached===true && att.nodeName==='__detached') {
continue; }
                    clonedNode.setAttribute(att.nodeName,
externalNode.getAttribute(att.nodeName));
                }
            }
            /* are we going after children too, and does the node have
any? */
            if (deepCopy && externalNode.childNodes &&
externalNode.childNodes.length>0) {
                for (var i= 0, il= externalNode.childNodes.length; i<il;
i++) {
                    var node= externalNode.childNodes[i];
                    if (detached===true) {
                        node.__detached= true;
                    }
                    clonedNode.appendChild(OpenLayers.Element.importNode(doc,
node, deepCopy));
                    if (detached===true) {
                        node.removeAttribute('__detached');
                    }
                }
            }

            if (vmlNode) {
                // insert node back ...
                externalNode.removeAttribute('__detached');
                vmlNode.parentNode.insertBefore(externalNode,vmlNode);
                vmlNode.parentNode.removeChild(vmlNode);
                vmlNode= null;
            }
            break;
        case Node.TEXT_NODE:
        case Node.CDATA_SECTION_NODE:
            clonedNode= doc.createTextNode(externalNode.nodeValue);
            break;
        case Node.COMMENT_NODE:
            clonedNode= doc.createCommentNode(externalNode.nodeValue);
            break;
        default:
            //OpenLayers.Console.log('unsupported
'+externalNode.nodeType+'=['+externalNode.innerHTML+']');
            break;
        }
        return clonedNode;
    };


On the popup window side, I set the document.namespaces as
OpenLayers.Renderer.VML does it. Here is the control (under construction)
that does the job :


/*
 * Copyright (c) 2008-2010 Institut Geographique National France, released
under the
 * BSD license.
 */
/*
 * @requires OpenLayers/Control.js
 */
/**
 * Class: OpenLayers.Control.PrintMap
 * Implements a button control for printing the current map.
 *
 * Inherits from:
 *  - <OpenLayers.Control>
 */
OpenLayers.Control.PrintMap= OpenLayers.Class(OpenLayers.Control, {

    /**
     * Property: type
     * {String} The type of <OpenLayers.Control> -- When added to a
     *     <Control.Panel>, 'type' is used by the panel to determine how to
     *     handle our events.
     */
    type: OpenLayers.Control.TYPE_BUTTON,

    /**
     * APIProperty: title
     * {String} Print page's title (i18n used).
     *      Defaults to *'olControlPrintMap.title'*
     */
    title: null,

    /**
     * APIProperty: popupSettings
     * {String} Attributes of the popup window that will contain the
printable
     * page.
     *      Defaults to
*'toolbar=no,location=no,directories=no,menubar=no,scrollbars=no'*
     */
    popupSettings:
"toolbar=no,location=no,directories=no,menubar=yes,scrollbars=no",

    /**
     * APIProperty: cntrlsVisibility
     * {Object} List of control's class name to hide during the printing
     * preparation.
     *      Each object holds :
     *      * visible : current status of the control to hide;
     *      * toggleFunc : function to call for hidding the control. This
     *      function is assumed to have one boolean parameter;
     *      * scope : context for call 'toggleFunc', if none the map is
     *      used.
     */
    cntrlsVisibility: null,

    /**
     * APIProperty: onLoad
     * {String} Javascript code to invoke when loading the popup window.
     *      Defaults to *"self.print();self.close();"*
     */
    onLoad: "self.print();self.close();",

    /**
     * Constructor: OpenLayers.Control.PrintMap
     * Build a simple print preview button.
     *
     * Parameters:
     * options - {Object} any options usefull for control.
     */
    initialize: function(options) {
        OpenLayers.Control.prototype.initialize.apply(this, arguments);
        if (!this.title) {
            this.title= this.displayClass+'.title';
        }
    },

    /**
     * APIMethod: trigger
     * Do the print by openning a popup that contains the map's div
content to
     * be printed.
     *      Can be overwritten to modify the printing output.
     */
    trigger: function() {
        // open a new window by copying the inner content of the map's div
...
        // add attributions/originators for each layer on the window.
        if (!this.map) { return; }
        // hide controls :
        for (var cn in this.cntrlsVisibility) {
            var cntrl= this.map.getControlsByClass(cn)[0];
            if (cntrl && cntrl.div.style.display!='none') {
                this.cntrlsVisibility[cn].visible= true;
                this.cntrlsVisibility[cn].toggleFunc.apply(this.cntrlsVisibility[cn].scope
|| this.map,[false])
            }
        }
        var T= this.getPageContent();
        var printableDocument= null;
        var settings= this.popupSettings;
        if (!settings.match(/width/i)) {
            settings+=",width="+(16+this.map.div.clientWidth);
        }
        if (!settings.match(/height/i)) {
            settings+=",height="+(16+this.map.div.clientHeight);
        }
        printableDocument= window.open((T.isUrl? T.html:""),"",settings);
        if (printableDocument) {
            if (!T.isUrl) {
                printableDocument.document.open();
                printableDocument.document.write(T.html);
                if (!!document.namespaces) {
                    for (var i= 0, l= document.namespaces.length; i<l; i++) {
                        var ns= document.namespaces.item(i);
                        printableDocument.document.namespaces.add(ns.name,
ns.urn);
                    }
                }
                // we return the imported clone of the map's div to
preserve rendered SVG/VML/...
                var mapDiv=
OpenLayers.Element.importNode(printableDocument.document,this.map.div,true);
                if (mapDiv) {
                    printableDocument.document.getElementById('container_'+this.id).appendChild(mapDiv);
                }
            }
            printableDocument.document.close();
        }
        // show controls :
        for (var cn in this.cntrlsVisibility) {
            if (this.cntrlsVisibility[cn].visible===true) {
                this.cntrlsVisibility[cn].visible= false;
                this.cntrlsVisibility[cn].toggleFunc.apply(this.cntrlsVisibility[cn].scope
|| this.map,[true])
            }
        }
    },

    /**
     * Method: getStyles
     * Retrieve CSS rules governing this map.
     *
     * Returns:
     * {String} the CSS links et styles found in the current map.
     */
    getStyles: function() {
        var csses= '';
        for (var i= 0, li= document.styleSheets.length; i<li; i++) {
            var css= document.styleSheets.item(i);
            var ownerNode= css.owningElement || css.ownerNode;
            if (css.href) { //LINK
                csses+=
'<link '+
  'rel="stylesheet" '+
  'type="text/css" '+
  (ownerNode.id? 'id="'+ownerNode.id+'" ':'')+
  'href="'+css.href+'"'+
'/>\n';
            } else {        //STYLE
                csses+=
'<style type="text/css"'+(ownerNode.id? ' id="'+ownerNode.id+'"':'')+'>\n'+
'<!--\n';
                var rules= css.rules || css.cssRules;
                for (var j= 0, lj= rules.length; j<lj; j++) {
                    var rule= rules.item(j);
                    csses+= rule.selectorText+'{'+rule.style.cssText+'}\n';
                }
                csses+=
'  -->\n'+
'</style>\n';
            }
        }
        return csses;
    },

    /**
     * APIMethod: getPageContent
     * Build page content or URL to print.
     *
     * Returns:
     * {Object} with an indicator of page content or URL ('isUrl' field),
     * the HTML code or URL for the page to print ('html' field) and the
base URL
     * of the popup window ('base' field).
     */
    getPageContent: function() {
        var base= document.location.pathname.split('?')[0];
        var parts= base.split('/');
        base= parts.pop();
        base= parts.join('/');
        var baseUrl=
            document.location.protocol+'//'+
            document.location.hostname+
            (document.location.port? ':'+document.location.port : '')+
            base+'/';
        var page=
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n'+
'<html xmlns="http://www.w3.org/1999/xhtml">\n'+
  '<head>\n'+
    '<title>'+OpenLayers.i18n(this.title)+'</title>\n'+
    '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>\n'+
    '<base url="'+baseUrl+'"/>\n'+
    this.getStyles()+
    '<style type="text/css">\n'+
    '<!--\n'+
      '@media print {\n'+
        'body{display:block!important;}\n'+
      '}\n'+
    '  -->\n'+
    '</style>\n'+
  '</head>\n'+
  '<body onload="'+this.onLoad+'">\n'+
    '<center>\n'+
      '<div id="container_'+this.id+'"></div>\n'+
    '</center>\n'+
  '</body>\n'+
'</html>\n';
        return {'isUrl':false, 'html':page, 'base':baseUrl};
    },

    /**
     * Constant: CLASS_NAME
     * {String} *"OpenLayers.Control.PrintMap"*
     */
    CLASS_NAME: "OpenLayers.Control.PrintMap"
});


Tests (based on panel.html and kml-layer.html) :

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>OpenLayers: Control Panel</title>
    <link rel="stylesheet" href="../theme/default/style.css"
type="text/css" />
    <link rel="stylesheet" href="style.css" type="text/css" />
    <style type="text/css">
        .olControlPanel div {
          display:block;
          width:  24px;
          height: 24px;
          margin: 5px;
          background-color:red;
        }

        .olControlPanel .olControlMouseDefaultsItemActive {
          background-color: blue;
          background-image: url("../theme/default/img/pan_on.png");
        }
        .olControlPanel .olControlMouseDefaultsItemInactive {
          background-color: orange;
          background-image: url("../theme/default/img/pan_off.png");
        }
        .olControlPanel .olControlZoomBoxItemInactive {
          width:  22px;
          height: 22px;
          background-color: orange;
          background-image: url("../img/drag-rectangle-off.png");
        }
        .olControlPanel .olControlZoomBoxItemActive {
          width:  22px;
          height: 22px;
          background-color: blue;
          background-image: url("../img/drag-rectangle-on.png");
        }
        .olControlPanel .olControlZoomToMaxExtentItemInactive {
          width:  18px;
          height: 18px;
          background-image: url("../img/zoom-world-mini.png");
        }
        .olControlPanel .olControlPrintMapItemActive,
        .olControlPanel .olControlPrintMapItemInactive {
          width: 24px;
          height: 22px;
          background-image: url("../theme/default/img/draw_point_on.png");
        }

    </style>
    <script src="../lib/Firebug/firebug.js"></script>
    <script src="../lib/OpenLayers.js"></script>

    <script type="text/javascript">
        var lon = 5;
        var lat = 40;
        var zoom = 5;
        var map, layer;

        function init(){
            map = new OpenLayers.Map( 'map', { controls: [] } );
            layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
                    "http://labs.metacarta.com/wms/vmap0", {layers:
'basic'} );
            map.addLayer(layer);

            map.addLayer(new OpenLayers.Layer.GML("KML", "kml/lines.kml",
               {
                format: OpenLayers.Format.KML,
                formatOptions: {
                  extractStyles: true,
                  extractAttributes: true,
                  maxDepth: 2
                }
               }));

            zb = new OpenLayers.Control.ZoomBox(
                {title:"Zoom box: Selecting it you can zoom on an area by
clicking and dragging."});
            var panel = new OpenLayers.Control.Panel({defaultControl: zb});
            panel.addControls([
                new OpenLayers.Control.MouseDefaults(
                    {title:'You can use the default mouse configuration'}),
                zb,
                new OpenLayers.Control.PrintMap(
                    {title:"preview",onLoad:'javascript:void(0);'})
            ]);
            map.addControl(panel);

            map.zoomToExtent(new
OpenLayers.Bounds(-112.306698,36.017792,-112.03204,36.18087));
        }
    </script>
  </head>
  <body onload="init()">
    <h1 id="title">Custom Control.Panel</h1>
    <p id="shortdesc">
      Create a custom control.panel, styled entirely with
      CSS, and add your own controls to it.
    </p>
    <div id="panel"></div>
    <div id="map" class="smallmap"></div>

  </body>
</html>



In the end, all VML nodes are copied (with all of their attributes), but
they never show in the popup window !(

Any advice/hint ?
Did I miss something in the process ?

Regards,

didier
-- 
RICHARD Didier - Chef du pôle technique du Géoportail
2/4, avenue Pasteur - 94165 Saint Mandé Cedex
Tél : +33 (0) 1 43 98 83 23



More information about the Dev mailing list