The Environment
When you load and render pages in Pencil, an environment of variables is calculated. This is what allows you to reference variables defined from other pages.
But Pencil’s variable closure works differently than most programming languages. Consider this example:
print value
if (true) {
let value = "Hello!"
}
In most languages, this wouldn’t run because (1) you can’t use something before it’s declared and (2) value
’s scope exists only inside the if
-block. But because Pencil is purpose-built for building web pages, we bashfully reject these sane limitations.
Pencil’s variable scoping allows any page in the structure to reference any variable from any other page in the structure.
Both layout.html
and about.html
can declare new variables in the preamble, and they can access each other’s variables. They can also access, of course, the “global” variables defined in the Config
’s environment.
The reason for this is simple: it’s often the case that the outer pages (e.g. the layout) will need to use a variable defined in the inner pages. When you render a blog post, the blog post’s title is defined in the inner page, but the layout may need to know it as part of its header tag: <h1>${postTitle}</h1>
.
The reverse is also true. Often times, an inner page will need access to an outer page’s variables. For example, a comment widget that requires the blog post ID.
Duplicate Variable Rule. If two pages in the structure define the same variable name, then the value is based off the context:
- If the variable is defined in the page, then the page gets that variable’s value.
- Otherwise, the deepest page’s value wins.
Manipulating the Environment
Though page preambles and variable scoping provide a common pattern for most pages, sometimes you’ll need to manipulate the variable values yourself, in Haskell code. Pencil provides several helper functions to help you gain access to the raw variable context.
As seen in the first tutorial, you can define “global” variables in the config.
These variables follow the same rule as page-defined variables. Think of them as a part of a “Page 0” that begins every structure.
You can use updateEnv
with methods like insert
, insertText
, insertPages
, adjust
.
You can also use getPageEnv
and setPageEnv
, along with the functions above, to manipulate each page’s environment to your liking. These are the functions that Pencil.Blog
uses to build up the default blogging functionality.
Running inside a modified environment
If you modify an environment, you’ll want to run code inside that environment. Pencil provides withEnv
, which is one way to do it:
postsEnv <- (insertPages "posts" posts env >>= insertPages "recommendedPosts" recommendedPosts)
withEnv postsEnv (render (layoutPage <|| indexPage))
You can also use Control.Monad.Reader
’s local
(which is re-exported by the Pencil module, so you don’t need to import any new modules). The example below modifies the display value function to toTextRss
, so that the RSS content is HTML-escaped.