Skip to content

Instantly share code, notes, and snippets.

@cimmanon
Last active March 1, 2018 18:13
Show Gist options
  • Save cimmanon/6dd3368b63e1bfd7cc2aab238fb054ea to your computer and use it in GitHub Desktop.
Save cimmanon/6dd3368b63e1bfd7cc2aab238fb054ea to your computer and use it in GitHub Desktop.
Heist Attribute Splices explained with examples

There aren't any good guides for using AttrSplices from Heist. The tutorial from the Snap website fails to show how to bind them, and the only public code I can find that uses them is Digestive Functors Heist.

How does it work?

type AttrSplice m = Text -> RuntimeSplice m [(Text, Text)]

The AttrSplice type signature says that it takes a Text and returns a Monad containing a list of tuples of Text. The first argument would only be used if you're planning on using it as a point of comparison within the template, as shown in the Attribute Splices Tutorial (n.b. the type signature listed here is slightly different, this might reflect an older version of Heist):

autocheckedSplice :: Text -> StateT Text IO [(Text, Text)]
autocheckedSplice v = do
    val <- get -- app-specific retrieval of the appropriate value here
    let checked = if v == val
                    then [("checked","")]
                    else []
    return $ ("value", v) : checked

In this case, when the autoCheckedSplice is bound to the autocheck attribute, it will receive the value of the autocheck attribute from your template (eg. red, green, or blue). If you're using an empty attribute (ie. an attribute without a value like checked), this will be a zero length value (eg. an empty string).

For cases where you aren't interested in the value from the template, you might want to pass in a specific value elsewhere. Here is an example AttrSplice that accepts a Bool and returns an AttrSplice that ignores the passed in Text argument:

checkedSplice :: Monad m => Bool -> AttrSplice m
checkedSplice True = const $ return [("checked", "checked")]
checkedSplice _    = mempty

Binding AttrSplices

When you bind attribute splices, you do so without arguments. Heist will pass the value in later. In the following example, I am binding the checkedSplice to the isFeatured attribute. From there, the binding is a little more verbose than normal, since you'll have to use localHS in combination with bindAttributeSplices.

imageSplice :: Monad m => Image.Image -> Splice m
imageSplice i =
	let
		attrSplices = do
			"autocheck" ## autocheckedSplice
			-- ^ no arguments here for autocheckedSplice
			"isFeatured" ## checkedSplice (Image.featured i)
			-- ^ passing in a value to an AttrSplice that discards the attribute value
	in localHS (bindAttributeSplices attrSplices) $ runChildrenWith $ imageSplices i
	-- ^ binding it to the HeistState

If you find that you're using this pattern pretty often, you may want to create a helper function for that:

foo :: Monad m => Splices (AttrSplice m) -> Splices (Splice m) -> Splice m
foo attrSplices splices = localHS (bindAttributeSplices attrSplices) $ runChildrenWith splices

If you just have a one-off page that needs to have attribute splices added, you can bind them like this:

heistLocal (bindAttributeSplices attrSplices) $ render "mytemplate"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment