Tuesday, 9 June 2009

Safari 4: Does Acid pass the Acid test?

OK, so I updated to Safari 4 and yes, sure enough it passes the Acid3 test with flying colours. Must be good, then. So I tested it against my two outstanding bugs which I stumbled across:
Corrupt screen memory: Go to that link after visting another page and you'll see the new page just billboarded on top of the one you were looking at.



Try resizing the window to see ugly stuttering visual history as the window moves...



...and then there's simple (but big number) arithmetic inside XSLT. Safari just gets it wrong. Come on guys, where are those 64 bits you promised us? Heres an example of hex conversion, where the large integer here simply gets converted to zero inside safari, but the correct answer is given in Firefox.

So Acid 3 is all very well at testing the fancy new features, but I would have thought that CSS styling of Plain Old Xml would be even more fundamental than testing the new selectors.. so Acid 3 is failing my acid test, at least.

Friday, 5 June 2009

Parametrized Xslt from javascript, using prototype

Here's the final product; the original page has 'a' links in a tag with id="displayableModules". The links have the form href="BadChannelsFile.xml#12345.0" where the module id is indicated after the file. This is a kind of xpointer syntax, but I remould it so the id gives the module part and then listen for 'click' events on those ids; this causes an XSLT to be run with the id as a parameter and a new window contains the SVG which results. Along the way, I experimented with various file loading mechanisms (so I made a separate loading class) but stuck with a synchronous Ajax request, although discovered that Safari requires the method to explicitly stated as 'get' in lower case. Enjoy...

var Loader = Class.create({

  initialize: function(){

    this.result="";

  },

  

  loadXml: function(docPath){

    this.ajaxLoad(docPath);

  },

  

  ajaxLoad: function(docPath){

  //bind the passed function to the original class

  //the method (get) needs to be explicitly written in lowercase for the Safari browser!

    new Ajax.Request(docPath, {asynchronous: false, onSuccess: this._callback.bind(this), method: "get",

    onFailure: function(r){alert("load failed");}})

  },

  

  _callback: function(r){

    this.result=r.responseXML;

  },

  

  doc: function(){

    if (this.result==""){

      alert("no doc");

    }

    return this.result;

  }

});


var Transformer = Class.create({

initialize: function(){

  this.processor = new XSLTProcessor();

  this.paramNames=[];

  this.paramValues=[];

  this.xslPath="";

  this.xmlPath="";

  this.fileLoader = new Loader();

},


setXslt: function(xslPath){

  this.xslPath = xslPath;

  this.fileLoader.loadXml(xslPath);

  this.stylesheet = this.fileLoader.doc();

},


setXml: function(docPath){

  this.xmlPath = docPath;

  this.fileLoader.loadXml(docPath);

  this.xmlDoc = this.fileLoader.doc();

},


setParam: function(paramName, paramValue){

  this.paramNames.push(paramName);

  this.paramValues.push(paramValue);

},


exec: function(thisdoc){

   this.processor.importStylesheet(this.stylesheet);

  for (var i=0;i<this.paramValues.length;i++){

    this.processor.setParameter("",this.paramNames[i], this.paramValues[i]);

  }

  var fragment = this.processor.transformToFragment(this.xmlDoc, thisdoc);

  return fragment;

}


});

//global functions

function changeLinks(){

  //take existing links in the div 'displayableModules' and add an id, and suppress the href link

  var allLinks=$("displayableModules").getElementsByTagName("a");

  for (var i=0;i

    var thisElement=$(allLinks[i]);

    var thisElementUrl=String(thisElement.href);

    var moduleId="module_".concat(thisElementUrl.substring(thisElementUrl.lastIndexOf('#') + 1));

    thisElement.setAttribute("id", moduleId);

    thisElement.setAttribute("name", thisElementUrl.substring(0,thisElementUrl.lastIndexOf('#')));

    thisElement.setAttribute("href","");

  }

  listenToLinks();

 }


function listenToLinks(){

//take existing links in the div 'displayableModules' and listen to them...

var allLinks=$("displayableModules").getElementsByTagName("a");

  for (var i=0;i

    var thisElement=$(allLinks[i]);

    var thisModule=String(thisElement.id);

    thisElement.observe('click',transform);

  }

}


function transform(e) {

  var moduleElement=e.element();

  var thisModule=moduleElement.id;

  var sourceFile=moduleElement.name;

  var transformer = new Transformer();

  transformer.setXslt("./xsl/graph.xsl");

  transformer.setXml(sourceFile);

  transformer.setParam("id",thisModule.substring(7));

  w=window.open("",thisModule,"width=670,height=500");

  w.document.open();

  w.document.write(''<span style="color: #000000"> + thisModule + </span>'

Bad channels for module ' + thisModule.substring(7) + '

');

  f=transformer.exec(w.document);

  w.document.getElementById('insert').appendChild(f);

  w.document.close();

}

document.observe('dom:loaded', changeLinks);



Wednesday, 3 June 2009

Prototype and running XSLT from JavaScript

I had some problem marrying the usual method of running an XSLT with prototype's library. Here's my project: Given an xml file of 'bad channel identifiers' for a module (where channels run 0-767), display the bad channels as bars on a graph in SVG in response to a user clicking on another web page link somewhere. I chose to generate the SVG using a parametrised XSLT, but I need to pass the parameters...so I'll be running the XSLT 'manually' from JavaScript instead of using the stylesheet association.
Step 1. : Perform a transform manually using a loaded stylesheet and xml. I'll load both via an Ajax request... (this is not production code, but might help someone... and note that I already loaded the prototype library)
var Transformer = Class.create({

initialize: function(){
  this.processor = new XSLTProcessor();
},

setXslt: function(xslPath){
  //need to bind, otherwise 'this' refers to the event context, not the class
  new Ajax.Request(xslPath, {asynchronous: false, onSuccess: this._setXsl.bind(this),
  onFailure: function(r){alert("failure to load xsl");}});
  
},
_setXsl: function(r){
  this.stylesheet=r.responseXML;
  if (this.stylesheet==''){
    alert("Empty stylesheet");
  }

  this.processor.importStylesheet(this.stylesheet);
},

setXml: function(docPath){
  //need to bind, otherwise 'this' refers to the event context, not the class
   new Ajax.Request(docPath, {asynchronous: false, onSuccess: this._setXml.bind(this),
  onFailure: function(r){alert("failure to load xml");}});
},

_setXml: function(r){
  this.xmlDoc = r.responseXML;
  if (this.xmlDoc==''){
    alert("Empty xml doc");
  }
 },

exec: function(){
  var fragment = this.processor.transformToFragment(this.xmlDoc, document);
  return fragment;
}

});

function transform() {
var transformer = new Transformer();
transformer.setXslt("xsl/test.xsl");
transformer.setXml("xml/hello.xml");
f = transformer.exec();
$('insert').appendChild(f);
}