| Recently I’ve been creating xml output for mobile using XSLT. While doing this I ran into a couple of complex problems because of the visual layout I wanted to achieve. In one XSLT template I create a list of items by iterating over a collection of nodes. In that same template I also want to output several attributes on the node collections parent node. This template also contains a nested template to output the individual list items. To get the parent node is simple. We can use the “ancestor” axis to indicate what the parent node is: select="ancestor::Project"
Now you might think that to get an attribute “Client” from the parent, you would simply use square braces “[]” and you’d be wrong. select="ancestor::Project[Client]" or select="ancestor::Project[@Client]" will error out. Instead the XPath definition should indicate the attribute as a node of its parent: select="ancestor::Project/@Client". That’s because an attribute is in fact an attribute node of the element it belongs to. Just make sure you indicate in your XPath that it is an attribute node by using the “@” prefix.
<xsl:template match="Technologies">
<div class="Technologies">
<p><strong><xsl:value-of select="ancestor::Project/@Client"/></strong></p>
<p><xsl:value-of select="ancestor::Project/@Project"/></p>
<ol class="TechnologyItems">
<xsl:apply-templates select="Technology"/>
</ol>
</div>
</xsl:template>
Problem solved. Next problem: I wanted to grab the first two of a set of images that had local paths and no alt values in the xml. Creating an absolute path from the relative one is fairly simple. Instead of “select”, I’ll use “match” while iterating over the collection of image nodes. I need to limit the match to only the first two images. I can do this by using postion() and testing whether the current image is less than or equal to two (note the escaped less than sign because a quoted attribute cannot contain a < character):
<xsl:template match="Images/Image[position() <= 2]">
Next I construct the absolute path to the image from an attribute on the image node. This is really straightforward. Use curly braces around the attribute name:
<img src="http://MySite.com/{@ImageSource}"
Here’s how the template should look so far:
<xsl:template match="Images/Image[position() <= 2]">
<img src="http://MySite.com/{@ImgSource}" class="Illustration" />
</xsl:template>
Finally I had to figure out a way to create an alt value for each image. At first I looked into parsing the attribute value to extract a substring with which to create the alt value. However, since I’m outputting the value directly into a tag’s attribute, I couldn’t take advantage of XSLT advanced text manipulating functions. So I came up with a poor man’s alt alternative (pun intended). Since the images in this case were relevant to a project, I would append some text indicating which image they were in the image collection for that project. Better than nothing. To find which image I’m on I can once again use position(), but inside of curly braces:
<img src="http://MySite.com/{@ImgSource}" alt="Image {position()}: " />
Finally I complete the alt’s value with the value of the image collection’s Project ancestor in curly quotes:
{ ancestor::Project/@Project}
The complete image template is:
<xsl:template match="Images/Image[position() <= 2]">
<img src="http://MySite.com/{@ImgSource}" alt="Image {position()}: { ancestor::Project/@Project}" class="Illustration" />
</xsl:template>
If you enjoy XSLT and have your own favorite tricks and tips, leave me a comment. |
| Today Google announced their new OS: Google Chrome OS. They specifically mentioned that it was targeted at netbooks. It’s really just a minimal Linux that boots directly into the Google Chrome browser. Applications on Chrome OS are the same as Web apps on Chrome or other standards-based browsers now. This is not a platform where Adobe will be porting Photoshop any time soon. It’s about email, social networking and Web apps like the Google docs suite. This seems really boring and uninspiring to me. It sounds like an attempt by Google to knock Windows 7 off of netbooks. I have a suspicion that there will be a very high return rate once users realize that their netbook with Google Chrome OS only has Web apps and doesn’t have or support real applications. On the iPhone, developers have the ability to deploy Web apps or native apps. You will be hard pressed to find a Web app on the app store. You would similarly be hard pressed to get an iPhone developer to switch development from native code to a Web app. The biggest complain against the iPhone when it first came out was that it only had Web apps. The Google people did mention that all apps developed for Google Chrome OS would also be available to anyone running the regular Google Chrome browser on their desktop. Once again this reinforces to me that this is not really a new platform. It’s just a browser as a platform for Web apps which technically should also run just as well on Safari or Firefox. I don’t see anything that grabs me about Google Chrome OS. I do see a lot in Google Chrome OS that makes me feel like a yawning. Google Chrome OS is not exciting. You can watch the Google presentation of Google Chrome OS for yourself. Be warned, it’s a yawner. |
| One of the big news Items out of Microsoft PDC09 was the early work being done on IE9. In particular they showed huge speed improvements in JavaScript performance. They also showed of some advanced CSS3 features. JavaScript Improvements I'd like to go a bit deeper about all of these to bring things into better perspective for developers. There were some very nice interviews on Channel 9 with the IE team. I'm going to talk about the JavaScript engine first. The JScript team mentioned that they have totally rewritten the JavaScript engine in native code. This is a big deal. Previously the JavaScript engine was COM objects. That's a poor choice for a language that needs to be light and dynamic. Just doing that gave them a huge speed improvement. To be honest, they also acknowledge other bottlenecks, which they will address: faster regex parsing, array looping, etc. They aren't trying to beat Chrome or Webkit, but I suspect they will be very close. The speed of JavaScript on Chrome, Webkit, Firefox and Opera is already amazing. We could not have predicted these speeds two or three years ago. Now the JScript team is focusing on catching up. From what I've seen, I do expect them to be neck and neck with the other browsers. They were testing against a number of suites, including SunSpider and Acid3 test. That brings us to the next point. To pass Acid3 Microsoft with need to support a number of things presently lacking in their browsers: HTML5, Canvas and SVG. Since Eliot Graff, a member of the IE team, is helping on editing the W3C draft for the Canvas element, I expect them to add support for this at some time. Right now IE9 only scores 32 out of 100 on the Acid3 test. All other major browsers score 100 or very close. The team didn't say they wanted to achieve a score of 100, so there was definitely some abiguity there. For that matter, Firefox 3.5 only scores 93. But then, the alpha of Firefox 3.7 scores 96. CSS3 Finally Comes to IE Now lets talk about CSS3. In the PDC demo they showed IE9 rendering CSS border radius. They rendered beautifully in the demo, with proper anti-aliasing for smoothing of edges, even with animation. At present the following browsers support CSS3 border radius: Webkit, Mozilla, Chrome (Opera doesn't support this yet). Webkit and Mozilla's rendering of the border radius is excellent, even with animation. However, Chrome's rendering is substandard, displaying pixelation. It looks like Chrome does do any anti-aliasing yet. So the initial implementation of border radius in IE9 is on par with Webkit and Mozilla. From GDI to D2D IE9 uses D2D for rending. This has a big effect on text clarity and text animation. The team also demoed animated rounded corners using D2D. Christian Fortini, an IE team member working on the graphics backend, also mentioned box shadow. Christian who talked in length about animation, at one point mentioned that developers wanted better animation and transitions in their Web apps. Since he mentioned transitions, is this an allusion to CSS transforms and transitions presently supported by Webkit and Mozilla? Cross your fingers. Since they're using D2D, it should be trivial for them to implement box shadow, text shadow, CSS background gradients or CSS transforms and transitions. One thing you can also expect to see is improved Image scaling. IE9 off-loads this to the GPU for smoother and faster rendering. CSS Selectors Test And finally, CSS3 selectors. The test team showed IE9 running the CSS3 selectors test. This tests 43 different CSS3 selectors in 578 tests. IE8 only passes 349 out of the 578 tests. IE9 passes 574 of the 579. This is really great news for CSS geeks. So, I see IE9 as finally leveling the playing field and broadening the platform for feature-rich Web user interfaces implemented with standard Web technologies, HTML5, CSS3 and JavaScript Harmony. Of course, from a practical view point, it will take years before institutions switch over to IE9, but at least IE9 is the first step from Microsoft to enabling a broad, modern standards-based Web development approach across browsers. |
| Recently I found myself trying to change the background color of a Silverlight element from code behind. I was expecting that I was going to have to convert the hex color values to rgb or argb. Fortunately this isn’t necessary. You can just use the Color.FromArgb method. It takes both decimal and hex values, well and octal too. As a matter of fact, you can mix decimal and hex values. You do need to indicate hex values with the 0x prefix. The following three lines all produce the same orange color: myGrid.Background = new SolidColorBrush(Color.FromArgb(255, 249, 132, 36));
myGrid.Background = new SolidColorBrush(Color.FromArgb(255,0xf9,0x84,0x24));
myGrid.Background = new SolidColorBrush(Color.FromArgb(0xff,0xf9,132,36));
If you have a lot of colors to manage, you might want to store them as values in an array. Then you can access them using the array indeces. Just create and array that holds values of type byte:
Byte[] kolor = new Byte[] { 127, 20, 0x84, 150 };
myRect.Fill = new SolidColorBrush(Color.FromArgb(kolor[0], kolor[1],
kolor[2], kolor[3]));
FromArgb makes manipulating colors easy.
|
| I’d like to thank all the people who attended my Silicon Valley Code Camp sessions. You were enthusiastic and put up with cramped conditions and poor projector conditions as well. Below are links to down files from my session the sessions. Each download as the relevant Powerpoint Presentation. Those can also be found on If you do not see material for your session, come back later. I gave four sessions so I have a lot of material to go through before I can post all of it online. JavaScript Animation: Saturday 2:30 PM room 3525 Codecamp-JavaScript-Animation Expression Blend: Saturday 3:45 PM room 3525 WPF Button Example Styled Controls Webkit: Saturday 5:15 PM room 4301 CodeCamp Webkit jQuery Plugins: Sunday 1 PM room 4301 CodeCamp-jQuery-Plugin |
| Back in September of 2009 Microsoft decided to ship and support the JavaScript library jQuery with Visual Studio. One of the great things about jQuery is how well it shields the developer from all the cross-browser differences that make it so hard to just write some code that works reasonably in all browsers. jQuery is small (20KB), fast, and uses a DOM selector API to its advantage. It is also easily extensible. This has lead to the creation of hundreds of plugins for added functionality. By using just the plugins you need, you can avoid the bloat of some of the other more fleshed out JavaScript libraries. There are plugins for practically everything you can think of. But if you can't find a plugin to fill your need, it is a fairly straightforward process to write your own. Moreover, if you find yourself writing similar code again and again, you should probably look into turning that code into a plugin. That way, if you need to update the code, you only have to do it in one place. You can also use plugins as inside of plugins to speedup development. First of all you'll need to enclose your plugin code inside a jQuery namespace closure. That will look like this:
(function($) {
})(jQuery);
Basically we're creating a JavaScript closure, passing in jQuery as a parameter and aliasing it to the dollar sign. Now, depending on what your plugin will do, there are several ways to implement your plugin. You can attach it directly to the jQuery object, or you can make it an extension of the jQuery object. If all you're doing is invoking a method and passing it some parameters, then attaching it directly to the jQuery object is sufficient. However, if you want it to inherit all the properties of the jQuery object and be chainable, you'll want to extend the jQuery object.
Attaching a Plugin to the jQuery Object
(function($) {
$.nameOfYourPluginHere = function() {
// Define method here.
};
})(jQuery);
This is a nice start. But we probably would expect the need for some basic initialization. We can do this by setting up some default values which the user can override. Here's how that would look:
(function($) {
$.nameOfYourPluginHere = function(options) {
$.nameOfYourPluginHere.defaults = {
value1 : "",
value2 : "",
value3 : ""
};
var options = $.extend($.nameOfYourPluginHere.defaults, options);
// Define method here.
};
})(jQuery);
Now all that's left is to do something with our plugin. Usually you would do something to a collection of nodes returned by a selector query. This would look something like this:
(function($) {
$.nameOfYourPluginHere = function(options) {
$.nameOfYourPluginHere.defaults = {
targetElems : "a",
value1 : "",
value2 : "",
value3 : ""
};
var options = $.extend($.nameOfYourPluginHere.defaults, options);
// Define method here.
$($.nameOfYourPluginHere.defaults.targetElems).each(function() {
var $this = $(this);
$this.css({"background-color" : "Yellow", "color" : "Red"});
});
};
})(jQuery);
In the above example we have a targetElems default value of "a", but we could pass in any selector we want:
$.nameOfYourPluginHere({targetElems : "#globalNav li a"});
Also you might want to attach an event to each node:
(function($) {
$.nameOfYourPluginHere = function(options) {
$.nameOfYourPluginHere.defaults = {
targetElems : "a",
value1 : "",
value2 : "",
value3 : "",
speed : "slow"
};
var options = $.extend($.nameOfYourPluginHere.defaults, options);
// Define method here.
$($.nameOfYourPluginHere.defaults.value1).each(function() {
var $this = $(this);
$this.click(function() {
// Do whatever you need to do with $this as each individual node:
$this.doSomethingHere(speed);
});
});
function doSomethingHere(speed) {
// Define method using speed here.
}
};
})(jQuery);
Making Your Plugin an Extension of jQuery
A limitation to the above plugin is that we can chain it like normal jQuery methods. In order to accomplish that we need to make our plugin an extension of the jQuery object. We can do this very easily by making the plugin an extension of the jQuery.fn method. As an example, let's make a hilite plugin:
(function($) {
$.fn.hilite = function(options) {
// Define some defaults.
$.fn.hilite.defaults = {
foreground: 'red',
background: 'yellow'
};
// Allow the user to override the defaults:
var opts = $.extend($.fn.hilite.defaults, options);
// Iterate over the collection while returning each item.
return this.each(function() {
$this = $(this);
// Define what you want to do with your items.
$this.css({
backgroundColor: opts.background,
color: opts.foreground
});
});
};
})(jQuery);
We can now inovke our plugin and chain other jQuery methods to it to get more complex results:
$(document).ready(function() {
$("#sideColumn p").hilite({background: "red", foreground: "yellow"}).
css({"font-size" : "24px"}).animate({ "margin-left" : "100px"});
});
|
|
Anyone who has worked with JavaScript is familiar with its prototype feature as a means of object extension. Although there are no classes for inheritance, an object can be created from another object and thus inherit its properties through the prototype of its parent. To mirror this capability DOM level 2 added prototypal inheritance as well. This allowed a developer to modify basically everything by extending an object's prototype. However, this was never possible with IE because, although JScript had prototypes, the DOM implementation was done with COM. This left us with torturous workarounds and kludges to accommodate IE. Yesterday the IE team announced that IE8 will at last support full prototypal extension for the DOM. This means developers can now extend anything they need to just as they do on other browsers. This allows you to do this:
1: var oldSetAttribute = Element.prototype.setAttribute; 2: // Apply the change to the Element prototype... 3: Element.prototype.setAttribute = function (attr, value) { 4: if (attr.toLowerCase() == 'classname') { 5: // Older scripts expect 'className' to work, don't 6: // disappoint them. Avoiding creating a 'className' 8: attr = 'class'; 7: // attribute in IE8 standards mode 9: } 10: oldSetAttribute.call(this, attr, value); 11: };
They also decided to add getters and setters, finally! Interestingly, getters and setters have been by other browser vendors to their JavaScript engine following Mozilla's lead. Unfortunately, this was done before it was standardized. Now that the standard has been clarified, these implementations are not quite correct. The IE team decided to implement getters and setters like the other browser vendors but will implement the fully compliant standards version for IE8 RC1. This means that for now you can try out getters and setters as you would with other browsers, and when RC1 ships, you can make adjustments for the standardized version.
1: Object.defineProperty( document.body, "firstChild", { 2: getter: function () { 3: return this.firstChild.nextSibling; 4: }, 5: setter: function ( element ) { 6: throw new Error("Sorry! This property can't be set. Better luck next time."); 7: } 8: } );
I was hoping that the IE team would also finally add array extras to IE's JScript. That's not happening now, nor did they seem that interested. However, with the new prototype extendability that was just added, it's relatively easy to add array extras to JScript. |
| The jQuery team have made tremendous progress with jQuery UI and are close to releasing version 1.6. It's presently at RC4 status. They've completely refactored all the JavaScript and created a new CSS framework to handle all widgets. You can try them out here: jQuery UI. I'm really looking forward to jQuery and jQuery UI being integrated into Visual Studio so that we can create some truly compelling interfaces with it. jQuery UI for the front end and user experience behavior and ASP.NET for the back end. |
| Saturday, November 4th was the third annual Code Camp Sillicon Valley held at Foothill College in Los Altos. I gave two sessions: When Browsers Behave Badly, focusing on how I fix display issues in IE6, IE7, Firefox and Safari. It was a great turnout overall and all attendees seems enlivened and enthusiastic. I'd like to thank all the people that attended my sessions, especially those who sat through both sessions back to back. You can download the slide show as pdfs and samples of the material I used for the presentation. Enjoy! |
| Truly Microsoft Virtual Earth is one cool piece of technology. The API offers ASP.NET developers a great deal of customization. However, after struggling to create mashups with it, I can say that it was not created with Front End developers in mind. If you've ever tried to include custom JavaScript to run on the same page as Virtual Earth, you know what I'm talking about. It seems like Virtual Earth just won't let you get anything in edge wise -- very frustrating. For the first time I decided to investigate what Virtual Earth was doing that preventing any of my JavaScript events from firing. Running it in Firefox and watch the console, I noticed that Virtual Earth is constantly firing postbacks every few milliseconds. The image below illustrates this:
Virtual Earth loads when the page loads and then starts firing these postbacks non-stop. Since most custom JavaScript needs to fire right after page load, Virtual Earth's behavior creates a situation where you can never get to run your script(s). I did discover one sneaky way to get arround this. As part of the JavaScript that is used to launch Virtual Earth on a page you will find the following code block:
1: function CreateMap() {
2: map = new VEMap('VEMap');
3: map.onLoadMap = EventMapLoad;
4: map.LoadMap();
If you want to fire off your JavaScript, you can insert your launch script inside this block, either before or after the VE specific parts:
1: function CreateMap() {
2: // Your custom handler here
3: myCustomJavaScriptHandler();
4: // Virtual Earth stuff here
5: map = new VEMap('VEMap');
6: map.onLoadMap = EventMapLoad;
7: map.LoadMap();
8: }
In my case, I needed to attach events to DOM elements to get a MouseOver/MouseLeave and MouseDown/MouseUp UX effect. Because of the constant postbacks, I wasn't able to query the DOM to get pointers to nodes.In the end it turned out to just be easier to attach a bunch of onmouseover, onmouseleave, onmousedown and onmouseup events directly to the elements:
1: <asp:LinkButton ID="FindPlaceButton" CssClass="FindPlaceButton" runat="server" onmouseover="findPlaceOver()"
2: onmouseout="findPlaceOff()" onmousedown="findPlaceDown()" onmouseup="findPlaceOff()" />
Unfortunately this required individualized functions for each event for each element, but luckily I didn't need to do this for too many elements. This second approach precludes the possibility of modular, efficient code with reuse, but it works.
So, depending on what kind of JavaScript you need to accomplish when Virtual Earth is on the page, you can try either of these two routes. |
View in Web Browser /personal/rbiggs/Blog/_layouts/VisioWebAccess/VisioWebAccess.aspx?listguid={ListId}&itemid={ItemId}&DefaultItemOpen=1 0x0 0x1 FileType vdw 255 Compliance Details javascript:commonShowModalDialog('{SiteUrl}/_layouts/itemexpiration.aspx?ID={ItemId}&List={ListId}', 'center:1;dialogHeight:500px;dialogWidth:500px;resizable:yes;status:no;location:no;menubar:no;help:no', function GotoPageAfterClose(pageid){if(pageid == 'hold') {STSNavigate(unescape(decodeURI('{SiteUrl}'))+'/_layouts/hold.aspx?ID={ItemId}&List={ListId}'); return false;} if(pageid == 'audit') {STSNavigate(unescape(decodeURI('{SiteUrl}'))+'/_layouts/Reporting.aspx?Category=Auditing&backtype=item&ID={ItemId}&List={ListId}'); return false;} if(pageid == 'config') {STSNavigate(unescape(decodeURI('{SiteUrl}'))+'/_layouts/expirationconfig.aspx?ID={ItemId}&List={ListId}'); return false;}}, null); return false; 0x0 0x1 ContentType 0x01 898 Edit in Browser /_layouts/images/icxddoc.gif /personal/rbiggs/Blog/_layouts/formserver.aspx?XsnLocation={ItemUrl}&OpenIn=Browser&Source={Source} 0x0 0x1 FileType xsn 255 Edit in Browser /_layouts/images/icxddoc.gif /personal/rbiggs/Blog/_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser&Source={Source} 0x0 0x1 ProgId InfoPath.Document 255 Edit in Browser /_layouts/images/icxddoc.gif /personal/rbiggs/Blog/_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser&Source={Source} 0x0 0x1 ProgId InfoPath.Document.2 255 Edit in Browser /_layouts/images/icxddoc.gif /personal/rbiggs/Blog/_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser&Source={Source} 0x0 0x1 ProgId InfoPath.Document.3 255 Edit in Browser /_layouts/images/icxddoc.gif /personal/rbiggs/Blog/_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser&Source={Source} 0x0 0x1 ProgId InfoPath.Document.4 255 View in Browser /personal/rbiggs/Blog/_layouts/xlviewer.aspx?id={ItemUrl}&DefaultItemOpen=1 0x0 0x1 FileType xlsx 255 View in Browser /personal/rbiggs/Blog/_layouts/xlviewer.aspx?id={ItemUrl}&DefaultItemOpen=1 0x0 0x1 FileType xlsm 255 View in Browser /personal/rbiggs/Blog/_layouts/xlviewer.aspx?id={ItemUrl}&DefaultItemOpen=1 0x0 0x1 FileType xlsb 255 View in Browser /personal/rbiggs/Blog/_layouts/xlviewer.aspx?id={ItemUrl}&DefaultItemOpen=1 0x0 0x1 FileType ods 255 Document Set Version History javascript:SP.UI.ModalDialog.ShowPopupDialog('{SiteUrl}/_layouts/DocSetVersions.aspx?List={ListId}&ID={ItemId}') 0x0 0x0 ContentType 0x0120D520 330 Send To other location javascript:GoToPage('{SiteUrl}/_layouts/docsetsend.aspx?List={ListId}&ID={ItemId}') 0x0 0x0 ContentType 0x0120D520 350 |
|
|
|
|