For people coming to Backdrop from Drupal 7 (and those starting Backdrop from scratch), layouts are often one of the most difficult new concepts to grasp. One of the challenging aspects can be figuring out (or setting) which URL(s) a given layout will be applied on. In this blog post, I’ll discuss some of the technical aspects of layouts that might help users build an overall “mental model” of how layouts work.
To start, let me point out some existing useful links relevant to layouts:
For the purpose of this article, I’ll focus on the problem of setting which URL(s) a given layout will be applied to. The main concepts that play a role are the concept of a layout path and the concept of placeholders. We’ll get to both.
But first, note that there are two types of layouts. The User Guide to Layouts and Templates refers to them as “stand-alone” layouts and “dynamic” layouts, stating that
A stand-alone layout will only ever affect a single page on the site. The layout itself generates this page. A good example of a stand-alone layout is the default Home page.
A dynamic layout is a layout that can affect multiple pages. In this case, the pages that are affected are always generated by other sources. This type of layout will require the “Main page content” block.
For consistency, we will continue using these terms.
The distinction between the two comes down to what’s in the layout’s path:
- A stand-alone layout has a path with no placeholders, like
home
, orsite-info/introduction
. - A dynamic layout has a path that contains one or more placeholders, indicated by a percent (
%
) character in its path, likenode/%/edit
.
Both standalone and dynamic layouts can create pages that don’t yet exist and customize (or override) existing pages:
-
If a page with the path already exists, the layout will be used for that page (depending on its visibility conditions). The layout can be used to position other blocks around the content that is provided by the existing page in the “Main page content” block.
-
If a page with the path doesn’t exist, a page will be created at that path and its content will come entirely from the blocks placed in the layout.
Note that the behavior of Layouts with respect to existing page paths can in principle depend upon the order in which you create layouts and/or enable modules. (This rarely comes up in practice so I won’t go into it further here, but if you’re interested, see issue 4986 in the Github issue queue.)
Stand-alone layouts can be used to create a new page with a special layout (like, for example, the home page, which is one of the out-of-the-box layouts provided in the default Backdrop installation).
They can also be used to customize the layout for a single existing page, such as one provided by a module via hook_menu()
.
Dynamic layouts can be used to create a set of related pages that display different information, with the differences provided by the path-specific values in the placeholders, which can then be passed to the blocks that make up the page via contexts (see more below).
They can also be used to customize the layout of an existing set of pages, however—and this is the most important point of this article—there needs to be a path already in existence that contains the same placeholders as the layout path.
That’s where things get a bit complicated; the complication stems from the nature of placeholders and some ambiguity around the term “path”.
In Backdrop, there are several things that can be referred to as a path and confusion can arise if we interpret “path” the wrong way:
- A router path appears in the menu routing table and can contain placeholders, e.g.,
node/%/edit
. - A system path (also called a normal path) is a specific example of a router path with placeholders filled in, e.g.,
node/1/edit
. - A URL alias (formerly path alias, but that term is deprecated) is a path that replaces a normal path, e.g.,
page/title-of-my-node
. - The request path is the path in the URL (i.e., what appears in the browser bar after “https://mydomain.com/”) and it is (most of the time) either a normal path or a URL alias. In fact, both types of path can result in displaying the same page; in the standard Backdrop installation, the request path
node/1
(a normal path) and the pathposts/your-first-post
(a URL alias) both display the same page.
(When is a request path not a normal path or URL alias? If the request path isn’t an existing normal path but a truncated version of the request path is a normal path, then the system will display the page for the truncated version; for example, the request path admin/content/foo/bar
results in the page that has normal path admin/content
.)
In the Layouts interface, the path that is defined for the layout is a router path, and so it may contain placeholders.
Placeholders (%
) behave in some ways like the wildcard asterisk (*
) that you may be familiar with from Block visibility conditions (and are also used in Layout visibility conditions). But they are distinctly different animals, both more limited and more powerful than simple wildcards:
-
Placeholders are more limited than wildcards because they can only appear in a path
- between two
/
’s, likenode/%/something
, or - after the last
/
, likenode/%
, - but not at the beginning of the path;
%/node/something
is invalid, - and not combined with other characters in the same slot:
node/%foo/bar
is invalid. (Note: inhook_menu()
implementations, there are paths with special forms of placeholders, like%node
and%user
, but these end up stored as plain%
in the menu router table.)
- between two
-
Wildcards (
*
) in visibility conditions, by contrast, can appear anywhere in a path, even in the middle of a word. In a visibility condition, the pathnode/foo*baz
would match bothnode/foobaz
andnode/foobarbaz
(and much else).
Placeholders in layout paths are more powerful than mere wildcards because they can define a context, a chunk of related information, that can be passed to the blocks in the layout. For example, in a path node/%/something
, the %
can be interpreted as the node ID of a node (via the context mechanism), which results in passing the referenced node to the block as context for the block. (The context can also be passed to the block as simply the text string of the placeholder, or as several other things.)
Importantly, though, dynamic layouts—layouts with paths containing placeholders—are only applied to pages that already exist in the menu router table whose router paths were defined to include placeholders.
This is an important distinction and can be the source of some bewilderment. “I created a layout for foo/%
; why isn’t it applying on page foo/bar
? The reason could be that if the existing router path for the page was foo/bar
and not foo/%
, the paths don’t match, and the layout won’t be applied.
This situation easily arises with Views that have multiple displays. Suppose you created a View that had two displays: one with path page/1
, and one with path page/2
. It might seem plausible that a layout with path page/%
would be applied to both. But it would not, because there is no menu router entry for /page/%
, only for the two specific paths page/1
and page/2
created by the View.
On the other hand, you could create a View with a contextual filter that had the path page/%
, and in this case, the layout would be applied to both page/1
and page/2
, because now the router path page/%
exists, created by that View.
It would be nice, of course, if the first case—a View with multiple displays whose paths are similar, but defined explicitly—could be handled with the same Layout. There is ongoing discussion about extending the layout handling in Backdrop core to make it easier to do this. For now, though, there is a contrib module that might help: Layout Wildcard. If you have interest in this topic, take a look through the core issue queue (and Layout Wildcard’s issue queue, from which this article was adapted) to see what’s evolving, being considered, and/or possibly to make your own contributions.