Adding javascript to a XML file transformed with XSLT using a Greasemonkey script

greasemonkey, javascript, xml, xslt

Solution

Don't use `onclick`. This goes triple for userscripts, as there are additional scope and/or sandbox conflicts.

Also, it's a poor idea to try and add JS into the XSLT file/text, and there is no need for script injection in this case either.

Use the script to do whatever JS manipulation you have in mind. For example:

// ==UserScript==
// @name        _XML Renderer with javascript functionality
// @description Stylesheet and javascript for xml results
// @include     http://YOUR_SERVER.COM/YOUR_PATH/*.xml
// @resource    xslFile  Q_17998446_transform.xsl
// @grant       GM_getResourceText
// ==/UserScript==

var xsl_str     = GM_getResourceText ("xslFile");
var processor   = new XSLTProcessor ();
var dataXSL     = new DOMParser ().parseFromString (xsl_str, "text/xml");

processor.importStylesheet (dataXSL);

var newDoc      = processor.transformToDocument (document);

//-- These next lines swap the new, processed doc in for the old one...
window.content  = newDoc;

document.replaceChild (
    document.importNode (newDoc.documentElement, true),
    document.documentElement
);

//-- Use JS to smarten-up the new document.
var firstCols   = document.querySelectorAll ("#results td:first-child");

for (var J = firstCols.length - 1;  J >= 0;  --J) {
    var tdNode = firstCols[J];
    tdNode.style.cursor = "pointer";
    tdNode.addEventListener ("click", clickCellHandler, false);
}

function clickCellHandler (zEvent) {
    var cellContents = zEvent.target.textContent.trim ();

    alert ('The clicked cell contains "' + cellContents + '".');
}

where `Q_17998446_transform.xsl` is a file saved in the same folder from where you install your script (you may need to uninstall, and reinstall, the script).

`Q_17998446_transform.xsl` contains this, exactly:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="html"/>
    <xsl:template match="/">
        <html>
            <head></head>
            <body>
                <table id="results" border="1" cellspacing="0" cellpadding="0">
                    <thead>
                        <tr>
                            <th class="name">id</th>
                            <th class="name">category ID</th>
                            <th class="name">name</th>
                            <th class="name">phone</th>
                        </tr>
                    </thead>
                    <tbody>
                        <xsl:for-each select="Results/Result/Listings/Res">
                            <tr>
                                <td class="small" width="120">
                                    <xsl:value-of select="Result/id"/>
                                </td>
                                <td class="small" width="120">
                                    <xsl:value-of select="Result/category"/>
                                </td>
                                <td class="small" width="120">
                                    <xsl:value-of select="Result/name"/>
                                </td>
                                <td class="small" width="120">
                                    <xsl:value-of select="Result/phone"/>
                                </td>
                            </tr>
                        </xsl:for-each>
                    </tbody>
                </table>
            </body>
        </html>
    </xsl:template>
</xsl:stylesheet>

When you run that script on the appropriate XML file, it adds a click-handler to the first table column (sans header) -- the "Web 2.0" way.

When one of the first-column cells is clicked, it alerts, for example:

The clicked cell contains "876553".

Problem

As follow up to my question here - How to transform an XML file with XSLT, using a Greasemonkey script? - I'm facing another problem: I want to use some basic javascript functions in my XSL template in order to control the display of some divs. However, no matter how I include these javascript functions, they don't seem to be recognized. I already investigated a lot but I can't seem to get around it. I tried 2 things: - Adding the javascript in the XSL template in a `<script>` tag - Appending a new `<script>` tag in the Greasemonkey script itself I would prefer not to use jQuery or an external JS file (which I also tried) to keep it as simple as possible, but if that would solve the problem I'm open to change the whole thing! In either case when I call the function I get a `ReferenceError: x is not defined`. I do see that the javascript code sits nicely in the final HTML result though. When I use Firebug to append a new `<script>` tag with a simple function that alerts "hello" to a plain html page then it works perfectly. It's only when this is done on top of a XSLT transformation things go wrong (for the sake of simplicity I'm just using a simple function to show an alert box). Here's my sample data: ``` <?xml version="1.0" encoding="utf-8"?> <Results> <Result> <Listings total="2"> <Res> <Result index="0"> <id>123456</id> <name>My Business</name> <category>Restaurants</category> <phone>9872365</phone> </Result> </Res> <Res> <Result index="1"> <id>876553</id> <name>Some Other Business</name> <category>Restaurants</category> <phone>9834756</phone> </Result> </Res> </Listings> </Result> </Results> ``` Here's the first attempt where I just added a `<script>` tag in the `<head>` tag: ``` // ==UserScript== // @name _Test XML Renderer // @description stylesheet for xml results // @include * // @grant none // ==/UserScript== var xsl_str = '<?xml version="1.0" encoding="utf-8"?>\n\ <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">\n\ <xsl:output method="html"/>\n\ <xsl:template match="/">\n\ <html>\n\ <head><script type="text/javascript">function hello() {alert("hello")};</script></head>\n\ <body>\n\ <table id="results" border="1" cellspacing="0" cellpadding="0">\n\ <thead>\n\ <tr>\n\ <th class="name">id</th>\n\ <th class="name">category ID</th>\n\ <th class="name">name</th>\n\ <th class="name">phone</th>\n\ </tr>\n\ </thead>\n\ <tbody>\n\ <xsl:for-each select="Results/Result/Listings/Res">\n\ <tr>\n\ <td class="small" width="120">\n\ <a href="#" onclick="hello()"><xsl:value-of select="Result/id"/></a>\n\ </td>\n\ <td class="small" width="120">\n\ <xsl:value-of select="Result/category"/>\n\ </td>\n\ <td class="small" width="120">\n\ <xsl:value-of select="Result/name"/>\n\ </td>\n\ <td class="small" width="120">\n\ <xsl:value-of select="Result/phone"/>\n\ </td>\n\ </tr>\n\ </xsl:for-each>\n\ </tbody>\n\ </table>\n\ </body>\n\ </html>\n\ </xsl:template>\n\ </xsl:stylesheet>\n\ '; var processor = new XSLTProcessor (); var dataXSL = new DOMParser ().parseFromString (xsl_str, "text/xml"); processor.importStylesheet (dataXSL); var newDoc = processor.transformToDocument (document); //-- These next lines swap the new, processed doc in for the old one... window.content = newDoc; document.replaceChild ( document.importNode (newDoc.documentElement, true), document.documentElement ); ``` Here's my other attempt where I add the "hello" function outside the XSL template: ``` // ==UserScript== // @name _Test XML Renderer // @description stylesheet for xml results // @include * // @grant none // ==/UserScript== var xsl_str = '<?xml version="1.0" encoding="utf-8"?>\n\ <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">\n\ <xsl:output method="html"/>\n\ <xsl:template match="/">\n\ <html>\n\ <head></head>\n\ <body>\n\ <table id="results" border="1" cellspacing="0" cellpadding="0">\n\ <thead>\n\ <tr>\n\ <th class="name">id</th>\n\ <th class="name">category ID</th>\n\ <th class="name">name</th>\n\ <th class="name">phone</th>\n\ </tr>\n\ </thead>\n\ <tbody>\n\ <xsl:for-each select="Results/Result/Listings/Res">\n\ <tr>\n\ <td class="small" width="120">\n\ <a href="#" onclick="hello()"><xsl:value-of select="Result/id"/></a>\n\ </td>\n\ <td class="small" width="120">\n\ <xsl:value-of select="Result/category"/>\n\ </td>\n\ <td class="small" width="120">\n\ <xsl:value-of select="Result/name"/>\n\ </td>\n\ <td class="small" width="120">\n\ <xsl:value-of select="Result/phone"/>\n\ </td>\n\ </tr>\n\ </xsl:for-each>\n\ </tbody>\n\ </table>\n\ </body>\n\ </html>\n\ </xsl:template>\n\ </xsl:stylesheet>\n\ '; var processor = new XSLTProcessor (); var dataXSL = new DOMParser ().parseFromString (xsl_str, "text/xml"); processor.importStylesheet (dataXSL); var newDoc = processor.transformToDocument (document); var script = "function hello() {alert('hello')};"; var newElem = newDoc.createElement('script'); newElem.type = 'text/javascript'; newElem.appendChild(newDoc.createTextNode(script)); newDoc.getElementsByTagName('head').item(0).appendChild(newElem); //-- These next lines swap the new, processed doc in for the old one... window.content = newDoc; document.replaceChild ( document.importNode (newDoc.documentElement, true), document.documentElement ); ```

Original source

Related problems