Git Product home page Git Product logo

fue's Introduction

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>"

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>"

Please note: For better work with HTML templates, literals syntax can be marked with both 'single quotes' or "double quotes"

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>"

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:

<!--Template basics-->
{{{value}}} - Static value
{{{function()}}} - Function value
{{{value1 |> fun1}}}

<!--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.

fue's People

Contributors

dzoukr avatar petejohanson avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.