A module that saves the state of Drupal's collapsible fieldsets.

We encourage users to post events happening in the community to the community events group on https://www.drupal.org.
jrockowitz's picture

I figured my first attempt at a custom and hopefully contributed module should be something that is simple and usable, while a little bit challenging.

I love the simplicity of the FAPI collapsible fieldset but I was disappointed that it didn't remember whether I wanted it to be collapsed or open. So this is my attempt at fixing that problem.

I made very short 3 1/2 minute screencast demonstrating how this module helps improve the usability on the enable/disable module page. (Administer › Site building › Modules)

Here is a very simple demonstration page.

Download the 'Fieldset_helper' module.

Finally, here is the API documentation.

Any feedback is greatly appreciated. I would like to fix and resolve any major issues before applying for a CVS account.

Thanks,
Jake

Comments

Comments:

Garrett Albright's picture

Comments:

Your demonstration page is inaccessible to non-logged-in users. Ouch.

Your code looks sane. You commit the usual sin of not declaring arrays before you start adding items to them (I don't care if PHP doesn't complain by default, and I don't care that core does it; it's still wrong), and many of your comment lines stretch beyond 80 columns, but for the most part you seem to have the "Drupal way" down.

Your jQuery should be fired using the Drupal.behaviors method instead of jQuery's standard $(document).ready method.

Just on a more general level, the way you've accomplished this seems a bit "heavy." Dependency on a non-core jQuery library? Overriding theme_fieldset()? Not the way I would have gone about it. Consider this:

  1. hook_form_alter() adds a JS file to forms, as well as a #submit function.
  2. When the form page is loaded by the browser, the JS attaches a function to the form submission event.
  3. When the user submits the form, the JS finds all fieldsets in the same form as the one that was submitted (in case there are multiple forms on the page) and notes their IDs and collapse states. It serializes that data and inserts it into a new form element, then lets the browser submit the form.
  4. Back on the server, the #submit function added earlier fires and reads the data the JS added to the form. It notes the ID of the form, the ID of that form's fieldsets, and the collapse states of those fieldsets and crams it into the user's $user->data.
  5. The next time a form is loaded, hook_form_alter() checks to see if the current user has information saved on the collapsed state of that form's fieldsets. If so, it crawls the form and alters each fieldset's #collapsed value accordingly.
  6. GOTO 1

No AJAX, no custom theming… Of course, feel free to do it your own way, but that's the way I'd personally try to go about it first.

Thanks for your feedback

jrockowitz's picture

Garrett: Thanks for your feedback

I fixed the demonstration page and cleaned up the JavaScript code.

Your solution is really clean and thought out. I really appreciate you spending the time write it out.

There were three goals and challenges I found when writing this modules.

First, collapsible fieldsets are not always used in a form. Here is a very simple example of a CCK content-type displaying a collapsible fieldset that is not associated with a form. On the Module page (admin/build/modules) the fieldsets are not actually built using a FAPI form, they are rendered within the theme_system_modules() function.

These two issues eliminated the possibility of reliably using hook_form_alter() to catch every possible collapsible fieldset and forced me to override theme_fieldset().

I really hesitated overriding theme_fieldset(). If there was something like a 'hook_preprocess_fieldset()' function I would have preferred to have used that. My only hope here is that most modules and themes are not overriding this function and I can accurately document this potential issue.

Since fieldsets are not always within a form I could not use a hidden input field to manage, store, and transfer the state of all the collapsible fieldsets. The solution I decided was to use cookies for storage and a little AJAX to retrieve the fieldset ids from the server.

Second, I wanted to be able save collapsible fieldsets states for anonymous users. This requirement immediately eliminated storing anything in the user object, since anonymous users share the same user object. The only remaining options was storing the collapsible fieldset state information in a user's server-side SESSION variable or their browser cookie. I chose to use a cookie because I did not want to load the SESSION table with tons of 'kind of useless' information.

Finally, I wanted to be able to insure that the collapsible fieldset's state would be saved even on a cached 'client-side' page. For example, using this module, you can toggle any collapsible fieldset's state, navigate away from the page by clicking any menu link, and when click the browser's back button to return the page the collapsible fieldsets will be restored to exactly where you left them.

One possibility would be for me to integrate your solution and give site admins a choice on which mechanism, server-side or client-side, they would like to use. For sites that do not want tweak the module page and would never use fieldsets outside of a form your solution is ideal. The risk here is bloating this module and confusing most end-users who probably don't know what we are talking about to begin with.

Thanks again.

A nice reply. I didn't

Garrett Albright's picture

A nice reply. I didn't consider saving the statuses of fieldsets which weren't part of forms, but I could see where that functionality would be desirable. I also didn't know that the modules list page doesn't use FAPI… that's surprising (and frustrating).

jrockowitz's picture

Even though, I can justify most of module's the design concepts, I still felt that the module was, as Garette had pointed out, a little bit "heavy". I decided to step-back and re-think my use of AJAX and realized it wasn't even needed. I am still stuck with overriding the fieldset's theme but without the AJAX it should perform a little faster and it is now a little bit "lighter".

Garette, thanks again for your feedback.

At our site you can now see

Frank Steiner's picture

At our site you can now see a real-world usage of this feature: http://drupal.bio.ifi.lmu.de/
A page with some fieldsets is e.g. http://drupal.bio.ifi.lmu.de/mitarbeiter/volker-heun

I'm not using your module but only took its code part for cookie handling because our use of fieldsets is different: Any user can include fileldsets with a special class in his page:

<fieldset class="lfebio collapsible collapsed">
<legend>My Fieldset</legend>
Some text

We use an input filter to convert this into

<div class="lfebio collapsible collapsed">
    <a id="fieldset-node-35-1" href="javascript://" onClick="Drupal.bio_toggle_fieldset(this)"
          class="febio collapsible collapsed">My Fieldset </a>
    </a>
<div>
<fieldset class="lfebio collapsible collapsed">
Some Text
</fieldset>

I.e., we remove the legend and replace it by a div and an anchor. We did this for formatting reasons, because getting the grey background when opening a fieldset at our site was just not possible to get with IE, FF and Opera, because all of them have different problems/errors handling the legend-tag.

Now by already using such a filter we can easily create a unique id for every fieldset! The id is sth. like fieldset-$_GET['q']-<static in-page-counter> and thus we don't have to care about forms, ids etc. After applying the filter, every fieldset that we want is in a usable form!

Note that using a input filter has a better performance for slow clients because fieldsets are not turned into collapsible ones by the client (via collapse.js) but by the server.

Our code for (un)collapsing is a bit different due to the new structure, but with jquery not very difficult. We have also implemented a way to jump to an anchor inside a collapsed fieldset by uncollapsing all parent fieldsets. You can see this at http://drupal.bio.ifi.lmu.de/forschung/structural-bioinformatics/ppm/vis...

I thought about making our filter a module so that people could use and css-style fieldsets more flexible, but it turns out I don't have time for this. If someone is interested in doing this I won't mind to post our filter code and the javascript stuff that we use.

Two issues/features I will try to implement.

jrockowitz's picture

First, somewhere in my server and client code is a cookie state manager API that could be used to manage the state of any client-side element. For now I am going to focus on just this module but I will isolate the cookie state manager API so that it can be re-used (via cut-n-paste) in other peoples custom modules. BTW, someone emailed me and said they had re-used similar code from my module for their project.

Second, I think it makes a lot of sense to build a collapsible fieldset filter that would allow the fieldset state to be saved. One challenge is I need to insure that every fieldset must have a reliable unique id to properly save the state.

I am going to apply for a CVS account so that I can better manager this module, and I will put sometime in next week to address the above features.

Frank: I have had a similar problem with styling fieldsets and legends. I am not ready to switch over to using divs, I still have hope that there is css guru with a fix/hack for your above problem.

I've given up on the styling

Frank Steiner's picture

I've given up on the styling issue ;-) The main reason is that firefox doesn't give the legend element a width, so I can't get the 100%-width-grey-bar. Doing it as top-border works, but Opera adds the width of the top-border additionally as top-margin. These are the two bugs I never got around.

Anyway, using the additional div/a solved the problem with the ids, too, because those elements were new, so I didn't have to care about ids in the fieldset (that might already be specified).

Following up

jrockowitz's picture

I refactored the code and created a project for the fieldset helper module.

I cleanup up the cookie state manager and added support for saving the state of a static html collapsible fieldset entered within the body of a node. (based on this page http://drupal.org/node/118343)

Frank: I took it as personal challenge to figure out a way to style a collapsible fieldset's legend as a header without having to convert the legend to header tag. It is possible that my solution would remove the need to create a filter and additional javascript for a 'stylized' collapsible fieldset.

Very interesting, I will

Frank Steiner's picture

Very interesting, I will check this. Maybe it was the display:block that I was missing and without that FF can't show the background over the full width for an anchor in the legend!

There's one thing I can't

Frank Steiner's picture

There's one thing I can't get working: We are using icons to the left of the anchor that should be clickable, too, like those little triangles in Drupals default style. But when I add e.g. 20px padding-left to have space for the icon, then the anchor gets width "100%+20px", floating out of my box. I'm not sure if this is related to my theme.

Could you try that to re-add those drupal icons and see if it works with your settings?

jrockowitz's picture

Frank: Here is a full working example with your icons.

Basically, I am using nested containers to prevent the "100%+20px" issue.
http://www.google.com/search?q=css++100%25+%2B+padding

I am not great at walking people through my code so feel free to ask more questions.

Ok, I see. That's what the

Frank Steiner's picture

Ok, I see. That's what the span is for! Thanks for the explanations!

Just a minor bug: The

Frank Steiner's picture

Just a minor bug: The fieldset styling on the demo pages lose styling when javascript is turned off in the browser.

Default States

DaPooch's picture

This is an awesome module. Exactly what I've been looking for and a good solution to one of my biggest qualms with Drupal's admin interface (excessive clutter).

My only wish (and perhaps it's already doable somehow and I've missed it) is that I can set default states for new forms that have yet to be cookied. I realize this could be painful considering all the possible fieldsets that can exist on a given node (should be configurable via the Manage Fields screen IMHO) but even a global "all collapsed" or "all expanded" would be a very useful addition to me. I've seen quite a bit of debate around the default behavior but if it were configurable people can do it however they like. Personally I would like to default to all collapsed and then let the cookie do it's thing for the pages as I save them.

You are making me start to think about version 2.x...

jrockowitz's picture

For cleaning up the admin interface's excessive clutter I strongly recommend using Vertical tabs.

I do like the idea of a global expand/collapse all block or link.

Collapsing all fieldsets by default might work if the admin can control what pages should be collapsed by default.

Both the above features will likely be added to the next version of this module which I am going to start working on in the next few weeks.

BTW, the Admin menu module includes the ability to collapse all fieldset on the module page. Also the Form Alter UI module allows you to override a fieldset's default state.

Thanks, I'll check out

DaPooch's picture

Thanks, I'll check out Vertical tabs and Form Alter UI. Hadn't seen those yet. I have noticed the ability of Admin menu to collapse the module page fieldsets, I definitely use that.

For the time being I just wrote a small custom module to set the default states and weights for various fieldsets I want to have shown and hidden like so using the form_alter hook (key is to be sure your module weight is heavier than all the weights of the modules you're trying to affect so the properties are set last- System table of DB for that:

function [MODULE_NAME]_form_alter(&$form, $form_state, $form_id) {
 
  if ($form['#id'] == 'node-form') {
    $form['path']['#collapsed'] = TRUE;
    $form['menu']['#collapsed'] = TRUE;
    $form['revision_information']['#collapsed'] = TRUE;
  
    $form['nodewords']['#collapsed'] = FALSE;
    $form['hierarchy']['#collapsed'] = FALSE;
    $form['hierarchy']['#weight'] = -4;
  
  }
 
}

Fieldset helper 6.x-2.x

jrockowitz's picture

alanpuccinelli,

The fieldset helper 6.x-2.x branch (http://drupal.org/node/946000) includes all your requested features. I am finding collapse all by default very useful.

I will definitely be making a few more tweaks before releasing a 2.x beta. Please post any issues or requests to the fieldset helper issue queue. (http://drupal.org/project/issues/fieldset_helper)

Thanks for you feedback.

Custom modules are sometime the way to go...

jrockowitz's picture

I am using a very similar function for my most recent project. Once quick recommendation is to make sure the fieldset/element is defined before setting its properties. If the element does not exist and you set some property, you might see some mysterious behavior like an empty element being created.

if (isset($form['path']) {
  $form['path']['#collapsed'] = TRUE;
}

One other quick tip, is you can hide elements and fieldsets by setting their #access property (http://api.drupal.org/api/drupal/developer--topics--forms_api_reference....) which is really helpful if you want to simplify the node edit form.

Excellent tips! I'll be sure

DaPooch's picture

Excellent tips! I'll be sure to use them. Thanks!

Hey, I support a base theme

KrisBulman's picture

Hey, I support a base theme called aether at http://drupal.org/project/aether and am looking for some useful tips on how to save collapsed states within theme-settings. I would much rather include js instead of asking users to use a module for theme-settings collapsed states.

What I am looking to do is a little different than what the module in this discussion does.

The theme settings page uses autotabs to loop through 5 sets of fieldsets, all with the same name just with a different numbered suffix (1 through 5). The sub fieldsets on these pages all point to fieldsets all with the same classnames for their counterparts on each of the 5 tabs. My end goal is to expand/collapse all 5 fieldsets that have the same class names (one on each vertical tab) any time one is clicked.

I'm using a simple bit of JS to hide/show each of the 5 fieldsets based on a selected option (below).. hoping someone can enlighten me on an equally simple solution for the above requirement.

  $('#edit-layout fieldset').click(function() {
    if ($(this).find('input.form-radio[value=4]').attr('checked')) {
      $(this).parent().parent().find('fieldset[class^="sidebar-layouts"]').fadeIn();
      $(this).parent().parent().find('div[class^="form-item form-type-select form-item-sidebar-second"]').fadeOut();
    }
  });

Contributed Module Ideas

Group organizers

Group notifications

This group offers an RSS feed. Or subscribe to these personalized, sitewide feeds: