Link to home
Start Free TrialLog in
Avatar of LizHelpMe
LizHelpMe

asked on

XSLT, Javascript & Breadcrumb problem

I have the above code working really well and consistently being able to successfully
click through my xml and step back again. It's like Windows Explorer through a web browser.

I have managed to get this to work across all browsers. Previously I was unable to do this
cross browser because I used import and also passed paramaters into the style sheet
from javascript with Sarissa. I have found that passing parameters is not possible with webkit
browsers, so I have found this way. I just need some help on a decent approach to creating a
breadcrumb trail.

Can anyone give decent assistance on how to create a breadcrumb trail with the above code.
Here is the XML:

<?xml version="1.0" encoding="utf-8"?>
<items id="0">
  <region id="1" name="New Items">
    <group id="8" name="Apple">
      <region id="9" name="artichoke">
        <region id="15" name="aeroplane">
          <extension id="10008" name="0005" />
          <extension id="10001" name="0009" />
        </region>
      </region>      
    </group>   
  </region>
</items>

Here is the XSLT:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">    
  <xsl:template match="/">
    <div id="testDiv">
      <xsl:if test="items/@parentId!='0'"> 
        <div id="myBackDiv" onmouseover="reportOn(this.id)" onmouseout="reportOff(this.id)">
          <xsl:attribute name="onclick">
            <xsl:text>StepBack('</xsl:text>
            <xsl:value-of select="items/@parentId"/>
            <xsl:text>');</xsl:text>
          </xsl:attribute>
          <img src="Images/png/back.png" width="32px" height="32px"/>                   
          <span>
            <xsl:attribute name="style">
              <xsl:text> font-style:bold; text-decoration:underline;</xsl:text>
            </xsl:attribute>
            <xsl:text>up again</xsl:text>
          </span>
        </div>
      </xsl:if>
      
        <xsl:for-each select="items/*">
          <xsl:if test ="self::region | self::division | self::group | self::site" | self:extension>
            <div id="{@id}" onmouseover="modalOn(this.id)" onmouseout="modalOff(this.id)">
              <xsl:if test="*[@id]">
                <xsl:attribute name="onclick">
                  <xsl:text>StepThrough('</xsl:text>
                  <xsl:value-of select="@id"/>
                  <xsl:text>');</xsl:text>
                </xsl:attribute>
              </xsl:if>
              <span>               
                <xsl:attribute name="style">
                  <xsl:if test="*[@id]">
                    <xsl:text> font-style:bold; text-decoration:underline; cursor:pointer;</xsl:text>
                  </xsl:if>
                </xsl:attribute>                
                <xsl:value-of select="@name" />
              </span>
            </div>
          </xsl:if>         
        </xsl:for-each>
    </div>
  </xsl:template>
</xsl:stylesheet>

Here is the Javscript:

var xml;
if (window.XMLHttpRequest) {
    xhttp = new XMLHttpRequest();
}
else {
    xhttp = new ActiveXObject("Microsoft.XMLHTTP");
}

function loadXMLDoc(dname) {
    
    xhttp.open("GET", dname, false);
    xhttp.send("");
    return xhttp.responseXML;
}

function displayResult(pXml) {  
    xsl = loadXMLDoc("cdcatalog.xsl");
    // code for IE
    if (window.ActiveXObject) {
        ex = pXml.transformNode(xsl);
        document.getElementById("availableSearchItems").innerHTML = ex;
    }
    // code for Mozilla, Firefox, Opera, Chrome, etc.
    else if (document.implementation && document.implementation.createDocument) {
        xsltProcessor = new XSLTProcessor();
        xsltProcessor.importStylesheet(xsl);
        resultDocument = xsltProcessor.transformToFragment(pXml, document);
        document.getElementById('availableSearchItems').innerHTML = "";
        document.getElementById("availableSearchItems").appendChild(resultDocument);
    }
}

function StepThrough(pId) {
    var newTest = "<?xml version='1.0' encoding='utf-8'?><items id='0' parentId='" + pId + "'>";
    var nodeFrom = xml.selectSingleNode('//*[@id=' + pId + ']');
    var name = nodeFrom.getAttribute("name");

    for (var i = 0; i < nodeFrom.childNodes.length; i++) {
        var xmlString = new XMLSerializer().serializeToString(nodeFrom.childNodes(i));
        newTest += xmlString;  
    }
    newTest += "</items>";
    var xmlObj = (new DOMParser()).parseFromString(newTest, "text/xml");
    displayResult(xmlObj);
}

function StepBack(pId)
{   
    var nodeFrom = xml.selectSingleNode('//*[@id=' + pId + ']');
    var parentItem = nodeFrom.parentNode;
    var parentId = parentItem.getAttribute("id");
    StepThrough(parentId)
}

Open in new window

Avatar of Gertone (Geert Bormans)
Gertone (Geert Bormans)
Flag of Belgium image

Well, I see your problem (I think)

You cut out part of the XML, based on the @id
Doing that, of course you loos the context for building the breadcrump

I thought that all Webkit issues with Sarissa were fixed by now,
but have not tested enough to be sure

But there is another way to "pass" in a parameter
Instead of using addParameter, you could sneekily change the XSLT in place
It would definitely simplify the javascript A LOT
Just have an xsl:param the same way you would for adding a parameter
Then access that param in a DOM method, and change it for the correct id
(pretty much as you do with selectSingleNode)

This way the XML is passed on fully, so you can calculate the breadcrump easily
It is the XSLT that was changed prior to the transform


Just to be curious, are you sure that the addParameter is not working for sarissa/webkit?
Note that for parameter passing you need a template processor
As soon as breadcrumps are involved I see many people have both the breadcrump and the rest of the XSLT result in a div
Sarissa requires the transform result to be wellformed.
That could be biting you... just a thought
Avatar of LizHelpMe
LizHelpMe

ASKER

Hi, As far as I am aware there is no way to pass a param to xslt when using webkit browsers, though I could easily be wrong. I just haven't found any documentation on that. Basically, you got what I'm trying to say down to a tee. My way around it is by passing the new xml into xslt each time. Thus losing any paramter.

This bit where I step through and re-create the xml with all childnodes

function StepThrough(pId) {
    var newTest = "<?xml version='1.0' encoding='utf-8'?><items id='0' parentId='" + pId + "'>";
    var nodeFrom = xml.selectSingleNode('//*[@id=' + pId + ']');
    var name = nodeFrom.getAttribute("name");

    for (var i = 0; i < nodeFrom.childNodes.length; i++) {
        var xmlString = new XMLSerializer().serializeToString(nodeFrom.childNodes(i));
        newTest += xmlString;  
    }
    newTest += "</items>";
    var xmlObj = (new DOMParser()).parseFromString(newTest, "text/xml");
    displayResult(xmlObj);
}

On the above code I do believe I make it well-formed. It does work. I figured a way to do this would be to add an extra attribute to the top level items tag like I do for parentId which is used for the back button.

var newTest = "<?xml version='1.0' encoding='utf-8'?><items id='0' parentId='" + pId + "'>";

Then I can access that Id and get reversing through my xml looking for parentNodes all the way until there are none. Can you help mme with this. Doing it this way will mean creating a completely new breadcrumb for every time I step through/backward in the xml.
Hi, You said about using the DOM to change a param element actually in the XSLT. I didnt know this could be done at all. This sounds an interesting way. I'm getting on much better with XSLT these days. It's good stuff.
Well, for myself, I would definitely prefer changing the parameter using the DOM way and get rid of all the navigation code in JavaScript.
Could you show me how to do it? Thanks.
have to get something out for a customer this afternoon, will work on this later today
Ok so I have the breadcrumb object. It is just passing an id field into my xslt file and then invoking the below template to successfully reverse through each step until the end and display the breadcrumb. I understand that you must be very busy and I can wait however long you like.

Thanks again.
<xsl:template name="breadcrumb">
    <xsl:param name="node"/>
    <xsl:choose>
      <xsl:when test="$node/parent::region">
        <xsl:call-template name="breadcrumb">
          <xsl:with-param name="node" select="$node/parent::region[1]"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:when test="$node/parent::division">
        <xsl:call-template name="breadcrumb">
          <xsl:with-param name="node" select="$node/parent::division[1]"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:when test="$node/parent::site">
        <xsl:call-template name="breadcrumb">
          <xsl:with-param name="node" select="$node/parent::site[1]"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:when test="$node/parent::group">
        <xsl:call-template name="breadcrumb">
          <xsl:with-param name="node" select="$node/parent::group[1]"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:when test="$node/parent::extension">
        <xsl:call-template name="breadcrumb">
          <xsl:with-param name="node" select="$node/parent::extension[1]"/>
        </xsl:call-template>
      </xsl:when>
    </xsl:choose>
    <xsl:text> \\ </xsl:text>
    <xsl:text> \\ </xsl:text>
    <span id="{$node/@id}"  onmouseover="BreadCrumbOn(this.id)" onmouseout="BreadCrumbOff(this.id)" onclick="displayDirectory('{$node/@id}', '{ $node/@name }')">
      <xsl:value-of select="$node/@name"/>
    </span>
  </xsl:template>

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of Michel Plungjan
Michel Plungjan
Flag of Denmark image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I gave your way a good go. I just keep getting wrong number of arguments, invalid property assignment. gertone mentioned I could use selectSingleNode. Im sure he will give me the help I need as he is a complete genius. Thanks for giving me tip though Im going to continue hacking away at it until I get some where.
Okeee:

 
The XSLTProcessor in Firefox also allows you to set XSLT parameters. The setParameter() method accepts three arguments: the namespace URI, the parameter local name, and the value to set. Typically, the namespace URI is null and the local name is simply the parameter's name. This method must be called prior to transformToDocument() or transformToFragment():

    var oProcessor = new XSLTProcessor()
    oProcessor.importStylesheet(oXslDom);
    oProcessor.setParameter(null, "message", "Hello World!");
    var oResultDom = oProcessor.transformToDocument(oXmlDom);

so in your case:

  xsltProcessor = new XSLTProcessor();
  xsltProcessor.importStylesheet(xsl);
  xsltProcessor.setParameter(null, "parmName", "parm value");
  resultDocument = xsltProcessor.transformToFragment(pXml, document);
Ah thanks for this I appreciate it.
I've put this in my XSL

<xsl:param name="detail-id" >testvalue</xsl:param>

Im trying to get at it like this...

 xsl = loadXMLDoc("cdcatalog.xsl");

    var paramNode=xsl.selectSingleNode("//xsl:param[name=detail-id]")
    alert(paramNode);

Will have a look at what you have provide right away.

Thanks.
Im fine with firefox, IE and Opera because I can easily use sarissa with these, no probs, all working there.. It's the webkit ones. I've seen that JQuery transform. After speaking to you guys I suppose I can just change set a value of xslt param through using DOM, as after all xsl will just be like an xml file. This is the direction I need to take, because then I can put my old code back with the breadcrumb that works fine and just use that.

So changing the param value by using DOM would mean I can go back to initial way without cutting out pieces of xml to get the desired result. Im nearly there.

I will definately pass on saome points to you.

Thanks.
Here is the code from that plugin in case you just want to see how to do it in IE and Fx
if($.browser.msie) {
  var xml_X = ["MSXML4.DOMDocument", "MSXML3.DOMDocument", "MSXML2.DOMDocument", "MSXML.DOMDocument", "Microsoft.XmlDom"];
	var objXML = false;
	var objXSL = false;
	for(var i = 0; i<xml_X.length;i++) {
	  try {
		  objXML = new ActiveXObject(xml_X[i]);
			objXSL = new ActiveXObject(xml_X[i]);
			i = xml_X.length;
		} catch(ex) {
		  objXML = false;
			objXSL = false;
		}
  }
  objXSL.loadXML(o.c.xslstr);
  objXML.loadXML(o.c.xmlstr);

  var addparams = function(op, xObj) {
	  for(var p in op) {
      var strParam = "//xsl:param[@name='" + p + "']";
	    try {
		    if(isNaN(parseInt(op[p])) && op[p].indexOf("'") < 0) {
				  op[p] = op[p].indexOf("\"") >= 0 ? op[p] = op[p].replace(/"/, "'") : op[p] = "'" + op[p] + "'";
		    };
		    var xslParam = xObj.selectSingleNode(strParam);	    				
				xslParam.setAttribute("select",op[p]);
			} catch(ex) {
			 //param failed
      }
    }
  };
					
	addparams(o.c.xslParams,objXSL);
  tel.html(objXML.transformNode(objXSL));
} else {
  var parser = new DOMParser();
	var processor = new XSLTProcessor();
					
	var objXSL = parser.parseFromString(o.c.xslstr,"text/xml");
	var objXML = parser.parseFromString(o.c.xmlstr,"text/xml");
					
	var addparams = function(op) {
	  for(var p in op) {
	    processor.setParameter(null, p, op[p]);
		}
	};
					
	addparams(o.c.xslParams);
	processor.importStylesheet(objXSL);
	var resultDoc = processor.transformToFragment(objXML, document);
	tel.empty().append(resultDoc);
};

Open in new window

<xsl:param name="detail-id" >yoyoyo</xsl:param>

This was works to a degree in the both webkit browsers.

var paramNode=xsl.selectSingleNode("//xsl:param[@name='detail-id']")
    var xmlString = new XMLSerializer().serializeToString(paramNode);
    alert(xmlString);

Have to bring it back to string as usual. It displays the whole xsl:param as a string. I just want the value which is as above "yoyoyo". Once I can isolate that then I can hopefully change it and put it back. This is the direction I am taking.
var values = paramNode.getAttribute("name");
alert(values);

AS bove line gets the 'detail-id'. What I want is to print "yoyoyo" to screen. I'm assuming name/value pair. So...

var values = paramNode.getAttribute("value");

But no, it gives me null. Ah well, who needs logic anyway. Still hacking.
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Got there in the end.

xsl = loadXMLDoc("cdcatalog.xsl");
var paramNode=xsl.selectSingleNode("//xsl:param[@name='detail-id']")    
var values = paramNode.childNodes(0).data = "I have been changed";

Yey, I did it like this. So now I can just revert back to my old code as if a parameter has been passed in. This will mean I don't need to cut up the xml, so the breadcrumb will work again. This time over all browsers, using .setParamter with firefox and IE. Would you say this is ok.

Thanks.
I have evenly distributed. I hope this is ok.
welcome,
this sounds very much OK,
given that addParameter() doesn't work in webkit browsers (I still have to check on that),
this is exactly how I would have done this

I think you made the breadcrump XSLT code overly complex
You could actually iterate over ancestor axis with a simple recursive call and build up the breadcrump,
but that is just food for thought

Distribution is OK, don't worry.
I am happy Michel stepped in with his JavaScript expertise, mine is a bit rusty :-)
I still want to see how to insert an xsl:param from scratch :)

Your final solution worked because you already had one defined (cool too)

I will return if I get the answer
Well, it makes sense to have one in place and use that one.
That would have been my comment on your suggestion to create a new one.
If i had to develop the XSLT, I would like one in there allready, so I can test the stuff working outside the JavaScript.
The risk is that if you forget to remove it after development, you end up with to and the XSLT does not compile
Sure. But it is still cool to have in your arsenal
that is correct :-)