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 ); ```