F# Bolero - A cóż to takiego? - HTML templates
Posty z tej serii:
- F# Bolero - A cóż to takiego? - wprowadzenie
- F# Bolero - A cóż to takiego? - HTML templates
- F# Bolero - A cóż to takiego? - view components
- F# Bolero - A cóż to takiego? - routing
- F# Bolero - A cóż to takiego? - routing - page models
- F# Bolero - A cóż to takiego? - remoting
Konstruowanie elementów UI w kodzie F#. Czy jest to dobre podejście? Kiedy pierwszy raz zobaczyłem taką możliwość, pomyślałem - ciekawe, ale nie jestem do końca przekonany. Z jednej strony mamy wsparcie kompilatora, który pilnuje poprawności wyniku. Z drugiej strony, każda, nawet najmniejsza zmiana wymaga re-kompilacji całości. Z trzeciej strony, dla osób, które piszą backend, re-kompilacja po zmianie to norma. Z czwartej strony, nie da się podzielić pracy tak, aby projektowaniem UI oraz przygotowaniem prototypu zajmowały się osoby od projektowania, a programowaniem całości zajmowały się osoby od programowania. Wszystko jest w kodzie F#, więc wszystko trzeba zaprogramować. Na chwilę obecną nadal podchodzę z pewną rezerwą do takiego podejścia, ale nie skreślam całkowicie.
Bolero umożliwia także alternatywny sposób konstruowania elementów UI - za pomocą tzw. HTML Templates. Do realizacji używamy standardowych znaczników HTML, w których możemy zawrzeć tzw. Holes. Służą one jako łącznik pomiędzy kodem UI napisanym w HTML, a logiką napisaną w F#. Dzięki temu możemy wyświetlać dynamiczne elementy na stronie oraz pobierać informacje, wprowadzane w formularzach. Nazywa się to bindowaniem danych. Możemy również wydzielać powtarzalne części za pomocą tzw. Nested Templates.
Zobaczmy, na przykładach, jak to wszystko działa.
Całą implementację znajdziesz na GitHub’e.
Simple Counter
W poprzedniej części stworzyliśmy prosty licznik, przesuwający wartość w dwie strony. Zamienimy kod renderujący widok tak, aby używał HTML Templates.
Tworzymy plik counter.html
z poniższym kodem i umieszczamy w katalogu wwwroot
:
Między znacznikami HTML znajdują się Holes ${Decrement}
, ${Value}
, ${Increment}
oraz ${Reset}
pod które podstawimy wartości w kodzie F#.
W pliku Main.fs
definiujemy typ, który będzie reprezentował Template:
Zamieniamy definicję funkcji view
z:
na:
Konstruktor Counter()
inicjalizuje Template. Bolero rozpoznaje Holes zdefiniowane w pliku counter.html
. Dla każdego tworzy odpowiednik w postaci metody z taką samą nazwą oraz pasującą sygnaturą. W ten sposób możemy zakodować zachowanie programu:
Decrement
,Increment
orazReset
reprezentują kliknięcie odpowiedniego przycisku na stronieValue
reprezentuje wyświetlaną wartość licznikaElt
jest wewnętrzną metodą Bolero, która konwertuje całą definicję do końcowego typu
Wszystko jest silnie typowane. Mamy pełne wsparcie kompilatora oraz podpowiadanie składni. W JetBrains Rider wygląda to tak:
Stan licznika wyświetlamy, wołając metodę Value
. Do parametru metody przekazujemy wartość model.value
, skonwertowaną z typu int
- taki przechowujemy w modelu, na typ string
- takiego spodziewa się metoda. Służy do tego operator o nazwie string
.
Zachowanie kodujemy, wykorzystując funkcję dispatch
, której podajemy odpowiedni Message
reprezentujący konkretną akcję.
A jak działa bindowanie? Sprawdźmy to, dodając do licznika funkcjonalność ustawiania liczby, która będzie wskazywać, o ile licznik ma się przesuwać.
Rozszerzamy counter.html
o możliwość wprowadzenia wartości:
Atrybut bind
łączy wartość znacznika input
z Hole ${Step}
. Przykład zawiera dodatkową funkcjonalność - wyświetlenie informacji o naciśniętym klawiszu - Holes ${KeyDown}
, ${Key}
oraz ${KeyCode}
Rozszerzamy model dodając elementy step
, key
oraz keyCode
:
Ustawiamy wartości początkowe initModel
:
Dodajemy Message’e:
SetStep
- wysyłany w momencie wprowadzania nowej wartości przesunięcia licznika - wartość podawana w parametrze typu int
KeyDown
- wysyłany w momencie naciśnięcia klawisza na klawiaturze - klawisz oraz jego kod podawane w parametrze typu string * string
- Tuple
Aktualizujemy funkcję update
:
Licznik przesuwany jest o wartość, zapamiętaną w model.step
. Reset
ustawia step
na wartość początkową. SetStep
ustawia wartość przesunięcia, a KeyDown
informacje o naciśniętym klawiszu.
Rozszerzamy funkcję view
:
Metoda Step
wyśle Message
aktualizujący wartość przesunięcia tylko wtedy, kiedy wprowadzony napis jest liczbą. W tym miejscu powinna być pełna walidacja, ale dla uproszczenia pomijamy ten element. Metody Key
oraz KeyCode
wyświetlają informacje o klawiszu. Metoda KeyDown
wysyła Message
aktualizujący informacje o naciśniętym klawiszu. Tak jak w poprzednim przykładzie, tak i tu, wszystko jest silnie typowane i wspierane przez kompilator F#.
Ostatecznie otrzymujemy program:
Zobaczmy kolejny przykład.
Enter Values
W sytuacji, kiedy pewne elementy zawierają tę samą strukturę kodu HTML, ale różnią się wartościami, warto stworzyć dla nich re-używalny komponent. W Bolero możemy to zrobić, używając Nested Templates. Zobaczmy, jak to działa na przykładzie programu wyświetlającego wprowadzane napisy.
Definiujemy Template:
Zasada działania jest taka sama jak w przykładzie z licznikiem - HTML + Holes:
${NewValue}
- reprezentuje wprowadzaną wartość${EnteredValues}
- reprezentuje listę wprowadzonych wartości${ClearValues}
- reprezentuje naciśnięcie przycisku czyszczącego wprowadzone wartości
Nowością jest <template id="ShowValue">
- definicja Nested Template. Template nie jest wyświetlony na ekranie, można go użyć w kodzie F#. Hole ${Value}
reprezentuje pojedynczą wyświetlaną wartość.
Druga nowość to zastąpienie atrybutu bind
przez bind-onchange
. Dzięki temu Message
wysyłany jest po naciśnięciu klawisza ENTER
, a nie po każdorazowym wprowadzeniu znaku.
Pozostaje zakodować logikę w F# wg standardowego schematu.
Model:
Wartości trzymamy jako listę napisów.
Init:
Message:
Zgłaszamy wprowadzenie nowej wartości lub wyczyszczenie wprowadzonych danych.
Template:
Ładowany z pliku enterValues.html
Update:
Operator @
pozwala połączyć dwie listy w jedną.
View:
Do Nested Template odwołujemy się po identyfikatorze zdefiniowanym w pliku .html, wg schematu - Template.NestedTemplate()
. W przykładzie jest to EnterValuesTemplate.ShowValue()
.
Wartości z listy, podajemy jako parametr do metody EnteredValues
. Każdy element wyświetlany jest jako osobny Nested Template.
Ostatecznie otrzymujemy program:
Hot Reloading
Wspólną cechą framework’ów webowych generujących wynik po stronie klienta, jest automatyczne odświeżanie widoku, w momencie zapisu zmienionego kodu. Nazywa się to Hot Reloading. Bolero również posiada taką możliwości. Po zapisaniu zmian w pliku *.html, następuje przeładowanie i wynik dostępny jest na stronie. Oczywiście, jeśli zmiany wprowadzamy w kodzie F#, to dalej trzeba dokonać kompilacji.
Dla mnie, jako backend’owca, kompilacja kodu po zmianach to standardowa, wręcz naturalna rzecz. Obecnie, gdy działam z Bolero nie używam mechanizmu Hot Reloading. Być może to się zmieni, kiedy będę pisał więcej kodu UI, zwłaszcza na etapie prototypowania interfejsów.
Generowany kod za pomocą dotnet new bolero-app -o MyApp
zawiera konfigurację Hot Reloading. Szczegóły, jak włączyć tę funkcjonalność we własnym projekcie znajdują się na stronie dokumentacji.
Tak wygląda programowanie w Bolero z użyciem HTML Templates. W następnym artykule zajmiemy się mechanizmem View Components.
=