Sabtu, 23 Agustus 2025

HTML - XML - XLS - Popup

 

Out of Cache but Still Stylin'

June 4, 2001    To be archived July 2, 2001

It's June already, the month of the longest day of the year. Heidi has been waxing lyrical about just how long that is in her new homeland of Sweden. All that sunshine is dangerous; it might make us want to whip out our water balloons and forget our readers. Fear not, we have our blinds drawn and our noses to our screens digging up answers for our faithful readers. We have a new twist on the ever popular question about how to avoid caching and an answer on using XSL to display XML data with different fields every time.

We see more and more questions regarding XML and it won't be long before we start getting questions on ASP.Net and Web Services! We've come a long way from our early columns. Keep challenging us with your questions, but remember, we don't check the comments section of the column when we are looking for material for our next exciting episode.

Don't tell Heidi, but if you happen to have a formula we can use to figure out the trajectory for a red water balloon from here to Sweden, send it to us! Happy Midsummer to all our readers!

Contents

XML in a Squeeze—don't cache and apply a cold compress

Show Us What You've Got—displaying unknown XML data in a table

Hide and Seek—pop-up window notifications

Web Team in Short

XML in a Squeeze

Dear Web Team:

Is there a way in which to prevent IE [Internet Explorer] to put a dynamically changed XML document in the cache, like one can do for the HTML docs with the expires trick? Now I keep getting the older versions of the XML-doc.

Egbert Bleyenburg, the Netherlands

The Web Team replies:

We're going to guess you are currently using the <Meta> tag header to set your expiration value. The preferred way is to set the HTTP headers; for further information, please see the Knowledge Base article Q234067 - HOWTO: Prevent Caching in Internet Explorer. Using HTTP headers allows you to set the expiration values for all sorts of files. Try out the following code:

<%@ Language="VBScript" %>
<%
Response.CacheControl = "no-cache"
Response.AddHeader "Pragma", "no-cache"
Response.Expires = -1
Response.ContentType= "text/xml"
%>

<time><%response.write now()%></time> 

If you refresh your browser a few times, you will see that the file has not been cached.

While we are on the subject of HTTP headers, some time ago we noticed with interest that there is a Content-Encoding header that supports a value of 'gzip' or 'deflate' (gzip is a publicly available file compression algorithm). Not having any particular need at that point, we just filed it away as an interesting tidbit.

XML is a pretty wordy syntax and large files are just asking to be compressed to save on network bandwidth. For small, dynamic, unique queries, the server processing to compress the data would probably not be worth the saved bandwidth, but if you have data that doesn't change very often, it could make sense to store the compressed XML. If you stored gzipped data in your database, you could just add the following line to your ASP file to indicate to the browser that the coming information is compressed:

Response.AddHeader "Content-Encoding", "gzip"

With that header set, stream your data to the response buffer. When testing the compression we used an ASP file include statement to send the gzipped data to the browser.

If you would prefer to keep your compressed XML on the server hard disk, you can also configure IIS to add HTTP headers to any specific file, or files from a specific directory. We had fun experimenting with this and found that we could compress the XML file and the XSL file that the compressed XML file pointed to, and Internet Explorer happily decompressed and used them both. Use the following steps to configure your IIS to add the content encoding headers:

  1. Open Internet Services Manager.

  2. Navigate to the file or folder to which you wish to add the headers.

  3. Right Click on the file and select Properties from the menu.

  4. Select the HTTP Headers tab.

  5. Click on the Add button.

  6. Enter Content-Encoding in the Custom Header Name field.

  7. Enter gzip or deflate into the Custom Header Value field.

  8. Click on the OK button to close the Add dialog.

  9. Click on the OK button to close the Properties dialog.

A little note: we found it worked best if we removed the .gz file extension and left the files with their original names. If you want a reminder that they are compressed files, try naming your files something like this: myfile.gz.xml.

Show Us What You've Got

Dear Web Team:

I am returning a record set as XML to the HTML page. Now on the HTML page I want to display the XML in a table. Since I don't known how many columns and column names are returned from the ASP page, I am not able to write a static XSL. Is there any XSL that can read the dynamically all the attributes of the element in schema and display data with column names?

Thank you
Ravi

Web Team Answers

That is an interesting question. In ASP.Net it would be a piece of cake. You can easily put XML into an ADO.Net object and then bind it to an ASP.Net data grid on the server and presto, it's all laid out for you. If you want to know more, have a look at the ASP.Net docs online. It is a little more challenging using XSL and XML.

The example below uses the syntax from the MSXML 2.5 object since that is what is installed with Internet Explorer 5.x. MSXML 3.0 contains some great improvements like parameters, conformance to the XSLT specification, and better performance on the server. XML 3.0 is available for download now. For more information see the XML Developer Center.

If you can use a more freeform presentation style, it is not too difficult to display unknown data recursively and expose all the data in it. To solve this problem using a table, it helps to have some idea of the structure of the data to avoid having tons of nested tables which are usually hard to read and waste screen real estate. We made a sample assuming a data structure like the following XML.

Catalog.xml

<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="table.xsl"?>

<inventory store="online">
Catalog information text
   <record color="red">Shirts
      <count size="M">5</count>
      <cost currency="$US">11.99</cost>
      <logo type="Embroidered">Office XP</logo>
   </record>
   <record color="blue">Shorts
      <count size="L">9</count>
      <cost currency="$US">15.99</cost>
      <logo type="Silk Screen">Windows XP</logo>
   </record>
</inventory>

The following XSL will display any data that follows something like this structure and it shouldn't take much to tailor it to other types of structures. One assumption is that the XML is consistent, so the column names are extracted from the child nodes of the first record. Handling completely freeform data would be challenging in a named column table.

Table.xsl

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">

<xsl:template match="/">
   <xsl:apply-templates />
</xsl:template>

<xsl:template match="/*">
   <html>
   <style>
   body,table {font-family:verdana;font-size:8pt}
   .attribute {color:darkgray}
   .headerRow { background: lightblue}
   </style>
   <body>
   <h4><xsl:node-name /></h4>
   <b><xsl:value-of select="text()" /></b>
   <xsl:apply-templates select="@*" />   
   <table cellpadding="3">
      <tr class="headerRow">
         <td>Contents</td>
         <xsl:for-each select="/*/*[index()=0]/*">
            <td><xsl:node-name /></td>
         </xsl:for-each>
      </tr>
      <xsl:apply-templates select="node()"/>

   </table>
   </body>
   </html>
</xsl:template>

<xsl:template match="/*/*">
   <tr>
   <td>
      <xsl:value-of select="./text()" />
   </td>
      <xsl:apply-templates select="node()"/>
   </tr>
</xsl:template>

<xsl:template match="/*/*/*">
   <td>
      <xsl:value-of select="." /><br />
      <xsl:apply-templates select="@*" />
   </td>
</xsl:template>

<xsl:template match="@*">
   <span class="attribute"><xsl:node-name />=<xsl:value-of select="." /></span>
</xsl:template>
</xsl:stylesheet>

The snippet below is the one that generates the row with the column names.

      <tr class="headerRow">
         <td>Contents</td>
         <xsl:for-each select="/*/*[index()=0]/*">
            <td><xsl:node-name /></td>
         </xsl:for-each>
      </tr>

The select in the for each statement selects all the child elements of the first child node off of the root. For each child of the first node, a table cell is generated containing the node name.

The easiest way to understand this code is to save the XSL and XML files and look at the results when you navigate to the XML with your browser.

For comparison, the XSL below would display all the information in any XML file vertically using unordered list items (<ul>) to indent each tier. Note how much simpler this XSL is. Using templates in this way allows indefinite recursion of data.

Vertical.xsl

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<xsl:template match="/">
   <html>
   <style>
   body {font-family:verdana;font-size:8pt}

   .attribute {color:darkgray}
   </style>
   <body>
      <xsl:apply-templates select="/*" />   
   </body>
   </html>
</xsl:template>

<xsl:template match="*[node()]">
   <ul>
      <b><xsl:node-name /></b>
      <xsl:apply-templates select="@*" />   
      <xsl:apply-templates select="node()"/>
   </ul>
</xsl:template>

<xsl:template match="@*">
   <span class="attribute"><xsl:node-name />=<xsl:value-of select="." /></span>
</xsl:template>

<xsl:template match="text()">
   <xsl:value-of select="." />
</xsl:template>
</xsl:stylesheet>

To see how this XSL sheet works on our data, don't forget to change the name of the XSL sheet specified in the XML file.

Hide and Seek

Dear Web Team:

I have found much use for the new pop-up object in IE 5.5, especially for developing various popup controls in combination with the new behaviors feature.

However, I've been looking for a way to get noticed when the pop-up is hidden, like an "onhide" event or such, but have yet to find a way. Is there any way to achieve this without capturing all parent window events that could possibly make the popup hide?

Thanks in advance!
Noak Eldh

The Web Team replies:

The pop-up window can be hidden either programmatically by explicitly calling its hide method or by other user actions like clicking elsewhere on the document, switching to a different application, and so on. In the cases where the hide method is called programmatically by either the parent or the pop-up, this can be channeled through a function on the parent which will call hide(), so you are always aware that the pop-up will be getting hidden whether you call the hide method from the parent or the pop-up.

The problem arises when user actions mentioned above causes the pop-up to be hidden. The generic onhide type of event that you are looking for is the onunload event for the pop-up's document. We hear some grunts about been there, done that, doesn't work, from those who have tried just about every possible way to get this working. This does work and we have a sample to demonstrate how this can be done, but as always, there's a catch! The onunload event handler must be set before the pop-up is displayed, and also the pop-up's document must be populated using either the innerHTML or the insertAdjacentHTML methods. Using document.write will prevent the unload event handler from working correctly.

We can already see the "how do I differentiate this event being called when the pop-up is hidden programmatically versus due to losing focus" question coming! You can set a flag in your central function that calls hide() on the pop-up window, and check this flag in your onunload handler to differentiate whether the pop-up is being hidden programmatically or otherwise. Remember to reset that flag at the end of your onunload event handler though.

Here is a sample file to demonstrate how to set the onunload handler.

PopupDemo.htm

<HTML>
<HEAD>
<TITLE>Popup notifications demo</TITLE>
<SCRIPT LANGUAGE="JSCript">

var hideCalled = false;
var number = 0;
var oPopup = window.createPopup();

function popupUnload()
{
   var output = "Popup is hidden ";
   number++;
   if (hideCalled == true)
      output += "programmatically ";
   output += (": " + number + "<BR>");
   div1.insertAdjacentHTML("beforeEnd", output);
   hideCalled = false;
}

function showPopup(x, y, width, height, oElt)
{
   oPopup.show(x, y, width, height, oElt);
}

function hidePopup()
{
   hideCalled = true;
   oPopup.hide();
}

function initPopup()
{
   var oPopupBody = oPopup.document.body;
   with (oPopupBody.style)
   {
      backgroundColor = "lightyellow";
      fontSize = "12px";
      padding = "2px";      
   }   
   oPopupBody.innerHTML =  "<DIV onclick='parent.hidePopup()'><UL><LI>Clicking here will cause the hide method to be called.</LI><LI>Or click 
outside this popup or switch to a different application to hide this popup automatically</LI></UL></DIV>";
   oPopupBody.onunload = popupUnload;   
}
window.onload = initPopup;

</SCRIPT>
</HEAD>
<BODY>
This page demonstrates how to hook the unload event of the popup so that the parent is notified whenever the popup window is hidden either by 
programmatically calling the <B>hide</B> method or by clicking elsewhere in the document, switching to a different application etc.
<P></P>
<INPUT TYPE=button ID=iButton VALUE="Show Popup" ONCLICK="showPopup(0,0,300,60,this)">
<P>&nbsp;</P>
<P>
Whenever the popup is unloaded, the event handler in the parent writes the unload event information here indicating whether the popup was hidden 
programmatically by setting a flag in the central method that calls the hide method and checking for this flag in the onunload handler. We are 
using a number that is incremented everytime the popup is hidden to show that it is being called in all scenarios.
</P>
<DIV ID=div1></DIV>
</BODY>
</HTML>

Tidak ada komentar: