It's usually a good idea in general to build your project as modular as possible (without getting too much in the way of productivity of course). With that said, if you can clearly identify a component or service you may want to use in the future and can easily be split off from the project you should put those into separate libaries. Some examples from our sites: database connectivity utilities (not the actual queries but the connection logic), logging, commenting which we use across multiple sites, exposing the APIs of sites. Usually anything we may want to reuse.

Static files can be neatly organized into folders based on their purpose. We do most our markup with client and server-side templates: one template per site, a master-template and subtemplates to insert into the holes. You can do soemthing like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
open WebSharper.Sitelets
open WebSharper.Html.Server

type MasterPage =
    {
        Title : string
        Header : Content.HtmlElement seq
        Body : Content.HtmlElement seq
        Footer : Content.HtmlElement seq
    }

let masterTemplate = 
    Content.Template<MasterPage>("~/Master.html")
        .With("title", fun x -> x.Title)
        .With("header", fun x -> x.Header)
        .With("body", fun x -> x.Body)
        .With("footer", fun x -> x.Footer)

let paymentsTemplate =
    Content.Template<string list>("~/pages/Payments.html")
        .With("payment-list", fun x -> x |> List.map (fun e -> LI [Text e]) |> UL)

let paymentsPage (ctx : Context<_>) payments =
    let p = paymentsTemplate.Run(payments, root = ctx.RootFolder)
    let pg =
        {
            Title = "Payments"
            Header = []
            Body = p
            Footer = []
        }
    Content.WithTemplate masterTemplate pg

Not a very comprehensive module but gives you an idea how to break down your pages into little templates and then compose them as needed.

On the client-side we usually have one template per form in UI.Next with as much holes as needed for reusability. Or if we use Piglets we separate the view and the model into different modules. We start with small Piglets and controls and compose them into bigger ones.

With UI.Next it's especially easy to make the code entangled with all the mutable state and dependencies between the variables. The best way there is (surpirse surpirse) to split your code into as little functions as possible to identify clear dependencies between them, and not have one huge function with a state you lose track of in minutes (this is usually the point when i decide to refactor my UI.Next code).

A very clear and "cheap" thing you can do is to factor your code into three main components: server-side, client-side, and remoting to glue them together. Remoting will sit between the server and client, and since the server never has (or can) call into client-side code (except when you insert the client-side code as Sitelet content).

For css and JS dependencies use WebSharper's dependency management so you don't have to keep track of them by hand in the html files.

Hope that helps you a bit to better otganize your code.

By on 8/13/2015 12:55 AM ()

Thank you for your explanation. It's the first time I see an usage of the template.Run function and I think is very powerful. My F# skills aren't very good by now, but I'm in love with it. Any sample of dependency management with IE conditional suport? For example:

1
2
3
4
<!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->

Thank you!

By on 8/13/2015 6:18 AM ()

Actually it is possible to create such custom resources. You can take a look at the JSON shim for IE7 in WebSharper itself here. In your case, since it's external scripts, it can be simplified to something like this:

1
2
3
4
5
6
7
8
9
10
module Re = WebSharper.Core.Resources

type Resource() =
    interface Re.IResource with
        member this.Render ctx html =
            let html = html Re.Scripts
            html.WriteLine "<!--[if lte IE 7.0]>"
      		html.WriteLine "<script src=\"https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js\"></script>"
      		html.WriteLine "<script src=\"https://oss.maxcdn.com/respond/1.4.2/respond.min.js\"></script>"
            html.WriteLine "<![endif]-->"
By on 8/14/2015 3:24 AM ()

Hmm your link doesn't show that code exactly but it does something similar. I think WebSharper documentation needs more detailed info about those low level objects.

By on 8/14/2015 5:15 AM ()

Yeah, we should probably add a couple common examples to the Render pseudo snippet on the Resources documentation page.

By on 8/14/2015 12:09 PM ()

Yes, basically what happens in the linked code is that Json.js and Json.min.js are embedded files in the project, declared as web resources here. WebSharper extracts such files to the Scripts subfolder of the web application. The method ctx.GetWebResourceRendering generates a <script> tag that points to the right path under /Scripts/....

Since in your case the resources are external URLs, it is not necessary to go through such complication: you can just emit the tags as static strings.

By on 8/14/2015 6:01 AM ()

I did'nt know you work IntelliFactory until now. I'm kinda envy! Loïc, can you share some best practices you think it's good to organize big websharper projects? File structure, usage of folders or libraries? Or do you think a very big project with all the code in a modular fashion per file is really enough? I'm looking at the soyrce code of ui next sample and it's kinda fuzzy, but it's because my lack of F# experience or can be better organized? Thank you!

By on 8/14/2015 11:28 AM ()

Sadly I don't think you can do conditional script includes like that with the dependency management. The only way I know of is to put those into the html file.

By on 8/13/2015 6:25 AM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper