In some cases, you'd want the dropbox be able to influence your hierarchy (i.e. the hierarchy displayed in the hierarchical select).

This was requested by d.o user rkoeten. He explains:

What I need to do is the following: - users must be able to make one or more appointment selections
- a single selection consists of a teacher + date/day + timeslot (hence
the need for unique hierarchy selection; all teacher availability schedules
can be different)
- once a first selection is made, a second and/or third selection cannot
involve the first selected teacher, nor can it overlap in time/date with
the first appointment (users still can't be at 2 places at the same time); i.e. the hierarchy selection of the first influences the dataset of the
2nd, 3rd, 4th, etc. selection. - once a 2nd selection is made, the user must be able to change the 1st
selection, albeit that the selection set of the 1st one is now "influenced"
by the selection of the 2nd selection. And so forth.

Attached is a patch that changes HS to do just this. Normally, I'd commit this and then let this be tested. However, I want to make sure that:
- HS doesn't break since it's so close to a final and this requires an API change, as well as extensive (but simple) changes to the more complex parts of code in HS.
- it really does support the use case Rob (rkoeten) needs

So I will need you to test this thoroughly for me, Rob. In my own tests, HS kept working as it should.

Note: while this changes the API, it only *extends* it, it doesn't require any changes to existing implementations!

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Wim Leers’s picture

Fixed one small bug I had missed.

rkoeten’s picture

First of all, thank you Wim for turning an "email support request" into a "feature request" and then delivering a corresponding patch within the same day. Great work.

I was able to test out the second patch (dropbox_influence_hierarchy.patch posted on 9/23 18:40). Note that the hierarchical_select.module RC3 version that I'd picked up (1.131 2008/08/27 16:21:44) was somewhat out of sync with the patch baseline that you'd used, but I was able to apply the patch anyhow. The patch didn't break any existing functionality.

The testing that I did with a multiple select into the dropbox, did indeed provide me the ability to dynamically change the remaining selection hierarchy (i.e. removal of options based on one or more selections). I was able to remove both top-level hierarchy items as well as deepest level entries.
I still have to find a way to also remove mid-level hierarchy items (in case there are no items left at the deepest level) since it breaks the enforce_deepest model. E.g. if there are no valid selection options left at level 3 then level 2 becomes the "current" deepest for that particular hierarchy branch and selection is allowed (but doesn't make sense and shouldn't be possible in my case).
Other than that it certainly does the job for me. And it pretty much eliminates 400-500 lines of custom javascript code that I had to create to do a similar selection in a standalone webpage.

Four questions:
1) what is the reason not to provide the dropbox selection to hook_hierarchical_select_valid_item()? Rather than dealing with "removal" in 2 places in the code, select_valid_item would provide a single point for both root and children items.
2) can you provide a bit more insight into the sequence of the hook_hs_ API calls that are made? I've noticed that sometimes root_select, child_select seem to get called several times for the same information (item level) in a single page rendering, potentially resulting in as many trips to the database. I'm trying to understand where I could or potentially need to optimize.
3) Also are there indeed restrictions with the item names that can be used in the name/value pairs that are passed in the hs API? As I mentioned in a separate email, when I used pure numeric labels for the top level hierarchy items, things started to break. If I prefix the item names with a letter (a-z), then everything is fine. This seems to be a type comparison or array referencing issue, but nonetheless very painful to debug.
4) this last one may turn into a feature request; would it be possible to change a selection into a "disabled" html option (i.e. a grey'd-out item in a selection list) rather than completely removing/omitting the item from the list? This may be more intuitive to the user since options remain visible, but it becomes clear that another selection changed the actual selectable options. I.e. items don't magically disappear, but visibly become "disabled" as a result of the user's selection.

RK

Wim Leers’s picture

I was able to test out the second patch (dropbox_influence_hierarchy.patch posted on 9/23 18:40). Note that the hierarchical_select.module RC3 version that I'd picked up (1.131 2008/08/27 16:21:44) was somewhat out of sync with the patch baseline that you'd used, but I was able to apply the patch anyhow. The patch didn't break any existing functionality.

The patch was against HEAD indeed. HEAD contains improvements over RC 3.

The testing that I did with a multiple select into the dropbox, did indeed provide me the ability to dynamically change the remaining selection hierarchy (i.e. removal of options based on one or more selections). I was able to remove both top-level hierarchy items as well as deepest level entries.

Great!

I still have to find a way to also remove mid-level hierarchy items (in case there are no items left at the deepest level) since it breaks the enforce_deepest model. E.g. if there are no valid selection options left at level 3 then level 2 becomes the "current" deepest for that particular hierarchy branch and selection is allowed (but doesn't make sense and shouldn't be possible in my case).

Let's elaborate on this.
- Why can't you remove mid-level hierarchy items? It should be possible, since mid-level hierarchy items are also retrieved through the _children() hook, the same hook as for deepest-level hierarchy items.
- Why does it break the enforce_deepest model? enforce_deepest is dynamic: it enforces the deepest level depending on how deep the tree actually goes. So in that sense, the current algorithms should still work on your dynamically changing hierarchy.

Note to self: dropbox-influenced hierarchies can NOT be cached, since items that previously had children, may not have children after being added to the dropbox. That might be causing problems, but only if you're using WebKit, because that's the only browser supporting HTML 5 databases.

Other than that it certainly does the job for me. And it pretty much eliminates 400-500 lines of custom javascript code that I had to create to do a similar selection in a standalone webpage.

Awesome :)

Four questions:
1) what is the reason not to provide the dropbox selection to hook_hierarchical_select_valid_item()? Rather than dealing with "removal" in 2 places in the code, select_valid_item would provide a single point for both root and children items.

valid_item() is only used to validate items in the dropbox, to ensure that the user didn't tamper with the data. Basically it just means "item does exist in the hierarchy". And you really can't do anything more advanced than this, because at this point, you can't pass in the dropbox, because that's the very thing that's being validated!
So in that sense, the user could still hamper with the data now: you could dynamically alter the hierarchy, but if the user would change this data to contain the same item twice, it would still be accepted. Then again, the user would have to change a serialized array. I could add a countermeasure to this by adding hash to guarantee no alterations I guess, or safer: store the selections in the dropbox on the server side. Right now I'm not sure anymore why I didn't do that in the first place, but I'm sure I had a good reason.
The point is: this is a design flaw at this point, but an acceptable one, and an understandable one because I didn't foresee this use case.

2) can you provide a bit more insight into the sequence of the hook_hs_ API calls that are made? I've noticed that sometimes root_select, child_select seem to get called several times for the same information (item level) in a single page rendering, potentially resulting in as many trips to the database. I'm trying to understand where I could or potentially need to optimize.

Use the HEAD version and then use Firebug (Firefox) or the Web Inspector (Safari) to get a basic performance analysis. Typically, you'd want to statically cache anything you get from the database, you should do that in any case, even when writing db queries not for the HS API.
The sequence of hook calls … well … that's pretty much impossible to say. Just look how many settings are available for HS, and know that each of those combinations of settings will result in a different sequence ;)

3) Also are there indeed restrictions with the item names that can be used in the name/value pairs that are passed in the hs API? As I mentioned in a separate email, when I used pure numeric labels for the top level hierarchy items, things started to break. If I prefix the item names with a letter (a-z), then everything is fine. This seems to be a type comparison or array referencing issue, but nonetheless very painful to debug.

Impossible. Taxonomy is also 100% numeric items and works just fine. You just have to make sure that each item in the hierarchy has a UNIQUE identifier. If you repeat the same number for different items, then yes, you will get in trouble. It's also possible that your _lineage() implementation is faulty.

4) this last one may turn into a feature request; would it be possible to change a selection into a "disabled" html option (i.e. a grey'd-out item in a selection list) rather than completely removing/omitting the item from the list? This may be more intuitive to the user since options remain visible, but it becomes clear that another selection changed the actual selectable options. I.e. items don't magically disappear, but visibly become "disabled" as a result of the user's selection.

This would require the ability to disable <option>s within <select>s. This is unfortunately not supported by HTML, so can't be done.
What I could do, is using an <ul> instead of a <select>, but then it would no longer be gracefully degradable. And would require special code for the case when JS is enabled. So … also a no-go. Definitely for the D5 version.

Wim Leers’s picture

Status: Needs review » Needs work

This is going to become a .1 feature.

Also, this patch needs work, because cache systems cannot be used when the dropbox can influence the hierarchy!

rkoeten’s picture

I can understand that the patch needs further work/review. I'm still working with the patch "as is", with no caching done on my part (I need the "influence" functionality for my application). However I'm running into the following issue:
In order to get the correct dropbox content passed via the HS API (to influence other parts of the hierarchy) I assume that I need to set #config['save_lineage'] to TRUE (I need to do this anyhow since I've set "enforce_deppest"). Upon callback (to hook_hierarchical_select_root_level and hook_hierarchical_select_children) I get the complete lineage passed (my item identifiers are made of concatenated tokens, separated by ":". e.g. for my 3 levels I generate the following items: , : and ::). As such I can dynamically adjust my selection items; so far so good.
Upon callback to hook_form_submit() though, I'm only getting the top 2 level items (2 out of 3) in the result array (instead of all 3 that are in the lineage) :
Array
(
[id] => 0
[select_schedule] => Array
(
[0] =>
[1] => :
)

[op] => Save
[add] => Save
[mode] => edit
[form_token] => fad47f2081e1ab464ee0db27c216147c
[form_id] => confsched_signup_edit
[hs_form_build_id] => hs_form_4de966f95918ff04c3d2af11ff9203ae
)
When I disable "save_lineage" then I'll get the actual desired form contents (::), but then obviously I am not getting the dropbox content passed, so I can't dynamically adjust the selection options. What's different in the submit when I select "save_lineage"?
How do I control (if at all possible) what depth/length of the lineage gets passed to the form contents upon submit (it's the same issue for hook_form_validate())?

Wim Leers’s picture

As long as you're not using Safari, client-side caching won't even work, so it won't affect you.

The save_lineage setting is independent from whether the dropbox will be passed or not. The only relevant setting is $config['dropbox']['status'], which must be set to 1.

In the docs:

 - save_lineage (optional, defaults to 0)
   Triggers the lineage saving functionality. If enabled, the selection can
   consist of multiple values.

So I'd actually say you never want save_lineage to be enabled, because then you wouldn't be able to make selections like:
- BMW > 6 series
- BMW > 7 series

Because if the first lineage would have been added to the dropbox (BMW > 6 series), then BMW would have been removed from the hierarchy, and therefore the user wouldn't be able to select BMW > 7 series.

Why do you insist that you don't get the dropbox content passed when save_lineage is disabled?

rkoeten’s picture

So I finally figured out what was wrong; three minor coding/interpretation errors on my end on top of each other such that they masked each other with save_lineage enabled and caused some interesting but totally different side-effects with save_lineage disabled. I can now consistently get the dropbox data. I've had some other issues with passing $params, so I'm running without that right now. If you don't have straight/singular item identifiers (i.e. just plain integers) then the HS API gets quite tricky and is very unforgiven; in my case where I use multi-level tokens it's been a challenge.

Meanwhile I actually have your code with patch running on my site and have a couple hundred people successfully using your HS widget to schedule multiple appointments (congrats, even though it's pre-release code). I didn't have have much of a choice since there aren't any modules that provide this level of client +server side flexibility with a usable API.
Sofar I have had one client side user complaint, where her issue appeared linked to the Safari caching issue that you warned about (mac only; the windows Safari 3.1.2 browser that I test with works fine).

I've glanced/peeked through your code a couple times, but not enough to get a full comprehension of what's all going on. In order to do a patch code review I'd need to spend some more time going your code. If that's helpful to you then I can do that, though it may take a couple weeks. Let me know.

RK

Wim Leers’s picture

It's not necessary to do a review of the patch, the patch in itself is good. I didn't commit it right away because I didn't want to postpone a final release for HS 3 even further.
And yes, it's sad that I can't be very confident about my code. But truth be told: it's badly engineered. It's been extended *so* many times because *so* many settings have been added that all affect the hierarchy. It needs to be rewritten, OO-style, and unit-tested.

What I would appreciate, is if you could reroll the patch against the DRUPAL-5--3 branch. No worries: it's the same as the old HEAD branch. HEAD will now be used for the Drupal 6 port. Rerolling the patch should be fairly trivial. This patch will go in, since it works, as soon as I've tagged the final release of HS 3. So it'll be included in the first minor release.

Thanks for actively following up on this issue! :)

P.S.: the Safari issue, if it's related to client-side caching, can be fixed by disabling the "Cache in a HTML 5 client-side database" setting at admin/settings/hierarchical_select.

Wim Leers’s picture

Version: 5.x-3.x-dev » 6.x-3.x-dev
Wim Leers’s picture

Status: Needs work » Postponed (maintainer needs more info)

Postponing this due to lack of response.

Wim Leers’s picture

Status: Postponed (maintainer needs more info) » Postponed
robby.smith’s picture

subscribing - i hope with HS 3.0 out this can be further developed? thanks!

Wim Leers’s picture

Issue tags: +HS4

Tagging for HS4. Included in the HS4 roadmap: http://drupal.org/node/1052670.

klonos’s picture

Version: 6.x-3.x-dev » 7.x-3.x-dev

This will happen in the 7.x branch.