backdrop-issues icon indicating copy to clipboard operation
backdrop-issues copied to clipboard

[UX] Using * or % as a wildcard in layout paths does not have expected behavior

Open jenlampton opened this issue 9 years ago • 74 comments

bug report from email:

I have result pages where I need the full page width for content. Created custom layout, one column, picked the url string (results/*) as a page identifier, saved. Cleared all the caches. But the standard layout is still used on those pages too. Is it me? Is it the default theme? Hard to guess.

Most users would expect that entering a path with an asterisk into the layout path field would function as though a second "default" layout were created that applied to all pages under that path. Is there any way we can make this happen automatically when someone enters an asterisk?


This issue has grown in scope considerably since it was originally opened. Documenting potential use-cases for shared layouts here:

  1. I want different Layout Pages to use the same configuration. I have three stand-alone "layout pages" at /modules, /themes and /layouts on backdropcms.org. These pages are identical, with the exception of a single block in the content area. I would like to manage these pages as a single "Layout" with three blocks in the content area, with each block using visibility conditions. (Technically: this layout would be responsible for three menu router config files, but only a single layout config file.)

  2. I want several Views Page Displays to use the same layout I have three views page displays at /foo, /bar, and /baz that all have different content in the "content" area, but I want them to share the same blocks everywhere else. (Technically, This layout is responsible for zero menu router config files, and only a single layout config file)

  3. I want several Views Page Displays with contextual filters to use the same layout I have three views page displays at /foo/%, /bar/%, and /baz/% that all have different content in the "content" area, but I want them to share the same blocks everywhere else. (Technically, This layout is responsible for zero menu router config files, and only a single layout config file) --- The context on these three pages is always a string pass-through (never an object like user, node, or term) so will not present a problem.

  4. I want all my 'user' pages to use the same layout. I want my user/login, user/password and user/% page to use the same layout. (Technically: this layout would be responsible for zero menu router config files, and only a single layout config file) -- The required user context for only one of these paths presents a problem.

jenlampton avatar Feb 04 '16 20:02 jenlampton

I am wondering whether it may be necessary to also positively exclude results/* on the standard layout? I feel sure I have done something similar and will check.

Graham-72 avatar Feb 04 '16 20:02 Graham-72

Ah no, of course, you have to use % not * as the wildcard.

Graham-72 avatar Feb 04 '16 20:02 Graham-72

My fault - email report was wrong, I actually used % as a wildcard, it doesn't work.

maxmeno avatar Feb 05 '16 00:02 maxmeno

I think part of the problem is that people expect both the placeholder (%) and wildcard (*) to work like a wildcard (*) did in Drupal 7, and stand-in for anything at all. But that's not how it works. Instead, the % is a placeholder only for a context. So user/% will only work when the % is a user ID, so user/1 and user/3 will match, but not user/login.

jenlampton avatar Feb 29 '16 23:02 jenlampton

Here's a use-case that was reported via e-mail as having trouble:

I'm trying to set-up my site to use a specific "layout" for these paths: user/login user/[UID]/edit user/[UID]/content

I think the only current solution for this is to have three different layouts, one at user/login, one at user/%/edit, and one at user/%/content.

I tried to set up a single layout at user/%/% where the second context was a string pass-through. This should have worked for both user/[UID]/edit and user/[UID]/content, but that didn't work. I think this is a bug, see https://github.com/backdrop/backdrop-issues/issues/1672

jenlampton avatar Feb 29 '16 23:02 jenlampton

Needing multiple layouts for multiple paths sounds like a shortcoming in layouts, so I have created https://github.com/backdrop/backdrop-issues/issues/1671

jenlampton avatar Feb 29 '16 23:02 jenlampton

"Here's a use-case that was reported via e-mail as having trouble:" That was me, thank you Jen for posting this. Yes, the only "work-around" I could come up with was "add Layout" (and use the same layout) for each path. Optimal would be of course: "user/*" and all paths under "user" would use that layout.

But... I'm still confused as to why there is also a "path" in "Add visibility condition"... ?? If the "path" is "user/*" (or "user/%") why would I need a 2nd "path" in visibility conditions?

JugglingCoder avatar Mar 01 '16 01:03 JugglingCoder

I think the only current solution for this is to have three different layouts, ...Needing multiple layouts for multiple paths sounds like a shortcoming in layouts, so I have created #1671

:+1: I would have done it if you haven't beaten me to it.

klonos avatar Mar 01 '16 02:03 klonos

I've started a solution for this and here documenting my thoughts:

Background When you visit a page Backdrop checks the path in layout_get_layout_by_path(), to see if there is a layout matching exactly that path.

If you visit company/dept, then layout_get_layout_by_path() will be passed exactly that as the path, so a layout with path company/% will not match.

But entity load paths (such as node/3 or user/5) are passed to layout_get_layout_by_path() with the wildcard, so when you visit node/3, the function gets node/% as the path, so if there is a layout with path matching node/% that layout will match.

So what this PR attempts to do is to breakdown a path passed to layout_get_layout_by_path() into possible wildcard options, then check to see if there is a layout matching this option. So for a path company/dept/secretaries/sheila, we'd check for layouts with a path

  • company/%/secretaries/sheila
  • company/dept/%/sheila
  • company/%/secretaries/%
  • company/dept/%/sheila
  • company/%/%
  • company/% etc

So if a person visits path company/dept/secretaries/sheila:

  • if there is a layout: company/% this path should match that layout
  • if the same site has a layout company/%/secretaries this should not match
  • if the same site has another layout company/%/secretaries/% this should match
  • if the same site has a layout company/%/% this should match

Need to do more on the cascading of these paths.

Also, related, a path node/%/edit is not matching a layout node/%/% because Backdrop is finding both the Admin default and the node/%/% layout as designed, but the Admin layout has a higher weight so its chosen first.

Would be good to consider as well matching by aliases, but perhaps another PR.

docwilmot avatar May 06 '20 17:05 docwilmot

I suspect that everyone is likely confusing a * with a %.

  • A star (*) is a wildcard and means anything that matches
  • A precent (%) is a system-placeholder and means that there is a known backdrop system menu router that matches that pattern (including the %) exactly.

This confusion is happening because in the Layout UI the description text on that field actually says that the % is a wildcard, when it is not. (The term wildcard is also used in the Block visibility description text to mean *)

I don't think that we should expand the meaning of of % to also include the meaning of *. I think that's going to confuse people even more. I also don't think the right approach here is to try to explain to people what the percent actually means. The percent is used in code, and is a developer-concept, and I don't believe it belongs in the UI.

I'd prefer to re-do that field to use a star instead of the percent, and then do all the expected checking, as though it were a true wildcard (as explained in the previous comment - but with * in place of %)

@docwilmot would it be had to adjust the above to use a star instead of a percent?

jenlampton avatar May 06 '20 21:05 jenlampton

Not so sure about this. Fact is everyone may be "confused" because a % symbol seems intuitive for this purpose. I think conversely adding a new cryptic symbol is more confusing.

Right now to the end user node/%/% means "a path that starts with 'node' followed by any two things". I think that's a benign assumption. Backdrop can simply handle the context stuff in the background.

Would welcome more opinions.

docwilmot avatar May 06 '20 22:05 docwilmot

I think conversely adding a new cryptic symbol is more confusing.

Yes! This is exactly my point :)

We added a % when Layouts went in. Switching to this new cryptic symbol confused everyone, because what they were already familiar with was a *. (And worse, because we called it a wildcard which is what the * was called before)

Now everyone thinks the % should behave like a * and are confused when it doesn't.

The proposed suggestion above changes how it behaves so that it would behave more like a * but without actually replacing the new cryptic symbol (%) with the old cryptic symbol we are changing it to behave like (*).

I think it will make things even more confusing if we have two different symbols that do the same thing. If we do this, people will wonder why a wildcard is represented by a * everywhere except this one Layouts interface, where for some reason it is a %.

Right now to the end user node/%/% means "a path that starts with 'node' followed by any two things".

Exactly! But what "a path that starts with 'node' followed by any two things" should really be is node/*/*. But since the star is not supported in layouts, people try to use the percent instead, which leads to frustration. :(

Backdrop can simply handle the context stuff in the background.

I'd rather Backdrop handle the context stuff in the background without people needing to understand the difference between a % (here in Layouts UI) and a * (in every other place wildcards are used). If we can do that by using a single cryptic symbol (preferably the * people already understand) instead of two, with subtle differences, that's a win :)

jenlampton avatar May 06 '20 23:05 jenlampton

I understand all of this, but point is we already have the %. For a path node/NID/ANYTHING what is your suggestion that we adopt to enable this functionality in Backdrop going forward?

  1. node/%/%
  2. node/%/*
  3. node/*/*

docwilmot avatar May 06 '20 23:05 docwilmot

For a path node/NID/ANYTHING what is your suggestion that we adopt to enable this functionality in Backdrop going forward?

Any path that has an ANYTHING needs to use a star, so my preference would be node/*/*, or even node/* would work.

Is this possible right now? IIRC we can only do the pre-defined paths at node/%/% (which does not handle the ANYTHING part -- the %s would need to be one of the strings Backdrop already knows about)

jenlampton avatar May 06 '20 23:05 jenlampton

So are you proposing we change the symbol used in layouts totally to an asterisk?

docwilmot avatar May 07 '20 01:05 docwilmot

Yes. If we're going to change it to work like an asterisk (which I think we should -- that's what people expect) then it should be an asterisk.

jenlampton avatar May 07 '20 18:05 jenlampton

Interesting: seems @jenlampton makes more sense after a 9-month break and two cups of coffee: I agree wholeheartedly with your recommendation now ma'am. Well played. 😃

docwilmot avatar Feb 23 '21 17:02 docwilmot

There's still a difference between placeholders (%) and wildcards (*), though; it might be useful to maintain the distinction.

  • Wildcards (as they are used in URL Path visibility conditions) can appear anywhere within a path: foo* is a meaningful wildcard expression, but foo% is invalid as a layout path.
  • Paths could potentially begin with a wildcard, e.g., * to match anything; but % is not allowed as a layout path.
  • Placeholders can provide context. Wildcards presumably couldn't.
  • Mixing wildcards and placeholders would make it pretty difficult to provide context for the placeholders: */% would be problematic to handle for providing context for the placeholder.

So how about supporting them both in layout paths, but with the proviso that you can't mix * and % in the same expression?

And then maybe a "more info" link or tooltip could help explain to newcomers what the distinction is and why one would choose one over the other.

bugfolder avatar Feb 23 '21 18:02 bugfolder

The wildcard should be able to handle the use-case for the placeholder too.

I'd rather Backdrop handle the context stuff in the background without people needing to understand the difference between a % (here in Layouts UI) and a * (in every other place wildcards are used). If we can do that by using a single cryptic symbol (preferably the * people already understand) instead of two, with subtle differences, that's a win :)

We might still have to support both to make this change in a way that is backwards-compatible, though I'd still prefer not to.

jenlampton avatar Feb 23 '21 19:02 jenlampton

Ah. If we only use *, then /*/ or /* (at the end) would be interpreted as a placeholder, and any other form would be an ordinary wildcard. Reasonable?

bugfolder avatar Feb 23 '21 20:02 bugfolder

There's still a difference between placeholders (%) and wildcards (*), though; it might be useful to maintain the distinction.

I agree with that! The "%" placeholders mean something different (to me). Keep in mind, that % has a special meaning in hook_menu(). Think of "%node", which loads the node object. That's something totally different than "node/*" which translates to paths like "node/1", "node/2", ... "node/749".

Using "%" and "*" as equivalent might cause more confusion, a placeholder that loads an object is not the same as a wildcard on paths.

indigoxela avatar Feb 24 '21 06:02 indigoxela

As I think about how to try to explain this to users on the "Configure layout" page, given the differences between wildcards and placeholders and the limitations on the latter, I'm more firmly thinking it will be less confusing to users to use two distinct symbols to provide the two concepts.

For example, in the "Path" section of that form, the instructions under the path might say:

Use an asterisk ("*") for wildcards. Use a percent ("%") for placeholders.

  • A wildcard * can represent any string of text and can appear anywhere in the path.
  • A placeholder % can only appear between two "/" or at the end of the path and represents data that can be passed to a block as a context (see below).

See more explanation and examples.

That last link would give a popup that gives examples of both and an explanation of the difference (like the token browser). I think we do need a more extensive explanation that doesn't always take up space but is easily accessible from the help text.

The problem with using the same symbol for both arises from the limitations on placeholders, and how the Context section of the form lets you assign a context to placeholders based on their position.

Suppose we used * for both. Consider, then, the path */*/n*/*n/*. Based on the current rules for placeholders, only positions 2 and 5 would be eligible to be interpreted as placeholders. If a person entered that path, but contexts were only displayed for positions 2 and 5 with no explanation, that would probably be confusing.

But using two symbols, the proper way to enter that path and get context would be */%/n*/*n/%. If a user entered a % in an invalid place, they'd get immediate feedback on why it was invalid, and it would be clearer in the "Contexts" section that the two possible contexts are for the two locations of a % symbol.

@jenlampton said:

If we can do that by using a single cryptic symbol (preferably the * people already understand) instead of two, with subtle differences, that's a win :)

The thing is, the differences are not that subtle. It's worth using two symbols to emphasize the differences.

So I would ask folks to (re)consider keeping the % for placeholders, and adding * for wildcards.

bugfolder avatar Feb 24 '21 15:02 bugfolder

If I may add one more bit of argument in favor of having both placeholders (%) and wildcards (*) in layout paths, versus using * for everything: I remember seeing that backward compatibility was an important principle in Backdrop. With that in mind, if we use two symbols, then:

  • People who already used layouts with context will see everything still works the same as ever; update.php didn't modify their layout paths containing % with *'d versions, which would have to happen if we switch to one symbol.

  • People who come newly to Backdrop and are familiar with * wildcard paths in in blocks (from either Backdrop or D7) will see that they can use * wildcards in layout paths, and so will already know what to expect from them.

  • Less-experienced users won't know what a layout context is or when to use it, but don't need to worry about them until they're ready to try them out.

  • More-experienced users can start mixing wildcards and placeholders whenever they're ready or feel the need.

And let me reiterate a point from my previous comment: the distinctions between wildcards and placeholders (whether we use one symbol or two) are significant enough that they need a decent amount of explanation and examples, and that explanation should be accessible directly from the UI, not tucked away in a user guide on the docs website. Ideally, right at the point where someone sees "you can can put either a wildcard or a placeholder into this path", there should be something they can click on (or tap) that takes them to enough information to help them make that choice.

bugfolder avatar Feb 25 '21 03:02 bugfolder

The main issue with wildcards is not really to do with Layouts at all; it would require re-examining how Backdrop manages paths, altogether.

Say you try visit the path admin/config. Core checks the router table to see if that path exists (ie provided by a hook_menu() implementation somewhere). Then if that path is found, core checks if a layout is assigned.

If you visit a path provided by Layout module itself, like newpath/no-hook-menu-implementation that could still work even if no module or core provides a hook_menu() implementation because Layouts creates an automatic hook_menu() for you and saves that in the router table too.

If we allow wildcards though:

If you save a layout with path admin/* and visited admin/config you would succeed, because admin/config is already in the router table. Core first checks that path exists then finds the layout.

BUT, if you created layout with path newpath/* and visited newpath/no-hook-menu-implementation that's impossible right now, because that's not in the router table. Core wouldn't even get as far as checking for Layouts, it would just 404.

We'd need to teach the menu system new tricks, not Layout. We'd need to tell the menu system that if a user tries to visit newpath/no/hook/menu/implementation/at/all (notice slashes) to calculate every possible permutation of wildcard and placeholder for this path, including:

  • newpath/no/hook/menu/implementation/at/all
  • newpath/%/hook/menu/implementation/at/all
  • newpath/no/hook/*/*/at/all
  • newpath/no/hook/%/*/at/all
  • newpath/no/*/*/%/at/all
  • newpath/*/hook/menu/*/at/* etc etc

then check the router table for every one of those. With a max path parts limit of 9 that's a lot of database work for the menu system to do, and this would need to be on every page load. Would simplify greatly if we put in some limits, like * can only be on the end. But this would still require a rethinking of the menu system, not just Layouts.

See menu_get_ancestors() for how core does placeholder mapping for each path visit. We'd need to modify that function to check also for router paths with a * on the end for each path fragment, which wouldn't be much more in number but I cannot say what impact this would have on performance.

docwilmot avatar Feb 25 '21 04:02 docwilmot

Ah, thanks for the very clear explanation. I'd not realized that Layouts can create menu router paths, and indeed, that changes everything (or at least, it changes the basis of my assumptions).

I'm still thinking, though, that there'd be great utility in being able to apply a single layout on multiple disparate paths. I'd been thinking of layout paths as behaving like path aliases; they only applied to paths that already existed in the menu router table, so to determine layout availability, you'd filter the layout paths against the router table.

Obviously, that's not the case. But that looks like the approach that Layout Wildcard was following, so I'll look further at that module and its work with path aliases issue.

bugfolder avatar Feb 25 '21 20:02 bugfolder

I was trying Layout Wildcard and was a bit confused. I removed it to see what's possible with Layout and Views. I already understand that a creating a View page like list/content you can get a Layout to apply by adding a layout path of list/content. But I thought that maybe a layout path of list/% would also work. Doesn't seem to, though it does add a "string pass-through" (I should know what this does but I don't). I think Views might be unique here in that they're not path aliases, but they're also not entities like nodes or terms where Layout supports node/% or taxonomy/term/%. So maybe this would be a narrow use case which core could support? I guess it's a little complicated because Layout needs to know it's a View. (Perhaps it looks up the page_callback from the menu router table.)

herbdool avatar Apr 27 '21 20:04 herbdool

Layout's path-handling can be confusing (at least, I'm still trying to fully wrap my head around all the scenarios). Beginning with the fact that when a module and a layout both want to create the same system path (like mypath/%), the one that gets into the menu router table is the one that was created first chronologically.

One thing I found that helps in sorting things out is to keep track of whether the path and/or layout is a system path (like node/%) or a normal path (like node/1). When you go to a URL like http://example.com/list/1, the normal path is list/1, but the system path could be either list/1 or list/%, depending on what's in the menu router table.

A View can create system paths that include placeholders, if you define the View with a contextual parameter (so that the page path of the view has a % placeholder in it). Or you can create separate view displays for list/1, list/2, and so forth, but in those cases, the system paths will be list/1 and list/2; both will have entries in the menu router table. If in the second case, you create a Layout with a path list/%, that will add an entry in the menu router table, but core Layout won't try to apply it to your View pages because core behavior matches layouts on the system path, and list/1 != list/% when it comes to system path comparison.

(Some of these issues can probably be handled by upgrades in Layout Wildcard, but I'm going cautiously on implementing upgrade changes there because I don't want to do something dumb that would make it hard to change direction later.)

bugfolder avatar Apr 27 '21 20:04 bugfolder

@bugfolder your explanation helps. I tested and indeed it does work that way. A View with a path list/% creates a system path in menu_router table of the same. And creating a layout for list/% works. But a View path of list/1 doesn't work with the same layout.

herbdool avatar Apr 28 '21 18:04 herbdool

@herbdool, I've started working on the roadmap for Layout Wildcard, putting some introductory comments and explanation into this issue. They might be helpful. The View scenario you're describing is one of the example scenarios.

bugfolder avatar Apr 28 '21 21:04 bugfolder

Small change in new PR, allows single wildcard characters at the end of layout-provided paths: So you can set a layout path as fruit/* and this will match fruit/mango and fruit/apple; but not fruit/apples/fuji.

But this doesnt match aliases, so path patterns will not match; ie posts/* will still not match all posts, even though they do have the URL pattern posts/NID because thats an alias, not a layout-provided path.

docwilmot avatar Dec 01 '21 02:12 docwilmot