LA.NET [EN]

GadgetsArchive

May 20

Sidebar gadget bug list

Posted in Gadgets       Comments Off on Sidebar gadget bug list

Jon Abbot has a list with several know bugs (with code that reproduces those problems) here. Very cool resource. Just wish that there was a similar one for silverlight.

May 18

well, almost, that is. And what a ride it was, letting me get some important info on several aspects related with C# coding and Javascript interop.

First, I”d like to thank Bruce Williams for providing the answer for the issue related with running Silverlight on my 64 bits machine: the problem is that you can”t load the Silverlight gadget on the 64x sidebar.exe. So, if you want to run it, you”ll have to kill the default sidebar process and go to the program files(x86) folder and launch the 32 bits version. Sucks, but at least it works.

There are some quirks related with the way the current bits work. In my case, I tried using one of those cool round rectangles for the background but it simply didn”t work out. While trying this, I ended up getting a white rectangle for the area of the Silverlight activex control (ie, the clipping I set up on  my canvas was filled with white). I”ve also tried setting the gadget to windowless (which really is needed, as we”ll see next) and set the background attribute of the object passed to the createSilverlight object to the same  color as the top canvas background. It didn”t work either. I really didn”t had any time left to re-read the docs,so maybe i”m doing something wrong here.

Another important thing: if you”re silverlight canvas occupies all the available space of the html page used by the gadget,you ”ll want to set its isWindowless property of the anonymous js object passed to the createSilverlight method to true. Not doing this will result in not getting the traditional right side menu that lets you close the gadget, get its settings or drag it around the page.

Another quirk i”ve found is related with flyouts. In my simple example, I have a flyout that shows a traditional HTML page. This page has a close button which, when clicked, should close the flyout. It seems like the siverlight control “eats”the focus and clicking on that button doesn”t do anything. In fact, in order for the button to get the click, I need to press the TAB key. After doing this, the flyout looses its grayed appearance (btw, I”ve only noticed that is as little grayed out after pressing the TAB key) and after that, it responds correctly to clicks and key presses. I need to investigate this a little more to see what”s really going on there.

The WORST problem i”ve faced is related with  cross-domain calls and the BrowserHttpWebRequest class. I know I”ve mentioned it here, but i must really do it again. You cannot make cross-domain calls with this object. By now, some of you must be thinking: “you”re really stupid. cross-domain calls are disabled and since the class is a light wrapper around the XMLHttpRequest object (well, i didn”t had the time to check this, but if you look at my previous post, you”ll see that there”s a link that seems to confirm this), you really shouldn”t get away with it”. Most of this is true (though there are some things you can do to make cross-domain calls possible in some scenarios).

However, it”s a completly different story when you”re building gadgets. In those cases, cross-domain calls are indeed allowed (in fact, most sidebar gadgets use these kind of calls!). Unfortunately, the public constructor of the BrowserHttpWebRequest class always sets the value of the enableCrossDomainCalls of the internal constructor – responsible for creating the object – to false. This ends up generating an exception.

What does this really mean in my case? It basically means that  I had to add a public Scriptable method to my canvas class in order to get the XML from my remote service. And yes, i”m using javascript and XMLHttpRequest object to make the cross-domain call and then calling the scriptable method and passing it the XML I”ve received. ironic, right? I mean, this shouldn”t really be needed and I”m hoping that future versions remove this ridiculous restriction.

And i think this sums up my experiences…

Mar 16

Techdays gadget update

Posted in Gadgets, Trivia       Comments Off on Techdays gadget update

Today I”ve done a small change to the Techdays gadget I”ve talked about here. You can get it here.

Mar 15

Getting serious about sidebar gadgets?

Posted in Gadgets       Comments Off on Getting serious about sidebar gadgets?

Well, then check Jonathan Abbot”s blog. It has already saved me the time to code for automatically registering an activex dll :,,)

Mar 14

I”ve been playing with SideBar gadgets for some time now. Besides some quirks (ok, they”re really bugs 🙂 ,,), I”ve been please with how easy it is to build a gadget and deploy it. One of the things I”ve been missing though, is an auto-update feature that automatically checks a web site and downloads a new version (when it”s available). Since gadgets run at full trust, you can do several interesting things, like accessing the installation folder and making XML HTTP requests that (in theory, this looks like a security hole). After having played for some hours, I”ve built a small class that tries to do just that: checks a server and downloads a new version if it”s available. I”ve called it GadgetChecker.

Before going on, you should note that I didn”t run exaustive tests on it and I”m not giving any support. So, if you decide to use it and find bugs, I don”t give you any garantees of fixes. You can, however,  tweek its code and use it as you like. Now that we”ve got this straight, lets proceed…

The idea is to have a web site (or virtual dir) which has the manifest file with the latest version available and a zip file with the contents of the gadget. GadgetChecker will always compare the version number of the gadget.xml file that exists on the installation folder with the one that you”ve got online (currently, the online file must be called gadget.xml – to change that nameyou”ll have to change the code). If the online version is newer (ie, has a bigger number), it”ll download the zip file and copy its contents to the installation folder of the gadget. After doing that, it”ll call a callback method (if is is set).

The first thing we need is to get the current version number of the gadget, the url of the online gadget and the url of the possible zip file with the new version:

function loadGadgetXmlFile(){
  var path = System.Gadget.path + “\gadget.xml”
  var gadgetDoc = null;
  try{
       gadgetDoc = new ActiveXObject( “Msxml2.DOMDocument.3.0” );
       gadgetDoc.load( path );
       var versionNode = gadgetDoc.selectSingleNode( “/gadget/version” );
       _version = versionNode.text;
       var urlNode = gadgetDoc.selectSingleNode( “/gadget/hosts/host[0]/site/@url”);
       _url = urlNode.text;
       var fileNode = gadgetDoc.selectSingleNode( “/gadget/hosts/host[0]/site/@file” );
      _urlFile = fileNode.text;
  }
  catch(ex){
     System.Debug.outputString( ex.message );
  }
  finally{
     gadgetDoc = null;
  }
}

As you can see, I”m just using the MS XML DOMDocument to load the xml contained in the manifest. Btw, note that I”ve added a custom element (called site) to the 1st host element of the gadget file. This element is supposed to have 2 attributes (url and file) which point to the vdir where the manifest and zip file are (the url of the zip file is obtained by concatenating the url and fle attributes).

After getting these values, we must get the online gadget and compare its version with the one we”ve just got from the installation file manifest:

function _checkForUpdates(){
  var request = new XMLHttpRequest(); 
  request.onreadystatechange = function(){
    if( request && request.readyState == 4 ){   
       if( request.status != 200 ) {
          return;
       }
       var remoteGadgetManifest = request.responseXML;
       if( remoteGadgetManifest ){
          var newVersionNode = remoteGadgetManifest.selectSingleNode( “/gadget/version” );
          var newVersion = newVersionNode.text;
          var existsNewerVersion = _compareVersions( _version, newVersion );
          if( existsNewerVersion ){
              //download newer version
              var newFilePath = _url;
              if( newFilePath.lastIndexOf( “/” ) !== newFilePath.length – 1){
                 newFilePath += “/”
              }
              var urlFile = newFilePath + _urlFile;
              _donwloadFile( urlFile );
          }
       }
    remoteGadgetManifest = null;
   } 
  }//end of callback function
  var remoteGadgetPath = _url;
  if( remoteGadgetPath.lastIndexOf( “/” ) !== remoteGadgetPath.length – 1){
        remoteGadgetPath += “/”
   }
   remoteGadgetPath += “gadget.xml”
   request.open( “GET”, remoteGadgetPath, true );
   request.setRequestHeader( “If-Modified-Since”, “Sat, 1 Jan 2000 00:00:00 GMT” );
   request.send();
}

Several things happen in this method:

  • The gadget is obtained through an async XMLHttpRequest method call;
  • The If-Modified-Since header is used to avoid caching (I didn”t try it, but I”m assuming it works);
  • The _compareVersions auxiliary method is responsible for comparing the gadgets” versions;
  • When the online gadget has a newer version that the one we”ve got from the installation dir, it”s time to download the new zip file and copy its contents to the gadget installation folder. That”s what the _donwloadFile method does.

Let”s take a quick peek at the _compareVersions method. It will only perform a comparison if both versions have the same “version parts” and it”ll only return true if the second version is newer:

function _compareVersions( version1, version2 ){
  var parts1 = version1.split( “.” );
  var parts2 = version2.split( “.” );
  if( parts1.length != parts2.length ){
         return false;
  }
  for( var i = 0; i < parts1.length; i++ ){
      var v1 = parseInt( parts1[ i ] );
      var v2 = parseInt( parts2[ i ] );
     if( v1 < v2 ){
         return true;
     }
  }
  return false;
}

The _downloadFile isn”t really difficult, as you can see:

function _donwloadFile( fileName ){
    var requestZip = new XMLHttpRequest();
    requestZip.onreadystatechange = function(){
         if( requestZip && requestZip.readyState == 4 ){
             if( requestZip.status != 200 ){
               return;
            }
            var fileName = System.Ga
dget.path;
            if( fileName.lastIndexOf( “\” ) < fileName.length ){
                 fileName += “\”
            }
            fileName += _urlFile;
           _saveFile( fileName, requestZip.responseBody );
      }
    }
    requestZip.open( “GET”, fileName, true );
    requestZip.setRequestHeader( “If-Modified-Since”, “Sat, 1 Jan 2000 00:00:00 GMT” );
    requestZip.send();
}

The responseBody property is great: it lets you get an array of bytes with the response returned from the server. Saving it to the disk is the tricky part. As the great Eric Lippert said, Binary files and the File System Objects do not mix! Yep, that means that you CANNOT use FSO to save the bytes to disk. Looking at the gadget API, it really looks like you can only read and get items, but can”t really create new files. The solution I”ve used (which, btw, is also presented in the comments of the Eric”s post) is to use the ADODB.Stream object:

function _saveFile( fileName, bytes ){
   var fso = null;
   try{
     fso = new ActiveXObject( “ADODB.Stream” ); //garantee that binary data is saved correctly
     fso.Type = 1; //binary
     fso.Open();
     fso.Write( bytes );
     fso.SaveToFile( fileName, 2 );
     //unpack
     _unpackFiles( fileName );
     _deleteFile( fileName );
     if( _cb ){
       _cb();
     }
   }
   catch( ex ){
       System.Debug.outputString( ex.message );
   }
   finally{
      fso = null;
   }
}

Hurray! now, the only thing left is getting the contents of the zip file. As I”ve said before, the files must be zipped (and not rared, for instance). Why? well, because i can treat zip files like folders, making it get its content a child”s play. As you can see from the previous code excerpt, after unpacking the zip contents, the downloaded file is deleted and a callback method (which can be  passed as an argument to the constructor) is called.

The _unpackMethod is simple and it uses the Gadgets API to copy the items to the current gadget installation folder. It”ll overwrite all the items (including folders). You do need to garantee that none of the files is “locked” by another process (don”t worry about the html file used as the gadget entry point because the sidebar doesn”t lock it -at least it didn”t in my pc). Here”s that method:

function _unpackFiles(fileName){
  var currentFolder = System.Shell.itemFromPath( System.Gadget.path );
  var pathToZip = fileName;
  var folder = System.Shell.itemFromPath( pathToZip );
  for( var i = 0; i < folder.SHFolder.Items.count; i++ ){
     var item = folder.SHFolder.Items.item(i);
     currentFolder.SHFolder.copyHere( item, 16 + 256 + 512 );
  }
}

I guess I could also move the items, instead of copying them. The only thing that you need to know is that the whole process I”ve described here starts when you create a new GadgetChecker object and call its initialize method.

The demo code has a very simple (almost too simple) gadget that you can use to test the code. To do that, you only need to create or use an existing vdir and, for example, copy the gadget manifest and the updatable.htm file (which is used as the entry point of the gadget) to that folder. Then, change the version number of that manifest file (for instance, set it 1.0.2) and change the message of the html file for something like version 2 (or whatever you want). Then compress both files into a zip file (which you can call test.zip). Note that you”ll have to change the url attribute on the gadget.xml file so that it points to the folder url where the gadget file is (another gotcha: using localhost won”t work – you need to use the name of the machine if you”re hosting the site on the same machine where the gadget is running).

After doing that, install the original gadget (by copying it to the gadgets folrder or changing its extension and double clicking it) and add it to the sidebar. You should get a message saying that a newer version has been downloaded and that you need to restart the sidebar.

Before ending this long post, I must say that I”ve tried restarting the gadget, but I didn”t manage to do it. Calling window.location.reload( true ) resulted in a system crash because the System object went dead while reloading the html page. That”s why the simple gadget I”ve built uses a flyout to alert the user about the new version. Btw, note that you need to exit and restart the gadget in order to get the new version. Closing it and reopening isn”t enought to get a refresh.

wow! I may never used, but I can assure you that the 3 hours I”ve need to build this small class were lots of fun 🙂

Mar 13

Today I”ve just lost 30 mins trying to enumerate the items that exist inside a folder with the Gadget API. Guess what? The docs aren”t correct (I”m not sure why, since Gadgets exist for a long time now). Several things I”ve found today:

  • I couldn”t manage to get the System.Shell.Folder.parse method to work;
  • There isn”t an Items property on a System.Shell.Folder object
  • The same thing goes for the methods of that class (at least, copyHere and moveHere – I tried these)

Solutions: for the parse method,  I”ve manage to use the System.Shell.itemFromPath to get a reference to a folder object (I”m still not sure on why the parse method didn”t work with me – I”m probably doing something wrong). If you need to get the items that exist inside a folder, you must use the SHFolder property (which the docs don”t mention) and only then you”ll get references to those elements. Here”s a quick example of how you can use this info (the sample shows how to copy items from one folder to another):

var currentFolder = System.Shell.itemFromPath( “some path here” );
var folder = System.Shell.itemFromPath( “some path here” );
for( var i = 0; i < folder.SHFolder.Items.count; i++ ){
   var item = folder.SHFolder.Items.item(i);
   currentFolder.SHFolder.copyHere( item, 16 + 256 + 512 );
}

btw, I”ve just opened a new thread on the gadget forums. If you know the answer, please add it to the comments or as reply to that thread.