Donquixote: Btw, I am always happy about feedback!

And I'm happy to give it... :-)

Note that the following is entirely based on my experience with the 7.x version of the module; I have not tried the 6.x version which is likely much more complete and easier to use. I also have to say that I have decided not to use it for my current project, as the 7.x version still seems to have some way to go before it's stable...

Overall my impression of crumbs is fairly good. Especially the underlying architectural design seem to be quite solid. My very first impression wasn't too good, because module page and docs are not easy to understand at first sight! I did come back for a closer look eventually however, and I was pleasently surprised by the architectural design. (Your ddd presentation slides also helped.)

The main problem which I was trying to solve, was the problem of using node reference fields for determining the "parent item". I haven't tried all the breadcrumb modules out there, but many of them seem to have top-to-bottom approach; the concept of the ParentFinder in crumbs fits my usecase perfectly.

How does crumbs compare to other breadcrumb-customizing modules?

For my first attempt at solving the issue described above, I went with the Menu position module (see code in this sandbox). Unfortunately, menu position does something strange with the menu, which came back to bite me (it inserts a "temporary menu item" which is used as the "parent item" for all underlying rules...).

Compared to Crumbs, I had less problems understandying menu position's API; Crumbs has a lot of documentation, but the crumbs api is more complex and docs sometimes didn't tell me the things I needed to know. Also, crumbs source code is rather hard to read and understand; it doesn't fully comply to drupal coding standards and naming conventions, and sometimes has very little to no inline documentation -- this is even worse, when the d.o. docs don't tell me everything.

I think the documentation pages could be improved with some concrete examples, and more details on method return types etc. (As an example, I struggled to find out what to return/do in a defineOne() or defineMany() method.). "Reading the source code" also doesn't help as mentioned above.

A positive thing about Crumbs, compared to other modules, is, that it doesn't have a complicated backend for storing rules and configuration. This helps the issue of "exportability" a lot, which is rather difficult with other modules that use special database tables etc.

Is there any use case for a breadcrumb that can not be achieved with Crumbs?

Can't think of one. I think, if the 7.x version was finished, it would probably do most of what I'd expect. Unfortunately there's still a few nasty bugs (or things that haven't been ported yet); menu (or menu-hierarchy) plugins for example throw critical errors when enabled.

How many % of site builders understand the configuration page, with those hierarchic weights?

I'm afraid, I don't really understand it. "Enabled" and "Disabled" are easy enough to understand, but I have for example no idea what "Inherited" does to my breadcrumbs... I think a simple graphical UI similar to the block configuration page (i.e. some drag-and-drop mechanism for enabling/disabling and ordering of rules) might make things a lot easier for your users.

How easy/difficult and practical is the API ?

The Crumbs API seems flexible and powerful in terms of functionality. As mentioned above, my main issue is with the documentation...

I also have a vague feeling that there's quite a few things that could be replaced with "standard code/apis" from core or contrib; (I'm thinking e.g. of the "ctools plugin system"). Using those might make things simpler and easier to understand...

Hope this helps! I'm looking forward to a stable D7 version of crumbs...

(In the meantime I'm going to use a little spin-off module I put together; I uses a similar architecture ("parent finder" concept; ctools-based plugin system), but has almost no plugins bundled and no admin ui at the moment... I hope I can eventually migrate over to crumbs).

Comments

donquixote’s picture

Thanks a lot for the feedback!

The crumbs module page and documentation, also the module itself, has been sleeping for quite a while. I was a little disappointed, because while clients notice very clearly if a breadcrumb sucks, they have zero imagination that there is work involved with it. They just expect it to work, no questions asked.

Now I am on a cute D7 community project, and suddenly I am back on track.

using node reference fields for determining the "parent item"

I was going to reply to a lot of concerns, but this looks a lot more interesting to reply to immediately :)
A more general reply is going to follow.

Basically, you need a findParent__node_x() method, extract the $node object from the router item, look at the node reference field, construct a parent path based on that, and return it as a return value.

Now, probably you figured that out already.
The point where you struggle, I suppose, is all the stuff around that. Writing the class, and telling Crumbs about it.
Maybe you even made this thing work. But you ask yourself, why does my plugin look different than other plugins? Why do I return a string, while others return an array?

Continued in next post..

bforchhammer’s picture

The point where you struggle, I suppose, is all the stuff around that. Writing the class, and telling Crumbs about it. Maybe you even made this thing work.

Oh yeah, I did figure it out eventually; It was just harder than I expected. I actually managed to get parent-nodereference-finder plugin working fine!

The only reason why I decided against crumbs in the end, was because of the fatal errors after enabling other plugins (menu hierachy); in order to fix them I would have had to start hacking the crumbs module... writing my own seemed easier at that point.

donquixote’s picture

I think most of the difficulties in the API design and in the interface are coming from the flexibility that Crumbs aims at.
The fine-grained sorting of plugins and rules.

Let me start with a little metaphor.

A man, who knows nothing about cooking, and does not know any creative thinking, is sent out with a shopping list.
Buy product X in shop A, product Y in shop B, etc.
Quite simple, so far.
But, what if product X is not available in shop A? Ok, then you go to shop A2.. duh. How to encode that in a shopping list?

In the end, the shopping list might look like this:
First, a list of stuff the guy is asked to buy.

Then, a recommendation for the different shops:
If shop A has any strawberries, they are probably better than any other shop.
Otherwise, shop B is the best for any fresh fruit.
Shop C has quite ok stuff, IF you get a special offer. Otherwise, too expensive.
Did I mention, don't buy apples in shop B, unless you can't find them anywhere else.
Ok, whatever you have not found yet, you can buy in shop A.

Following this list, the guy would visit the shops in a sequence of A, B, C, B, A. Five steps. A bit of redundancy, it seems, but he had to visit all three shops, before he finally knows that it has to be B.

----------

Another story.
A company wants to delegate buying any stuff to a computer.
Whatever there is to buy, the computer should follow a priority list for the available distributors.

If you get Fred on the phone for distributor A, then buy buy buy, we owe him.
Otherwise, if distributor B has anything freshly imported from Japan, buy there.
Otherwise, distributor C is generally preferable.
If this all doesn't help, try the other distributors from our phone list.
That said, B is usually worse than the others.
Did I mention, if Fred from A wants to sell you any used equipment, don't buy them, unless other distributors did not have any.

One thing to notice from this list:
Some instructions are more generic, others are more specific. The more specific ones define exceptions to the generic ones.
Also, we could imagine more of the specific instructions, which are not mentioned. Such as "If Annie is on the phone for distributor A", or "if distributor B has anything freshly imported from Russia". Those instructions are theoretically available for the respective distributor, but we do not mention them. Instead, the fallback instruction is used. For "Annie on distributor A", this would be just "Distributor A". Or, for distributor D, it would be "the other distributors from our phone list".

In fact, one could make a hierarchy of these things.

* (any from the list)
A.*
A.Fred.*
A.Fred.used
A.Annie.*
B.*
B.freshly-imported.*
B.freshly-imported.from-Japan
C.*
D.*

This does describe the available options, but it does not describe our priorities yet.

Now, with priorities:

A.Fred.* (we owe him)
B.freshly-imported.from-Japan
C.*
*
B.*
A.Fred.used

This would be our explicit instructions.

The other instructions would be "implicit". The situations they describe can still happen, but the machine will fall back to the default behavior.

A.* -> fallback to *
A.Annie.* -> fallback to *
B.freshly-imported.* -> fallback to B.*
D.* -> fallback to *

Also, there might be some distributors that we know so little about, that we don't have any distinguishing cases. Instead of E.*, we say just E.

In Crumbs, the priorities would be in the "enabled" section, whereas the fallback stuff would be in the "inherit" section. Yep, "inherit" could be translated to "fallback", if that is any easier.

----------------------

Now about the strategy.
Every phone call does cost money, so we want to find the result with the fewest phone calls possible.

Brute force would be, to call the distributors in sequence of A, B, C, others, B, A.
Or, if we are really stupid, we would ask them in a fixed order, no matter what the priority list says.
If we are smart, and Fred has some not-used goods, we are finished, and don't need to call the other distributors.
If we are just medium smart, we tell Fred that we are not interested in any used goods. Later we might have to call him again.
If we are really smart, we remember whatever Fred told us, and if we don't find anything else, we can still choose Fred's used equipment, without yet another phone call.

So, our strategy is like this:
- Ask every distributor only once, and always ask for all available products.
- Call the distributors in a clever order, so there is a chance that we can skip some of the other phone calls.

--------------

What about the boss who is writing the priority list?

The boss needs:
- A list of distributors
- For each distributor, the list of available products, or rather, the list of available cases, that the automatic salesperson is able to distinguish. Such as, Fred vs Annie.
- For some distributors, the boss just needs to know that there are no further conditions, there is no fine-grained configuration possible.

To be continued..

donquixote’s picture

I think the documentation pages could be improved with some concrete examples, and more details on method return types etc. (As an example, I struggled to find out what to return/do in a defineOne() or defineMany() method.). "Reading the source code" also doesn't help as mentioned above.

For the configuration page, Crumbs needs to know the different rules provided by a given plugin.
The defineMany() method is supposed to return an array of such rules as key => title.
The findParent() or findTitle() methods then return an array of results, which can then be sorted based on the priority list.
It should be noticed, that the plugin itself has no idea about the priority list, so it always has to return all possible results.
In fact, most plugins return only an array with only one result, because the different cases are mutually exclusive.

Some plugins don't allow fine-grained prioritization beyond the plugin name.
These have a defineOne() method instead of defineMany(), and the findParent() and findTitle() methods return a string instead of an array.

---------

Atm I am not really happy about the defineOne() and defineMany(). There are probably better ways to do the same. Such as, the hook_crumbs_plugins() could define if a plugin provides one or many rules. The defineOne() would be obsolete. And the defineMany() could have a different name.

That said, I think the entire concept of hierarchical prioritization, while powerful, will always feel awkward to people.
How could I fix this, without losing flexibility?

donquixote’s picture

I have for example no idea what "Inherited" does to my breadcrumbs...

Yup.. that's a difficult UI design task.
The concept of hierarchical is logically easy, but not easy to explain in human language, or to visualize. And feels a bit awkward.

Also, I imagine some mental resistance against the fact that this is all about one single step of parent-finding.

For the "inherit" section, I hope the metaphors two posts above did help.
In the "enabled" section, a weight is assigned to every rule. If a weight of 5 is assigned to menu.*, then every menu.something.* in the "inherit" section implicitly has the same weight.
Or the other way round: Every rule in "inherit" inherits the weight from the best matching wildcard rule in "enabled".
For menu.hierarchy.primary_links sitting in "inherit", Crumbs would first look for menu.hierarchy.*, and then for menu.*, then for *, to determine the weight, and whether this rule is enabled or disabled.

The "inherit" section is also where new rules end up first.
And, it provides a "pool" of available rules to be put into the enabled or disabled section, to override more generic rules.

donquixote’s picture

I also have a vague feeling that there's quite a few things that could be replaced with "standard code/apis" from core or contrib; (I'm thinking e.g. of the "ctools plugin system"). Using those might make things simpler and easier to understand...

Can you think of a way to achieve the same with Ctools plugins, now that I explained the hierarchical weights stuff?

donquixote’s picture

I think the main idea of ctools plugins is the automatic class detection. Right?
Then the class is instantiated by ctools directly.

I initially did something similar for Crumbs, but then decided that I want a factory hook in between. This would allow for plugin classes to be parameterized, and instantiated more than once.
However, I never ran into a use case for parameterized plugins.

For instance, the menu plugin could be parameterized with the menu name. Then it would only find those menu parent items that are in the specified menu. This would be repeated for every registered instance of that plugin.
However, it turned out to be cheaper to fetch all menu parents at once, no matter which menu they are in. Even if this way a few results would be discarded, this is still better than having a separate plugin instance for every menu.
This is the same for most other plugins.

Another idea was to sort not just plugins, but make each method on a plugin a separate rule that can be sorted.
I am not really convinced of this idea.

donquixote’s picture

Actually, sth like "menu position" would be nice as a crumbs plugin!
This would provide a UI for things that nowadays have to be defined in code.

donquixote’s picture

And now there is a big API rewrite in the 7.x-api-rewrite branch.
http://drupalcode.org/project/crumbs.git/tree/refs/heads/7.x-api-rewrite

The plugin for menus is rewritten, but untested. (the site I am working on, simply has no menus).
entityreference is not implemented.
og works, comment works, crumbs home-title works.

The difference of "defineOne" vs "defineMany" kind of plugins is made more explicit and strict. Now instead of these methods, plugin classes have to implement one of two interfaces: crumbs_MonoPlugin, or crumbs_MultiPlugin.

Documentation sits on InjectedAPI classes, on interfaces and pseudo-interfaces in crumbs.module and crumbs.api.inc.

Classes have moved into the lib folder, following something PSR-0-ish, as we will hopefully get in D8 (without namespaces, obviously).

donquixote’s picture

And now this branch does support entityreference.

bforchhammer’s picture

Wow, thanks for your extensive replies!

I think I now understand the reason for having the different types of instructions. The way I would describe it is that you have:

  • Enabled rules: apply in order of appearance
  • Exceptions: never apply, even if a respective * rule is enabled
  • Disabled rules: ignore completely

(This may or may not be easier to understand, but I think it covers the same cases, right?)

That said, I think the entire concept of hierarchical prioritization, while powerful, will always feel awkward to people.
How could I fix this, without losing flexibility?

I agree, it still is fairly complex and not easy to understand. I think reducing it on some level, might actually be a good idea if you want to make the Crumbs plugin more accessible for more users; so here's two possibilities:

  • Get rid of complexity completely; keep simple cases, have everything else be handled by custom plugins... (an exception to a rule can possibly be as simple as if($path == 'abc') return false; programmatically). What you'd be left with is just enabled/disabled + priorities. To make things easier for developers, you could provide a set of abstract base classes for plugins which do all the heavy lifting but allow custom code for the "when to use" logic.
  • Hide complexity away from user. Possibly into "plugin configuration" screen; allow the user to spawn multiple instances of a plugin and configure each plugin with the cases in which it should be used (or not used respectively; "exceptions"); this would require quite some refactoring work and a completely new UI. Easy exportability might go over board.

I think in the end it depends on what user group your module is aimed at. It feels like the preeminent user group at the moment are site builders (who don't want to write code); so if you go more towards developers, things may actually be easier to do in custom code (i.e. 1st idea, remove complexity); if you go the other way, the interface needs to be simplified (i.e. hide complexity, 2nd. idea).

Either way, these are just two quickly thought-off ideas; you have obviously spent way more time on this topic and can decide whether either makes any sense. ;-)

The topic of the module's "target user group" leads me to the question of: what are the pre-dominant use-cases? Do you have any data in regard to how many people actually have complicated use cases that require and use that kind of complex architecture? What do breadcrumbs typically look like? How do people typically use your plugin?

(Not sure I'm a typical user, but in my case I don't have any "exception rules" at all (as far as I'm aware atm); so I probably wouldn't need the complexity. I also think that, being a developer, I would probably write a custom plugin for handling edge cases if needed).

I think the main idea of ctools plugins is the automatic class detection. Right?

From what I understand, ctools only eases the plugin discovery and loading process; instantiation of classes is still left to you, so you could use any kind of constructors you want. Have a look at inc/parentfinder.inc and plugins/parentfinder/yabapi_menu_parentfinder.inc from my sandbox for example usage. (I'm only using the bare basics; for all available options please check ctools' "advanced help" info).

Other points in regard to "using existing APIs" could be: database api (new version in 7.x); form api (for admin pages);

donquixote’s picture

One reason for the "complexity" / fine-grained prioritization is to reproduce what other breadcrumb-customizing modules provide.
E.g., modules that build breadcrumbs based on the menus, will let you choose which menus should get a higher priority.
Modules that build a breadcrumb based on nodereference something, will let you choose for which node types this behavior should be enabled.
Or for taxonomy, it will let you choose which vocabularies.
And checkboxes and radios all over the place.

The awkwardness of hierarchic prioritization has to be compared to having a bunch of distinct settings forms, with checkboxes and radios that often have unpredicted effects, that conflict with each other, etc. At least, that don't have a uniting mental model.

---------

I think the solution is to make a pleasant drag+drop GUI, but keep the internal structure.
Also, the API revamp in the new branch will hopefully make things more clear to developers.
This might still feel somewhat awkward.. but atm it is the best I can think of.

I am thinking of a tabledrag with an additional "pool" for new items.. I need to check available jQuery plugins..

--------

what are the pre-dominant use-cases?

Use cases:
1) Large community or organization website, with a lot of custom site structure and hierarchy.
2) Any site where the core breadcrumbs don't work as expected.
3) Support contrib modules that explicitly ask to install an additional breadcrumb-customizing module. One such module is Organic Groups - see #1162824: OG Breadcrumbs.

I think the difficulty is with (2) and (3), if there is no genius site builder around who understands how rules have to be sorted.

donquixote’s picture

And now, the 7.x-api-rewrite branch does support:
- taxonomy
- menu
- entityreference
- og
- comment
- path (alias)
- crumbs home-title (make "Home" the title of the root item)

I am going to play with this and test, before I merge it back into the main branch.

bforchhammer’s picture

Use cases:

I actually ment more in terms of specific "breadcrumb rules"... i.e. to better understand why the complexity is needed/how it is used.

donquixote’s picture

Use case: Custom-tailored Organic Groups breadcrumbs
http://drupal.org/node/1400992

bforchhammer’s picture

Something I noticed while looking through the code:

spl_autoload_register('_crumbs_autoload');

You can autoload classes by adding their respective files to the files[] array in the module .info file btw. See docs.

donquixote’s picture

Yup..
I don't like that very much.

while working on the Crumbs module, I was also participating in the discussion about autoload in D8.
The site that I work on currently has two experimental autoload things:

  • Sth that lets me use wildcards like files[] = lib/**/*.inc in the *.info file.
  • An autoloader that will find [module name]_Class_Name in [module dir]/lib/Class/Name.inc.

I was considering to publish these..

Anyway, probably better to do it the boring way.

bforchhammer’s picture

Use case: Custom-tailored Organic Groups breadcrumbs
http://drupal.org/node/1400992

Thank you for the description of that usecase and the included example. That's the kind of documentation that really helps site builders in my opinion! :-)

One question though: does that example require any complexity beyond "enabled + prioritized list of rules"? (I don't see it)

Btw. if you want to discuss more interactively, I hang out in irc #drupal sometimes... ;-)

donquixote’s picture

does that example require any complexity beyond "enabled + prioritized list of rules"?

I am going to add another example with menu + taxonomy, to show the use of fine-grained hierarchic prioritization. For instance, assign higher priority to a specific vocabulary for a specific node type, but ignoring other vocabularies.

donquixote’s picture

I am going to add another example with menu + taxonomy

https://drupal.org/node/1403816

bojanz’s picture

I must say that this module saved me.
I was pretty unhappy about having to use custom_breadcrumbs and taxonomy_breadcrumbs in Kickstart v2, since both are unmaintained and full of D6-era code with confusing UIs and confusing approaches. Additionally, custom_breadcrumbs has a submodule that deprecates taxonomy_breadcrumbs, and I wasn't even aware of that.
So I started thinking about how it should actually be built, with rules and weights, and just when I was completing my mental image, I come across this module which does exactly that. So, Kickstart v2 will now use Crumbs :)

I've already opened issues with patches about individual problems that I found.

The main two problems I immediately saw:
1) The code is really badly documented, sometimes with whole files without a single docblock.
2) The UI for rules is really bad. Shouldn't be hard to fix, once we agree on how we want it to look. Left my thoughts in the relevant issue.

So, in general, kudos, and see you in the queue.

klonos’s picture

So far it's been a life saver! Thanx ;)

Perhaps the only two things currently missing IMO are #2040059: Wrap the current page at the end of the breadcrumb trail in a span and add a class. and #2040047: Allow custom trail separator (char or image).

klonos’s picture

...and since you're always happy for feedback, you'd be happy to know that I'm perfectly happy with it. Lots of happiness ;)

I personally consider this module feature-complete now that the two features I mention in my previous comment are implemented. Anything else that comes will be the icing on the cake. Thank you 1mil!

donquixote’s picture

Thanks!
The next "sexy" feature will be this: #2008514: Add manual path parent mappings
And we need something about language: #1848610: Allow Crumbs' settings to be language dependant
Path breadcrumbs has some SEO stuff that might be interesting..
The rest does not seem as important.

And finally, I think Crumbs (the module page) has a SEO issue. Maybe because "breadcrumb" is not part of the path alias..

ptvv’s picture

I found Crumbs via the Comparison of Breadcrumb Customizing modules, and since I was looking for breadcrumbs derived from entity references it was the only choice. As the issues history shows, it was a bit painful, but with some help I've been able to get it set up as I wanted.

Strengths:

  • Full marks to donquixote for patience and involvement.
  • Flexibility
  • The "Debug" tab, which is not perfect but is still useful in allowing the site builder to see what is happening.
  • The main module page links to documentation. This seems to be rarer than I would hope with Drupal modules.

Potential improvements:

  • The "Debug" tab could be even more useful if it showed not only which plugins were used but which ones were applicable but disabled. (This would help the situation with respect to crumbs.home_title).
  • It takes a bit of thought (and probably some experience with the module) to work out in what order to read the debug output. It might be clearer as a list, or at least with the rows reversed so that it reads top-left to bottom-right rather than bottom-left to top-right with occasional jumps down to the bottom for (default).
  • The tabledrag interface is a pain to use when there are more than ten items (and there are more than 10 plugin weights just from the default menu hierarchy). It doesn't interact well with needing to scroll vertically. The textual interface from labs is preferable, but requires enabling an extra module.
  • The "disabled" vs "disabled by default" dichotomy is confusing. It seems to me that a simpler solution would be to actually default them to disabled: i.e. on installation, to assign them to "disabled".
  • As a followup to 2070181, I've just found the taxonomy use-case doc and I observe that it shows plugin keys like taxonomy.termReference.field_animal_category.*, whereas the plugin keys I observe are like taxonomy.field_quux.*. Given that taxonomy.termReference.* is "disabled by default", I wonder whether this discrepancy is an error in the code and an alternative root cause of my problems in 2070181.
donquixote’s picture

As a followup to 2070181, I've just found the taxonomy use-case doc and I observe that it shows plugin keys like taxonomy.termReference.field_animal_category.*, whereas the plugin keys I observe are like taxonomy.field_quux.*. Given that taxonomy.termReference.* is "disabled by default", I wonder whether this discrepancy is an error in the code and an alternative root cause of my problems in 2070181.

#2072839: Regression: taxonomy.termReference.* plugins have been renamed.

Overall, I get the impression I was too fast to make 7.x-2.x the recommended version.

donquixote’s picture

  • The "Debug" tab could be even more useful if it showed not only which plugins were used but which ones were applicable but disabled. (This would help the situation with respect to crumbs.home_title).
  • It takes a bit of thought (and probably some experience with the module) to work out in what order to read the debug output. It might be clearer as a list, or at least with the rows reversed so that it reads top-left to bottom-right rather than bottom-left to top-right with occasional jumps down to the bottom for (default).

#2072849: Further improve debug page

I am all for it!

donquixote’s picture

The tabledrag interface is a pain to use when there are more than ten items (and there are more than 10 plugin weights just from the default menu hierarchy). It doesn't interact well with needing to scroll vertically. The textual interface from labs is preferable, but requires enabling an extra module.

The textual thing is what we had in 7.x-1.x. Then people started asking for tabledrag..
I am not really happy with either. But let's discuss in the appropriate issue.

The "disabled" vs "disabled by default" dichotomy is confusing. It seems to me that a simpler solution would be to actually default them to disabled: i.e. on installation, to assign them to "disabled".

I need to think about this..

donquixote’s picture

The "disabled" vs "disabled by default" dichotomy is confusing. It seems to me that a simpler solution would be to actually default them to disabled: i.e. on installation, to assign them to "disabled".

#2074473: Improve handling of default weights for plugin keys.

donquixote’s picture

Anyone who is going to DrupalCon Prague and wants to share their use case:
#2094933: Crumbs BoF/meeting on DrupalCon Prague 2013?

fhdrupal’s picture

whenever I update to new version, same settings doesn't work, I have to start over for new changes.
whenever updated first time crumb disappears. Some time I have to install previous version again, if nothing works.

donquixote’s picture

@fhdrupal:
So the problem is that the settings schema changes between versions, and hook_update_N() is not properly implemented to migrate the settings from older versions?
Which version updates did you try that suffered from this?

fhdrupal’s picture

I was using "7.x-2.0-beta13+0-dev" upgraded to "7.x-2.2", everything disappeared. I changed settings to the same but breadcrumbs would show only home>current-page. No taxonomy or category in the middle. installed old one back everything is OK now except the current page name is shown as node[123].