Recently about JavaScript

Have you ever had an enum in your server code that you wanted to access in client side code? Or wanted the safety of compile time errors when there are discrepancies between your server-side and client-side code? Or had a C# Data Transfer Object (DTO) that you wanted to enable Intellisense for in JavaScript? I hadn't found a good solution either ... until now.

Compiling C# to What!?

Compiling C# code into JavaScript may seem foreign, but Script# is a mature technology that is absolutely worth a look. Our team has been using it very happily for about three months. We've found a number of benefits including:

  • Consistently working Intellisense
  • Better encapsulation
  • Simpler Object Orientation
  • Easier deployment (Script# compiles multiple files to a single, optionally minified file)
  • Safer refactoring
  • Simpler unit testing
  • Type safety; and
  • Design time syntax checking (no more mistyping a variable and accidentally declaring it to the global scope)

Scott Hanselman covered most of these topics in last month's Hanselminutes episode: Script# Compiles to JavaScript. If you have a spare car ride I definitely recommend the listen. But what wasn't covered were some additional benefits provided by an off the beaten path approach to this great technology. The main benefit is tighter server side C# to client side JavaScript integration. A secondary benefit is less of a dependency on Script#.

To the Command Line

Typically to get going with Script# you:

  1. Install the Script# Visual Studio plug-in
  2. File -> New project
  3. Select "Script Library" and
  4. Compile to generate JavaScript

Nice and easy. The down side to this approach is you can't easily include files outside of your project. Specifically, you can't include data transfer objects or enum's from your server side code. Furthermore, all of your team members must install Script#. If instead you compile with Script#'s "ssc.exe" command you obtain more control and get less dependency.

How-To

The how-to looks something like this:

  1. Download and install Script#
  2. Add a Class Library (not a Script# project)
  3. Project properties -> Build -> Advanced -> "Do not reference mscorlib"
  4. Ideally move the Script# files (C:\Program Files (x86)\ScriptSharp) locally to the solution and check them into source control to a Libs or something similar
  5. Remove all references, but add: ScriptSharp.dll, ScriptSharp.Web.dll, Script.Jquery
  6. Edit your .csproj to manually reference Script#'s mscorlib (right click, Unload project, Edit MyProject.csproj)

    <Reference Include="mscorlib, Version=0.7.0.0, Culture=neutral, PublicKeyToken=8fc0e3af5abcb6c4, processorArchitecture=MSIL">
      <
    SpecificVersion>True</SpecificVersion>
      <
    HintPath>..\Libs\ScriptSharp\v1.0\mscorlib.dll</HintPath>
    </
    Reference>


  7. Modify AssemblyInfo.cs and remove the following lines:

    [assembly: ComVisible(false)]
    [assembly: Guid("b5e2449f-193c-46d1-9023-9143618d8491")]

  8. Modify AssemblyInfo.cs and add the following:

    [assembly: ScriptAssembly("ScriptSharpDemoAssembly")

  9. Ensure it compiles in Visual Studio
  10. Create a batch script or PowerShell script that compiles using ssc.exe like this:

    ..\Libs\ScriptSharp\v1.0\ssc.exe ^
        /debug ^
        /out:MyScriptSharp.js ^
        /ref:"..\Libs\ScriptSharp\v1.0\Framework\mscorlib.dll" ^
        .\Properties\AssemblyInfo.cs ^
        .\Class1.cs

Notice the last two lines in the script are a list of the files that you want to include. The point of this exercise is that you can now include files outside of your main Script# project in that list.

Now for completeness if you put a simple static method in Class1.cs, something like this:

namespace MyScriptSharp
{
    public class Class1
    {
        public static string HelloWorld()
        {
            return "Hello World!";
        }
    }
}

Then run the batch file you should get something like this:

Type.registerNamespace('MyScriptSharp');

////////////////////////////////////////////////////////////////////////////////
// MyScriptSharp.Class1

MyScriptSharp.Class1 = function MyScriptSharp_Class1() {
}
MyScriptSharp.Class1.helloWorld = function MyScriptSharp_Class1$helloWorld() {
    return 'Hello World!';
}

MyScriptSharp.Class1.registerClass('MyScriptSharp.Class1');

Obviously you could get these results faster with approach #1. But now you have a lot more control.

Summary

The main downside to this approach is maintaining the batch file is a bit of a hassle. But the upsides are that you can include any file from your server-side C# code. And any changes in that server-side code are automatically reflected in your JavaScript. And any breaking changes in your server-side code generate compile time errors in your client side code. And furthermore none of your team members need to install Script#. For our team it's an easy tradeoff. What about for yours? Please share your thoughts in the comments or on twitter.

Looking through the web for standards on how to make your web pages more performant (i.e. using tools like YSlow!), one of the things that is mentioned is to put your css at the top and your javascript at the bottom. The reason to do this is so that your content will render faster (and be styled) and not have to wait for the parsing of the javascript in order to display.

Recently I have been getting into using jQuery UI a lot more. I like the common look and feel, the ability to theme it easily, and am excited for all of the new enhancements and widgets that are coming. The issue that I have found is that when jQuery UI initializes your widgets it adds the theming classes from the javascript. This causes the page content to render (in a very ugly unstyled fashion) and then it "pops" into the nice styled page. As a user also, I tend to hate this effect and find it very annoying. So I set out to see what I could do to fix this and still keep the site performant.

First attempt - pure CSS

At first I thought, well what if I just put the jQuery UI classes on the elements that I know will already receive those classes when jQuery UI ran? This worked well for some of the page but there were still issues:

  1. Some of the jQuery UI widgets add in extra DOM elements to accomplish the look they are going for, so this still had the jumping effect
  2. I was now bound to a specific version of jQuery UI because if they ever changed or tweaked their class names I would have to update all of my elements with the changes
  3. By adding the classes on the server I increased the page size and content that needed to be downloaded to the browser

Second attempt - rearrange the JS

Since the CSS way wasn't ideal and would cause a lot of maintenance I decided to see what would happen if I rearranged where I loaded the JS. I put all of the JS back in the head tag, so it would load (and parse) the JS before any content would display to the user. This solution was better, however, there was still a little jumping of the page before it was fully styled.

Third attempt - tweaking the JS calls

Finally, I took a look at how I was calling the jQuery UI widget initializers. I found that I was calling them inside of jQuery(window).load call. I then did some research and realized that the difference between jQuery(window).load and jQuery(document).ready is actually pretty significant if you have a lot of images. This is due to the fact that window load waits for the images to finish before being called where as document ready is called when the DOM is finished loading. Changing those calls to be inside of a jQuery(document).ready block did the trick and the page loads without any jumping.

So from this exercise I found that as the web world moves towards more and more javascript based UI's we will need to look more closely at these blanket performance statements and include usability in those measurements.

Extra nugget:

The pages in question above also load various content through ajax after the page loads. I found a small performance gain when I kept the jQuery UI initializers in the document ready block and moved the ajax calls into the window load block.

A common CSS problem is the need to crop and scale an image on a page. Scaling an image is easily done using the hight and width attributes of the image. Likewise cropping an image can be accomplished by using the image as a background of an element and setting the hight and width of the element. Both scaling and cropping an image is a bit more involved however, and if it's something that's done often it pays to have a class that handles the creation of cropped and scaled images. For this purpose I've written the Croppenscaler.

The idea here is to create a div element with a set size that has a nested div within that displays a relatively positioned image. I found that it was useful to have a hash of image data that defines the default size and crop position of an image. These values are adjusted when the frame is built so that no matter what scale the resulting frame has, the image is positioned in the same place.

CSS Classes

Let's examine the nessessary CSS classes first:


  .croppenscaler {
    display: inline-block;
    overflow: hidden;
  }

  .croppenscaler img {
    position: relative;
    border: none;
  }

The important attributes to note above are the use of overflow on the main croppenscaler, this ensures that the top frame has the intended size. The nested image is positioned relatively. All of the other CSS attributes are written in the style attribute because they have to be calculated when the frame is built.

The Croppenscaler Class:

The javascript below uses the Prototype framework, but it could just as easily use JQuery or raw javascript to accomplish the same thing.


var imageData = {
  kittah: {  width:900, top:-175, left:-165, url: 'http://www.nearinfinity.com/blogs/assets/jcato/images/Kittah.jpg' },
};

var Croppenscaler = Class.create();
Croppenscaler.prototype = {

  initialize: function(options) {
    var image = imageData[options.image];

    this.url = image.url;
    this.frameHeight = options.height || 400;
    this.frameWidth = options.width || 300;

    var scale = this.frameWidth / 300;

    this.imageWidth = (image.width) * scale;
    this.positionTop = (image.top) * scale;
    this.positionLeft = (image.left) * scale;
  },

  buildFrame: function() {

    var img = new Element('img',{src:this.url, 'class':'zoom'});
    img.setStyle({
      width: this.imageWidth+'px',
      top: this.positionTop+'px',
      left: this.positionLeft+'px',
    });

    var frame = new Element('div',{'class':'croppenscaler'});
    frame.update(img);
    frame.setStyle({
      height:this.frameHeight+'px',
      width:this.frameWidth+'px',
    });

    return frame;
  },
};

The class has two functions. The constructor takes a hash of options for the resulting frame; the image key and the height and width of the frame. In this version the constructor references an imageData hash to get the details about the image to display, but they could be passed into the function as well. The important thing is that the constructor has access to the height and width of the frame, as well as the default width of the image and the top and left attributes that define where the upper left corner of the image should be when it's displayed at its default width.

The buildFrame() function takes the calculated sizes and uses them to construct the frame div, with a nested image at a scale that fits within the frame, and positioned the same at any scale.

Then to use the class, create a new Croppenscaler with a hash that at least includes the image key, and an optional height and width, call the buildFrame() function and insert the resulting element onto the page.

var loadImages = function() {
  $('cats').insert(new Croppenscaler({image:'kittah'}).buildFrame());
  $('cats').insert(new Croppenscaler({image:'kittah',height:300}).buildFrame());
  $('cats').insert(new Croppenscaler({image:'kittah',width:400,height:250}).buildFrame());
  $('cats').insert(new Croppenscaler({image:'kittah',width:200,height:300}).buildFrame());
  $('cats').insert(new Croppenscaler({image:'kittah',width:100,height:150}).buildFrame());
}

The above code will then produce the clowder of cats below.

Today is the Near Infinity Spring Conference. We have one conference in the fall and one in the spring for all our developers as well as invited guests. Today I gave a presentation on CoffeeScript and shared the slides here.

This blog post will lay out some of steps for exporting an ExtJs GridPanel to Word XML.  This is useful if you want to save or print what's currently displayed in the grid.  I am writing a subsequent post with steps on how to actually generate a Word XML file, but for now I'll focus on defining a grid, getting the grid's state, sending the grid's state to a controller, and prompting the user to save the generated Word XML document.

Technologies used:
- Ruby on Rails
- mod_xsendfile for Apache2/Apache2.2
- ExtJs
- javascript

At a high level, the following happens:
1. Gather and send the GridPanel state/configuration to the controller.
2. The controller runs a query and generates Word XML based on the query results.
3. The Word XML file is presented to the user via an "Open/Save As" dialog using XSendFile.

Section 1: Getting the current state/configuration of the GridPanel

If you're familiar with ExtJs GridPanel's you know they can be configured by each user viewing the panel.  Columns can be added, removed, grouped, shortened, lengthened and repositioned.  To capture the GridPanel's current state and pass it to the exporter, I wrote the following javascript.  It calculates and returns an array containing the column order, which column data indexes belong to which titles, the grouped field (if any), the sorted field, the sorted direction, and the widths of each column.  You will see where this function is used when I define the GridPanel in the next section.


var prepareExport = function(gridStore, gridColumnModel) {
    var sort = null;
    var direction = null;
    if (gridStore.getSortState()) {
        sort = gridStore.getSortState().field();
        direction = gridStore.getSortState().direction;
    }
   
    var columnOrder = []; //ordered list of columns reflecting the GridPanel's state
    var columnsToTitles = {}; //map of ext field mappings to column titles
    var columnsToWidths = {}; //width of each column
    var totalWidth = 0;
    var groupByField = gridStore.groupField; //the store's current grouping field ('false' if not grouped)
   
    var storeReaderMapping = gridStore.fields.items;
   
    //check to see if the groupField has a corresponding mapping
    for (var i = 0; i < gridReaderMapping.length; i++) {
        if (gridReaderMapping[i].name == gridStore.groupField && gridReaderMapping[i].mapping != null) {
            groupByField = gridReaderMapping[i].mapping;
            break;
        }
    }
   
    //get the total width of the displayed columns (this will be used later when generating the Word XML)
    gridColumnModel.getColumnsBy(function(c) {
        if (!c.hidden) {
            totalWidth = totalWidth + c.width;
        }
    });
   
    //iterate over the grid's column model.  For displayed columns, get the ext field names (mappings) and column titles
    gridColumnModel.getColumnsBy(function(c) {
        if (!c.hidden) {
            for (var i = 0; i < gridReaderMapping.length; i++) {
                if (gridReaderMapping[i].name == c.dataIndex && gridReaderMapping[i].mapping == null) {
                    columnOrder.push(gridReaderMapping[i].name);
                    var key = gridReaderMapping[i].name;
                    columnsToTitles[key] = c.header;
                    columnsToWidths[key] = c.width/totalWidth;
                    break;
                }
                else if (gridReaderMapping[i].name == c.dataIndex && gridReaderMapping[i].mapping != null) {
                    //if the name and mapping are different (i.e. a mapping exists), we need to push the mapping and not the name
                    columnOrder.push(gridReaderMapping[i].mapping);
                    var key = gridReaderMapping[i].mapping;
                    columnsToTitles[key] = c.header;
                    columnsToWidths[key] = c.width/totalWidth;
                    break;
                }
            }
        }
    });
   
    return [columnOrder, columnsToTitles, groupByField, sort, direction, columnsToWidths];
};



Section 2: The GridPanel

Here's a basic ExtJs GridPanel.  Note the "exportButton" is defined in Section 3.

var gridReaderMapping = [
    {name: 'id'},
    {name: 'col_one'},
    {name: 'col_two', mapping: 'some_mapping'},
    {name: 'col_three'}
];

var gridColumnModel = new Ext.grid.ColumnModel({
    defaults: {sortable: true},
    columns: [
        {header: 'ID', dataIndex: 'id'},
        {header: 'Column One', dataIndex: 'col_one'},
        {header: 'Column Two', dataIndex: 'col_two'},
        {header: 'Column Three', dataIndex: 'col_three'}
    ]
});

var store = new Ext.data.GroupingStore({
    proxy: new Ext.dataHttpProxy({
        url: 'give/me/my/data'
        method: 'GET'
    }),
    reader: new Ext.data.JsonReader({
        root: 'data',
        totalProperty: 'total',
        messageProperty: 'message'
    }, gridReaderMapping)
});

var grid = new Ext.grid.GridPanel({
    id: 'gridpanel',
    ds: gridStore,
    cm: gridColumnModel,
    sm: new Ext.grid.RowSelectionModel({
        singleSelect: true
    }),
    tbar: [exportButton],
    view: etc, etc, etc, etc...
});


Section 3: The Export Button

When the export button is clicked it gathers all the necessary data from the GridPanel (columns, widths, etc) and passes that data along with query parameters to the controller.  The controller runs a query and generates the Word XML file.  Section 4 describes what happens when the Ajax request returns the data successfully.

As I said at the beginning of this post, I will follow on with another post about the actual Word XML generation.  The Word XML generation isn't too scary.  If you're impatient, here are a few tips on Word XML generation.
    1. You can start by creating a small Word XML file with a single table using MS Word or Open Office. 
    2. Open the XML file with the XML viewer/editor of your choice.  Your table will be buried somewhere between <w:body> and </w:body>.  All the other sections of the XML can remain the same. 
    3. You can create a series of templates for Word XML tables and rows based on the patterns you see in your Word XML sample.
    4. In your Word XML generator, substitute values into these templates to build tables.
    5. Insert the generated templates into a "master" template.

var exportButton = new Ext.Button({
    renderTo: this.wrap,
    listeners: {
        click: function (node, event) {
            var exportConfig = prepareExport(grid.getStore(), grid.getColumnModel());
           
            Ext.Ajax.request({
                url: 'url/to/query/for/data'
                params: {
                    'columnsToTitles': Ext.util.JSON.encode(exportConfig[1]),
                    'columnsToWidths': Ext.util.JSON.encode(exportConfig[5]),
                    'columnOrder[]': exportConfig[0],
                    'groupByExport': exportConfig[2],
                    'groupBy': gridStore.groupField,
                    'groupDir': 'ASC',
                    'sort': exportConfig[3],
                    'dir': exportConfig[4],
                    'export': true
                    'query': build_some_query_params_to_pass_to_the_controller
                },
                success: function(response, opts) {
                    window.location.href = 'path/to/exporter_controller/export_word_XML'
                },
                failure: function(response, opts) {
                    //output error message
                },
                scope: this
            });
        }
    }
});


Section 4:  Downloading the Word XML

Using XSendFile to prompt the user with an "Open/Save As" dialog box.

class ExportController < ApplicationController
    def export_word_XML
        filename = "#{X_SENDFILEPATH}/word_doc.xml"
        if (File.exist?(filename))
            send_file filename, :x_sendfile => true
        else
            render :nothing => true
        end
    end
end

Stay tuned for the upcoming Word XML generation blog post!