Quick Tour

This page serves as a quick tour to get you up to speed quickly with Adam.JSGenerator.

First things first

Every snippet of JavaScript you'll ever need can be expressed as an object graph containing instances of descendants of the abstract Expression and Statement classes. In fact, Expression derives from Statement, as you can use any expression as a statement, but not vice versa. The result of using the helper methods and extensions in the library is always an object that derives from Statement, and therefore implements ToString() just like any System.Object would, but actually does something useful and thus can be passed to any method that accepts an object and calls System.ToString() on it, like TextWriter.Write(), TextWriter.WriteLine(), StringBuilder.Append(), String.Format(), and so forth.

Now that we get that out of the way, let's get kickin'.

The basics

Identifiers are very important in JavaScript. What's more important is that they're valid. So when you need an identifier, you want it to be valid. We create an identifier like this:

IdentifierExpression id = JS.Id("id");

This results in an instance of IdentifierExpression, can then be used whenever we need to supply this identifier, in any case when we need a statement or expression, but also when an object of type IdentifierExpression is required, for instance when accessing a property:

IdentifierExpression arr = JS.Id("arr");
IdentifierExpression length = JS.Id("length");
var prop = arr.Dot(length);

Placing an IdentifierExpression in a variable is a good idea, especially if we need to refer to the same identifier multiple times. This way we cannot have naming inconsistencies throughout our code. Luckily, we can use implicit conversion whenever we need an IdentifierExpression, so we can actually do this as well:

IdentifierExpression arr = "arr";
var prop = arr.Dot("length");

and for convenience, there's the JS.ParseId method:

var prop = JS.ParseId("arr.length");

Implicit conversion can also be used whenever an instance of Expression is needed, and works for integers, floats, strings and arrays:

Expression i = int.MaxValue;
Expression d= Math.Pi;
Expression s = "Hello, World!\n";
Expression arr = new [] {1, 2, 3};

Object Literals

JSON is syntactially a subset of object literals in JavaScript, in which you can do much more with it. An empty object literal can be created quite easily

Expression obj = new ObjectLiteralExpression();

and properties can be added by the fluent syntax:

obj = obj
    .WithProperty("name", "Dave")
    .WithProperty("function", "Developer");

This produces the following snippet:

{"name":"Dave","function":"Developer"};

This way gives you the most control, as you can add properties with names that are calculated. A quicker way is to use the JS.Object() method, that allows you to pass in either a dictionary with expression to expression mappings, or any object that has public properties, like an anonynous type:

Expression obj = JS.Object(new { name = "Dave", function = "Developer" });

It produces:

{name:"Dave","function":"developer"}

As you can see, it takes care of using "string" notation for properties whose names are not valid identifiers. You can make it explicitly produce valid JSON by giving ToString() some options:

obj.ToString(GenerateJavaScriptOptions.Json);

It also supports nested objects, arrays, arrays of objects, and so on. Have a look at the tests project to find more elaborate examples.

The JS static class

The JS static class is a helper class that gives you factory methods with practical names to create the expresion and statement objects that you need. We've touched them already, but here are some more examples:

Expression arr = JS.Array(1, 2, 3);
Expression function = JS.Function("name").Parameters("a", "b").Do(JS.Alert("alert!"));
Expression b = JS.Boolean(true);
Expression n = JS.New(JS.Id("Class"));
Expression n = JS.Null();
Expresson r = JS.Regex("/\d+/g");
Expression s = JS.Snippet("Not really JavaScript here.");
Expression decl = JS.Var(JS.Id("a").AssignWith(10));

Many more are available, as I'll demonstrate while we take a look at statements.

Statements

Other than expression, the statements that are available in Adam.JSGenerator are most easily created using the JS static helper class. They are the following:

JS.Break();
JS.Continue();
JS.Do().While();
JS.Empty();
JS.For().Do();
JS.For().In().Do();
JS.If().Then().Else();
JS.If().Then().ElseIf().Then().Else();
JS.Return();
JS.Switch().Case().Do().Case().Do().Default().Do();
JS.Throw();
JS.Try().Catch().Finally();
JS.While().Do();
JS.With().Do();

Most statements have one or more bodies to fill with statements. The conditional statement if for example, has two bodies: one in case the condition evaluates to true, and one in case the condition evaluates to falsy. For this purpose, it has two extension methods: Then() and Else(). Other statements that only have one body (such as the for loop statement) have an extension method named Do(). They all work in a similar way in that they take either an array of Statements (adorned with the 'params' keyword for convencience) or a sequence of statements. This produces a new instance of the same statement, with a copy of all the references to the specified statement's body, and adds the specified statements, so that the extension method does not produce side effects. This behavior is consistent among all statements.

Loop constructs

There are four loop statement constructs. A regular loop (for statement) has two varieties: the simple LoopStatement and the IteratorStatement. The following snippet produces a regular LoopStatement:

var a = JS.Id("a");
var loop = JS.For(JS.Var(a.AssignWith(5)), a.IsGreaterThan(0), a.PostDecrement())
    .Do(JS.Alert(a));

This produces the following output:

for(var a=5;a>0;a--)alert(a);

The second variety is when iterating over an object, using the for (...in...) statement. This is an IteratorStatement, and can be produced by starting out with a LoopStatement but using the In extension method:

var a = JS.Id("a");
var value = JS.Object(new { prop1 = "prop1", prop2 = "prop2" });
var iterator = JS.For(JS.Var(a)).In(value).Do(
    JS.Alert(a));

It should produce the following:

for(var a in {prop1:'prop1',prop2:'prop2'})alert(a);

The remaining two loop statements look almost the same, but produce different output: The DoWhileStatement produces a do-while statement and can be created using the JS.Do() static method, and the WhileStatement produces a while statement and can be created using a JS.While() static method call.

Conditional Statements

The first conditional statement, if, can be expressed as follows:

JS.If(condition).Then(trueStatements).Else(falseStatements);

In addition to this, you can also chain them, using ElseIf():

var @if = JS.If(condition).Then(trueStatements).ElseIf(condition2).Then(alsoTrueStatements)...

The other conditional statement, switch, works a bit differently. Observe an example:

var @switch = JS.Switch(expression)
    .Case(value, ...).Do(
        JS.Alert("message1"),
        JS.Break())
    .Case(value)
    .Case(value)
    .Case(value).Do(
        JS.Alert("message2"),
        JS.Break())
    .Default().Do(
        JS.Alert("default!"));

In this case, multiple values can be passed to the Case() extension method, which will add a series of empty case statements to the switch. You can also simply call Case() multiple times to the same effect. The Do() extension method will then add statements to the last added case statement, or to the default (created using the Default() extension method) which always must come last.

Exception Handling

Exception handling in JavaScript is accomplished using the try, catch and finally statements, which are represented by the ExceptionHandlingStatement class.

Leftovers

Last edited Aug 14, 2010 at 6:25 PM by DaveVdE, version 11

Comments

luisbocaletti Oct 27, 2010 at 5:59 PM 
The option to generate Json that you have documented doesn't work
obj.ToString(GenerateJavaScriptOptions.Json);

ToString expects a boolean and then a script option so it should be
obj.ToString(false, ScriptOptions.Json);

However calling this function doesnt generate nice json format instead it give me something like this:
\"property1\":\"value1\"

Am I missing something?