Skip to content

ymeister/reflex-dom-attrs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

reflex-dom-attrs

Enhanced attribute management for Reflex-DOM applications.

Overview

reflex-dom-attrs provides a composable, type-safe system for managing HTML attributes in Reflex-DOM applications. It offers a clean DSL for working with both static and dynamic attributes while optimizing performance by avoiding unnecessary Dynamic creation.

Features

  • Composable attributes via Semigroup/Monoid instances
  • Performance optimized - uses static elements when possible
  • Type-safe attribute builders with the ~: operator
  • Polymorphic type classes for classes, styles, and attributes
  • Self-referential attributes that can access the created element
  • Enhanced element functions with built-in attribute support

Installation

Add to your .cabal file:

build-depends: reflex-dom-attrs

Quick Start

{-# LANGUAGE OverloadedStrings #-}

import Reflex.Dom.Attrs

-- Simple static attributes
myButton :: Widget t m => m (Event t ())
myButton = elButton_
  [ "class" ~: "btn btn-primary"
  , "style" ~: ["padding" ~:: "10px", "color" ~:: "white"]
  , "data-id" ~: "submit-btn"
  ] $ text "Click me!"

-- Dynamic attributes
dynamicDiv :: Widget t m => Dynamic t Text -> m ()
dynamicDiv dynClass = elDiv
  [ "class" ~: dynClass
  , "style" ~: ["display" ~:: "flex"]
  ] $ text "Dynamic content"

-- Composing attributes
baseAttrs, extraAttrs :: Attrs t m
baseAttrs = "class" ~: "widget" <> "data-role" ~: "container"
extraAttrs = "class" ~: "enhanced" <> "style" ~: ["margin" ~:: "5px"]

combined :: Widget t m => m ()
combined = elDiv [baseAttrs, extraAttrs] $ text "Composed"
-- Results in: class="widget enhanced" data-role="container" style="margin:5px"

Core Concepts

The Attrs Type

The central type that represents a collection of HTML attributes:

data Attrs t m = Attrs
  { attrs_class    :: Text                           -- Static classes
  , attrs_style    :: Map Text Text                  -- Static styles
  , attrs_attrs    :: Map Text Text                  -- Static attributes
  , attrs_dynClass :: Maybe (Dynamic t Text)         -- Dynamic classes
  , attrs_dynStyle :: Maybe (Dynamic t (Map Text Text)) -- Dynamic styles
  , attrs_dynAttrs :: Maybe (Dynamic t (Map Text Text)) -- Dynamic attributes
  , attrs_self     :: Maybe (El t m -> m [Attrs t m])   -- Self-referential
  }

Attribute Creation

The ~: Operator

The primary way to create attributes:

-- Text attributes
"id" ~: "my-element"
"class" ~: "container"

-- Style attributes (using lists)
"style" ~: ["color" ~:: "red", "padding" ~:: "10px"]

-- Dynamic attributes
"class" ~: dynClass
"data-value" ~: dynValue

Type Classes

Polymorphic interfaces for specific attribute types:

-- Classes
_class_ "my-class"                    -- Static
_class_ dynClass                      -- Dynamic

-- Styles
_style_ $ Map.fromList [("color", "blue")]  -- Static
_style_ dynStyles                           -- Dynamic

-- Generic attributes
_attrs_ $ Map.fromList [("data-id", "123")] -- Static
_attrs_ dynAttrs                            -- Dynamic

Element Creation

All standard HTML elements are supported with attribute-aware variants:

-- Basic elements
elDiv  :: [Attrs t m] -> m a -> m a
elSpan :: [Attrs t m] -> m a -> m a
elImg  :: [Attrs t m] -> m a -> m a

-- Elements returning the DOM element
elDiv'  :: [Attrs t m] -> m a -> m (El t m, a)
elSpan' :: [Attrs t m] -> m a -> m (El t m, a)

-- Interactive elements
elButton  :: [Attrs t m] -> m a -> m (Event t (), a)
elButton' :: [Attrs t m] -> m a -> m (El t m, Event t (), a)

-- Form elements
elInput    :: [Attrs t m] -> InputElConfig t m -> m (InputEl t m)
elTextArea :: [Attrs t m] -> TextAreaElConfig t m -> m (TextAreaEl t m)

Advanced Usage

Self-Referential Attributes

Attributes that depend on the created element:

selfRefExample :: Widget t m => m ()
selfRefExample = elDiv
  [ _self_attrs_ $ \el -> do
      -- Access element after creation
      performEvent_ $ domEvent Click el $> liftIO (putStrLn "Clicked!")
      pure []
  ] $ text "Click me"

Dynamic Attribute Lists

Change entire attribute sets dynamically:

conditionalAttrs :: Widget t m => Dynamic t Bool -> m ()
conditionalAttrs isActive = elDiv
  [ foldDynAttrs $ isActive <&> \active ->
      if active
        then ["class" ~: "active", "style" ~: ["color" ~:: "green"]]
        else ["class" ~: "inactive", "style" ~: ["color" ~:: "gray"]]
  ] $ text "Conditional styling"

Performance Optimization

The library automatically optimizes element creation:

  • When all attributes are static, uses elAttr internally
  • When any attribute is dynamic, uses elDynAttr
  • Avoids creating unnecessary Dynamic values
-- This creates a static element (efficient)
staticEl = elDiv ["class" ~: "static"] $ text "Fast"

-- This creates a dynamic element (when needed)
dynamicEl dynClass = elDiv ["class" ~: dynClass] $ text "Reactive"

API Design Philosophy

  1. Composability First: Attributes combine naturally via Semigroup
  2. Performance Aware: Static paths optimized automatically
  3. Type Safety: Leverage Haskell's type system for correctness
  4. Ergonomic: Clean, intuitive syntax with the ~: operator
  5. Incremental Adoption: Works alongside standard reflex-dom

Examples

Form with Validation Styling

validatedInput :: Widget t m => m (Dynamic t Text)
validatedInput = do
  rec
    input <- elInput
      [ "class" ~: isValid <&> \valid ->
          if valid then "form-control" else "form-control error"
      , "placeholder" ~: "Enter email"
      ] def

    let value = _inputElement_value input
        isValid = value <&> \v -> '@' `T.elem` v

  pure value

Responsive Container

responsiveContainer :: Widget t m => Dynamic t Bool -> m () -> m ()
responsiveContainer isMobile content = elDiv
  [ "class" ~: "container"
  , "style" ~: isMobile <&> \mobile ->
      if mobile
        then ["padding" ~:: "5px", "font-size" ~:: "14px"]
        else ["padding" ~:: "20px", "font-size" ~:: "16px"]
  ] content

Button with Loading State

loadingButton :: Widget t m => Dynamic t Bool -> m (Event t ())
loadingButton isLoading = elButton
  [ "class" ~: isLoading <&> \loading ->
      if loading then "btn btn-disabled" else "btn btn-primary"
  , "disabled" ~: isLoading <&> \loading ->
      if loading then "true" else ""
  ] $ dynText $ isLoading <&> \loading ->
      if loading then "Loading..." else "Submit"

License

BSD3 - See LICENSE file for details

Author

Yuri Meister

Contributing

Contributions are welcome! Please feel free to submit issues or pull requests.

About

Reflex-DOM extended attributes

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •