XML Literals in JavaScript?
Posted by Chris | Filed under JavaScript, XML
If you're a web developer and haven't heard of E4X (EMCAScript for XML), it's a shame, but you're likely in the majority. From the Mozilla Developer Center link above:
ECMAScript for XML (E4X) is a programming language extension that adds native XML support to JavaScript. It does this by providing access to the XML document in a form that feels natural for ECMAScript programmers. The goal is to provide an alternative, simpler syntax for accessing XML documents than via DOM interfaces.
Translation? E4X allows you create variables out of well-formed inline XML:
var thumbXHTML = <div class="thumbContainer"> <div class="thumbImg"> <img src="summer.png" alt="Summer" /> </div> <div class="thumbCaption">Summer</div> </div>;
While the specification (ECMA-357)—standardized and published in 2005—makes the JavaScript example above standardized, chances are that your browser blows up after evaluting it. And that's a shame.
XHTML and Templating with E4X
What's so great about E4X and in-lining XML or XHTML in JavaScript that makes the missing support such a disappointment? The ability to create JavaScript templates.
Not only can you inline XML with E4X, but you can insert variables into the XML literals:
// Thumbnail object function Thumbnail(thumb) { return <div> <div class="thumbImg"> <img src="{thumb.imgsrc}" alt="{thumb.caption}" /> </div> <div class="thumbCaption">{thumb.caption}</div> </div>; }
Pass in a
thumbElement.innerHTML = new Thumbnail({ imgsrc: 'summer.png', caption: 'Mmm, summertime.' }).toXMLString();
In this way, we can generate the markup for multiple JavaScript objects that represent thumbnails. For example, one could post an Ajax request to a service, fetching an array of server-side Thumbnail objects serialized to JSON, and update the UI by templatizing the array objects using
ImagePage = { init: function() { ajax(..., function(thumbnails) { ImagePage.displayThumbnails(thumbnails); }); } displayThumbnails: function(thumbnails) { var innerHTML = []; for (thumb in thumbnails) { innerHTML.push(new Thumbnail(thumb).toXMLString()); } $('thumbContainer').innerHTML = innerHTML.join(''); } }
XML literals can also be manipulated with standard DOM node methods like
// This is the template function Table(fields) { var xmlTable = <table></table>; fields.forEach(function(field) { xmlTable.appendChild( <tr> <td class="cellLabel">{field.label}</td> <td class="cellValue"><input type="{field.type}" name="{field.name}"/></td> </tr> ); }); // Convert XML table object into DOM nodes var shell = document.createElement('div'); shell.innerHTML = xmlTable.toXMLString(); return shell.childNodes[0].cloneNode(true); } // Using the template... var addressTable = new Table([ { label: 'Name, First:', type: 'textbox', name: 'firstName' }, { label: 'Name:, Last', type: 'textbox', name: 'lastName' }, { label: 'Street Address', type: 'textbox', name: 'address' } ]); document.getElementById('adressTable').appendChild(addressTable);
There's a project that uses E4X for templating called seethrough that employs something similar to the rudimentary implementations of E4X templating I've shown.
There's tons of other cool features like the selectors in E4X and an
Support for E4X
Disappointingly, the only two organizations who have backed E4X by implementing the specification are Mozilla and Adobe: Mozilla has implemented E4X in SpiderMonkey and Rhino—their two JavaScript engines, and Adobe has implemented E4X in ActionScript for Flex, Flash, etc. All I can find on E4X for V8—Chrome's JS engine—are scattered posts in forums and usergroups asking about or requesting E4X support. (Visit Wikipedia's entry for a complete list of implementations.)
For web developers whose users may browse using IE or WebKit, that means E4X doesn't exist. There's always lag between committee development and market adoption, but the fact that it was standardized over four years ago does not bode well. Though I wish I were, I don't think I'd be mistaken by calling E4X stillborn offspring of Web 2.0. With XML being replaced more and more by JSON for web services and data interchange, the chances of E4X reemerging are even slimmer.
I showed just a couple ideas of how E4X could be used for templating—I'm sure that if E4X were ubiquitous, we'd see a lot more functional and elaborate templating systems. XML literals as primitives in JavaScript is quite the compelling and exciting idea, and I think I'm not the only one who dreams of writing JS with them.
Asana, a startup "building a Collaborative Information Manager that we believe will make it radically easier for groups of people to get work done," must think that the ability to inline XML is good idea too. The decorated fellows of Asana are developing LunaScript, an in-house language that makes XML literals first-class primitives:
return <table> <tr><td> messages.map(renderMessage) </td></tr> <tr><td> <img src=(session.user.small_pic_url) /> <div> <input data=session.user.name /> <b>' (your nickname)'</b> <form onsubmit=postMessage> <input data=session.new_comment hasFocus=true /> </form> </div> </td></tr> </table>;
At the moment I'm trying to figure out if any work is being done around implementing XML literals in JavaScript besides with E4X, but no success so far. For now, I guess we'll just have to live with alternative JS templating techniques (some of which are very good and similar to the example E4X techniques I've shown).
Only time will tell whether XML literals will ever appear in JavaScript. If the success of E4X tells us anything though, it may be never.
Tags: javascript xml xhtml primitives
JavaScript & ASP.NET: Executing on an Event onclick
Posted by Chris | Filed under ASP.NET, JavaScript
I've been doing a lot of front-end debugging with ASP.NET and JavaScript lately, and one thing has become very apparent: there's way too many ways to specify that something happens when you click on an link or other element.
First, you could specify what happens when an element is clicked using the classic but naive
<a href="#" onclick="doSomething(); return false;">A</a>
If the element is created by an ASP.NET control, there's the
<asp:Button OnClientClick="doSomething();" />
This simply generates:
<input ... onclick="test();" />
Again—bad idea. Adding on
You're best served by keeping behavior in the behavior layer—in JavaScript. If you're using a framework, forget about
<script type="text/javascript"> // MooTools example window.addEvent("domready", function() { $("element_name").addEvent("click", function() { alert("do something!"); }); }); // Or use $(element).click() with jQuery </script>
Or, JavaScript without a framework:
<script type="text/javascript">
document.getElementById("my_element_id").onclick = doSomething();
</script>Be consistent. Think of the people debugging your mess before you go around being inconsistent with how you wire up your events:
- If you're using a JavaScript framework, select your element then add an Event, and always place the <script> containing this at the end of your page. This will make it easy to find the click events for all your elements:
// jQuery <script type="text/javascript"> $(document).ready(function() { $("#element_name").click(function() { alert("do something!"); }); }); </script> - If you're not using a JavaScript framework, select your element then specify its
onclick , and always place the <script> at the end of your page. This will make it easy to find the click events for all your elements:<script type="text/javascript"> // Not using a framework? Better hope the DOM is ready! document.getElementById("my_element_id").onclick = doSomething(); </script>
- If you're looking to make enemies, specify the event by using an inline
onclick attribute in the tag. This will make it more difficult to find the click events for your elements:<a onclick="doSomething(); return false;">Clickity-click</a>
- If you're a real pain, use the codebehind to access the element and attach an
onclick via one of the dreadful options shown below. This will make it somewhat painful to find the click events for all your elements./* A) */ this.HyperLink1.Attributes["onclick"] = "doSomething();return false"; /* B) */ this.RegisterClientScriptBlock(...); /* C) */ this.RegisterStartupScript(...);
Tags: javascript onclick asp.net
.NET, Philosophically
Posted by Chris | Filed under .NET
Question: Who deserves a kick in the nuts more—the Microsoft BCL developer who brilliantly decided to name his
public static class UriHelper { public static bool TryParse(string s, out Uri uri) { try { uri = new Uri(s); return true; } catch { uri = null; return false; } } }
Really, can you blame the guy? Who knows—maybe he knows better and is actually sticking a big FU to the MS man for deviating from the TryParse idiom. We can always hope.
Speaking of, .NET 4.0 is getting
Tags: humor
Config files for DLLs
Posted by Chris | Filed under .NET, C#
Every once in a while I'll write a DLL and wish I could easily externally configure settings for it like you one do with web.config files and
I finally got around to doing something about not having this same functionality for class libraries. Now, with AssemblyConfig, you can access settings from a .config file for your DLLs just like you can with ASP.NET.
Config File Format
AssemblyConfig looks for config files that match the name of the assembly. If your assembly is Image.dll, create a config file called Image.dll.config. Use the same XML structure that you'd use with an ASP.NET application or .exe.configs:
<configuration> <appSettings> <add key="setting" vaue="settingValue" /> </appSettings> </configuration>
Retrieving Settings
Accessing settings is just as easy as with
string value = AssemblyConfig.Setting("key");
Or you can access settings via XPaths:
string copyright = AssemblyConfig.Setting("/configuration/site/copyright");
AssemblyConfig even lets you use generics to type the return value of a setting if the type you specify implements IConvertible*:
int cacheTimeout = AssemblyConfig.Setting<int>("CacheTimeOutInSecs"); DateTime copyStart = AssemblyConfig.Setting<DateTime>("/configuration/site/copyrightStart");
*Types that implement IConvertible are:
One last thing AssemblyConfig lets you do is access the entire config file using LINQ by returning the config's root
<configuration> <appSettings> <add key="setting" vaue="settingValue" /> </appSettings> <people> <person> <name... </person> </people> </configuration>
The people data can then be accessed using the
var people = AssemblyConfig.Root.Descendants("person");
Implementing AssemblyConfig
To use AssemblyConfig, add a reference to AssemblyConfig.dll in your project and call
Be on the lookout for a few exceptions when using AssemblyConfig. Instead of silently failing, like
DuplicateSettingException —if there are two settings with the same key or more than one element at a specified XPathConfigFileMissingException —if an attempt is made to retrieve a setting and there is no config fileConfigFileMalformedException —if the config file is not a valid XML document or is missing the<configuration> root
Pretty straight forward, no? It's a small and simple enough tool that I haven't created an open source project out of it, but if you have any good suggestions, I'd love to implement them and update the post here to reflect any changes.
Tags: web.config config file settings
Short GUID
Posted by Chris | Filed under .NET, C#
If you've used
AAD5E2E7-AD78-487a-9B37-458B9F1EE897
That may suite your purposes, but if you're case-sensitive, you can actually get a shortened version of the same GUID. Here's how:
string shortGuid = Convert.ToBase64String(Guid.NewGuid().ToByteArray()) .Substring(0, 22) .Replace("/", "_") .Replace("+", "-");
The output:
b14I850fIE6cuyv7VnP6WA
Or, as an extension method:
public static class GuidExtensions { /// <summary>Get a 22-character, case-sensitive GUID as a string.</summary> public static string ToShortString(this Guid guid) { return Convert.ToBase64String(guid.ToByteArray()) .Substring(0, 22) .Replace("/", "_") .Replace("+", "-"); } }
Used like:
string shortGuid = Guid.NewGuid().ToShortString();
That's 32 characters vs. 22. Nothing magical happens here:
Hope this helps if you're looking for a shorter global identifier!
Update: 11/16/09
zm asked how this could be converted back to a valid
With
string shortGuid = new ShortGuid(Guid.NewGuid());
To convert that short GUID back into a
Guid regularGuid = ShortGuid.Parse(shortGuid);
When you compare that they are equal, it's
Guid regularGuid = Guid.NewGuid(); string shortGuid = new ShortGuid(regularGuid); // Writes "True" Console.WriteLine(regularGuid == ShortGuid.Parse(shortGuid));
Here's the
public class ShortGuid { private readonly Guid guid; private readonly string value; /// <summary>Create a 22-character case-sensitive short GUID.</summary> public ShortGuid(Guid guid) { if (guid == null) { throw new ArgumentNullException("guid"); } this.guid = guid; this.value = Convert.ToBase64String(guid.ToByteArray()) .Substring(0, 22) .Replace("/", "_") .Replace("+", "-"); } /// <summary>Get the short GUID as a string.</summary> public override string ToString() { return this.value; } /// <summary>Get the Guid object from which the short GUID was created.</summary> public Guid ToGuid() { return this.guid; } /// <summary>Get a short GUID as a Guid object.</summary> /// <exception cref="System.ArgumentNullException"></exception> /// <exception cref="System.FormatException"></exception> public static ShortGuid Parse(string shortGuid) { if (shortGuid == null) { throw new ArgumentNullException("shortGuid"); } else if (shortGuid.Length != 22) { throw new FormatException("Input string was not in a correct format."); } return new ShortGuid(new Guid(Convert.FromBase64String (shortGuid.Replace("_", "/").Replace("-", "+") + "=="))); } public static implicit operator String(ShortGuid guid) { return guid.ToString(); } public static implicit operator Guid(ShortGuid shortGuid) { return shortGuid.guid; } }
Expiring Cache in C#
Posted by Chris | Filed under .NET, C#
Ever need a simple cache that automatically expires items, removing them after a given time has elapsed? Well now you've got it: ExpiringValueCache and ExpiringObjectCache. ExpiringValueCache is used to store value types. ExpiringObjectCache is used to store reference types.
A background thread continuously runs, checking for expired items in a generic Dictionary. The size of the collection does not affect the productivity of this background expiration process. The scalability of the caches is the same as that of a generic Dictionary of the items that you're storing.
First, to create one of these expiring caches, pass in a TimeSpan for which items should live before being automatically removed. All objects in the cache use the same TimeSpan as thier lifespan. Here are caches with one-hour expirations:
var peopleCache = new ExpiringObjectCache(new TimeSpan(1, 0, 0));
var idCache = new ExpiringValueCache(new TimeSpan(1, 0, 0));
Next, to add an item to the cache, call its Add method. The first argument is the key for the item, and the second argument is the item.
peopleCache.Add("chris", people[0]);
idCache.Add("chris", people[0].Id);
To check for the existence of an item or retrieve it, use the cache collection's indexer. For ExpiringObjectCache, the object will be returned if the item exists; null if it's not or no longer (i.e. it expired) in the cache:
var chris = peopleCache["chris"]; if (chris == null) { // Not in cache. Expired? } else { // In cache, do something... }
Use ExpiringValueCache the same way—it also returns null if the item doesn't exist. It does this by wrapping the result in a System.Nullable:
var chrisId = idCache["chris"]; if (chrisId == null) { // Not in cache. Expired? } else { var id = chrisId.Value; }
Lastly, since the caches have a background thread expiring stale items, remember to Dispose() of the cache when you're done with it. The thread is a background thread, so it won't prevent the process from terminating, but you should always cleanly dispose of your resources.
That's it. Suitably simple, I hope.
Recursively processings XML elements in C#
Posted by Chris | Filed under Uncategorized
Recursion can be tricky. But when you've nailed it, it's damn sexy. Below is a basic outline for recursively processing an XML document for all its child elements, and keeping track of what depth the element is at (how many ancestors, or parents, are above it):
void Process(XElement element, int depth) { // For simplicity, argument validation not performed if (!element.HasElements) { // currently at child element with no descendants } else { // currently at a parent element with children depth++; foreach (var child in element.Elements()) { Process(child, depth); } // currently leaving a parent depth--; } }
To begin processing a document, pass in the root element and a depth of 0:
Process(XDocument.Load(@"C:\test.xml").Root, 0);
Here's an example of recursively displaying an XML document in a console program:
using System; using System.Linq; using System.Xml.Linq; class Program { static void Main(string[] args) { XDocument.Load(@"C:\test.xml").Root.RecursivelyProcess ( Program.ProcessChild, Program.ProcessParentOpen, Program.ProcessParentClose ); Console.Read(); } static void ProcessChild(XElement child, int depth) { Console.WriteLine ( string.Format ( "{0}<{1}>{2}</{1}>", "".PadLeft(depth, '\t'), // {0} child.Name.LocalName, // {1} child.Value // {2} ) ); } static void ProcessParentOpen(XElement parent, int depth) { Console.WriteLine("".PadLeft(depth, '\t') + "<" + parent.Name.LocalName + ">"); } static void ProcessParentClose(XElement parent, int depth) { Console.WriteLine("".PadLeft(depth, '\t') + "</" + parent.Name.LocalName + ">"); } } /// <summary> /// Extension methods for the .NET 3.5 System.Xml.Linq namespace /// </summary> public static class XExtensions { /// <summary> /// Recursively perform operations on a XML element. /// </summary> /// <param name="element"></param> /// <param name="childAction"> /// What to do when an element with no children is reached. /// The XElement is the child element, the int is the depth. /// To do nothing, pass null. /// </param> /// <param name="parentOpenAction"> /// What to do when an element with children is reached. /// The XElement is the parent element, the int is the depth. /// To do nothing, pass null. /// </param> /// <param name="parentCloseAction"> /// What to do when finished processing element with children. /// The XElement is the parent element, the int is the depth. /// To do nothing, pass null. /// </param> public static void RecursivelyProcess ( this XElement element, Action<XElement, int> childAction, Action<XElement, int> parentOpenAction, Action<XElement, int> parentCloseAction ){ if (element == null) { throw new ArgumentNullException("element"); } element.RecursivelyProcess ( 0, childAction, parentOpenAction, parentCloseAction ); } private static void RecursivelyProcess ( this XElement element, int depth, Action<XElement, int> childAction, Action<XElement, int> parentOpenAction, Action<XElement, int> parentCloseAction ){ if (element == null) { throw new ArgumentNullException("element"); } if (!element.HasElements) { // Reached the deepest child if (childAction != null) { childAction(element, depth); } } else { // element has children if (parentOpenAction != null) { parentOpenAction(element, depth); } depth++; foreach (XElement child in element.Elements()) { child.RecursivelyProcess ( depth, childAction, parentOpenAction, parentCloseAction ); } depth--; if (parentCloseAction != null) { parentCloseAction(element, depth); } } } }
Get the XPath to an XML Element
Posted by Chris | Filed under .NET, XML
Problem
Given an XElement, what is its XPath?
Preface
Many XPaths can be used that will select an XML element. To select one and only one element, indexes need to be used. For example, given the following XML data:
<people> <person> <name> <first>Chris</first> </name> </person> <person> ... </people>
The XPath "/people/person" will select all the "person" elements that are children to "people." To refer to the first "person" element, its index needs to be used in the XPath.
Like an array, an [index] can be used to refer to a specific node. In XPath, indexes begin at 1, not 0. To select the first "person" element, then, the XPath "/people/person[1]" must be used.
I think of this as an "absolute" XPath, because it points directly to one specific element, and is not relative. Therefore, when I ask, "given an element, what is its XPath?", I am referring to its absolute XPath—an XPath expression that will always return a specific element and its children, if it exists.
Solution
The following code snippet uses extension methods to get an absolute XPath to an XElement. Call the
using System; using System.Linq.Xml; /// <summary>Extension methods for the .NET 3.5 System.Xml.Linq namespace</summary> public static class XExtensions { /// <summary> /// Get the absolute XPath to a given XElement /// (e.g. "/people/person[6]/name[1]/last[1]"). /// </summary> /// <param name="element"> /// The element to get the index of. /// </param> public static string AbsoluteXPath(this XElement element) { if (element == null) { throw new ArgumentNullException("element"); } Func<XElement, string> relativeXPath = e => { int index = e.IndexPosition(); string name = e.Name.LocalName; // If the element is the root, no index is required return (index == -1) ? "/" + name : string.Format ( "/{0}[{1}]", name, index.ToString() ); }; var ancestors = from e in element.Ancestors() select relativeXPath(e); return string.Concat(ancestors.Reverse().ToArray()) + relativeXPath(element); } /// <summary> /// Get the index of the given XElement relative to its /// siblings with identical names. If the given element is /// the root, -1 is returned. /// </summary> /// <param name="element"> /// The element to get the index of. /// </param> public static int IndexPosition(this XElement element) { if (element == null) { throw new ArgumentNullException("element"); } if (element.Parent == null) { return -1; } int i = 1; // Indexes for nodes start at 1, not 0 foreach (var sibling in element.Parent.Elements(element.Name)) { if (sibling == element) { return i; } i++; } throw new InvalidOperationException("element has been removed from its parent."); } }
JavaScript Date Ago
Posted by Chris | Filed under JavaScript
In case you hadn't noticed, one of the hip things to do with user-visible timestamps these days is to format them as a how long ago the time was. Instead of seeing a date and time like "08/02/2009 2:41 PM PST," these so-called passed/relative/pretty/friendly dates appear like "2 hours ago" or "1 week ago."
Being a hip person myself (hah), I've written a few bytes of code to get these type of dates. One is JavaScript (here), and another is a C# class.
Examples
| Call | Output |
| dateAgo("05/15/1983"); | |
| dateAgo(new Date().setMilliseconds(0)); | |
| dateAgo("Tue Nov 03 1992 19:16:10 GMT-0800"); | |
| dateAgo(1249524995962); |
When the time ago is less than a second, "X ms ago" is be displayed; when the time is less than a minute, "X.XX secs ago" is displayed; anything longer is rounded to the nearest minutes, hours, days, weeks, months, or years.
Tags: ago, date, JavaScript
Fade background color in JavaScript
Posted by Chris | Filed under JavaScript
Background
A project I've been working on recently is a light-weight AJAX messageboard that looks a lot like Reddit's or Hacker News' comments pages. The spiffy part though is that there's no refresh needed to see new replies since the page has loaded—new posts aysnchronously appear in the thread as users post them.
To make it obvious when a reply is posted, I planned to have the background of the reply be colored so it stands out, then slowly fade to transparent. jQuery's UI core can accomplish this via animate:
$("#element").css("background-color", "lightblue") .animate({ backgroundColor: "white" }, 30000);
But the background color fails to fade about 10% of the time when I have 10 or 20 elements that I'm fading all at once. Unfortunately, that error rate is too high for use in a production, released product. Instead of trying to debug the API and find where the bug is in jQuery, I thought I'd try to whip up the JavaScript for the fade effect myself.
It turned out to be pretty simple. The basic outline of the algorithm is to get a starting color and an ending color, find the difference between the colors, and divide that difference into incremental steps based on how long it should take to fade the background color. Then at very short intervals (ms), take steps toward the final color until the total time for the transition has elapsed.
Demonstration
Here's two demonstrations of the effect. The left fades from light blue to white over 10 seconds, and the second fades from red to blue over 3 seconds.
|
|
|
Script Usage
Here's the script. (It's also shown at the end of this post.) To use it, invoke the following:
fadeBackground('box1',[98,180,232],[255,255,255],5000);
Where
The script has no external dependencies like jQuery.
Customization
The code is simple enough to modify to fade other elements besides background color. Locate the two lines lines below in
document.getElementById(elementId).style.backgroundColor = "rgb(" + step[0] + "," + step[1] + "," + step[2] + ")"; ... document.getElementById(elementId).style.backgroundColor = "rgb(" + endColor[0] + "," + endColor[1] + "," + endColor[2] + ")";
And change
Script
function fadeBackground(elementId, startColor, endColor, timeMs) { var startTime = new Date().getTime(); function stepFade(elementId, startColor, endColor, startTime, endTime) { var now = new Date().getTime(); var step = new Array(3); var progress = (now - startTime) / (endTime - startTime); step[0] = Math.round(startColor[0] + ((endColor[0] - startColor[0]) * progress)); step[1] = Math.round(startColor[1] + ((endColor[1] - startColor[1]) * progress)); step[2] = Math.round(startColor[2] + ((endColor[2] - startColor[2]) * progress)); document.getElementById(elementId).style.backgroundColor = "rgb(" + step[0] + "," + step[1] + "," + step[2] + ")"; if (now <= endTime) { setTimeout(function() { stepFade(elementId, startColor, endColor, startTime, endTime); }, 20); } else { document.getElementById(elementId).style.backgroundColor = "rgb(" + endColor[0] + "," + endColor[1] + "," + endColor[2] + ")"; } } stepFade(elementId, startColor, endColor, startTime, startTime + timeMs); }
Tags: JavaScript