In which GUI and breadcrumbs don't get along
Ah, breadcrumbs, how I hate you. Such a tiny little piece of a site, yet so much complexity and time spent. For some clients, you would think this is their number one must have feature based on how much time they spend checking and critiquing them page-by-page.
Existing solutions
In typical Drupal fashion, "there's a module for that!" Or more truthfully, "there are 17 modules for that!" Let's run down a few.
Please note that I am NOT hating on these modules because they're bad. Most of them are actually quite good for the tough problem they're trying to solve.
Custom Breadcrumbs
Custom Breadcrumbs is perhaps one of the earliest modules for this, dating all the way back to 2006. It has a bunch of submodules and a fancy interface that looks like this:
And that's just nodes. There are also interfaces for Views, Panels, Taxonomy, and Paths. Now, I'm not saying this interface is bad--it's not. But I bet you couldn't understand it at first glance. That's because breadcrumbs are just a hard thing to configure visually.(I'm going to say that again so be ready).
How much code does Custom Breadcrumbs ship with?
wc -l **/*
18 CHANGELOG.txt
870 custom_breadcrumbs.admin.inc
wc: custom_breadcrumbsapi: Is a directory
0 custom_breadcrumbsapi
15 custom_breadcrumbsapi/custom_breadcrumbsapi.info
75 custom_breadcrumbsapi/custom_breadcrumbsapi.install
195 custom_breadcrumbsapi/custom_breadcrumbsapi.module
209 custom_breadcrumbs_common.inc
wc: custom_breadcrumbs_identifiers: Is a directory
0 custom_breadcrumbs_identifiers
15 custom_breadcrumbs_identifiers/custom_breadcrumbs_identifiers.info
202 custom_breadcrumbs_identifiers/custom_breadcrumbs_identifiers.module
17 custom_breadcrumbs.info
219 custom_breadcrumbs.install
1245 custom_breadcrumbs.module
wc: custom_breadcrumbs_panels: Is a directory
0 custom_breadcrumbs_panels
19 custom_breadcrumbs_panels/custom_breadcrumbs_panels.info
83 custom_breadcrumbs_panels/custom_breadcrumbs_panels.install
262 custom_breadcrumbs_panels/custom_breadcrumbs_panels.module
wc: custom_breadcrumbs_paths: Is a directory
0 custom_breadcrumbs_paths
19 custom_breadcrumbs_paths/custom_breadcrumbs_paths.info
136 custom_breadcrumbs_paths/custom_breadcrumbs_paths.install
310 custom_breadcrumbs_paths/custom_breadcrumbs_paths.module
wc: custom_breadcrumbs_taxonomy: Is a directory
0 custom_breadcrumbs_taxonomy
146 custom_breadcrumbs_taxonomy/custom_breadcrumbs_taxonomy.admin.inc
419 custom_breadcrumbs_taxonomy/custom_breadcrumbs_taxonomy.inc
18 custom_breadcrumbs_taxonomy/custom_breadcrumbs_taxonomy.info
220 custom_breadcrumbs_taxonomy/custom_breadcrumbs_taxonomy.install
596 custom_breadcrumbs_taxonomy/custom_breadcrumbs_taxonomy.module
wc: custom_breadcrumbs_views: Is a directory
0 custom_breadcrumbs_views
17 custom_breadcrumbs_views/custom_breadcrumbs_views.info
118 custom_breadcrumbs_views/custom_breadcrumbs_views.install
174 custom_breadcrumbs_views/custom_breadcrumbs_views.module
339 LICENSE.txt
302 README.txt
6258 total
That's 6258 lines, counting a few text files and comments and blank lines. For Breadcrumbs. Debugging when things don't work as they should is obviously no picnic.
Crumbs
Crumbs is less than 4 years old so it's just a toddler compared to Custom Breadcrumbs. Let's take a look at a few of its admin pages.
Here's how you specify a breadcrumb pattern for content:
And here's a snippet of how you choose which plugins take priority over other plugins:
Again, these aren't bad interfaces by any means. They're just symptoms of the problem, which is that breadcrumbs are just a hard thing to configure visually.
How about code?
0 admin
249 admin/crumbs.admin.inc
160 admin/crumbs.debug.inc
105 admin/crumbs.entity_parent.inc
55 admin/crumbs.expansible.inc
29 admin/crumbs.tabledrag.inc
14 admin/crumbs.textual.inc
144 crumbs.api.php
13 crumbs.info
67 crumbs.install
629 crumbs.module
0 css
217 css/crumbs.admin.expansible.css
0 example
13 example/crumbs_example.info
41 example/crumbs_example.module
0 example/lib
0 example/lib/CrumbsMonoPlugin
71 example/lib/CrumbsMonoPlugin/NewsByDate.php
0 example/lib/CrumbsMultiPlugin
42 example/lib/CrumbsMultiPlugin/ListOfNews.php
0 images
0 images/queue
8 images/queue/businessmen.png
6 images/queue/cartpusher.png
7 images/queue/dinocar.png
14 images/queue/donkeyman.png
6 images/queue/duckie.png
5 images/queue/elephant.png
20 images/queue-sprite-color.png
18 images/queue-sprite-grayscale.png
0 js
746 js/crumbs.admin.expansible.js
0 labs
12 labs/crumbs_labs.info
34 labs/crumbs_labs.module
0 lib
0 lib/Admin
0 lib/Admin/ElementObject
78 lib/Admin/ElementObject/Abstract.php
13 lib/Admin/ElementObject/WeightsAbstract.php
85 lib/Admin/ElementObject/WeightsExpansible.php
128 lib/Admin/ElementObject/WeightsTabledrag.php
165 lib/Admin/ElementObject/WeightsTextual.php
214 lib/Admin/WeightsTable.php
39 lib/BreadcrumbBuilder.php
75 lib/CallbackRestoration.php
0 lib/Container
176 lib/Container/CachedLazyPluginInfo.php
39 lib/Container/LazyDataByPath.php
47 lib/Container/LazyData.php
20 lib/Container/LazyPageData.php
16 lib/Container/LazyServices.php
25 lib/Container/MultiWildcardDataIterator.php
54 lib/Container/MultiWildcardDataOffset.php
80 lib/Container/MultiWildcardData.php
12 lib/Container/README.txt
172 lib/Container/WildcardData.php
40 lib/Container/WildcardDataSorted.php
0 lib/CrumbsEntityPlugin
38 lib/CrumbsEntityPlugin/TokenDisabled.php
36 lib/CrumbsEntityPlugin/TokenEnabled.php
274 lib/CurrentPageInfo.php
0 lib/Debug
149 lib/Debug/CandidateLogger.php
0 lib/Drupal
0 lib/Drupal/crumbs
0 lib/Drupal/crumbs/Tests
51 lib/Drupal/crumbs/Tests/MenuLinkPluginTest.php
31 lib/Drupal/crumbs/Tests/README.txt
0 lib/EntityPlugin
68 lib/EntityPlugin/Callback.php
0 lib/EntityPlugin/Field
68 lib/EntityPlugin/Field/Abstract.php
21 lib/EntityPlugin/Field/EntityReference.php
43 lib/EntityPlugin/Field/TermReference.php
12 lib/EntityPlugin/Field/Text.php
29 lib/EntityPlugin.php
0 lib/InjectedAPI
38 lib/InjectedAPI/describeMonoPlugin.php
68 lib/InjectedAPI/describeMultiPlugin.php
461 lib/InjectedAPI/hookCrumbsPlugins.php
0 lib/MonoPlugin
16 lib/MonoPlugin/FindParentInterface.php
16 lib/MonoPlugin/FindTitleInterface.php
29 lib/MonoPlugin/FixedParentPath.php
59 lib/MonoPlugin/ParentPathCallback.php
18 lib/MonoPlugin.php
17 lib/MonoPlugin/SkipItem.php
59 lib/MonoPlugin/TitleCallback.php
29 lib/MonoPlugin/TranslateTitle.php
0 lib/MultiPlugin
43 lib/MultiPlugin/EntityFindAbstract.php
128 lib/MultiPlugin/EntityFindSomething.php
11 lib/MultiPlugin/EntityParent.php
11 lib/MultiPlugin/EntityTitle.php
16 lib/MultiPlugin/FindParentInterface.php
16 lib/MultiPlugin/FindTitleInterface.php
22 lib/MultiPlugin.php
71 lib/ParentFinder.php
203 lib/PluginEngine.php
336 lib/PluginInfo.php
4 lib/PluginInterface.php
0 lib/PluginOperation
231 lib/PluginOperation/describe.php
112 lib/Router.php
108 lib/ServiceFactory.php
79 lib/TrailFinder.php
4 lib/UnserializeException.php
115 lib/Util.php
339 LICENSE.txt
0 plugins
58 plugins/crumbs.blog.inc
76 plugins/crumbs.comment.inc
66 plugins/crumbs.commerce_checkout.inc
46 plugins/crumbs.crumbs.inc
19 plugins/crumbs.entityreference.inc
102 plugins/crumbs.entityreference_prepopulate.inc
204 plugins/crumbs.forum.inc
204 plugins/crumbs.menu.inc
217 plugins/crumbs.og.2.inc
193 plugins/crumbs.og.inc
37 plugins/crumbs.path.inc
11 plugins/crumbs.search.inc
55 plugins/crumbs.taxonomy.inc
32 plugins/crumbs.text.inc
86 plugins/crumbs.views.inc
47 README.txt
9135 total
Crumbs currently ships with 9135 lines of code, again including text files and comments and blank lines.
Hansel
Hansel is about as old as Crumbs and touts the slogan "Breadcrumbs done right!" It contains 7 modules (one each for Domain, OG, Taxonomy, Forum, Exporting, the UI, and core functionality) and weighs in at a lightweight 4421 lines currently.
wc -l **/*
wc: domain: Is a directory
0 domain
14 domain/hansel_domain.info
89 domain/hansel_domain.module
wc: export: Is a directory
0 export
12 export/hansel_export.info
343 export/hansel_export.module
wc: forum: Is a directory
0 forum
14 forum/hansel_forum.info
145 forum/hansel_forum.module
190 hansel.actions.inc
63 hansel.hooks.inc
11 hansel.info
114 hansel.install
735 hansel.module
221 hansel.switches.inc
wc: hansel_ui: Is a directory
0 hansel_ui
wc: hansel_ui/css: Is a directory
0 hansel_ui/css
61 hansel_ui/css/hansel_ui.css
11 hansel_ui/hansel_ui.info
883 hansel_ui/hansel_ui.module
62 hansel_ui/hansel_ui.registry.inc
88 hansel_ui/hansel_ui.test.inc
wc: hansel_ui/js: Is a directory
0 hansel_ui/js
34 hansel_ui/js/hansel_ui.js
6 hansel_ui/sprites.png
339 LICENSE.txt
wc: og: Is a directory
0 og
12 og/hansel_og.info
124 og/hansel_og.module
678 README.txt
wc: taxonomy: Is a directory
0 taxonomy
12 taxonomy/hansel_taxonomy.info
160 taxonomy/hansel_taxonomy.module
4421 total
Here's what it looks like:
Would you know what to enter where on that screen? Would you know how to build out breadcrumbs for nodes tagged with a taxonomy using that? Yet again, this isn't a problem with the module, the module is fine. Breadcrumbs are just a hard thing to configure visually.
Others
This list is by no means complete. Here are a few others that exist and are, in my perhaps grumpy opinion, either lacking in functionality or equally as bulky and confusing.
-
Path Breadcrumbs (here's a screenshot)
-
Breadcrumbs By Path (only works for items that follow a predictable nested URL aliasing convention)
-
Menu Breadcrumb (only works for items in menus)
-
Others? If you know of another breadcrumb module that you absolutely love, feel free to tell me all about it in the comments.
Why are we here?
Easy! Breadcrumbs are just a hard thing to configure visually!
They can depend on so many things and they need to satisfy so many different use cases, that it's just really hard to support all of that and do it in a way that isn't a GUI onslaught.
Let's take a specific example. Say we have just one single content type (just one!) called "Story". And let's pretend that there's a "Category" taxonomy attached to it. Here are some very common and completely valid ways one might want to set up those breadcrumbs:
-
Make all news authored by a specific user named John Writer follow the "Editorials > John Doe > Title Of Post" pattern.
-
Make all news under the "Press Release" taxonomy term (in the Categories vocabulary) follow the "Press Releases > Title Of Post" pattern
-
Make all news posted before the current calendar year follow the "News Archive > Year > Month > Title Of Post" pattern
-
Make all news posted in the "Blog" taxonomy term follow the "Blog > Author's Name > Year > Month > Title Of Post" pattern
So that's four completely different use cases all on the same content type with only one taxonomy attached to it. That doesn't even touch on multiple content types, multiple taxonomies, different entities (user profiles, and Drupal Commerce products are quite common) or even non-entity pages (Views pages, Panels pages, etc.).
Do you see what I mean? It's a losing game.
Blame "Invented Here" syndrome or Drupal's love of all things GUI, or both. Either way, most people are trying to make the best of a bad thing by working around the various failings of these contrib modules.
An alternative approach
You are a programmer. Do it in code. It's easy! Just use our frienddrupal_set_breadcrumb() like so:
<?php
// Build the breadcrumbs in the format Home > News > Title Of Post
$breadcrumb = array();
$breadcrumb[] = l(t('Home'), '<front>');
$breadcrumb[] = l(t('News'), 'news');
$breadcrumb[] = l(drupal_get_title(), base_path() . request_uri()); // Link to current URL
// Set the breadcrumbs
drupal_set_breadcrumb($breadcrumb);
Here's a rundown of how you could satisfy the 4 use cases above in code, in a few minutes.
-
Use hook_node_view($node), grab the author out of $node->uid, and if it's John Writer, set that custom beadcrumb usingdrupal_set_breadcrumb().
-
Use hook_node_view($node), grab the category out of $node->field_news_categories (or whatever the field is called), and run taxonomy_term_load() to grab the whole term, and if the$term->name is "Press Release", then set that breadcrumb.
-
Use hook_node_view($node), and if date('Y', $node->created) < date('Y') then set the breadcrumb by passingdate('Y', $node->created) and date('F', $node->created) into drupal_set_breadcrumb() for the year and month.
-
Same basic idea as #2 above.
Or maybe you're adding breadcrumbs for a Views page? Throw it in a Views hook like hook_views_pre_render(&$view). Oh, you're trying to add breadcrumbs to a taxonomy term page? Throw adrupal_set_breadcrumb() intohook_taxonomy_term_view($term) and call it a day.
See what I mean? This is basic stuff for any Drupal developer, and it saves you many thousands of lines of code and potentially a lot of time debugging that code and trying to understand the interfaces.
Caveats
This method won't work when you're dealing with either of the following scenarios:
-
You need to accommodate new breadcrumb patterns as things are changed without being able to make code changes. For example, a new promotion is being run tomorrow, with no notice, and that's not enough time to push a code change through QA and find a decent launch window by then. Or maybe you're just an editor who doesn't have code access at all.
-
You need to accommodate clients who want to add and configure their own breadcrumbs without relying on you to code it. There's still the difficulty in getting them a module with a UX they can wrap their non-technical brains around, but it's more doable than teaching them to code.
However, if you're not in one of those two situations, then I highly recommend you rid yourself of whatever breadcrumb GUI you've been using.
Or convince the client to get rid of breadcrumbs altogether!