CartoWeb 3.5.0
Download Now!


CartoWeb
Advanced Geographical Information System for the Web

How to run Cartoweb in an <IFRAME> that dynamically resizes as the Cartoweb contents change

Disclaimer: I am relatively new to Cartoweb and have not used all of its features. The solution I present here worked for my application, but may not be suitable for yours. I wanted to contribute in case it's useful to someone else, but I make no promises that this is the best way, or even a correct way to do it.

Related: HowToCorrectlyOverrideJavascript

Background and Purpose

I was asked to upgrade a Mapserver page to a Cartoweb-based application for a website. At the same time, the website was updated to a Joomla! framework, and the new Cartoweb application was supposed to run within an <IFRAME> in the content area of one of the web pages so that it was seamlessly integrated into the rest of the website.

It's simple enough to put the Cartoweb page into the <IFRAME>, but you are almost certain to wind up with scroll bars next to the <IFRAME> in addition to the scroll bars on the entire web page. This presents the user with a cluttered and confusing interface, so I needed a way to have the parent's <IFRAME> dynamically resize to fit the Cartoweb content as it grows and shrinks (for example, when the layer tree is expanded or collapsed, when query results are displayed or cleared, or when the map size is changed). By resizing the <IFRAME> to fit its contents, no extra scroll bars will appear, leaving the user with a single set of scroll bars to manage the whole browser window.

In my case, the main aim was to eliminate the vertical scroll bar on the <IFRAME>. Most of the size changes in our Cartoweb application were in the vertical direction, and we sized the layout to fit within a static width in the parent framework, so the solution below does not deal with horizontal resizing. However, it should be relatively straightforward to extend and modify the solution below to do horizontal resizing as well.

A final consideration was that we wanted a solution that would not break the Cartoweb application if it were run in a stand-alone window rather than inside the <IFRAME>. In fact, we added a link to the Cartoweb application to launch it in a separate window for users who want to run it with larger map sizes. This constraint meant that our changes needed to "keep out of the way" if the Cartoweb application was not running in an <IFRAME>.

Overview of Modifications

To create the dynamic <IFRAME>, you need some Javascript to run in the parent page (the page in the website that contains the <IFRAME>). This code will perform the actual resize to fit whatever the current page contents happen to be. The code needs to run when the page is first loaded or when it is reloaded.

The same code also needs to be run, however, in response to events within Cartoweb that change the size of the Cartoweb contents. For my relatively simple application, based on the sampleProject running with AJAX on, I only needed to handle 3 types of events: layer tree changes, query result changes, and mapsize changes.

Modifications to the Parent

The main modification to the parent is the addition of a <script> tag to reference some Javascript to do the resizing. I found some suitable code that handled the general case of <IFRAME> resizing and modified it to suit my needs. Here is the code, which I placed in a file called dynamiciframe.js:

// heavily modified by GISmatters from script found at: 
//   http://www.dynamicdrive.com/dynamicindex17/iframessi2.htm

// set this to match the id of the <IFRAME> in the parent page
var iframeid = 'mapiframe';

// set these to match the ids of the Cartoweb divs that change size
var contentid = 'content';
var leftbarid = 'leftbar';

// set these to tweak the fit of the <IFRAME> within the parent page
var IEextraHeight = 20;
var FFextraHeight = 50;

function resizeIframe() {
  // try to find the <IFRAME>
  var currentfr = document.getElementById(iframeid)
  if (currentfr) {
    // handle different browsers to get the Cartoweb page and set the height tweak
    if (typeof(currentfr.contentDocument) == 'object') {
      var iframedoc = currentfr.contentDocument;
      var extraHeight = FFextraHeight;
    } else if (typeof(currentfr.Document) == 'object') {
      var iframedoc = currentfr.Document;
      var extraHeight = IEextraHeight;
    }
    // set the <IFRAME> height to the taller of the Cartoweb divs, plus the tweak value
    var leftbardivheight = iframedoc.getElementById(leftbarid).offsetHeight;
    var contentdivheight = iframedoc.getElementById(contentid).offsetHeight;
    currentfr.height = ( leftbardivheight > contentdivheight ? leftbardivheight : contentdivheight ) + extraHeight;
  }
  // reset the event listener
  if (currentfr.addEventListener)
    currentfr.addEventListener("load", resizeIframe, false);
  else if (currentfr.attachEvent) {
    currentfr.detachEvent("onload", resizeIframe); // Bug fix line
    currentfr.attachEvent("onload", resizeIframe);
  }
}

// add an event to fire the resize script when the parent page first loads
if (window.addEventListener)
  window.addEventListener("load", resizeIframe, false);
else if (window.attachEvent)
  window.attachEvent("onload", resizeIframe);
else
  window.onload=resizeIframe;

Now that we have a script to do the resizing, we need to add the script to the parent page in the <head> section, like this (assuming the script is living in /js/dynamiciframe.js):

  <head>
    ...
    <script type="text/javascript" src="/js/dynamiciframe.js"></script>
    ...
  </head>

The <script> needs an <IFRAME> to do its thing; how you add the <IFRAME> will depend on how you are creating your web pages. If you have complete control over the HTML, then you can just add the <IFRAME> like this (assuming your Cartoweb application is found at "/map/index.php"):

  <body>
    ...
    <iframe id="mapiframe" src="/map/index.php" scrolling="no" frameborder="0" style="overflow:visible; width:100%; display:block;"></iframe>
    ...
  </body>

Of course, you could put the CSS style elements in a separate CSS stylesheet; I enter them here just to keep all of the information in one place.

Note that the id of the <IFRAME> must match the string assigned to the iframeid variable in the Javascript above.

If you do not have complete control over the HTML (for example in our case, where we were using Joomla!), you will have to figure out how to create an <IFRAME> and configure it to match the above settings as closely as possible. In our case, using Joomla!, for example, there is a mechanism for creating an <IFRAME> where you can specify the URL (src) and a few other options, but some parameters were not configurable and others were forced on us. For example, the <IFRAME> id was automatically set to "blockrandom", which meant we had to change the iframeid variable in our dynamiciframe.js file to that value. Joomla! also insisted on a value for the <IFRAME> height and width, but fortunately allowed a value of "auto" for them (setting an explicit size in pixels prevents the resize code from working). It was helpful to set things up in Joomla! and then look at the page's HTML source to see what <IFRAME> settings were being used; then we could make some additional changes and see their effect in the HTML source again, etc. We also used a stylesheet to set the values that Joomla! did not provide for us.

You now have a parent web page that includes your Cartoweb page in an <IFRAME>. With the Javascript included, the <IFRAME> will be resized to fit the current size of the Cartoweb contents when the page loads. Now we need to make sure it will also be resized when the contents change their size.

Modifications to your Cartoweb Project

I found that, for my relatively simple Cartoweb application, based on the sampleProject, I only needed to modify 2 Javascript files by making them call the parent page's resize routine when the Cartoweb contents changed.

htdocs/js/AjaxPlugins.js

In the function onAfterAjaxCall, add this pair of lines at the very end of the function:

        if (parent.resizeIframe)
          parent.resizeIframe();

This says "if this document's parent has a function called resizeIframe, call it". The "if" clause prevents us from trying to call the parent's routine if it doesn't exist, which takes care of our requirement that the Cartoweb application should still run as a stand-alone application. When it is running stand-alone (as opposed to running in an <IFRAME>), there is no parent Javascript function by that name, so nothing happens. When it is running inside an <IFRAME> configured as described above, there is such a function, so it gets called.

This modification to the "after AJAX call" code ensures that AJAX events that might change the size of the Cartoweb contents will cause the <IFRAME> to be resized.

coreplugins/layers/htdocs/js/layers.js

Add the exact same pair of lines as above to the very end of the following three functions: shift, expandAll, and closeAll. These ensure that the <IFRAME> (if any) will be resized when the entire layer tree is expanded or collapsed (expand/closeAll functions), or any individual layer is expanded or collapsed (the shift function).

Cross-domain Issues

In our case, it turned out that the main website was running from one domain (http://massacorn.net/) while the Cartoweb application was running from a sub-domain (http://makeamap.massacorn.net/). This arrangement ran afoul of the Same Origin Policy security restrictions. Briefly, to ensure security of applications and privacy, browsers prohibit access by Javascript programs to content in one browser window or frame from another if the windows or frames originate from different domains. By default, browsers interpret domains very strictly: if they aren't identical, they are considered different, even if they share some top-level domain (as they do in our case: both pages share the massacorn.net portion of the domain name).

In a case such as ours, where one page is actually on a sub-domain of another page's content, it is possible to tell the browser to treat the domains as the same. This is done with a bit of Javascript in each of the files that you want to inter-operate -- in our case, the parent page and the <IFRAME> page.

To overcome the same-origin policy restriction when you have related domains (i.e., a domain and a related sub-domain, or two related sub-domains), add a line of Javascript to each page's <head> section to identify the shared domain name:

  document.domain = 'yourdomain.com';

For example, in our Joomla! template for the parent pages, we added this <script> to the <head> section:

<script type="text/javascript">
<!--
  document.domain = 'massacorn.net';
//-->
</script>

and in our cartoclient.tpl template we added this to the existing <script> in the <head> section:

    {literal}
    try {
        var pd=parent.document.domain;
    }
    catch (e) {
        document.domain='massacorn.net';
    }
    {/literal}

This code says to try and get the parent's domain; if that attempt fails, set the domain to 'massacorn.net'. This lets our Cartoweb application run standalone or embedded in an <IFRAME>. If running in an <IFRAME> (and assuming a different domain or sub-domain), the attempt to get the parent's domain will fail because the two pages are from different origins; at that point, the error handler will execute the catch statement, setting this page's domain to match the parents. If running standalone, the "parent" domain is just the standalone domain, so there is no error and the domain is not altered.

The {literal} directives tell Smarty to avoid messing with anything inside (otherwise, Smarty tries to interpret the try and catch clauses as Smarty statements, and in this case Javascript requires the curly braces even for a single-line statement).

CartoWeb Wiki: HowToPutCartoWebInDynamicIframe (last edited 2020-07-09 03:46:53 by GISmatters)

© 2002-2007 CartoWeb by Camptocamp SA - Wiki powered by MoinMoin