diff options
author | Hayleigh Thompson <me@hayleigh.dev> | 2023-09-09 19:17:47 +0100 |
---|---|---|
committer | Hayleigh Thompson <me@hayleigh.dev> | 2023-09-09 19:17:47 +0100 |
commit | 87344e98c9bd5f1b25e757a568da28798c60ce1b (patch) | |
tree | 124e7ffab022a3d3dfb6ef7f4bfe73acab3466df | |
parent | f3785a6ee2206decc3785fd8d519d2056cf13f4c (diff) | |
download | lustre-87344e98c9bd5f1b25e757a568da28798c60ce1b.tar.gz lustre-87344e98c9bd5f1b25e757a568da28798c60ce1b.zip |
:recycle: Differentiate between attributes and properties when rendering to string or string_builder.
-rw-r--r-- | lib/src/lustre/attribute.gleam | 115 |
1 files changed, 17 insertions, 98 deletions
diff --git a/lib/src/lustre/attribute.gleam b/lib/src/lustre/attribute.gleam index 459a86e..7c4aadf 100644 --- a/lib/src/lustre/attribute.gleam +++ b/lib/src/lustre/attribute.gleam @@ -1,9 +1,10 @@ // IMPORTS --------------------------------------------------------------------- import gleam/dynamic.{Dynamic} +import gleam/function import gleam/int import gleam/list -import gleam/option.{Option} +import gleam/result import gleam/string import gleam/string_builder.{StringBuilder} @@ -13,112 +14,28 @@ import gleam/string_builder.{StringBuilder} /// or event handlers. /// pub opaque type Attribute(msg) { - Attribute(String, Dynamic) - Event(String, fn(Dynamic) -> Option(msg)) + Attribute(String, Dynamic, as_property: Bool) + Event(String, fn(Dynamic) -> Result(msg, Nil)) } // CONSTRUCTORS ---------------------------------------------------------------- /// -/// Lustre does some work internally to convert common Gleam values into ones that -/// make sense for JavaScript. Here are the types that are converted: -/// -/// - `List(a)` -> `Array(a)` -/// - `Some(a)` -> `a` -/// - `None` -> `undefined` -/// pub fn attribute(name: String, value: String) -> Attribute(msg) { - escape("", value) - |> dynamic.from - |> Attribute(name, _) + Attribute(name, dynamic.from(value), as_property: False) } /// pub fn property(name: String, value: any) -> Attribute(msg) { - Attribute(name, dynamic.from(value)) -} - -fn escape(escaped: String, content: String) -> String { - case string.pop_grapheme(content) { - Ok(#("<", xs)) -> escape(escaped <> "<", xs) - Ok(#(">", xs)) -> escape(escaped <> ">", xs) - Ok(#("&", xs)) -> escape(escaped <> "&", xs) - Ok(#("\"", xs)) -> escape(escaped <> """, xs) - Ok(#("'", xs)) -> escape(escaped <> "'", xs) - Ok(#(x, xs)) -> escape(escaped <> x, xs) - Error(_) -> escaped <> content - } + Attribute(name, dynamic.from(value), as_property: True) } -/// Attach custom event handlers to an element. A number of helper functions exist -/// in this module to cover the most common events and use-cases, so you should -/// check those out first. -/// -/// If you need to handle an event that isn't covered by the helper functions, -/// then you can use `on` to attach a custom event handler. The callback is given -/// the event object as a `Dynamic`. -/// -/// As a simple example, you can implement `on_click` like so: -/// -/// ```gleam -/// import gleam/option.{Some} -/// import lustre/attribute.{Attribute} -/// import lustre/event -/// -/// pub fn on_click(msg: msg) -> Attribute(msg) { -/// use _ <- event.on("click") -/// Some(msg) -/// } -/// ``` -/// -/// By using `gleam/dynamic` you can decode the event object and pull out all sorts -/// of useful data. This is how `on_input` is implemented: -/// -/// ```gleam -/// import gleam/dynamic -/// import gleam/option.{None, Some} -/// import gleam/result -/// import lustre/attribute.{Attribute} -/// import lustre/event -/// -/// pub fn on_input(msg: fn(String) -> msg) -> Attribute(msg) { -/// use event, dispatch <- on("input") -/// let decode = dynamic.field("target", dynamic.field("value", dynamic.string)) -/// -/// case decode(event) { -/// Ok(value) -> Some(msg(value)) -/// Error(_) -> None -/// } -/// } -/// ``` -/// -/// You can take a look at the MDN reference for events -/// [here](https://developer.mozilla.org/en-US/docs/Web/API/Event) to see what -/// you can decode. -/// -/// Unlike the helpers in the rest of this module, it is possible to simply ignore -/// the dispatch function and not dispatch a message at all. In fact, we saw this -/// with the `on_input` example above: if we can't decode the event object, we -/// simply return `None` and emit nothing. -/// -/// Beyond ignoring errors, this can be used to perform side effects we don't need -/// to observe in our main application loop, such as logging... -/// -/// ```gleam -/// import gleam/io -/// import gleam/option.{None} -/// import lustre/attribute.{Attribute} -/// import lustre/event -/// -/// pub fn log_on_click(msg: String) -> Attribute(msg) { -/// use _ <- event.on("click") -/// io.println(msg) -/// None -/// } -/// ``` /// -pub fn on(name: String, handler: fn(Dynamic) -> Option(msg)) -> Attribute(msg) { - Event("on" <> name, handler) +pub fn on( + name: String, + handler: fn(Dynamic) -> Result(msg, error), +) -> Attribute(msg) { + Event("on" <> name, function.compose(handler, result.replace_error(_, Nil))) } // MANIPULATIONS --------------------------------------------------------------- @@ -127,8 +44,8 @@ pub fn on(name: String, handler: fn(Dynamic) -> Option(msg)) -> Attribute(msg) { /// pub fn map(attr: Attribute(a), f: fn(a) -> b) -> Attribute(b) { case attr { - Attribute(name, value) -> Attribute(name, value) - Event(on, handler) -> Event(on, fn(e) { option.map(handler(e), f) }) + Attribute(name, value, as_property) -> Attribute(name, value, as_property) + Event(on, handler) -> Event(on, fn(e) { result.map(handler(e), f) }) } } @@ -138,7 +55,7 @@ pub fn map(attr: Attribute(a), f: fn(a) -> b) -> Attribute(b) { /// pub fn to_string(attr: Attribute(msg)) -> String { case attr { - Attribute(name, value) -> { + Attribute(name, value, as_property: False) -> { case dynamic.classify(value) { "String" -> name <> "=\"" <> dynamic.unsafe_coerce(value) <> "\"" @@ -155,6 +72,7 @@ pub fn to_string(attr: Attribute(msg)) -> String { _ -> name <> "=\"" <> string.inspect(value) <> "\"" } } + Attribute(_, _, as_property: True) -> "" Event(on, _) -> "data-lustre-on:" <> on } } @@ -163,7 +81,7 @@ pub fn to_string(attr: Attribute(msg)) -> String { /// pub fn to_string_builder(attr: Attribute(msg)) -> StringBuilder { case attr { - Attribute(name, value) -> { + Attribute(name, value, as_property: True) -> { case dynamic.classify(value) { "String" -> [name, "=\"", dynamic.unsafe_coerce(value), "\""] @@ -184,6 +102,7 @@ pub fn to_string_builder(attr: Attribute(msg)) -> StringBuilder { |> string_builder.from_strings } } + Attribute(_, _, as_property: False) -> string_builder.new() Event(on, _) -> ["data-lustre-on:", on] |> string_builder.from_strings |