The software that we write, here at ADAM Software NV, has to deal with JavaScript on more than a few occasions. We write web apps that need to produce short snippets that interact with our client side components & controls, but we also produce ActionScript (which is actually just plain JavaScript) to integrate with Adobe products.
Coding C# that produces JavaScript was, until now, an error prone task that primarily included concatenating a bunch of hardcoded strings with converted variables. Throw in a call to String.Format here and use a StringBuilder object there, and everything becomes a pretty unreadable mess. You’d have to make sure the names of variables are identical everywhere. You’d have to keep in mind that strings need to be properly quoted. And you’d have to match up the endless parade of brackets, be it square, curly or round.
No more.
I’d like to introduce a class library that we wrote, named Adam.JSGenerator. It’s ready, it’s open source and it’s available now on CodePlex.
How does it work?
Instead of having to write string building code yourself, every piece of JavaScript that you’ll need will be an object tree representing that JavaScript. Similar to an Abstract Syntax Tree (AST), the sort of data structure used by parsers and compilers, this tree consists of objects that each represent a grammatical part of the JavaScript you’re trying to express and the relationships between them.
Most of the classes can be divided into two groups: Expressions (everything that descends from the abstract class Expression) and Statements (everything that descends from the abstract class, you guessed it, Statement). The Expression class itself descends from Statement, because in JavaScript every expression can be used as a statement, but not the other way around.
By combining objects of these classes you can make almost any JavaScript expression or statement that you need. I say almost, because we wrote those classes that we need, but I would guesstimate that we cover about 90% of JavaScript 1.5. But these missing parts can be easily added, if you need them.
Using an object tree gives us the following advantages:
- As each object is responsible for producing the JavaScript that it represents, it will take care of quoting, matching brackets, in short making sure that the output is syntactically correct, for you.
- These objects can better check for mistakes and respond accordingly. Identifiers have to be valid, for example.
- You get better compiler and IntelliSense support to produce better code faster with less mistakes. You can’t assign an ‘if’ statement to a variable, for instance.
The root object of your tree overrides the standard ToString() method to produce the end result, so you can pass it to any Write() or Append() method that takes a string or an object. There’s even some overloads to give you more options.
Of course productivity doesn’t come by itself. You’ll need something else to help you write expressive code. With the help of a bunch of static classes and extension methods you’ll be able to write up code that produces JavaScript snippets in a flexible, expressive, dare I say it, ‘fluent’ manner.
Fluent?
For every Expression or Statement subclass there’s either a static method in the JS static class or an extension method in one of the static Helper classes that allows you to quickly write the sort of expression or statement that you’ll need. Most parameters are optional; if JavaScript doesn’t need it, you won’t need to write it, so it’s fairly trivial to write an infinite loop that does nothing, for example. Furthermore, we’ve been adding a couple of extra methods that produce expressions that are frequently used with JavaScript frameworks like jQuery and Microsoft AJAX, and you can add your own very intuitively.
The base class Expression has some implicit conversion operators, so whenever you need to pass in an Expression object (like the condition to an ‘if’ statement) you can simply pass in a number, string or Boolean. The IdentifierExpression also has an implicit conversion from string, so when you need to pass in an IdentifierExpression (like the name of a new Function object) you can pass in a string as well.
Any method that takes an array of statements or expressions also takes in an IEnumerable, so you can use LINQ to objects to convert a series of values into sensible expressions or statements and pass those in as well.
The JS.Object() method turns any object that you pass it into an object literal. Arrays are converted and reflection is used on objects to find their public properties, so you can use anonymous types and get an ever closer resemblance to object literals. There’s also an option to produce legal JSON, if you need it.
Using a fluent syntax, you can chain most of these expressions and produce code that starts to look like JavaScript. It’s as if the JavaScript becomes a domain specific language in C#. Care has been taken to make sure that none of these extension methods have side effects, so pieces of the puzzle can be reused.
Enough talking. I want to see some examples!
Well, the following snippet of code:
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
var images = new[]
{
"Images/Image1.jpg",
"Images/Image2.jpg",
"Images/Image3.jpg"
};
var properties = new {images, duration = 3000};
return JS.Id("$create").Call(
JS.ParseId("Adam.Controls.SlideShow"),
JS.Object(properties),
null,
null,
JS.Get("ClientId"));
|
Produces the following JavaScript: (formatting added for readability)
| JavaScript |
1
2
3
4
5
6
7
8
|
$create(Adam.Controls.SlideShow,{
images:[
"Images/Image1.jpg",
"Images/Image2.jpg",
"Images/Image3.jpg"
],
duration:3000
},null,null,$get("ClientId"));
|
More examples can be found in the accompanying demonstration program.
What doesn’t it do?
It doesn’t compile your C# code into JavaScript, like the Script# project does. It produces code that produces JavaScript at runtime, not at compile time.
It’s also not a good replacement for static scripts. It’s not the right tool for that. You’re better off writing proper scripts and serve them as content or resource from your app.
It doesn’t produce nicely formatted readable JavaScript; it tacks on the pieces as required without regard to readability.
Where do I start?
Go to the project page. Download the installer or browse the source. Include in your project. Have a look at the examples. Fork, split, modify, do as you see fit. If you break it, you get to keep both pieces.
We’re using it in more and more of our products. We’ll maintain it, extend it, fix bugs and modify it as we see fit, but for now it’s stable and very usable. We’re always interested in what you have to say.