<?php
// $Id: yui_form.module,v 1.6 2009/06/11 20:49:11 pounard Exp $

/**
 * @file
 * Provides some YUI elements as Drupal form elements
 */

define('YUI_FORM_API', 1);
define('YUI_FORM_CALENDAR', 2);
define('YUI_FORM_SLIDER', 3);

define('YUI_FORM_USE_YUI_SKIN', 'yui_form_use_yui_skin');

function yui_form_menu() {
  $items = array();
  $items['admin/settings/yui/common'] = array(
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'title' => 'YUI common',
    'weight' => 0,
  );
  $items['admin/settings/yui/form'] = array(
    'title' => 'Form elements',
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'drupal_get_form', 
    'page arguments' => array('yui_form_admin_settings'),
    'access callback' => TRUE,
    'file' => 'yui_form.admin.inc',
    'weight' => 1,
  );
  $items['admin/settings/yui/test'] = array(
    'title' => 'Sample form',
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'drupal_get_form', 
    'page arguments' => array('yui_form_test_form'),
    'access callback' => TRUE,
    'file' => 'yui_form.admin.inc',
    'weight' => 2,
  );
  $items['admin/settings/yui/ahah-test'] = array(
    'page callback' => 'yui_form_admin_ahah_test', 
    'access callback' => TRUE,
    'file' => 'yui_form.admin.inc',
  );
  return $items;
}

/**
 * Implementation of hook_elements().
 */
function yui_form_elements() {
  $elements = array();
  $elements['yui_calendar'] = array(
    '#input' => TRUE,
    '#process' => array('yui_form_calendar_process'),
    '#element_validate' => array('yui_form_calendar_validate'),
    '#drop_down' => FALSE,
    // Tell the widget to submit the current form on date change
    '#submit_onchange' => FALSE,
    // You can use AHAH change event on calendar. The AHAH event will be
    // attached to hidden (or shown in case of drop-down) textfield carrying
    // value element.
    '#ahah' => NULL,
    // If in dropdown mode, this setting will tell the input length where the
    // date is displayed 
    '#drop_down_size' => 10,
    // These are native YUI Calendar options you wish to override
    // Just as JSON array, keys are option name, value is data
    '#yui_options' => array(),
  );
  $elements['yui_slider'] = array(
    '#input' => TRUE,
    '#process' => array('yui_form_slider_process'),
    '#element_validate' => array('yui_form_slider_validate'),
    // If you set this to TRUE, you'll get an integer value between 0 and 100
    // If FALSE, you'll get a float value between 0 and 1
    '#use_percent' => TRUE,
    // Set this var to TRUE if you want to display the live value next to
    // the slider.
    // Using default CSS, its position will be on the right.
    '#live_display' => FALSE,
  );
  return $elements;
}

/**
 * Implementation of hook_theme().
 */
function yui_form_theme() {
  $theme = array();
  $theme['yui_calendar'] = array(
    'arguments' => array('element' => array()),
  );
  $theme['yui_slider'] = array(
    'arguments' => array('element' => array()),
  );
  return $theme;
}

function _yui_form_add_component_css($component) {
  if (variable_get(YUI_FORM_USE_YUI_SKIN, TRUE)) {
    if ($skin_name = substr(variable_get('yui_skin', ''), 9)) {
      $yui_source = variable_get('yui_source', 'http://yui.yahooapis.com/2.5.1');
      yui_add_css(NULL, $yui_source, '/yui/build/'.$component.'/assets/skins/sam/'.$component.'.css');
    }
  }
}

/**
 * Load needed files.
 */
function yui_form_load_js($type = YUI_FORM_API) {
  static $loaded = array();
  static $path;
  static $yui_source;

  if (! $loaded[YUI_FORM_API]) {
    // Set some static variables
    $path = drupal_get_path('module', 'yui_form');
    $yui_source = variable_get('yui_source', 'http://yui.yahooapis.com/2.5.1');
    drupal_add_css($path.'/yui_form.css');
    // Add common JS components
    yui_add_js(NULL, $yui_source, '/yui/build/yahoo/yahoo.js');
    yui_add_js(NULL, $yui_source, '/yui/build/event/event.js');
    yui_add_js(NULL, $yui_source, '/yui/build/dom/dom.js');
    $loaded[YUI_FORM_API] = TRUE;
  }

  switch ($type) {
    case YUI_FORM_CALENDAR:
      if (! $loaded[YUI_FORM_CALENDAR]) {
        _yui_form_add_component_css('calendar');
        yui_add_js(NULL, $yui_source, '/yui/build/calendar/calendar.js');
        // I18n support must be loaded before our JS
        _yui_form_i18n_js($type);
        drupal_add_js($path.'/yuicalendarcontroller.js');
        $loaded[YUI_FORM_CALENDAR] = TRUE;
      }
      break;

    case YUI_FORM_SLIDER:
      if (! $loaded[YUI_FORM_SLIDER]) {
        _yui_form_add_component_css('slider');
        yui_add_js(NULL, $yui_source, '/yui/build/dragdrop/dragdrop.js');
        yui_add_js(NULL, $yui_source, '/yui/build/slider/slider.js');
        // I18n support must be loaded before our JS
        _yui_form_i18n_js($type);
        drupal_add_js($path.'/yuislidercontroller.js');
        $loaded[YUI_FORM_SLIDER] = TRUE;
      }
      break;
  }
}

function _yui_browsing_language() {
  static $language = NULL;

  if (! $language) {
    if (module_exists('i18n')) {
      $language = i18n_get_lang();
    }
    else {
      global $language;
      if (is_object($language)) {
        $language = $language->language;
      }
    }
  }

  return $language;
}

function _yui_form_i18n_js($type) {
  $langcode = _yui_browsing_language();

  $filepath = NULL;

  switch ($type) {
    case YUI_FORM_CALENDAR:
      $filepath = drupal_get_path('module', 'yui_form') . '/translation/calendar.' . $langcode . '.js';
      break;
  }

  if ($filepath) {
    drupal_add_js($filepath);
  }
}

function _yui_form_settings($options = array()) {
  static $yui_options;

  return $yui_settings;
}

function yui_form_calendar_process($element, $edit, &$form_state, $form) {
  yui_form_load_js(YUI_FORM_CALENDAR);
  $drop_down = (bool) $element['#drop_down'];
  $submit_onchange = (bool) $element['#submit_onchange'];
  $element['#tree'] = TRUE;

  // Get a uniqid in case this widget will be used many times
  $uniqid = uniqid('ycf_');

  // This will carry our value
  $element['yui_form_calendar_data'] = array(
    '#type' => 'textfield',
    '#attributes' => array('class' => $uniqid.'-data'),
    '#size' => (int) ($element['#drop_down_size'] ? $element['#drop_down_size'] : 10), 
  );

  // Deal with drop-down
  if (! $drop_down) {
    $element['yui_form_calendar_data']['#attributes']['style'] = 'display: none;';
  }
  else {
    $element['yui_form_calendar_data']['#suffix'] = '<div class="'.$uniqid.'-show yui-form-calendar-show">&nbsp;</div>';
  }

  // And this will display our calendar
  $class = "yui-form-calendar-widget";

  if ($drop_down) {
    $class .= " drop-down";
  }
  if ($submit_onchange) {
    $class .= " submit-onchange";
  }

  $element['yui_form_calendar_display'] = array(
    '#type' => 'markup',
    '#value' => '<div id="'.$uniqid.'" class="'.$class.'"></div><div class="clear-block"></div>',
  );

  // Deal with native YUI settings
  if (is_array($element['#yui_options']) && ! empty($element['#yui_options'])) {
    drupal_add_js(array('yui_calendar' => array($uniqid => $element['#yui_options'])), 'setting');
  }

  // Deal with AHAH
  if (isset($element['#ahah'])) {
    $element['yui_form_calendar_data']['#ahah'] = &$element['#ahah'];
  }

  // Set default value
  if ($value = $element['#default_value']) {
    if (_yui_form_calendar_check_date($value)) {
      $element['#value'] = $value;
      $element['yui_form_calendar_data']['#default_value'] = $value;
    }
  }

  return $element;
}

function yui_form_calendar_validate($element, &$form_state) {
  if ($value = $element['yui_form_calendar_data']['#value']) {
    if (! _yui_form_calendar_check_date($value)) {
      $error_field = implode('][', $element['#parents']).']';
      form_set_error($error_field, t('Date is not well formated'));
      form_set_value($element, NULL, $form_state);
    }
    else {
      form_set_value($element, $value, $form_state);
    }
  }
  else {
    form_set_value($element, NULL, $form_state);
  }
}

function _yui_form_calendar_check_date($date) {
  return is_string($date) && preg_match('/[\d]{4}-[\d]{2}-[\d]{2}/is', $date);
}

function yui_form_slider_process($element, $edit, &$form_state, $form) {
  yui_form_load_js(YUI_FORM_SLIDER);
  $element['#tree'] = TRUE;
  $percent = (bool) $element['#use_percent'];
  $display = (bool) $element['#live_display'];

  // Get a uniqid in case this widget will be used many times
  $uniqid = uniqid('ysf_');

  // This will carry our value
  $element['yui_form_slider_data'] = array(
    '#type' => 'textfield',
    '#attributes' => array('class' => $uniqid.'-data', 'style' => 'display: none;'),
  );

  // Those classes control JS bahavior
  $class = "yui-form-slider-widget";
  if ($percent) {
    $class .= " use_percent";
  }
  if ($display) {
    $class .= " live_display";
  }

  // And this will display our slider
  $element['yui_form_slider_display'] = array(
    '#type' => 'markup',
    '#value' => '<div id="'.$uniqid.'" class="'.$class.'">'
      .'<div id="'.$uniqid.'-handle" class="yui-form-slider-handle"></div></div>',
  );

  // Add a div for live display
  if ($display) {
    $element['yui_form_slider_display']['#value'] .=
      '<div id="'.$uniqid.'-live" class="yui-form-slider-live"></div>';
  }

  // Cast value to the correct type
  $value = $element['#default_value'];
  // A subtile float cast as sring is enough to shut up FAPI that yells, in
  // form.inc, line 1320 that it can't use a float as array key.
  // It must exists a nicer way to this, but I just can't figure out how.
  $value = (string) ($percent ? (int) $value : (float) $value);
  if (_yui_form_slider_check_value($value, $percent)) {
    $element['#value'] = $value;
    $element['yui_form_slider_data']['#default_value'] = $value;
  }

  return $element;
}

function yui_form_slider_validate($element, &$form_state) {
  if ($value = $element['yui_form_slider_data']['#value']) {
    $percent = (bool) $element['#use_percent'];
    if (! _yui_form_slider_check_value($value, $percent)) {
      $error_field = implode('][', $element['#parents']).']';
      form_set_error($error_field, t('Slider value is not well formated'));
      form_set_value($element, NULL, $form_state);
    }
    else {
      form_set_value($element, $value, $form_state);
    }
  }
  else {
    form_set_value($element, NULL, $form_state);
  }
}

function _yui_form_slider_check_value($value, $percent = TRUE) {
  if (! is_numeric($value)) {
    return FALSE;
  }
  if ($percent) {
    $value = (int) $value;
    if ($value < 0 || $value > 100) {
      return FALSE;
    }
  }
  else {
    $value = (float) $value;
    if ($value < 0 || $value > 1) {
      return FALSE;
    }
  }
  return TRUE;
}

function theme_yui_calendar($element) {
  return theme('form_element', $element, '<div class="yui-form-calendar">'.$element['#children'].'</div>');
}

function theme_yui_slider($element) {
  return theme('form_element', $element, '<div class="yui-form-slider">'.$element['#children'].'</div>');
}