Common subdirectories: addresses/addresses_cck and addresses_new/addresses_cck
diff -du addresses/addresses.js addresses_new/addresses.js
--- addresses/addresses.js	2011-09-02 12:08:58.000000000 -0400
+++ addresses_new/addresses.js	2011-12-11 18:40:48.000000000 -0500
@@ -2,57 +2,191 @@
  * @author Cody Craven
  * @file addresses.js
  *
+ */
+
+/** DRUPAL BEHAVIOURS ********************************************************/
+
+Drupal.behaviors.addresses = function (context) {
+
+  /*
+  * Drupal.settings.addresses.addressClass = 'addresses-form';
+  * Drupal.settings.addresses.addressClassProcessed = 'addresses-processed';
+  * so:
+  * '.'+Drupal.settings.addresses.addressClass+':not(.'+Drupal.settings.addresses.addressClassProcessed+')' = '.addresses-form:not('.addresses-processed')'
+  */
+  // alert( '.'+Drupal.settings.addresses.addressClass+':not(.'+Drupal.settings.addresses.addressClassProcessed+')' );
+
+  $( '.'+Drupal.settings.addresses.addressClass+':not(.'+Drupal.settings.addresses.addressClassProcessed+')',context).each( function( index , address ) {
+    // create new address objects from all addresses in the context
+    new Drupal.address( address , true );
+  } );
+
+}
+
+/** DRUPAL ADDRESS OBJECT ***************************************************/
+
+/**
+ * An Address handler object
+ *
+ * @param address : an address DOM element ($() result)
+ * @param runProcessors : a boolean on wether or not toexecute
+ *    the associated address processors to do things like
+ *    bind events etc
+ */
+Drupal.address = function( address , runProcessors ) {
+  var self = this;
+
+  // give us the jQuery object
+  self.address = $(address);
+  self.fields = {};
+
+  // make sure that we have the children found
+  /**
+   * This is kind a configurable equivalent to:
+   * address.city = $('.address-field-city',address.address)[0];
+   */
+  $.each( Drupal.settings.addresses.fields , function( name , field ) {
+    self.fields[ name ] = self.address.find( '.'+field.class );
+  } );
+
+  // if we are running processors on this build
+  if ( runProcessors ) {
+
+    /**
+     * Run any of the associateds address processors
+     */
+    $.each( self.processors, function() {
+      this( self );
+    });
+
+  }
+
+  // mark this address collection as processed
+  this.address.addClass( Drupal.settings.addresses.addressClassProcessed );
+
+}
+
+/** PROTOTYPE METHODS *****************************************/
+
+/** Address Processors ******************************************************
+ *
+ * Address processors are functions like behaviours that are run on an address
+ * whenever it is executed processed.  this gives an extensible system of processors
+ * where any other code can add methods to processing
+ *
+ */
+
+Drupal.address.prototype.processors = {}; // methods which are run on every address process
+Drupal.address.prototype.handlers = {}; // event handlers which are keyed to allow easy unbinding
+
+/**
+ * Processor for binding country change events
+ *
+ * Adds Event Handler for when a country changes (bound to the change event on the country element)
  * Rebuild province field with a select list of provinces for country selected
  * on load and on change.
  */
-Drupal.behaviors.addresses = function(context) {
-  // Bind country changes to reload the province field and
-  // load province select element onLoad, do it once.
-  // See http://drupal.org/node/817244
-  $('.addresses-country-field:not(.addresses-processed)',context)
-    .addClass('addresses-processed')
-    .bind('change',function(){performProvinceAjax(this);})
-    .change();
+Drupal.address.prototype.processors.countryChange = function( address ) {
 
-  // Make province select list call
-  function performProvinceAjax(countryElement) {
-    // Country field's related province element
-    var provinceElement=$(countryElement).parent().siblings().children('.addresses-province-field');
-    var attributes={};
-    // Iterates over the element attributes to create an object of attributes to pass in the ajax call.
-    if(provinceElement.length){
-      $.each(provinceElement[0].attributes,function(index,attr){
-        if(!(attr.name in{'type':1,'value':1,'size':1,'id':1,'name':1,'class':1})){
-          attributes[attr.name]=attr.value;
-        }
-      });
-    }
-    $.ajax({
-      type:'GET',
-      url:Drupal.settings.basePath,
-      success:updateProvinceField,
-      dataType:'json',
-      data:{
-        q:'addresses/province_ajax',
-        country:$(countryElement).val(),
-        field_id:provinceElement.attr('id'),
-        field_name:provinceElement.attr('name'),
-        passback:provinceElement.parent().attr('id'),
-        province:provinceElement.val(),
-        language:Drupal.settings.addresses.language,
-        'attributes':JSON.stringify(attributes)
-      }
-    });
+  /**
+   * unbind, then rebind our country change event handler
+   */
+  $(address.fields.country)
+  .unbind('change' , address.handlers.countryChange )
+  .bind( 'change' , { address:address } , address.handlers.countryChange );
+
+  /**
+   * On the first run at page load, we implement the change
+   * event, but only on the first run, otherwise we'd have
+   * an infinite change loop
+   */
+  if (!( address.fields.country.hasClass( Drupal.settings.addresses.addressClassProcessed ) )) {
+    address.fields.country.addClass( Drupal.settings.addresses.addressClassProcessed );
+    address.fields.country.change();
   }
 
-  // Populate province field
-  function updateProvinceField(data) {
-    if(data.hide){
-      $('#'+data.passback).hide();
-    }else{
-      $('#'+data.passback).show();
+}
+/**
+ * Actual 'change' event handler for countryChange eventObject
+ */
+Drupal.address.prototype.handlers.countryChange = function( eventObject ) {
+
+  // get the address object for the event country
+  address = eventObject.data.address;
+  country = address.fields.country;
+  province = address.fields.province;
+
+  // we remove the current bind, as we will likely be replacing the province
+  $(country).unbind('change',this);
+
+  // collect the province attributes
+  attributes = address.getElementAttributes( province[0] );
+
+  // run the ajax process that will retrieve a new province field
+  $.ajax({
+    type:'GET',
+    url: Drupal.settings.basePath,
+    success: address.updateFieldFromData,
+    dataType: 'json',
+    data:{
+      q: 'addresses/province_ajax',
+      country: country.val(),
+      field_id: province.attr('id'),
+      field_name: province.attr('name'),
+      passback: province.parent().attr('id'),
+      province: province.val(),
+      language: Drupal.settings.addresses.language,
+      'attributes': JSON.stringify(attributes)
     }
-    $('#'+data.passback).html(data.field);
+  });
+}
+
+
+/** Utility Methods *******************************************/
+
+/**
+ * An AJAX callback handler to replace an #id with the returned html
+ */
+Drupal.address.prototype.updateFieldFromData = function( data ) {
+  passback = $('#'+data.passback);
+
+  // get the parent address element
+  // remove the processed class form the address, so that the addresses behaviour will apply again
+  parent = passback.parents( '.'+Drupal.settings.addresses.addressClass )
+    .removeClass( Drupal.settings.addresses.addressClassProcessed );
+
+  // show or hide the element
+  if(data.hide){
+    passback.hide();
+  }else{
+    passback.show();
   }
-};
-// vim: ts=2 sw=2 et syntax=javascript
+
+  // replace the elements contents with the new field
+  passback.replaceWith(data.field);
+
+  // implement drupal behaviours
+  /**
+   * @note it may be a waste to redo the whole processing behaviour for
+   * an address, but as we can't be sure what has changed, it seems that
+   * it is a necessary issue.
+   * @todo modify the passback/data to specify what has changed, and then
+   * modify the process method to handle this information.  This willkillall
+   * reduce overprocessing
+   */
+  Drupal.attachBehaviors( parent.parent() ); // parent.parent so that the context in the Drupal.behavors.addresses works on this address form
+
+}
+
+/**
+ * A method that retrieves certain attributes from an html/DOM element
+ */
+Drupal.address.prototype.getElementAttributes = function( element ) {
+  var attributes={};
+
+  $.each( element.attributes, function( index, attribute ) {
+    if (!( attribute.name in {'type':1,'value':1,'size':1,'id':1,'name':1}  )) { attributes[ attribute.name ] = attribute.value; }
+  } );
+
+  return attributes;
+}
\ No newline at end of file
diff -du addresses/addresses.module addresses_new/addresses.module
--- addresses/addresses.module	2011-09-02 12:08:58.000000000 -0400
+++ addresses_new/addresses.module	2011-12-11 17:20:53.000000000 -0500
@@ -189,9 +189,6 @@
 function addresses_elements_process($element, $edit, $form_state, $form) {
   global $language;
 
-  // Add the language definition in the settings so we can properly select words in a given language.
-  drupal_add_js(array('addresses' => array('language' => $language->language)), 'setting');
-
   // The $form['#field_info'] entry comes from CCK
   $settings = empty($form['#field_info'][$element['#field_name']])
     ? variable_get('addresses_user_settings', array())
@@ -255,8 +252,22 @@
       $extra[$ename]['#size'] = $settings[$ename . '_size'];
       $extra[$ename]['#attributes'] = array('class' => 'text');
     }
+
+    $js_settings_fields[$ename] =  (isset($settings[$ename . '_js']))?$settings[$ename . '_js']:array('class'=>'addresses-field-'.$ename);
+    _form_set_class( $extra[$ename] , array('addresses-field',$js_settings_fields[$ename]['class']) );
+
   }
 
+  // Add the language definition in the settings so we can properly select words in a given language.
+  drupal_add_js(array('addresses' => array(
+    'language' => $language->language,
+    'addressClass' => 'addresses-form',
+    // A class added to an address element to indicate that it has been processed
+    'addressClassProcessed' => 'addresses-processed',
+    // an indexed array of the fields to expect inside an address form
+    'fields' => $js_settings_fields
+   )), 'setting');
+
   // Add the extra fields to the element and return it.
   $element = array_merge($element, $extra);
   return $element;
Common subdirectories: addresses/addresses_phone and addresses_new/addresses_phone
diff -du addresses/addresses.settings.inc addresses_new/addresses.settings.inc
--- addresses/addresses.settings.inc	2011-09-02 12:08:58.000000000 -0400
+++ addresses_new/addresses.settings.inc	2011-12-12 10:38:41.853980816 -0500
@@ -86,6 +86,7 @@
       // see http://drupal.org/node/244471#comment-2499288
       $form['province']['#attributes']['class'] = 'addresses-province-field';
       $form['country']['#attributes']['class'] = 'addresses-country-field';
+      $form['#attributes']['class'] = 'addresses-address';
     }
     $form['#element_validate'][] = '_addresses_province_field_validate';
   }
@@ -306,47 +307,39 @@
   $passback = $_GET['passback'];
   // ISO-3166-2 code for the province or state to mark as selected.
   $province = $_GET['province'];
-  //JSON encoded list of field attributes;
-  $field_attributes = '';
+  //JSON encoded list of field attributes
   $attributes = json_decode($_GET['attributes']);
-  if (is_object($attributes)) {
-    foreach ($attributes as $attr => $val) {
-      $field_attributes .= check_plain($attr) . "='" . check_plain($val) . "' ";
-    }
-  }
 
   // Check that required fields are supplied
   if (empty($field_id) || empty($field_name) || empty($passback)) {
     return drupal_json(array('error' => 'Invalid call'));
   }
 
-  // $element is a form element we build below to get the needed HTML
-  $element = array();
-  // No need to use drupal_strtoupper() as ISO-3166-2 codes are latin based
-  $element['#value'] = drupal_strtoupper($province);
-  $element['#options'] = array();
-  $provinces = array();
-  $output = '';
-  $hide = FALSE;
+  // Build the replacement element
+  /**
+  * @todo replace this with an actual reference to the field being rebuilt somehow
+  */
+  $element = array(
+    '#id' => $field_id,
+    '#name' => $field_name,
+    '#title' => t('State/Province'),
+    '#value' => drupal_strtoupper($province),  // No need to use drupal_strtoupper() as ISO-3166-2 codes are latin based
+    '#options' => array(),
+    '#attributes' => $attributes,
+  );
 
+  // load the provinces for the country
   if (!empty($country)) {
     module_load_include('inc', 'addresses');
-    $provinces = _addresses_province_get($country);
     $element['#options'] = _addresses_province_get($country);
   }
 
-  if (empty($provinces)) {
-    $hide = TRUE;
-  }
-
-  // Generate province field HTML
-  $output .= '<label for="' . $field_id . '">' . t('State / Province: ') . '</label>';
-  $output .= '<select id="' . $field_id . '" name="' . $field_name
-    . '" class="addresses-province-field" ' . $field_attributes . '>'
-    . form_select_options($element) . '</select>';
+  // hide the element if there are no provinces in this country
+  $hide = empty($element['#options']));
 
+  // give the JSON return
   return drupal_json(array(
-    'field' => $output,
+    'field' => theme( 'select' , $element ),
     'passback' => $passback,
     'hide' => $hide,
   ));
@@ -788,3 +781,38 @@
 }
 
 // vim: ts=2 sw=2 et syntax=php
+
+/**
+* @NOTE PHP 5.2 functions in PHP 5.1
+* @TODO Remove when updating to PHP 5.2
+*/
+if ( !function_exists('json_decode') ){
+  function json_decode($json)
+  {
+
+      // Author: walidator.info 2009
+      $comment = false;
+      $out = '$x=';
+
+      if ( empty( $json ) ) {
+        $out .= '""';
+      }
+      else {
+        for ($i=0; $i<strlen($json); $i++)
+        {
+            if (!$comment)
+            {
+                if ($json[$i] == '{' || $json[$i] == '[')        $out .= ' array(';
+                else if ($json[$i] == '}' || $json[$i] == ']')    $out .= ')';
+                else if ($json[$i] == ':')    $out .= '=>';
+                else                         $out .= $json[$i];
+            }
+            else $out .= $json[$i];
+            if ($json[$i] == '"')    $comment = !$comment;
+        }
+      }
+
+      eval($out . ';');
+      return $x;
+  }
+}
\ No newline at end of file
Only in addresses_new: addresses.settings.inc~
Common subdirectories: addresses/addresses_user and addresses_new/addresses_user
Common subdirectories: addresses/addresses_user_views and addresses_new/addresses_user_views
Common subdirectories: addresses/countries and addresses_new/countries
