Overview of WebSharper’s templating engine – Part 2, Data holes

This is the second post of our series dedicated to WebSharper’s templating engine. In the first and previous post, we introduced the concept of text holes, a very simple but rather limited way to inject content in your HTML using F#. Today we are going to cover a more advanced mechanism that allows to inject whole chunks of HTML at once: data holes. There are two ways to declare data holes in your HTML template file:

  • using the data-hole attribute to decorate a given HTML element. Your content will be injected underneath the decorated element as a new child.
  • using the data-replace attribute to decorate a given HTML element. In this case, the whole element will be replaced by the injected content.

Let’s go over the data-hole attribute first.

Playing with data-hole

I modified the AdvancedTemplate.html file a bit and added:

  • a new data-hole called ‘somedatahole’
  • a new CSS class ‘.someStyle’ that we will be using in a moment.

As usual, you can also browse the full code on my GitHub repo.

<head>
    <style>
        .someStyle {
            color: darkcyan;
            font-style: oblique
        }
    </style>
</head>
<body>
    <div data-hole="somedatahole"></div>
</body>
</html>

We also need to make a few modifications to our F# code so we can inject new content into the hole. This is done in the Main.fs file. Here are the important bits:

type AdvancedPage = {
    Title: string
    SomeTextHole: string
    SomeDataHole: list<Element>
}

let Template =
    Content.Template<AdvancedPage>("~/AdvancedTemplate.html")
        .With("title", fun x -> x.Title)
        .With("sometexthole", fun x -> x.SomeTextHole)
        .With("somedatahole", fun x -> x.SomeDataHole)

let Main context endpoint title someTextHole someDataHole : Async<Content<EndPoint>> =
    Content.WithTemplate Template {
        Title = title
        SomeTextHole = someTextHole
        SomeDataHole = someDataHole
    }

As you can see, I added a new SomeDataHole field to our AdvancedPage type and mapped it to ‘somedatahole’. SomeDataHole is of type list<Element> where Element is a WebSharper type that can represent HTML content. We are ready to make our first test. Let’s extend our TemplatesPage function from the Site module to inject some HTML content into our page:

let TemplatesPage context =
    AdvancedTemplating.Main 
        context
        EndPoint.Templates
        "Templating in WebSharper" // Our title hole
        "Just a test page to play with the HTML templating engine in WebSharper!" // Our sometexthole hole
        // Our server-side data-hole content starts here
        [
            H1 [Text "This is inserted via a data-hole attribute (server-side)"]
            H3 [Text "Let's test a few HTML elements like H3..."]
            H4 [Text "... or unsorted lists:"]
            UL [
                LI [Text "one item..."]
                LI [Text "Another item!"]
                LI [Attr.Class "someStyle"]
                    -< [Text "and yet another "]
                    -< [B [Text "bold item"]]
                    -< [Text " with a style attached"]
            ]
        ]

Note that this will generate the HTML on the server-side. The gives us the following result (the content of the data-hole is represented by the red frame):

You may be wondering what is the strange -< operator doing in the following piece of code:

LI [Attr.Class "someStyle"]
	-< [Text "and yet another "]
	-< [B [Text "bold item"]]
	-< [Text " with a style attached"]

This operator simply allows to construct a new element based on a list of other elements. Here, the three text elements are merged into one and the CSS class ‘someStyle’ is applied to it. We can check it by opening the web console in the browser:


Next on the list, the data-replace attribute.

Playing with data-replace

I modified the code further to illustrate how the data-replace attribute works. Here is the gist of it:

<!-- HTML file -->
<body>
    <div data-replace="somedatareplacehole"></div>
</body>
// AdvancedTemplating.Template function
.With("somedatareplacehole", fun x -> x.ServerSideDataReplaceHole)

// AdvancedTemplating.Main function
let Main context endpoint title someTextHole someDataHole serverDataReplace : Async<Content<EndPoint>> =
    Content.WithTemplate Template {
        Title = title
        SomeTextHole = someTextHole
        SomeDataHole = someDataHole
        ServerSideDataReplaceHole = serverDataReplace
    }

// And finally, Site.TemplatesPage function
let TemplatesPage context =
    AdvancedTemplating.Main 
        
        // rest of the arguments...

        // Our server-side data-replace content starts here
        [
            H1 [Text "This is inserted via a data-replace attribute (server-side)"]
        ]

Here is the result when we run the program and load the page again:In the web console, we can effectively see that the whole div element was replaced by  h1:


Small bonus with data-replace

While going through the official documentation and various posts on the forum, I also discovered that data-replace can also be used to generate HTML that automatically includes all external JavaScript or CSS files required to run your page. This intrigued me so I decided to investigate. It took me quite some time to get it right, so the below will hopefully save you some time. First, here are the links to the related documentation:

In short, the documentation states the following that using data-replace=”scripts” in your HTML template will auto-load the required CSS / JavaScript dependencies to your page. I added a new, separate custom_styles.css file to the project and decided to check if WebSharper would be smart enough to load it automatically on the pages that were dependent on it. I added the following configuration to register the new css file:

<!-- HTML file -->
<body>
    <div data-replace="somedatareplacehole"></div>
    <script data-replace="scripts"></script>
</body>
<!-- CSS file -->
.styleFromSeparateCssFile {
    color: darkmagenta;
    font-size: large;
    background: lightblue
}
// HTML to be generated
Div [
    H1 [Attr.Class "styleFromSeparateCssFile"] -< [Text "This is inserted via a data-replace attribute (client-side)"]
    P [Attr.Class "styleFromSeparateCssFile"] -< [Text "This content is generated on client-side by JavaScript code!"]
]
module Resources =
    open WebSharper.Core.Resources

    // Declare resource files.
    [<assembly:System.Web.UI.WebResource("custom_styles.css", "text/css")>]
    do ()

    // Declare types for automatic dependency resolution
    type Styles() =
        inherit BaseResource("custom_styles.css")

And used the following Required attribute on my Site.TemplatesPage function to let WebSharper know of the dependency:

[<Require(typeof<Resources.Styles>)>]
let TemplatesPage context =
// rest of the code...

But it didn’t work! I tried to add the Require attribute to other functions, modules an even to the whole assembly, without success. After reading the documentation in details once again, I managed to find out what was wrong:

  • The CSS file needs to be added as an Embedded Resource to the Visual Studio project.
  • The mechanism only works for client-side generated HTML!

I changed the F# code so the HTML would be generated on the client-side instead:

[<JavaScript>]
module Client =
	[<Require(typeof<Resources.Styles>)>]
	let ReplaceDataExample () =
	    Div [
	        H1 [Attr.Class "styleFromSeparateCssFile"] -< [Text "This is inserted via a data-replace attribute (client-side)"]
	        P [Attr.Class "styleFromSeparateCssFile"] -< [Text "This content is generated on client-side by JavaScript code!"]
	    ]
module Site =
    let TemplatesPage context =
        AdvancedTemplating.Main 
        
            // rest of the arguments...
            // Our client-side data-replace content starts here
            [Div [ClientSide <@ Client.ReplaceDataExample() @>]]

And tadaaa, it worked!

As shown below in the web console, WebSharper automatically added our custom_styles.css file to the page:

However, it didn’t add it to our root page (the one under localhost:9000) because the CSS file was not needed there! Cool stuff.

Let’s wrap up!

That’s all for this post about data-hole and data-replace attributes in WebSharper, I hope this helped you understand better the basics of WebSharper’s templating engine. There’s more to talk about on the subject but that will be the topic of a future post. Once again, you can find the full code of the application on my GitHub repo.

Cheers!

3 thoughts on “Overview of WebSharper’s templating engine – Part 2, Data holes

  1. Pingback: Overview of WebSharper’s templating engine – Part 1, Text holes – Youenn Bouglouan

  2. Loic Denuziere

    Pretty good explanation!

    Regarding resources: indeed, the [] attribute can only be applied to client-side content (or to a resource, to express that this resource depends on another). However you can still add a dependency on a resource to server-side code by adding the following directly inside the markup, as if it was a child element:

    new Web.Require()

    Reply
    1. Youenn Post author

      Hi Loic,
      Thanks for the info, I didn’t know you could achieve the same on the server-side too. I’ll try to play with it when I find a moment.
      Keep up the great work!

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *