Hello,

I'm playing with this wonderful framework for two days now, and it does a great job. But when trying to add two kind of dynamic expanding elements in a form, it breaks everything.

Maybe I'm just missing something obvious ,but I'm really stuck ... Do you have any example of this working ? I've watched the simple, todos and polls examples, but they don't help for this problem.

Thank you.

Comments

yraber’s picture

I'm really desperate... spent three days trying to make this work.
It works almost : I can add/delete/modify one element types and add/delete/modify the other element dynamically.
But if I add one of the first type and then try to addone the other type, two are added. It seems that the callback function (diymaps_update_state_js/diymaps_update_states_js) are calles several times.

Here is the code :

<?php

//
// GENERAL FONCTIONS
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Implementation of hook_help().
 */
function diymap_help($section) {
  switch ($section) {
    case 'admin/help#diymap':
      $output = '<p>'. t('Create DIY Maps') .'</p>';
      return $output;
  }
}

/**
 * Implementation of hook_node_info().
 */
function diymap_node_info() {
  return array(
    'diymap' => array(
      'name' => t('DIY Map'),
      'module' => 'diymap',
      'description' => t("DIY Map"),
      'title_label' => t('Name'),
      'has_body' => FALSE,
    )
  );
}

/**
 * hook_menu - incremental update needs its own entry point
 */
function diymap_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'path' => 'diymap/diymap_update_states_js',
      'callback' => '_diymap_update_states_js',
      'access' => TRUE,
      'type' => MENU_CALLBACK,
    );
    $items[] = array(
      'path' => 'diymap/diymap_update_ranges_js',
      'callback' => '_diymap_update_ranges_js',
      'access' => TRUE,
      'type' => MENU_CALLBACK,
    );
  }
  return $items;
}

/**
 * Implementation of hook_access().
 */
function diymap_access($op, $node) {
  if ($op == 'create') {
    return user_access('create todo lists');
  }
}

/**
 * Implementation of hook_perm().
 */
function diymap_perm() {
  return array('create todo lists');
}

function diymap_form ($node) {
  drupal_add_css(drupal_get_path('module','diymap').'/diymap.css');

  $form['title'] = array(
    '#type' => 'textfield', 
    '#title' => t('Title'),
    '#size' => 60,
    '#maxlength' => 128,
    '#required' => TRUE,
    '#default_value' => $node->title,
    '#weight' => -20
  );

  $form['body_filter']['body'] = array(
    '#type' => 'textarea',
    '#title' => t('Body'),
    '#default_value' => $node->body,
    '#required' => FALSE, '#rows' => 3,
    '#weight' => -15
  );

  $form['body_filter']['filter'] = filter_form($node->format);

  $form['global'] = array(
   '#type' => 'item',
   '#title' => 'Global Map Config',
  );

  $form['global']['colors'] = array(
    '#type' => 'fieldset',
    '#title' => t('Colors'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );

  $form['global']['colors']['default_color'] = array(
    //'#prefix' => '<div class="form-inline">',
    '#type' => 'colorpicker',
    '#title' => t('Default'),
    '#size' => 8,
    '#default_value' => isset($node->default_color) ? $node->default_color : '#bbbbbb',    
    //'#suffix' => '</div>',
  );
  
  $form['global']['colors']['background_color'] = array(
    //'#prefix' => '<div class="form-inline">',
    '#type'  => 'colorpicker',
    '#title' => t('Background'),
    '#size' => 8,
    '#default_value' => isset($node->background_color) ? $node->background_color : '#ffffff',
    //'#suffix' => '</div>',
  );

  $form['global']['colors']['outline_color'] = array(
    //'#prefix' => '<div class="form-inline">',
    '#type'  => 'colorpicker',
    '#title' => t('Outline'),
    '#size' => 8,
    '#default_value' => isset($node->outline_color) ? $node->outline_color : '#666666',
    //'#suffix' => '</div>',
  );

  $form['global']['colors']['line_color'] = array(
    //'#prefix' => '<div class="form-inline">',
    '#type'  => 'colorpicker',
    '#title' => t('Line'),
    '#size' => 8,
    '#default_value' => isset($node->line_color) ? $node->line_color : '#666666',
    //'#suffix' => '</div>',
  );

  $form['global']['colors']['arc_color'] = array(
    //'#prefix' => '<div class="form-inline">',
    '#type'  => 'colorpicker',
    '#title' => t('Arc'),
    '#size' => 8,
    '#default_value' => isset($node->arc_color) ? $node->arc_color : '#666666',
    //'#suffix' => '</div>',
  );

  $form['global']['default_point'] = array(
    '#type' => 'fieldset',
    '#title' => t('Default Point'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#description' => t('This sets the default attributes for all points on the map. Set the default color in the <color> field. In this case, it will display a bright red. The <size> attribute sets the default size. The <opacity> attribute sets the default opacity. A <size> of 10 will show a dot 10 pixel diameter dot when zoomed out at 100%.'),
  );

  $form['global']['default_point']['default_point_size'] = array(
    //'#prefix' => '<div class="form-inline">',
    '#type' => 'select',
    '#title' => t('Size'),
    '#options' => array('1','2','3','4','5','6','7','8','9','10'),
    '#default_value' => isset($node->default_point_size) ? $node->default_point_size : 3,
    //'#suffix' => '</div>',
  );
  $form['global']['default_point']['default_point_opacity'] = array(
    //'#prefix' => '<div class="form-inline">',
    '#type' => 'select',
    '#title' => t('Opacity'),
    '#options' => array('0', '10','20','30','40','50','60','70','80','90','100'),
    '#default_value' => isset($node->default_point_opacity) ? $node->default_point_opacity : 30,
    //'#suffix' => '</div>',
  );
  $form['global']['default_point']['default_point_color'] = array(
    //'#prefix' => '<div class="form-inline">',
    '#type' => 'colorpicker',
    '#title' => t('Color'),
    '#size' => 7,
    '#default_value' => isset($node->default_point_color) ? $node->default_point_color : '#FFFFFF',
    //'#suffix' => '</div><div style="clear:both;"></div>',
  );

  $form['custom'] = array (
    '#type' => 'fieldset',
    '#title' => 'Custom XML map description',
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,    
  );

  $form['custom']['custom_xml'] = array(
    '#type' => 'textarea',
    '#rows' => 10,
    '#size' => 40,
    '#title' => t('Custom XML map description'),
    '#description' => t('Check <a href="http://www.backspace.com/mapapp/configure.html">here</a> for details |
                         WARNING : No validation at all !!! And this will override all other configuration ! '),
    '#default_value' => $node->custom_xml,
  );

  $form['ranges'] = array (
    '#type' => 'item',
    '#theme' => 'diymap_ranges_subform',
    '#title' => t('Map Ranges'),
    '#ahah_bindings' => array( 
      array (
        'selector' => 'input.diymap_range_remove',
        'event' => 'click',
        'wrapper' => 'diymap_ranges_wrapper',
        'path' => 'diymap/diymap_update_ranges_js',
      ),
    ),	    	
  );

  $form['states'] = array (
    '#type' => 'item',
    '#theme' => 'diymap_states_subform',
    '#title' => t('Map States'),
    '#ahah_bindings' => array( 
      array (
        'selector' => 'input.diymap_state_remove',
        'event' => 'click',
        'wrapper' => 'diymap_states_wrapper',
        'path' => 'diymap/diymap_update_states_js',
      ),
    ),	    	
  );

  $form['states'] += dynamic_subform_get_embedded('diymap_node_form', 'diymap_states_subform', $node);
  $form['ranges'] += dynamic_subform_get_embedded('diymap_node_form', 'diymap_ranges_subform', $node);
 
  return $form;  
}

function _diymap_process_commands( &$edit, $items ) {

  if( is_array($edit) && count($edit) > 0 ) {
    if( $edit['op'] == t( 'Add State' ) ) {
      $new_item = $edit['new_state'];
      if( $new_item['name'] ) { 
        $item_id = $edit['next_states_id'];
        $edit['next_states_id'] = $edit['next_states_id'] + 1;
	$items[$item_id] = $new_item;
        unset( $edit['new_state'] );
      }
    }

    elseif($edit['op'] == t( 'Add All States' ) ) {
      $next_state_id = $edit['next_states_id'];
      foreach(_diymap_states_ref() as $state_index => $state_name) {
        $new_item = array();
        $new_item['stateid'] = $state_index;

        $new_item['name'] = $state_name;
        $items[$next_state_id++] = $new_item;
      }
      $edit['next_states_id'] = $next_id++;
    }

    elseif( $edit['op'] == t( 'Add Range' ) ) {
      $new_item = $edit['new_range'];
      if( $new_item['range_data_min'] && $new_item['range_data_max'] && $new_item['range_color'] ) {
          $item_id = $edit['next_ranges_id'];
          $edit['next_ranges_id'] = $edit['next_ranges_id'] + 1;
	  $items[$item_id] = $new_item;
	  unset( $edit['new_range'] );
      }
    }

    foreach( $items as $item_id => $item ) {
      if( isset( $item['remove'] ) && $item['remove'] == 1 )  {
	unset( $items[$item_id] );
      }      
    }
  }  
  $edit['op'] = '';
  return $items;
}

function _diymap_extract(&$edit, $node, $type) {
  $items = array();
  $next_id_name = 'next_'.$type.'_id';
  if( is_array( $edit[$type] ) ) {
    $items = $edit[$type];
  }
  elseif( is_array($node->$type) ) {
    $items = $node->$type;
    $edit[$next_id_name] = $node->$next_id_name;
  }
  return $items;
}

//
// STATES 
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

function diymap_states_subform($node, $form_values=null, $pass=null) {
  if( is_array( $node ) ) drupal_set_message( "diymap_states_subform: missing node!");
  if( !empty($form_values) && !is_array($form_values)) drupal_set_message( "diymap_states_subform: bad form_values" );

  $states = _diymap_extract($form_values, $node, 'states');   // Retrieve the items from the form fields
  $states = _diymap_process_commands($form_values, $states);  // Process the commands : add, del,...

  $diymap = _diymap_expand_states($states);                   // Display the existing items 
  $diymap += _diymap_add_control_states($form_values, $pass); // Create the new item form
  
  return $diymap;
}

/**
 * Expand the items into form elements
 */
function _diymap_expand_states( $items ) {
  $element['states'] = array (
    '#id' => 'diymap_states',
    '#tree' => TRUE,
    '#theme' => 'diymap_states',
  );
  foreach( $items as $item_id => $item ) {
    $element['states'][$item_id] = array('#tree' => TRUE);
   
    $element['states'][$item_id]['stateid'] = array(
      '#type' => 'select',
      '#options' => _diymap_states_ref(),
      '#default_value' => $item['stateid']
    );
    $element['states'][$item_id]['name'   ] = array('#type' => 'textfield','#default_value' => $item['name'  ]);
    $element['states'][$item_id]['data'   ] = array('#type' => 'textfield','#default_value' => $item['data'  ]);
    $element['states'][$item_id]['hover'  ] = array('#type' => 'textfield','#default_value' => $item['hover' ]);
    $element['states'][$item_id]['url'    ] = array('#type' => 'textfield','#default_value' => $item['url'   ]);
    $element['states'][$item_id]['target' ] = array('#type' => 'textfield','#default_value' => $item['target']);
    $element['states'][$item_id]['remove' ] = array(
      '#type' => 'checkbox',
      '#default_value' => $item['remove'],
      '#attributes' => array( 'class' => 'diymap_state_remove' ),
    );
  }
  return $element;
}

function _diymap_add_control_states($edit, $pass=null) {
  $element['new_state'] = array (
    '#type' => 'fieldset',
    '#title' => t( 'Add State' ),
    '#theme' => 'diymap_new_state_control',
    '#tree' => TRUE
  );
  $element['new_state']['stateid'] = array ('#type' => 'select'   ,'#title' => t('State'  ),'#options' => _diymap_states_ref() );
  $element['new_state']['name'   ] = array ('#type' => 'textfield','#title' => t( 'Name'  ),'#size' => 10,'#maxlength' => 128);
  $element['new_state']['data'   ] = array ('#type' => 'textfield','#title' => t( 'Data'  ),'#size' => 10,'#maxlength' => 128);
  $element['new_state']['url'    ] = array ('#type' => 'textfield','#title' => t( 'URL'   ),'#size' => 40,'#maxlength' => 128);
  $element['new_state']['target' ] = array ('#type' => 'textfield','#title' => t( 'Target'),'#size' => 40,'#maxlength' => 128);
  $element['new_state']['hover'  ] = array ('#type' => 'textfield','#title' => t( 'Hover' ),'#size' => 40,'#maxlength' => 128);
  if ($pass=='render_pass') {
    $element['new_state']['stateid']['#value'] = $edit['new_state']['stateid'];
    $element['new_state']['name'   ]['#value'] = $edit['new_state']['name'   ];
    $element['new_state']['data'   ]['#value'] = $edit['new_state']['data'   ];
    $element['new_state']['url'    ]['#value'] = $edit['new_state']['url'    ];
    $element['new_state']['target' ]['#value'] = $edit['new_state']['target' ];
    $element['new_state']['hover'  ]['#value'] = $edit['new_state']['hover'  ];
  }
  $element['new_state']['add_state_button'] = array (
    '#type' => 'button',
    '#value' => t( 'Add State' ),
    '#id' => 'diymap_add_state_button', 
    '#ahah_bindings' => array ( 
      array( 
      'wrapper' => 'diymap_states_wrapper',
      'event' => 'click',
      'path' => 'diymap/diymap_update_states_js',
      ),
    ),
  );
/*
  $element['new_state']['add_all_states_button'] = array (
    '#type' => 'button',
    '#value' => t( 'Add All States' ),
    '#id' => 'diymap_add_state_button', 
    '#ahah_bindings' => array ( 
      array( 
      'wrapper' => 'diymap_states_wrapper',
      'event' => 'click',
      'path' => 'diymap/diymap_update_states_js',
      ),
    ),
  );
*/
  $element['next_states_id'] = array( 
    '#type' => 'hidden',
    '#value' => isset( $edit['next_states_id'] ) ? $edit['next_states_id'] : 0,
  );		
  return $element;
}

//
// RANGES
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

function diymap_ranges_subform($node, $form_values=null, $pass=null) {
  if( is_array( $node ) ) drupal_set_message( "diymap_ranges_subform: missing node!");
  if( !empty($form_values) && !is_array($form_values)) drupal_set_message( "diymap_ranges_subform: bad form_values" );

  $ranges  = _diymap_extract($form_values, $node, 'ranges');
  $ranges  = _diymap_process_commands($form_values, $ranges);

  $diymap  = _diymap_expand_ranges($ranges);
  $diymap += _diymap_add_control_ranges($form_values, $pass);

  return $diymap;
}

function _diymap_expand_ranges( $items ) {
  $element['ranges'] = array (
    '#tree' => TRUE,
    '#id' => 'diymap_ranges',
    '#theme' => 'diymap_ranges'
  );
  
  foreach( $items as $item_id => $item ) {
    $element['ranges'][$item_id] = array('#tree' => TRUE);
    $element['ranges'][$item_id]['range_type'] = array(
      '#type'          => 'select',
      '#default_value' => $item['range_type'],
      '#options'       => array('', 'point','line','arc'),
    );
    $element['ranges'][$item_id]['range_data_min'] = array(
      '#type'          => 'textfield',
      '#default_value' => $item['range_data_min'],
    );
    $element['ranges'][$item_id]['range_data_max'] = array(
      '#type'          => 'textfield',
      '#default_value' => $item['range_data_max'],
    );
    $element['ranges'][$item_id]['range_color'] = array(
      '#type'          => 'colorpicker',
      '#default_value' => $item['range_color'],
    );
    $element['ranges'][$item_id]['remove'] = array(
      '#type' => 'checkbox',
      '#default_value' => $item['remove'],
      '#attributes' => array( 'class' => 'diymap_range_remove' ),
    );
  }
  return $element;
}

function _diymap_add_control_ranges($edit, $pass=null) {
  $element['new_range'] = array (
    '#type'  => 'fieldset',
    '#title' => t( 'Add Range' ),
    '#theme' => 'diymap_new_range_control',
    '#tree'  => TRUE
  );

  $element['new_range']['range_type'] = array (
    '#type'  => 'select',
    '#title' => t('Type') , 
    '#options' => array('', 'point','line','arc'),
    '#default_value' => '#123456',
  );

  $element['new_range']['range_data_min'] = array (
    '#type' => 'textfield',
    '#title' => t( 'Data-min' ), 
    '#size' => 4,
    '#defualt_value' => '#123456', 
  );
  $element['new_range']['range_data_max'] = array (
    '#type' => 'textfield',
    '#title' => t( 'Data-max' ), 
    '#size' => 4, 
  );
  $element['new_range']['range_color'] = array (
    '#type'  => 'colorpicker',
    '#title' => t('Color') , 
    '#size'  => 7,
    '#default_value' => '#123456',
  );

  if ($pass=='render_pass') {
    $element['new_range']['range_type'    ]['#value'] = $edit['new_range']['range_type'    ];
    $element['new_range']['range_data_min']['#value'] = $edit['new_range']['range_data_min'];
    $element['new_range']['range_data_max']['#value'] = $edit['new_range']['range_data_max'];
    $element['new_range']['range_color'   ]['#value'] = $edit['new_range']['range_color'   ];
  }

  $element['new_range']['add_range_button'] = array (
    '#type' => 'button',
    '#value' => t( 'Add Range' ),
    '#id' => 'diymap_add_range_button', 
    '#ahah_bindings' => array ( 
      array( 
        'wrapper' => 'diymap_ranges_wrapper',
        'event' => 'click',
        'path' => 'diymap/diymap_update_ranges_js',
      ),
    ),
  );

  $element['next_ranges_id'] = array( 
    '#type' => 'hidden',
    '#value' => isset( $edit['next_ranges_id'] ) ? $edit['next_ranges_id'] : 0,
  );		
  return $element;
}


//
// THEMING FUNCTIONS - STATES
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/*
 *
 */
function theme_diymap_states(&$form) {
  foreach (element_children($form) as $key) {
    $row = array();
    $row[] = drupal_render($form[$key]['remove' ]);
    $row[] = drupal_render($form[$key]['stateid']);
    $row[] = drupal_render($form[$key]['name'   ]);
    $row[] = drupal_render($form[$key]['data'   ]);
    $row[] = drupal_render($form[$key]['hover'  ]);
    $row[] = drupal_render($form[$key]['url'    ]);
    $row[] = drupal_render($form[$key]['target' ]);
    $rows[] = array( 'data' => $row, 'class' => 'sortableitem' );
  }
  if( count($rows) > 0 ) {
    $header = array(t('Del'), t('State'), t('Name'), t('Data'), t('Hover'), t('URL'), t('Target'));
    $output = theme('table', $header, $rows, array( 'class' => 'sortable', 'id' => $form['#attributes']['id'] ) );
  }
  $output .= drupal_render($form);
  return $output;
}

function theme_diymap_new_state_control(&$form) {
  $rows   = array();
  $row    = array();

  $row1[]  = drupal_render($form['stateid']);
  $row1[]  = drupal_render($form['name']);
  $row2[]  = drupal_render($form['data']);
  $row2[]  = drupal_render($form['hover']);
  $row3[]  = drupal_render($form['url']);
  $row3[]  = drupal_render($form['target']);

  $rows[] = $row1;
  $rows[] = $row2;
  $rows[] = $row3;

  $output = theme('table', array(), $rows);
  $output .= drupal_render($form);

  return $output;
}

/*
 *
 */
function theme_diymap_states_view($items) {
  if( !empty( $items ) ) {
    foreach ($items as $item_id => $item) {
      $row = array();
      $row[] = $item['stateid'];
      $row[] = $item['name'   ];
      $row[] = $item['data'   ];
      $row[] = $item['hover'  ];
      $row[] = $item['url'    ];
      $row[] = $item['target' ];
      $rows[] = array( 'data' => $row, 'class' => 'sortableitem' );
    }
    if( count($rows) > 0 ) {
      $header = array( t('States'), t('Name'), t('Data'), t('Hover'), t('URL'), t('Target') );
      $output = theme('table', $header, $rows, array( 'class' => 'sortable' ) );
    }
  return $output;
  }
}

/*
 * Theme the dynamic state subform
 */
function theme_diymap_states_subform($form) {
  $group = '<div id="diymap_states_wrapper">'.drupal_render( $form ).'</div>';
  $output .= theme('fieldset', array(
    '#title' => t('States'), 
    '#children' => $group, 
    '#collapsible' => TRUE, 
    '#collapsed' => FALSE,
  ));
  return $output;
}

//
// THEMING FUNCTIONS - RANGES
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

function theme_diymap_ranges_view($items) {
  if( !empty( $items ) ) {
    foreach ($items as $item_id => $item) {
      $row = array();
      $row[] = $item['range_type'    ];
      $row[] = $item['range_data_min'];
      $row[] = $item['range_data_max'];
      $row[] = $item['range_color'   ];
      $rows[] = array( 'data' => $row, 'class' => 'sortableitem' );
    }
    if( count($rows) > 0 ) {
      $header = array( t('Range type'), t('Data min'), t('Data max'), t('Color'));
      $output = theme('table', $header, $rows, array( 'class' => 'sortable' ) );
    }
  return $output;
  }
}

function theme_diymap_ranges(&$form) {
  foreach (element_children($form) as $key) {
    $row = array();
    $row[] = drupal_render($form[$key]['remove' ]);
    $row[] = drupal_render($form[$key]['range_type']);
    $row[] = drupal_render($form[$key]['range_data_min']);
    $row[] = drupal_render($form[$key]['range_data_max']);
    $row[] = drupal_render($form[$key]['range_color']);
    $rows[] = array( 'data' => $row, 'class' => 'sortableitem' );
  }
  if( count($rows) > 0 ) {
    $header = array(t('Del'), t('Rang type'),t('Data min'), t('Data max'), t('Color'));
    $output = theme('table', $header, $rows, array( 'class' => 'sortable', 'id' => $form['#attributes']['id'] ) );
  }
  $output .= drupal_render($form);  
  return $output;
}

function theme_diymap_ranges_subform($form) {
  $group = '<div id="diymap_ranges_wrapper">'.drupal_render( $form ).'</div>';
  $output .= theme('fieldset', array(
    '#title' => t('Ranges'), 
    '#children' => $group, 
    '#collapsible' => TRUE, 
    '#collapsed' => FALSE));
  return $output;
}

function theme_diymap_new_range_control(&$form) {
  $rows   = array();
  $row    = array();

  $row[]  = drupal_render($form['range_type'    ]);
  $row[]  = drupal_render($form['range_data_min']);
  $row[]  = drupal_render($form['range_data_max']);
  $row[]  = drupal_render($form['range_color'   ]);

  $rows[] = $row;

  $output = theme('table', array(), $rows);
  $output .= drupal_render($form);

  return $output;
}

//
// THEMING FUNCTIONS - GENERAL
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

function theme_diymap_showmap($node) {
  $output = '<div class="diymap_showmap">'.$node->diymap.'</div>';
  return $output;
}

function diymap_view($node, $teaser = FALSE, $page = FALSE) {
  $node = node_prepare($node, $teaser);
  $node->content['diymap'] = array(
    '#value' => theme('diymap_showmap', $node),
    '#weight' => 1,
  );
  return $node;
}

//
// DATABASE INTERACTION FUNCTIONS
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/*
 *
 */
function diymap_insert($node) {
  $data = array();

  // If we have custom XML datas ... use them
  if($node->custom_xml) {
    $data['custom_xml'] = $node->custom_xml;
    _diymap_create_xml($node, $data['custom_xml'], TRUE);
  }
  else {
    $data['default_color'   ] = $node->default_color;
    $data['background_color'] = $node->background_color;
    $data['outline_color'   ] = $node->outline_color;
    if ( ! empty($node->states) ) {
      foreach($node->states as $state) {
        $data['states'][] = $state;
      }
    }
    if ( ! empty($node->ranges) ) {
      foreach($node->ranges as $range) {
        $data['ranges'][] = $range;
      }
    }
    _diymap_create_xml($node, $data);
  }
  db_query("INSERT INTO {diymap} (nid, data) VALUES (%d, '%s')",$node->nid,serialize($data));
}

/*
 *
 */
function diymap_load($node) {
  $ret = array();
  $result = db_fetch_object(db_query('SELECT data FROM {diymap} WHERE nid = %d',$node->nid));
  $data = unserialize($result->data);

  $ret['next_states_id'] = sizeof($data['states']);
  $ret['next_ranges_id'] = sizeof($data['ranges']);
 
  $ret['states'] = $data['states'];
  $ret['ranges'] = $data['ranges'];

  $ret['diymap'] = _diymap_embed_html($node->nid); // Add the embedded flash Map

  return $ret;
}

/*
 *
 */
function diymap_update($node) {
  diymap_delete($node);
  diymap_insert($node);
}

/*
 *
 */
function diymap_delete($node) {
  db_query('DELETE FROM {diymap} WHERE nid = %d',$node->nid);
   $dest_file = _diymap_get_directory().'/node'.$node->nid.'.xml';
   file_delete($dest_file);
   drupal_set_message("DIY Map XML config file deleted [".$dest_file."]");
}

//
// JAVASCRIPT FUNCTION
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/*
 *
 */

function _diymap_update_states_js() {
  $states = dynamic_subform_get_prepped( 'diymap_node_form', 'diymap_states_subform', null);
  print theme('status_messages') . drupal_render($states);
}

function _diymap_update_ranges_js() {
  $ranges = dynamic_subform_get_prepped( 'diymap_node_form', 'diymap_ranges_subform', null);
  print theme('status_messages') . drupal_render($ranges);
}

//
// INTERN FUNCTION
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/*
 *
 */
function _diymap_create_xml($node, $data, $custom = NULL) {

  $dest_path = _diymap_get_directory();
  $dest_file = '/node'.$node->nid.'.xml';

  if($custom) {
    $output = $data;
  }
  else {
    // Global - colors
    if (isset($node->default_color))
      $output .= _diymap_xml_output('default_color'   , array('color' => substr($node->default_color   ,1) ) ) ;
    if (isset($node->background_color))
      $output .= _diymap_xml_output('background_color', array('color' => substr($node->background_color ,1) ) );
    if (isset($node->outline_color))
      $output .= _diymap_xml_output('outline_color'   , array('color' => substr($node->outline_color    ,1) ) );
    
    $output .= _diymap_xml_output('default_point', array(
      'color'   => substr($node->default_point_color,1),
      'size'    => $node->default_point_size,
      'opacity' => $node->default_point_opacity,
      'src'     => $node->default_point_src)
    );
    if ($data['ranges']) {
      foreach($data['ranges'] as $range) {
        if($range['range_type']) {
          $output .= _diymap_xml_output('range_'.$range['range_type'], array (
                         'data'  => $range['range_data_min']." - ".$range['range_data_max'],
                         'color' => $range['range_color'])
                     );
        }
        else {
          $output .= _diymap_xml_output('range', array (
                         'data'  => $range['range_data_min']." - ".$range['range_data_max'],
                         'color' => $range['range_color'])
                     );  
        }
      }
    }

    if ($data['states']) {
      foreach($data['states'] as $state) {
        $output .= _diymap_xml_output($state['stateid'], 
          array(
            'name'   => $state['name'  ],
            'data'   => $state['data'  ],
            'hover'  => $state['hover' ],
            'url'    => $state['url'   ],
            'target' => $state['target'],
          )
        );
      }
    }
    $output ="<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<countrydata>\n".$output."</countrydata>\n";
  }
  file_save_data($output, $dest_path.$dest_file, $replace = FILE_EXISTS_REPLACE);
}

/*
 *
 */
function _diymap_xml_output($id, $params) {  
  $output = '';
  if( $params ) {
    $output = '  <state id="'.$id.'"'.">\n";
    foreach ($params as $name => $value) {
      if($value) 
        $output .= "    <${name}>${value}</${name}>\n";
    }
    $output .= "  </state>\n";
  }
  return $output;
}

/*
 *
 */
function _diymap_get_directory() {
  $dir = file_directory_path();
  file_check_directory($dir, FILE_CREATE_DIRECTORY, 'made up element name');
  $dir .= '/diymap';
  file_check_directory($dir, FILE_CREATE_DIRECTORY, 'made up element name');

  return $dir;
}

/*
 *
 */
function _diymap_embed_html($nid) {
   $swf_dir = '/'.drupal_get_path('module','diymap').'/world/world.swf';   
   $xml_dir = "/files/diymap/node${nid}.xml";

   $embed = <<<EOD
      <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
      codebase="http://download.macromedia.com/pub/
      shockwave/cabs/flash/swflash.cab#version=7,0,0,0"
      width="550" height="300" id="zoom_map" align="top">
      <param name="movie" value="${swf_dir}?data_file=${xml_dir}" />
      <param name="quality" value="high" />
      <param name="bgcolor" value="#FFFFFF" />
      <embed src="${swf_dir}?data_file=${xml_dir}" quality="high" bgcolor="#FFFFFF"
      width=500 height=300 name="Clickable World Map" align="top"
      type="application/x-shockwave-flash"
      pluginspage="http://www.macromedia.com/go/getflashplayer">
      </embed>
      </object>
EOD;
   return $embed;
}

/*
 * 
 */
function _diymap_states_ref() {
  return array(
   'AF' => 'Afghanistan','AL' => 'Albania','AG' => 'Algeria','AN' => 'Andorra','AO' => 'Angola','AC' => 'Antigua and Barbuda',
   'AR' => 'Argentina','AM' => 'Armenia','AA' => 'Aruba','AS' => 'Australia','AU' => 'Austria','AJ' => 'Azerbaijan',
   'AZ' => 'Azores (Pt.)','BF' => 'The Bahamas','BA' => 'Bahrain','BG' => 'Bangladesh','BB' => 'Barbados','BO' => 'Belarus',
   'BE' => 'Belgium','BH' => 'Belize','BN' => 'Benin','BD' => 'Bermuda','BT' => 'Bhutan','BL' => 'Bolivia',
   'BK' => 'Bosnia and Herzegovina','BC' => 'Botswana','BR' => 'Brazil','IO' => 'British Indian Ocean Territory',
   'VI' => 'British Virgin Islands','BX' => 'Brunei','BU' => 'Bulgaria','UV' => 'Burkina Faso','BY' => 'Burundi','CB' => 'Cambodia',
   'CM' => 'Cameroon','CA' => 'Canada','CP' => 'Canary Islands (Sp.)','CV' => 'Cape Verde','CJ' => 'Cayman Islands',
   'CT' => 'Central African Republic','CD' => 'Chad','CI' => 'Chile','CH' => 'China','CO' => 'Colombia','CN' => 'Comoros',
   'CF' => 'Congo','CW' => 'Cook Islands','CS' => 'Costa Rica','IV' => 'Cote d\'Ivoire','HR' => 'Croatia','CU' => 'Cuba',
   'CY' => 'Cyprus','EZ' => 'Czech Republic','CG' => 'Democratic Republic of the Congo','DA' => 'Denmark','DJ' => 'Djibouti',
   'DO' => 'Dominica','DR' => 'Dominican Republic','TE' => 'East Timor','EC' => 'Ecuador','EG' => 'Egypt','ES' => 'El Salvador',
   'EK' => 'Equatorial Guinea','ER' => 'Eritrea','EN' => 'Estonia','ET' => 'Ethiopia','FK' => 'Islas Malvinas / Falkland Islands',
   'FO' => 'Faroe Islands','FM' => 'Federated States of Micronesia','FJ' => 'Fiji','FI' => 'Finland','FR' => 'France',
   'FG' => 'French Guiana','FP' => 'French Polynesia','GB' => 'Gabon','GA' => 'The Gambia','GG' => 'Georgia','GM' => 'Germany',
   'GH' => 'Ghana','GI' => 'Gibraltar (UK)','GO' => 'Glorioso Islands','GR' => 'Greece','GL' => 'Greenland','GJ' => 'Grenada',
   'GP' => 'Guadeloupe','GQ' => 'Guam','GT' => 'Guatemala','GK' => 'Guernsey','GV' => 'Guinea','PU' => 'Guinea-Bissau',
   'GY' => 'Guyana','HA' => 'Haiti','HO' => 'Honduras','HK' => 'Hong Kong','HU' => 'Hungary','IC' => 'Iceland','IN' => 'India',
   'ID' => 'Indonesia','IR' => 'Iran','IZ' => 'Iraq','EI' => 'Ireland','IM' => 'Isle of Man','IS' => 'Israel','IT' => 'Italy',
   'JM' => 'Jamaica','JN' => 'Jan Mayen','JA' => 'Japan','DQ' => 'Jarvis Island','JE' => 'Jersey','JQ' => 'Johnston Atoll',
   'JO' => 'Jordan','JU' => 'Juan De Nova Island','KZ' => 'Kazakhstan','KE' => 'Kenya','KR' => 'Kiribati','KN' => 'North Korea',
   'KS' => 'South Korea','KU' => 'Kuwait','KG' => 'Kyrgyzstan','LA' => 'Laos','LG' => 'Latvia','LE' => 'Lebanon','LT' => 'Lesotho',
   'LI' => 'Liberia','LY' => 'Libya','LS' => 'Liechtenstein','LH' => 'Lithuania','LU' => 'Luxembourg','MC' => 'Macau',
   'MK' => 'Macedonia','MA' => 'Madagascar','ME' => 'Madeira (Pt.)','MI' => 'Malawi','MY' => 'Malaysia','MV' => 'Maldives',
   'ML' => 'Mali','MT' => 'Malta','RM' => 'Marshall Islands','MB' => 'Martinique','MR' => 'Mauritania','MP' => 'Mauritius',
   'MF' => 'Mayotte','MX' => 'Mexico','MQ' => 'Midway Islands','MD' => 'Moldova','MN' => 'Monaco','MG' => 'Mongolia',
   'MW' => 'Montenegro','MH' => 'Montserrat','MO' => 'Morocco','MZ' => 'Mozambique','BM' => 'Myanmar (Burma)','WA' => 'Namibia',
   'NR' => 'Nauru','NP' => 'Nepal','NL' => 'Netherlands','NT' => 'Netherlands Antilles','NC' => 'New Caledonia',
   'NZ' => 'New Zealand','NU' => 'Nicaragua','NG' => 'Niger','NI' => 'Nigeria','NE' => 'Niue','NF' => 'Norfolk Island',
   'CQ' => 'Northern Mariana Islands','NO' => 'Norway','MU' => 'Oman','PS' => 'Pacific Islands (Palau)','PK' => 'Pakistan',
   'GZ' => 'Palestinian Authority','PM' => 'Panama','PP' => 'Papua New Guinea','PA' => 'Paraguay','PE' => 'Peru',
   'RP' => 'Philippines','PL' => 'Poland','PO' => 'Portugal','RQ' => 'Puerto Rico','QA' => 'Qatar','RE' => 'Reunion',
   'RO' => 'Romania','RS' => 'Russia','RW' => 'Rwanda','SC' => 'Saint Kitts and Nevis','ST' => 'Saint Lucia',
   'SB' => 'Saint Pierre and Miquelon','VC' => 'Saint Vincent and the Grenadines','SM' => 'San Marino',
   'TP' => 'Sao Tome and Principe','SA' => 'Saudi Arabia','SG' => 'Senegal','SR' => 'Serbia','SE' => 'Seychelles',
   'SL' => 'Sierra Leone','SN' => 'Singapore','LO' => 'Slovakia','SI' => 'Slovenia','BP' => 'Solomon Islands','SO' => 'Somalia',
   'SF' => 'South Africa','SX' => 'South Georgia/South Sandwich Islands', 'SP' => 'Spain', 'CE' => 'Sri Lanka','SU' => 'Sudan',
   'NS' => 'Suriname','SV' => 'Svalbard','WZ' => 'Swaziland','SW' => 'Sweden','SZ' => 'Switzerland','SY' => 'Syria',
   'TW' => 'Taiwan','TI' => 'Tajikistan','TH' => 'Thailand','TZ' => 'Tanzania, United Republic of','TO' => 'Togo',
   'TN' => 'Tonga','TD' => 'Trinidad and Tobago','TS' => 'Tunisia','TU' => 'Turkey','TX' => 'Turkmenistan',
   'TK' => 'Turks and Caicos Islands','TV' => 'Tuvalu','UG' => 'Uganda','UP' => 'Ukraine','TC' => 'United Arab Emirates',
   'UK' => 'United Kingdom','US' => 'United States','UY' => 'Uruguay','UZ' => 'Uzbekistan','NH' => 'Vanuatu',
   'VE' => 'Venezuela','VM' => 'Vietnam','WQ' => 'Wake Island','WF' => 'Wallis and Futuna','WI' => 'Western Sahara',
   'WS' => 'Western Samoa','YM' => 'Yemen','ZA' => 'Zambia','ZI' => 'Zimbabwe'
  );
}
dayre’s picture

Status: Active » Needs review

I had an issue with attaching ahah bindings to a collection of checkbox form elements. Say i had checkboxes A, B, C and D. When i clicked on A, the ahah javascript properly sent the ajax request to the server. When i clicked on B, i got 2 requests... and C, got 3 requests. The issue was that everytime a "click" or "change" is triggered, the ahah javascript rebinds the click/change function calls to all the elements again. In order to prevent this accumulation of function calls, i had to insert jquery unbind() calls into the ahah_forms.js file. This removes any previous bindings and ensures only one click function associated with each element during every ajax call cycle:

	$(key).each( function() {
//		console.log( "Attaching:  " + el.id + " : " + el.event + " (" + el.wrapper +")" );
		if( element.event === 'click' ) {
		  $(this).unbind( "click" );
			$(this).click( function() {Ahah.update( element, this.id ); return false;} );
		}
		else if( element.event === 'change' ) {
		  $(this).unbind("change");
			$(this).change( function() {Ahah.update( element, this.id ); return false;} );
		}
  	} );

Not sure if this solves your particular problem, but it fixed one of my issues.

My other issue which i haven't resolved yet is i've got two forms on my page of checkboxes... when i add one form, everything works great, but the second form, the ajax submission process get's all screwd up and the ui updates get screwed up... still working on that one.

dayre’s picture

I resolved my other problem with multiple forms on a page..... fix is here: http://drupal.org/node/180412

dayre’s picture

One more comment....

Without this unbind() fix, some of you may be seeing slugish behavior in the browser when you have multiple form elements bound using ahah. This is due to the fact that multiple ajax calls are being made for the same form element trigger...

When unbind is added, things run smooth....

yraber’s picture

This is exactly the problems I had.
For now I changed strategy and continued without dynamic forms, but I'll give a try at your patch when I'll have time.

yraber’s picture

Great ! It's working ... finally :) Thank you so much.

I think it would be really nice to put this patch into the module distribution.

Cheers,

Yves.

starbow’s picture

Assigned: Unassigned » starbow
starbow’s picture

Status: Needs review » Closed (fixed)