web-page-0.2.0: Web/Page/GenId.hs
-- |
-- Module: Web.Page.GenId
-- Copyright: (c) 2014 Ertugrul Soeylemez
-- License: BSD3
-- Maintainer: Ertugrul Soeylemez <ertesx@gmx.de>
--
-- This module offers a monad transformer and a class for page-unique
-- identifier generation. To use it, simply add a state monad to your
-- monad stack with a state type that is an instance of the
-- 'HasIdStream' class (you can simply use @'Stream' 'Identifier'@, if
-- you don't need any other state). See the 'newId' action to see the
-- exact type of stack you need.
--
-- To construct the initial state you can use the predefined 'idsFrom'
-- function. It constructs a stream of unique identifiers from its
-- argument character set.
--
-- To generate a new identifier use the 'newId' action within your monad
-- stack. The generated identifiers are of type 'Identifier'. There
-- are helper functions for integrating an identifer into markup and the
-- stylesheet. See the DOM helpers and the CSS helpers sections in this
-- module. Also see the documentation for 'Identifier' for more
-- detailed information and an example.
--
-- Hint: It is perfectly valid to combine a widget writer and an
-- identifier generator, so you don't need to alternate between
-- constructing widgets and generating identifiers.
module Web.Page.GenId
( -- * Unique identifiers
Identifier(..),
HasIdStream(..),
idsFrom,
newId,
-- * DOM helpers
classId,
customId,
dataId,
domId,
-- * CSS helpers
idRef,
idSel,
classRef,
classSel
)
where
import qualified Clay as Css
import qualified Data.Stream as Str
import qualified Data.Text as T
import qualified Data.Vector.Unboxed as Vu
import qualified Text.Blaze.Html5.Attributes as A
import Control.Lens
import Control.Monad.State.Class
import Data.Data
import Data.Stream (Stream(..))
import Language.Javascript.JMacro (ToJExpr(..), JExpr(..), JVal(..))
import Text.Blaze.Html
-- | Instances of this class are types that embed an identifier stream.
class HasIdStream a where
-- | Lens into the identifier stream.
idStream :: Lens' a (Stream Identifier)
instance HasIdStream (Stream Identifier) where
idStream = id
-- | Identifiers that can be used with clay, jmacro and blaze-html.
--
-- An identifier of this type is supposed to be used for DOM ids,
-- classes or similar names. Note that the 'ToJExpr' instance converts
-- it to a JavaScript string, so that you can use it with jQuery or
-- functions like @getElementById@. When using jQuery, remember that it
-- expects selector syntax as in the following example:
--
-- > myWidget ::
-- > ( HasIdStream s,
-- > MonadState s m,
-- > MonadWidget url (MySection -> Html) m )
-- > => m ()
-- > myWidget = do
-- > myId <- newId
-- > addSection MySection (H.p "My boring paragraph." ! domId myId)
-- > addStyle $ idSel myId ? background gray
-- > addStyle $ idSel myId # ".groovy" ? do
-- > background darkblue
-- > color pink
-- > fontWeight bold
-- > addScriptLink "static/jquery.js"
-- > addScript [jmacro|
-- > fun init ->
-- > window.setTimeout
-- > (\() {
-- > $("#" + `myId`).addClass("groovy");
-- > $(".groovy").text("My groooovy paragraph!") })
-- > 2500;
-- > $(init) |]
newtype Identifier =
Identifier {
identifier :: String
}
deriving (Data, Eq, Ord, Show, ToValue, Typeable)
instance ToJExpr Identifier where
toJExpr (Identifier name) = ValExpr (JStr name)
-- | HTML5 @class@ attribute containing the given identifier.
classId :: Identifier -> Attribute
classId (Identifier n) = A.class_ (toValue n)
-- | Class refinement for the given identifier.
classRef :: Identifier -> Css.Refinement
classRef = Css.byClass . T.pack . identifier
-- | Class selector for the given identifier.
classSel :: Identifier -> Css.Selector
classSel n = Css.star Css.# classRef n
-- | Custom attribute containing the given identifier.
customId :: Tag -> Identifier -> Attribute
customId attr (Identifier n) = customAttribute attr (toValue n)
-- | HTML5 @id@ attribute containing the given identifier.
domId :: Identifier -> Attribute
domId (Identifier n) = A.id (toValue n)
-- | HTML5 data attribute (@data-*@) containing the given identifier.
dataId :: Tag -> Identifier -> Attribute
dataId attr (Identifier n) = dataAttribute attr (toValue n)
-- | Id refinement for the given identifier.
idRef :: Identifier -> Css.Refinement
idRef = Css.byId . T.pack . identifier
-- | Id selector for the given identifier.
idSel :: Identifier -> Css.Selector
idSel n = Css.star Css.# idRef n
-- | Infinite stream of identifiers built from the given character set.
-- The stream is sorted by identifier length, shortest first.
idsFrom :: [Char] -> Stream Identifier
idsFrom charList =
fmap (Identifier . map (chars Vu.!))
(Str.iterate next [0])
where
chars = Vu.fromList charList
numChars = Vu.length chars
next [] = [0]
next (x':xs)
| x < numChars = x : xs
| otherwise = 0 : next xs
where
x = succ x'
-- | Fetches the next identifier.
newId :: (HasIdStream a, MonadState a m) => m Identifier
newId = idStream %%= \(Cons x xs) -> (x, xs)