An EDSL for Emitting HTML Tables in Web Parts using F#
Embedded Domain specific languages (EDSL) are a great way of presenting functionality in a programming language for a solving a specific problem. Linq in VB.NET and C# is a great example. The F# language provides programming constructs for creating EDSLs – a good is example is this Logo implementation.
When writing web parts in C# for SharePoint the System.Web “table” class or “table” HTML elements are most often used for controlling layout (“div” aficionados keep your ears covered…). The code quickly gets ugly and difficult to manage – there’s no direct control to ensure correct matching of opening and closing of tags. So how about writing an EDSL that provides a syntax and constructs for doing this?
Following on from a previous post on creating Web Parts in F# let’s look first at how the EDSL might be used. First, the mundane stuff for declaring and creating the System.Web controls – this will create a label and two input boxes:
type public HelloWorldWP() = inherit WebPart() let labelRow1 = new Label() let inputRow1 = new TextBox() let inputRow2 = new TextBox() override x.CreateChildControls() = labelRow1.Text <- "Row 1 Label" x.Controls.Add(labelRow1) x.Controls.Add(inputRow1) x.Controls.Add(inputRow2)
The EDSL will now be used in the Render override to output the HTML to form the table. There will be two rows and two columns. Here’s the code:
override x.Render (writer:HtmlTextWriter) = let myTable = [ Row Cell Content (Control labelRow1) Cell Content (Control inputRow1) Row Cell Content (Static "Row 2 Static") Cell Content (Control inputRow2) ] let tedsl = new TableEDSL() tedsl.WriteTable(myTable, writer)
The “myTable” F# list contains the “statements” that comprise the EDSL “program”. They are:
- Row: Start a new table row.
- Cell: Start a new table cell in the current table row.
- Content: Output content to the current table cell. The content can be a System.Web control or static text.
Note that output of the “table” tag, and closing “tr” and “td” tags are implied in the language. Once the commands have been declared in the list “myTable”, an instance of the class “TableEDSL” is created and the method “WriteTable” executed to execute the statements in the list (“program”). This will result in the table being rendered in the web part:
Let’s now turn to how the language is created. Firstly, types are created to define what can be included in table (TableContent) and what can be included in a cell (CellContent) – these are the “statements” of the language used above:
type CellContent = | Static of string | Control of WebControl | InnerTable of TableContent list and TableContent = | Row | Cell | Content of CellContent
These are the only two types required to define the language. Note:
- These use F# discriminated union to declare what’s allowed in “CellContent” and “TableContent”.
- These types are self-referencing hence the use of “and”. Notice how a “CellContent” can contain a TableContent, and this allows a table cell to embed other tables.
- The “of” keyword declares the data type of the “statements” in the EDSL.
Now that the language is defined, a class need to be created that will interpret the language. First declare the class, some mutable values and two methods that check to see if “tr” and “td” tags need to be closed:
type TableEDSL() = let mutable haveCell = false let mutable haveRow = false member x.CheckCellClosure(writer:HtmlTextWriter) = if haveCell then writer.Write("</td>") haveCell <- false member x.CheckRowClosure(writer:HtmlTextWriter) = if haveRow then writer.Write("</tr>") haveRow <- false
The method “WriteTable” (which is called from the web part’s “Render” override) is passed the list of language “statements” and the HtmlTextWriter stream to which the HTML will be outputted:
member x.WriteTable (theTable, writer:HtmlTextWriter) = writer.Write("<table>") List.iter (fun ele -> x.Table (ele, writer)) theTable x.CheckCellClosure(writer) x.CheckRowClosure(writer) writer.Write("</table>")
- Outputs the “table” tag.
- Iterates across the list of language statements, calling the method “Table” on each statement in the list.
- Checks to see if there’s a cell to be closed through calling “CheckCellClosure”.
- Checks to see if there’s a row to be closed through calling “CheckRowClosure”
- Terminates the table.
Finally, we have the “Table” method that is called to execute each statement.
member x.Table (theTable, writer:HtmlTextWriter) = match theTable with | Row -> haveRow <- true x.CheckCellClosure(writer) writer.Write("<tr>") | Cell -> x.CheckCellClosure(writer) haveCell <- true writer.Write("<td>") | Content s -> match s with | Static str -> writer.Write(str) | Control ctl -> ctl.RenderControl(writer) | InnerTable tbl -> x.WriteTable(tbl, writer)
This method uses the “F#” match construct to provide implementations for each of the allowable statements in the language. The “Content” statement has an additional “match” to determine what type output is required. In the case of “InnerTable”, the method “Table” is called recursively.
Here’s a “program” that uses an “InnerTable”:
let myTable = [ Row Cell Content (Static "Embedded Table") Cell Content (InnerTable [ Row Cell Content (Static "Inner Table 1") Row Cell Content (Static "Inner Table 2") ]) ]
The table will be displayed as:
The language can easily be extended to, for example, allow CSS styles and other table formatting instructions to be applied to the table.
Click Here for a page showing all the source code.