• Stars
    star
    117
  • Rank 301,828 (Top 6 %)
  • Language
    F#
  • License
    Apache License 2.0
  • Created almost 8 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

F# templating library with simple syntax designed for smooth work with F# types.

Build Status

Fue

F# templating library with simple syntax designed for smooth work with F# types.

Why another templating library?

We have Razor, we have DotLiquid - why another templating library? I know, "rendering on server side is so 2010", but sometimes we just need (or want) to do it - for emails, for documents, even for HTML (yes, some oldschoolers still do it on server). And then pain starts: You need to have plenty of ViewModels to transform data from Discriminated Unions, Tuples, etc...

Wouldn`t be just great to have a library that allows you to use your original data without annoying only-for-template transformation? Good news! Fue was designed as ViewModels |> NoMore library with focus on minimalistic API.

Installation

First install NuGet package

Install-Package Fue

or using Paket

nuget Fue

Basic templating

Before we start, open these two modules:

open Fue.Data
open Fue.Compiler

Now we need storage for our view data. Function init from Fue.Data module is here for you. Once initiated, you can store values in it.

init |> add "name" "Roman"

Having data prepared, lets start rendering our values using simple {{{myValue}}} syntax.

init |> add "name" "Roman" |> fromText "{{{name}}}" // compiles to value "Roman"

Full example:

let html = "<div>{{{name}}}</div>"
let compiledHtml = init |> add "name" "Roman" |> fromText html
// compiledHtml now contains "<div>Roman</div>"

If a variable is not defined, it gets rendered as is - including the braces.

let html = "<div>{{{name}}}</div>"
let compiledHtml = init |> fromText html
// compiledHtml now contains "<div>{{{name}}}</div>"

However, if you want to have a default value in case the variable is not defined or null, you can do that with ??:

let html = "<div>{{{name ?? "Roman"}}}</div>"
let compiledHtml = init |> fromText html
// compiledHtml now contains "<div>Roman</div>"

Wanna use functions? No problem!

let html = "<div>{{{getName()}}}</div>"
let compiledHtml = init |> add "getName" (fun _ -> "Roman") |> fromText html
// compiledHtml now contains "<div>Roman</div>"

Or pipe forward operator?

let html = "<div>{{{myParam |> multiply}}}</div>"
let compiledHtml =
    init
    |> add "myParam" 10
    |> add "multiply" (fun x -> x * 2)
    |> fromText html
// compiledHtml now contains "<div>20</div>"

And combine own functions with literals.

let html = """<div>{{{printHello("Roman", myValue)}}}</div>"""
let compiledHtml =
    init
    |> add "printHello" (fun name1 name2 -> sprintf "Hello %s and %s" name1 name2)
    |> add "myValue" "Jiri"
    |> fromText html
// compiledHtml now contains "<div>Hello Roman and Jiri</div>"

You can also pass records to functions.

let html = """<div>{{{printHello({ name = "Roman" })}}}</div>"""
let compiledHtml =
    init
    |> add "printHello" (fun name1 name2 -> sprintf "Hello %s and %s" name1 name2)
    |> add "myValue" "Jiri"
    |> fromText html
// compiledHtml now contains "<div>Hello Roman and Jiri</div>"

Please note: For better work with HTML templates, literals syntax can be marked with both 'single quotes' or "double quotes" and """tripple quotes""" if you need multiline text

Record values can contain any other value including records.

{
    a: "bar" // literal as value
    b: abc   // variable as value
    c: abc() // function call
    d: "abc" |> abc // pipes
    c: {     // nested record
       a: "bar" 
       ....
    }
}

Supported types

Fue is designed to work with classes, records, tuples, options, discriminated unions as well as anonymous functions.

type MyRecord = { Name : string }
let html = """<div id="{{{id}}}">{{{fst myTuple}}} {{{myRec.Name}}}</div>"""
let compiledHtml =
    init
    |> add "myTuple" ("Hello", 35)
    |> add "fst" fst
    |> add "myRec" { Name = "John"}
    |> add "id" "someId"
    |> fromText html
// compiledHtml now contains """<div id="someId">Hello John</div>"""

Rendering from file

Common usage of template engines is to have templates separated as files. Function fromFile is here for you.

let compiledHtml =
    init
    |> add "someValue" "myValue"
    |> fromFile "relative/or/absolute/path/to/file.html"

Conditional rendering

True power of Fue library is in custom attributes which can affect how will be template rendered.

fs-if

Simple if condition attribute.

let html = """<div fs-if="render">This DIV won`t be rendered at all</div>"""
let compiledHtml =
    init
    |> add "render" false
    |> fromText html
// compiledHtml is empty string

fs-else

Simple else condition attribute.

let html = """<div fs-if="render">Not rendered</div><div fs-else>This will be rendered</div>"""
let compiledHtml =
    init
    |> add "render" false
    |> fromText html
// compiledHtml is "<div>This will be rendered</div>"

Please note: Else condition must immediately follow If condition.

fs-du & fs-case

Condition based on value of Discriminated Union.

type Access =
    | Anonymous
    | Admin of username:string

let html =
    """
    <div fs-du="access" fs-case="Anonymous">Welcome unknown!</div>
    <div fs-du="access" fs-case="Admin(user)">Welcome {{{user}}}</div>
    """
let compiledHtml =
    init
    |> add "access" (Access.Admin("Mr. Boosh"))
    |> fromText html
// compiledHtml is "<div>Welcome Mr. Boosh</div>"

Of course, if you do not need associated case values, you can ignore them.

let html =
    """
    <div fs-du="access" fs-case="Anonymous">Welcome unknown!</div>
    <div fs-du="access" fs-case="Admin(_)">Welcome admin</div>
    """
let compiledHtml =
    init
    |> add "access" (Access.Admin("Mr. Boosh"))
    |> fromText html
// compiledHtml is "<div>Welcome admin</div>"

Fue syntax allows you to do not extract any value (even if there is some associated).

let html =
    """
    <div fs-du="access" fs-case="Anonymous">Welcome unknown!</div>
    <div fs-du="access" fs-case="Admin">Welcome admin</div>
    """

fs-template

Non-rendered placeholder for text. Use with combination of other Fue attributes.

let html = """<fs-template fs-if="render">Rendered only inner Html</fs-template>"""
let compiledHtml =
    init
    |> add "render" true
    |> fromText html
// compiledHtml is "Rendered only inner Html"

List rendering

fs-for

For-cycle attribute

let html = """<ul><li fs-for="item in items">{{{item}}}</li></ul>"""
let compiledHtml =
    init
    |> add "items" ["A";"B";"C"]
    |> fromText html
// compiledHtml is "<ul><li>A</li><li>B</li><li>C</li></ul>"

Common task for rendering lists is to show row number, index or whole list length. Auto-created values {{{$index}}}, {{{$iteration}}}, {{{$length}}}, {{{$is-last}}} and {{{$is-not-last}}} are here to help.

let html = """<li fs-for="i in items">{{{i}}} is {{{$index}}}, {{{$iteration}}}, {{{$length}}}</li>"""
let compiledHtml =
    init
    |> add "items" ["A";"B";"C"]
    |> fromText html
// compiledHtml is "<li>A is 0, 1, 3</li><li>B is 1, 2, 3</li><li>C is 2, 3, 3</li>"

Sometimes you don't want to render all items of a list. You can either prefilter the items fs-for"i in filter(items)" or alternatively you can combine fs-for with fs-if

let html = """<li fs-if"filter(i)" fs-for="i in items">{{{i}}} is {{{$index}}}, {{{$iteration}}}, {{{$length}}}</li>"""

Since version 1.2.0 there is support for tuples destructuring:

let html = """<li fs-for="greetings,target in items">I say {{{greetings}}} to {{{target}}}</li>"""
let compiledHtml =
    init
    |> add "items" [("Hi","World");("Hello","Planet")]
    |> fromText html
// compiledHtml is "<li>I say Hi to World</li><li>I say Hello to Planet</li>"

Working with Option types

Option types are fully supported and you can use them as you would directly from F# code.

let html = """<div fs-if="myOptionValue.IsSome">I got {{{myOptionValue.Value}}}</div>"""
let compiledHtml =
    init
    |> add "myOptionValue" (Some "abc")
    |> fromText html
// compiledHtml is "<div>I got abc</div>"

or

let html = """<div fs-if="myOptionValue.IsNone">I got nothing</div>"""
let compiledHtml =
    init
    |> add "myOptionValue" None
    |> fromText html
// compiledHtml is "<div>I got nothing</div>"

Cheatsheet

Simple HTML snippet to show what can be achieved using Fue:

<!--Literals-->
{{{'single quoted literal'}}}
{{{"single line literal"}}}
{{{"""multi-line
literal"""}}}

<!--Template basics-->
{{{value}}} - Static value
{{{value ?? "default value"}}} - Default Value
{{{function()}}} - Function value
{{{value1 |> fun1}}}
{{{ { key = value; key2 = value2 } }}}
{{{ {
    key = value
    key2 = value2
} }}}

<!--For-cycle-->
<li fs-for="item in items">
    {{{item.Name}}} {{{$index}}} {{{$length}}} {{{$iteration}}}
    <div fs-if="$is-not-last">Shown if not last item of collection</div>
    <div fs-if="$is-last">Shown only for the last item of collection</div>
</li>

<!--For-cycle with direct tuple destructuring-->
<!--let items = [("Hi","World");("Hello";"Planet")]-->
<li fs-for="greetings,target in items">
    I say {{{greetings}}} to {{{target}}}
</li>

<!--Condition-->
<div fs-if="someCondition" id="{{{id}}}">Value</div>
<div fs-else></div>

<!--Option types-->
<div fs-if="someOption.IsSome">{{{someOption.Value}}}</div>
<div fs-if="someOption.IsNone">Nothing</div>

<!--Discriminated Union
type UserAccess =
    | Anonymous
    | Admin of section:string
-->
    <div fs-du="item" fs-case="Anonymous">Anonymous</div>
    <div fs-du="item" fs-case="Admin(section)">{{{section}}}</div>

<!--Placeholder-->
<fs-template fs-if="someCondition">
    Some value
</fs-template>

Used libraries

Fue is based on amazing Html Agility Pack library.

Contribution

Did you find any bug? Missing functionality? Please feel free to Create issue or Pull request.

More Repositories

1

Dapper.FSharp

Lightweight F# extension for StackOverflow Dapper with support for MSSQL, MySQL, PostgreSQL, and SQLite
F#
369
star
2

CosmoStore

F# Event store for Azure Cosmos DB, Table Storage, Postgres, LiteDB & ServiceStack
F#
168
star
3

SAFEr.Template

Strongly opinionated modification of amazing SAFE Stack Template for full-stack development in F#.
F#
101
star
4

Yobo

F# Yoga Class Booking System
F#
94
star
5

Feliz.Bulma

Bulma UI (https://bulma.io) wrapper for amazing Feliz DSL
F#
66
star
6

Feliz.DaisyUI

Feliz wrapper for DaisyUI TailwindCSS component library
F#
47
star
7

Funcaster

⚡ Serverless .NET solution for hosting your 🔊 podcasts with (nearly) zero costs using Azure Functions and Azure Storage Account.
F#
46
star
8

Talks

Talks, lightning talks and presentations
F#
22
star
9

Shaver

Lightweight F# library for Suave.io web server using Razor engine adding some extra features like template composing, custom return codes or localization resources support.
F#
22
star
10

Feliz.Quill

Quill rich text editor extension for Feliz
F#
20
star
11

SQLDumper

Dump your MSSQL database into file or stream using F# library or .NET CLI tool
F#
14
star
12

OpenAPIParser

Simple Open API F# Parser
F#
11
star
13

Tables.FSharp

Lightweight F# extension for the latest Azure.Data.Tables SDK
F#
10
star
14

FuncasterStudio

Free Docker-based solution for managing your 🔊 podcasts hosted with ⚡ Funcaster!
F#
6
star
15

MoonServerSpecification

Open API specification for simple file-based data (markdown, text, ...) publishing server
5
star
16

Workshops

F#
3
star
17

Hlasky

Legendary audio samples at one place
F#
1
star
18

Dockerfiles

Various dockefiles for Docker Hub automated builds
Dockerfile
1
star
19

WUGDays2023

F#
1
star
20

MindfulYoga

F#
1
star
21

WUG2020Book

Demo F# application for WUG 2020 talk
F#
1
star
22

FSharp.Rop

Helper functions for F# Result type
F#
1
star