Resolution

Added on latest development version.

Remaining tasks

Only tests at this stage.

User interface changes

Provides a new countries selection widget that allows users to filter by continent before selecting the country.

API changes

This patch has added a new widget that handles this. The other widgets still sit on top of the options list fields.

Original report by pcambra

I needed a continent field widget for this module so as that you select a continent and get a country list filtered by contintent. In the end it's "just" a form widget and doesn't store anything additional.

Patch attached with some awesome #ajax magic ;)

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Alan D.’s picture

Status: Needs review » Needs work

Nice. I like. This is a huge UI improvement in a 250 element select list. :)

A few things:

1) Make sure that you use t() on all strings.

+ '#title' => 'Select a country',
+ '#title' => t('Select a country'),

2) I do not think that the #description is needed on the iso2 sub-element and just "Country" rather than "Select a country". On a multi-value field, both become very repetitive.

3) A bit of internal structure could be done to help themers to condense this. Maybe a wrapper DIV with a clearfix class. That should be enough to easily float the sub-widgets inline if someone so desires.

If you are really keen, a custom theme callback could be warranted. Looking at the results, I want to move the Continent and Country labels into #descriptions rather than #titles, but this is my preferred style for multi-component fields, others dislike this.

4) The array_intersect_key logic should be moved into the widget form logic, otherwise there is the danger of interfering with other fields (ie: updating the drupal_static() result).

Un-related: This will expose the following "@todo I18n for user defined overrides." for continents. This is a separate issue though. @webflo any thoughts on this?

pcambra’s picture

Status: Needs work » Needs review
FileSize
3.57 KB

Ok, fixed #1 and #2

About #3, never been a huge fan of placing too much theme inside modules, added a clearfix for continents, but I don't thing that much more fancyness is required :)

I introduced #4 as a way to get continents filtered (there's no way to do that now) but it will probably make sense outside this patch and I moved the intersect to the widget.

Let's try again, thanks for the review :)

Alan D.’s picture

The patch didn't apply, so I'll try again tomorrow. I need to run through single / multiple entities and also the default value form field (post setting the widget).

BTW, I did mean wrapping both the continent and iso2 elements within the same DIV. I can not remember if you can just append to the base $element or not. Maybe:


    // Support floating inner floating elements.
    $element += array(
      '#prefix' => '',
      '#suffix' => '',
    );
    $element['#prefix'] .= '<div class="clearfix countries-continent-wrapper">';
    $element['#suffix'] = '</div>' . $element['#suffix'];
    // @todo I18n for user defined overrides. <<Removed a dr from here. This can be dropped as the todo is already flagging this elsewhere :)
pcambra’s picture

Status: Needs review » Needs work

Also found a problem with intersect, I'm attaching a new one

pcambra’s picture

Status: Needs work » Needs review
FileSize
3.89 KB

And here it is, with a container wrapper, I hadn't any issues applying the patch btw.

Alan D.’s picture

I found issues with the instance default value form. This gets regenerated a couple of times per submission, so the unique ID never seemed to match the settings. It works once, then fails. I think that it is fairly safe to create our own via the parameters passed in.

I've added a number of other slight modifications.

Alan D.’s picture

Another minor restructure.

pcambra’s picture

I don't know why you changed the widget callback, I think it's nicer to do it as in the first version

function _ajax_countries_continent_widget_callback($form, $form_state) {
  $element = drupal_array_get_nested_value($form, array_slice($form_state['triggering_element']['#array_parents'], 0, -1));
  return $form[$element['#field_name']][$element['#language']][$element['#delta']]['iso2'];
}
Alan D.’s picture

You can not assume that the field form is in this structure (ie: because of Profile2 and Field Collections);

Does this seem cleaner? (untested)

$parents = array_slice($form_state['triggering_element']['#array_parents'], 0, -1);
$parents[] = 'iso2';
return drupal_array_get_nested_value($form, $parents);
pcambra’s picture

Ok, included #9 and also had to remove the container element as it is breaking the visible condition for the country. Replaced it for a simple prefix thing.

Alan D.’s picture

I think that the "breaking the visible condition" was me overlooking the role of the JQuery selector. Reverting the empty value from NULL to 'none' should fix this.

ie:

      '#empty_option' => t('-- None selected --'),
-      '#empty_value' => NULL,
+      '#empty_value' => 'none',

Starting to focus on the minor details, reselecting '-- None --' does not reset the visibility of the country select loaded by Ajax. Any ideas?

Alan D.’s picture

But it is probably ready to go without the worrying too much about "reselecting '-- None --' does not reset the visibility". As per #7 with #empty_value bug that I introduced fixed. I'll re-roll in about 4 to 5 hours.

pcambra’s picture

Ok, let's finish this thing for once!

I've merged the changes starting from #7 but had to make some changes as the widget wasn't working properly yet. Prior to this patch, the widget worked just the first time, if you changed the continent, the countries weren't refreshed, that's because of the use of drupal_html_id, that is not really fitting us, you can see the reason in views_ui_add_ajax_trigger (http://api.drupal.org/api/views/includes!admin.inc/function/views_ui_add...)

// We do not use drupal_html_id() to get an ID for the AJAX wrapper, because
// it remembers IDs across AJAX requests (and won't reuse them), but in our
// case we need to use the same ID from request to request so that the
// wrapper can be recognized by the AJAX system and its content can be
// dynamically updated.

I also needed to force that the country widget is not shown when there's no country present.

  // Only return the element if it's not empty.
  if (count($element['#options']) == 1 && isset($element['#options']['none'])) {
    return;
  }

Hope this can get committed finally!

Let me know what you think

spouilly’s picture

Hi there,

I am evaluating the possibility to use your continent widget, and it kind of conflict with the CAPTCHA module.

I added a country field on the user profile (core profile), with the continent widget. I am using the captcha module to prevent bots from registering on my site, and the captcha module come with some kind of protection against session reuse. When trying to register a new user on my site with the continent widget, after selecting the continent, and before selecting the country itself, the captcha module display an alert: "CAPTCHA session reuse attack detected".

I don't know whether all ajax requests on the user registration form would generate such error message.

Cheers,

Sylvain

ps: using dev version with the continent patch of the country module, and the latest stable version of catcha.

Alan D.’s picture

Yes, it is an issue with the CAPTCHA module itself, see #918856: CAPTCHA Session Reuse message on webforms .

Alan D.’s picture

Status: Needs review » Fixed

Committed. Great work.

Some things that I found and fixed in testing.

1) Unsetting the iso2 element in _ajax_countries_continent_widget_callback() blocked all AJAX other submissions by the element. This is probably a bug in core, but I worked around by adding a hidden div wrapper. I updated the countries element to optionally hide if there are 0 options or 1 option of type #empty_value.

2) I got a PDO exception when saving a country value of type 'none'. I changed this to just ''. The JScript tests the continent filter value for the show / hide toggle.

You can see the diff here: http://drupalcode.org/project/countries.git/commitdiff/77dfd39?hp=f3f1f8...

pcambra’s picture

Amazing, thanks for doing such a follow up on this :)

Alan D.’s picture

My pleasure, it is a very useful addition to the module :)

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

llorberb’s picture

this patch is causing a break. is this just on my machine? any idea what could be causing the issue.

patching file countries.fields.inc
Hunk #1 succeeded at 268 (offset 13 lines).
Hunk #2 succeeded at 366 (offset 90 lines).

$ drush status
Drush command terminated abnormally due to an unrecoverable error.   [error]
Error: Cannot redeclare countries_field_widget_info() (previously
declared in
/applications/mamp/htdocs/location/sites/all/modules/countries/countries.fields.inc:263)
in
/applications/mamp/htdocs/location/sites/all/modules/countries/countries.fields.inc,
line 281
pcambra’s picture

You should not apply this patch to the latest dev, it's already there!

This was committed 20 days ago as Alan mentions in comment #16.

Alan D.’s picture

@pcambra

An issue was reported with this in #1620276: Widget "countries by continent" issues due to AJAX #limit_validation_errors and nested forms (Field Collections, Profile2), I'd love a second pair of eyes on the patch before committing this.

It is in relation to the widget simply breaking when used in Profile2 profiles. I managed to replicate this using Field Collections, and once replicated, easily fixed the error on my installation.

Belibaste’s picture

Hi, Thanks for this awesome piece of work.

Any hints on using it in FAPI style created form ?

Belibaste’s picture

Issue summary: View changes

Clear resolution message

vipul.jadvani’s picture

Status: Closed (fixed) » Needs review

Status: Needs review » Needs work

The last submitted patch, 13: 1459850-countries-add_continent_widget-13.patch, failed testing.

Alan D.’s picture

Issue summary: View changes
Status: Needs work » Closed (fixed)

@vipul.jadvani
This is already in the dev version if not the latest tagged release ;)