Custom Contact Forms - Version 4.0.2

Version Description

  • custom-contact-forms-front.php - Field instructions bug fixed
  • custom-contact-forms-admin.php - Display change
Download this release

Release Info

Developer tlovett1
Plugin Icon 128x128 Custom Contact Forms
Version 4.0.2
Comparing to
See all releases

Code changes from version 4.0.1 to 4.0.2

custom-contact-forms-admin.php CHANGED
@@ -60,6 +60,7 @@ if (!class_exists('CustomContactFormsAdmin')) {
60
 
61
  function contactAuthor($name, $email, $website, $message, $type) {
62
  if (empty($message)) return false;
 
63
  $mail = new PHPMailer();
64
  $body = "Name: $name<br />\n";
65
  $body .= "Email: $email<br />\n";
@@ -67,7 +68,6 @@ if (!class_exists('CustomContactFormsAdmin')) {
67
  $body .= "Message: $message<br />\n";
68
  $body .= "Message Type: $type<br />\n";
69
  $body .= 'Sender IP: ' . $_SERVER['REMOTE_ADDR'] . "<br />\n";
70
- require_once('modules/phpmailer/class.phpmailer.php');
71
  $admin_options = parent::getAdminOptions();
72
  if ($admin_options['mail_function'] == 'smtp') {
73
  $mail->IsSMTP();
@@ -351,7 +351,7 @@ if (!class_exists('CustomContactFormsAdmin')) {
351
  <li><a href="#create-styles"><?php _e("Create Styles", 'custom-contact-forms'); ?></a></li>
352
  <li><a href="#manage-styles"><?php _e("Manage Styles", 'custom-contact-forms'); ?></a></li>
353
  <li><a href="#manage-field-options"><?php _e("Manage Field Options", 'custom-contact-forms'); ?></a></li>
354
- <li><a href="#contact-author"><?php _e("Suggest a Feature", 'custom-contact-forms'); ?></a></li>
355
  <li><a href="#contact-author"><?php _e("Bug Report", 'custom-contact-forms'); ?></a></li>
356
  <li><a href="#custom-html"><?php _e("Custom HTML Forms", 'custom-contact-forms'); ?></a></li>
357
  <li><a href="#import-export"><?php _e("Import / Export (New!)", 'custom-contact-forms'); ?></a></li>
60
 
61
  function contactAuthor($name, $email, $website, $message, $type) {
62
  if (empty($message)) return false;
63
+ require_once('modules/phpmailer/class.phpmailer.php');
64
  $mail = new PHPMailer();
65
  $body = "Name: $name<br />\n";
66
  $body .= "Email: $email<br />\n";
68
  $body .= "Message: $message<br />\n";
69
  $body .= "Message Type: $type<br />\n";
70
  $body .= 'Sender IP: ' . $_SERVER['REMOTE_ADDR'] . "<br />\n";
 
71
  $admin_options = parent::getAdminOptions();
72
  if ($admin_options['mail_function'] == 'smtp') {
73
  $mail->IsSMTP();
351
  <li><a href="#create-styles"><?php _e("Create Styles", 'custom-contact-forms'); ?></a></li>
352
  <li><a href="#manage-styles"><?php _e("Manage Styles", 'custom-contact-forms'); ?></a></li>
353
  <li><a href="#manage-field-options"><?php _e("Manage Field Options", 'custom-contact-forms'); ?></a></li>
354
+ <li><a class="red" href="#contact-author"><?php _e("Suggest a Feature", 'custom-contact-forms'); ?></a></li>
355
  <li><a href="#contact-author"><?php _e("Bug Report", 'custom-contact-forms'); ?></a></li>
356
  <li><a href="#custom-html"><?php _e("Custom HTML Forms", 'custom-contact-forms'); ?></a></li>
357
  <li><a href="#import-export"><?php _e("Import / Export (New!)", 'custom-contact-forms'); ?></a></li>
custom-contact-forms-front.php CHANGED
@@ -158,7 +158,8 @@ if (!class_exists('CustomContactFormsFront')) {
158
  $req_long = ($field->field_required == 1) ? ' ' . __('(required)', 'custom-contact-forms') : '';
159
  $input_id = 'id="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'-'.$form_key.'"';
160
  $field_value = CustomContactFormsStatic::decodeOption($field->field_value, 1, 1);
161
- $instructions = (empty($field->field_instructions)) ? '' : 'title="' . $field->field_instructions . $req_long . '" class="ccf-tooltip-field"';
 
162
  if ($admin_options['enable_widget_tooltips'] == 0 && $is_sidebar) $instructions = '';
163
  if ($_SESSION['fields'][$field->field_slug]) {
164
  if ($admin_options['remember_field_values'] == 1)
@@ -169,13 +170,13 @@ if (!class_exists('CustomContactFormsFront')) {
169
  $add_reset = ' <input type="reset" '.$instructions.' class="reset-button '.$field->field_class.'" value="' . $field->field_value . '" />';
170
  } elseif ($field->field_type == 'Text') {
171
  $maxlength = (empty($field->field_maxlength) or $field->field_maxlength <= 0) ? '' : ' maxlength="'.$field->field_maxlength.'"';
172
- $out .= '<div>'."\n".'<label for="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'">'. $req .CustomContactFormsStatic::decodeOption($field->field_label, 1, 1).'</label>'."\n".'<input class="'.$field->field_class.'" '.$instructions.' '.$input_id.' type="text" name="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'" value="'.$field_value.'"'.$maxlength.''.$code_type.'>'."\n".'</div>' . "\n";
173
  } elseif ($field->field_type == 'Hidden') {
174
  $hiddens .= '<input type="hidden" name="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'" value="'.$field_value.'" '.$input_id.''.$code_type.'>' . "\n";
175
  } elseif ($field->field_type == 'Checkbox') {
176
- $out .= '<div>'."\n".'<input class="'.$field->field_class.'" '.$instructions.' type="checkbox" name="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'" value="'.CustomContactFormsStatic::decodeOption($field->field_value, 1, 1).'" '.$input_id.''.$code_type.'> '."\n".'<label class="checkbox" for="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'">' . $req .CustomContactFormsStatic::decodeOption($field->field_label, 1, 1).'</label>'."\n".'</div>' . "\n";
177
  } elseif ($field->field_type == 'Textarea') {
178
- $out .= '<div>'."\n".'<label for="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'">'. $req .CustomContactFormsStatic::decodeOption($field->field_label, 1, 1).'</label>'."\n".'<textarea class="'.$field->field_class.'" '.$instructions.' '.$input_id.' rows="5" cols="40" name="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'">'.$field_value.'</textarea>'."\n".'</div>' . "\n";
179
  } elseif ($field->field_type == 'Dropdown') {
180
  $field_options = '';
181
  $options = parent::getAttachedFieldOptionsArray($field->id);
@@ -186,8 +187,8 @@ if (!class_exists('CustomContactFormsFront')) {
186
  $field_options .= '<option'.$option_sel.''.$option_value.'>' . $option->option_label . '</option>' . "\n";
187
  }
188
  if (!empty($options)) {
189
- if (!$is_sidebar) $out .= '<div>'."\n".'<select '.$instructions.' '.$input_id.' name="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'" class="'.$field->field_class.'">'."\n".$field_options.'</select>'."\n".'<label class="checkbox" for="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'">'. $req .CustomContactFormsStatic::decodeOption($field->field_label, 1, 1).'</label>'."\n".'</div>' . "\n";
190
- else $out .= '<div>'."\n".'<label for="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'">'. $req .CustomContactFormsStatic::decodeOption($field->field_label, 1, 1).'</label>'."\n".'<select class="'.$field->field_class.'" '.$instructions.' '.$input_id.' name="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'">'."\n".$field_options.'</select>'."\n".'</div>' . "\n";
191
  }
192
  } elseif ($field->field_type == 'Radio') {
193
  $field_options = '';
@@ -195,7 +196,7 @@ if (!class_exists('CustomContactFormsFront')) {
195
  foreach ($options as $option_id) {
196
  $option = parent::selectFieldOption($option_id);
197
  $option_sel = ($field->field_value == $option->option_slug) ? ' checked="checked"' : '';
198
- $field_options .= '<div><input'.$option_sel.' class="'.$field->field_class.'" type="radio" '.$instructions.' name="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'" value="'.CustomContactFormsStatic::decodeOption($option->option_value, 1, 1).'"'.$code_type.'> <label class="select" for="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'">' . CustomContactFormsStatic::decodeOption($option->option_label, 1, 1) . '</label></div>' . "\n";
199
  }
200
  $field_label = (!empty($field->field_label)) ? '<label for="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'">'. $req .CustomContactFormsStatic::decodeOption($field->field_label, 1, 1).'</label>' : '';
201
  if (!empty($options)) $out .= '<div>'."\n".$field_label."\n".$field_options."\n".'</div>' . "\n";
@@ -404,9 +405,10 @@ if (!class_exists('CustomContactFormsFront')) {
404
  $admin_options = parent::getAdminOptions();
405
  $code_type = ($admin_options['code_type'] == 'XHTML') ? ' /' : '';
406
  $captcha = parent::selectField('', 'captcha');
407
- $instructions = (empty($captcha->field_instructions)) ? '' : 'title="'.$captcha->field_instructions.'" class="tooltip-field"';
 
408
  $out = '<img width="96" height="24" alt="' . __('Captcha image for Custom Contact Forms plugin. You must type the numbers shown in the image', 'custom-contact-forms') . '" id="captcha-image" src="' . get_bloginfo('wpurl') . '/wp-content/plugins/custom-contact-forms/image.php?fid='.$form_id.'"'.$code_type.'>
409
- <div><label for="captcha'.$form_id.'">* '.$captcha->field_label.'</label> <input class="'.$captcha->field_class.'" type="text" '.$instructions.' name="captcha" id="captcha'.$form_id.'" maxlength="20"'.$code_type.'></div>';
410
  return $out;
411
  }
412
 
158
  $req_long = ($field->field_required == 1) ? ' ' . __('(required)', 'custom-contact-forms') : '';
159
  $input_id = 'id="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'-'.$form_key.'"';
160
  $field_value = CustomContactFormsStatic::decodeOption($field->field_value, 1, 1);
161
+ $instructions = (empty($field->field_instructions)) ? '' : 'title="' . $field->field_instructions . $req_long . '" ';
162
+ $tooltip_class = (empty($field->field_instructions)) ? '' : 'ccf-tooltip-field';
163
  if ($admin_options['enable_widget_tooltips'] == 0 && $is_sidebar) $instructions = '';
164
  if ($_SESSION['fields'][$field->field_slug]) {
165
  if ($admin_options['remember_field_values'] == 1)
170
  $add_reset = ' <input type="reset" '.$instructions.' class="reset-button '.$field->field_class.'" value="' . $field->field_value . '" />';
171
  } elseif ($field->field_type == 'Text') {
172
  $maxlength = (empty($field->field_maxlength) or $field->field_maxlength <= 0) ? '' : ' maxlength="'.$field->field_maxlength.'"';
173
+ $out .= '<div>'."\n".'<label for="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'">'. $req .CustomContactFormsStatic::decodeOption($field->field_label, 1, 1).'</label>'."\n".'<input class="'.$field->field_class.' '.$tooltip_class.'" '.$instructions.' '.$input_id.' type="text" name="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'" value="'.$field_value.'"'.$maxlength.''.$code_type.'>'."\n".'</div>' . "\n";
174
  } elseif ($field->field_type == 'Hidden') {
175
  $hiddens .= '<input type="hidden" name="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'" value="'.$field_value.'" '.$input_id.''.$code_type.'>' . "\n";
176
  } elseif ($field->field_type == 'Checkbox') {
177
+ $out .= '<div>'."\n".'<input class="'.$field->field_class.' '.$tooltip_class.'" '.$instructions.' type="checkbox" name="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'" value="'.CustomContactFormsStatic::decodeOption($field->field_value, 1, 1).'" '.$input_id.''.$code_type.'> '."\n".'<label class="checkbox" for="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'">' . $req .CustomContactFormsStatic::decodeOption($field->field_label, 1, 1).'</label>'."\n".'</div>' . "\n";
178
  } elseif ($field->field_type == 'Textarea') {
179
+ $out .= '<div>'."\n".'<label for="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'">'. $req .CustomContactFormsStatic::decodeOption($field->field_label, 1, 1).'</label>'."\n".'<textarea class="'.$field->field_class.' '.$tooltip_class.'" '.$instructions.' '.$input_id.' rows="5" cols="40" name="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'">'.$field_value.'</textarea>'."\n".'</div>' . "\n";
180
  } elseif ($field->field_type == 'Dropdown') {
181
  $field_options = '';
182
  $options = parent::getAttachedFieldOptionsArray($field->id);
187
  $field_options .= '<option'.$option_sel.''.$option_value.'>' . $option->option_label . '</option>' . "\n";
188
  }
189
  if (!empty($options)) {
190
+ if (!$is_sidebar) $out .= '<div>'."\n".'<select '.$instructions.' '.$input_id.' name="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'" class="'.$field->field_class.' '.$tooltip_class.'">'."\n".$field_options.'</select>'."\n".'<label class="checkbox" for="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'">'. $req .CustomContactFormsStatic::decodeOption($field->field_label, 1, 1).'</label>'."\n".'</div>' . "\n";
191
+ else $out .= '<div>'."\n".'<label for="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'">'. $req .CustomContactFormsStatic::decodeOption($field->field_label, 1, 1).'</label>'."\n".'<select class="'.$field->field_class.' '.$tooltip_class.'" '.$instructions.' '.$input_id.' name="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'">'."\n".$field_options.'</select>'."\n".'</div>' . "\n";
192
  }
193
  } elseif ($field->field_type == 'Radio') {
194
  $field_options = '';
196
  foreach ($options as $option_id) {
197
  $option = parent::selectFieldOption($option_id);
198
  $option_sel = ($field->field_value == $option->option_slug) ? ' checked="checked"' : '';
199
+ $field_options .= '<div><input'.$option_sel.' class="'.$field->field_class.' '.$tooltip_class.'" type="radio" '.$instructions.' name="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'" value="'.CustomContactFormsStatic::decodeOption($option->option_value, 1, 1).'"'.$code_type.'> <label class="select" for="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'">' . CustomContactFormsStatic::decodeOption($option->option_label, 1, 1) . '</label></div>' . "\n";
200
  }
201
  $field_label = (!empty($field->field_label)) ? '<label for="'.CustomContactFormsStatic::decodeOption($field->field_slug, 1, 1).'">'. $req .CustomContactFormsStatic::decodeOption($field->field_label, 1, 1).'</label>' : '';
202
  if (!empty($options)) $out .= '<div>'."\n".$field_label."\n".$field_options."\n".'</div>' . "\n";
405
  $admin_options = parent::getAdminOptions();
406
  $code_type = ($admin_options['code_type'] == 'XHTML') ? ' /' : '';
407
  $captcha = parent::selectField('', 'captcha');
408
+ $instructions = (empty($captcha->field_instructions)) ? '' : 'title="'.$captcha->field_instructions.'" ';
409
+ $tooltip_class = (empty($captcha->field_instructions)) ? '' : 'ccf-tooltip-field';
410
  $out = '<img width="96" height="24" alt="' . __('Captcha image for Custom Contact Forms plugin. You must type the numbers shown in the image', 'custom-contact-forms') . '" id="captcha-image" src="' . get_bloginfo('wpurl') . '/wp-content/plugins/custom-contact-forms/image.php?fid='.$form_id.'"'.$code_type.'>
411
+ <div><label for="captcha'.$form_id.'">* '.$captcha->field_label.'</label> <input class="'.$captcha->field_class.' '.$tooltip_class.'" type="text" '.$instructions.' name="captcha" id="captcha'.$form_id.'" maxlength="20"'.$code_type.'></div>';
412
  return $out;
413
  }
414
 
custom-contact-forms.php CHANGED
@@ -3,7 +3,7 @@
3
  Plugin Name: Custom Contact Forms
4
  Plugin URI: http://taylorlovett.com/wordpress-plugins
5
  Description: Guaranteed to be 1000X more customizable and intuitive than Fast Secure Contact Forms or Contact Form 7. Customize every aspect of your forms without any knowledge of CSS: borders, padding, sizes, colors. Ton's of great features. Required fields, form submissions saved to database, captchas, tooltip popovers, unlimited fields/forms/form styles, import/export, use a custom thank you page or built-in popover with a custom success message set for each form.
6
- Version: 4.0.1
7
  Author: Taylor Lovett
8
  Author URI: http://www.taylorlovett.com
9
  */
3
  Plugin Name: Custom Contact Forms
4
  Plugin URI: http://taylorlovett.com/wordpress-plugins
5
  Description: Guaranteed to be 1000X more customizable and intuitive than Fast Secure Contact Forms or Contact Form 7. Customize every aspect of your forms without any knowledge of CSS: borders, padding, sizes, colors. Ton's of great features. Required fields, form submissions saved to database, captchas, tooltip popovers, unlimited fields/forms/form styles, import/export, use a custom thank you page or built-in popover with a custom success message set for each form.
6
+ Version: 4.0.2
7
  Author: Taylor Lovett
8
  Author URI: http://www.taylorlovett.com
9
  */
js/jquery.dataTables.js ADDED
@@ -0,0 +1,6815 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * File: jquery.dataTables.js
3
+ * Version: 1.7.4
4
+ * Description: Paginate, search and sort HTML tables
5
+ * Author: Allan Jardine (www.sprymedia.co.uk)
6
+ * Created: 28/3/2008
7
+ * Language: Javascript
8
+ * License: GPL v2 or BSD 3 point style
9
+ * Project: Mtaala
10
+ * Contact: allan.jardine@sprymedia.co.uk
11
+ *
12
+ * Copyright 2008-2010 Allan Jardine, all rights reserved.
13
+ *
14
+ * This source file is free software, under either the GPL v2 license or a
15
+ * BSD style license, as supplied with this software.
16
+ *
17
+ * This source file is distributed in the hope that it will be useful, but
18
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
19
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
20
+ *
21
+ * For details please refer to: http://www.datatables.net
22
+ */
23
+
24
+ /*
25
+ * When considering jsLint, we need to allow eval() as it it is used for reading cookies and
26
+ * building the dynamic multi-column sort functions.
27
+ */
28
+ /*jslint evil: true, undef: true, browser: true */
29
+ /*globals $, jQuery,_fnExternApiFunc,_fnInitalise,_fnLanguageProcess,_fnAddColumn,_fnColumnOptions,_fnAddData,_fnGatherData,_fnDrawHead,_fnDraw,_fnReDraw,_fnAjaxUpdate,_fnAjaxUpdateDraw,_fnAddOptionsHtml,_fnFeatureHtmlTable,_fnScrollDraw,_fnAjustColumnSizing,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnBuildSearchArray,_fnBuildSearchRow,_fnFilterCreateSearch,_fnDataToSearch,_fnSort,_fnSortAttachListener,_fnSortingClasses,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnFeatureHtmlLength,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnNodeToDataIndex,_fnVisbleColumns,_fnCalculateEnd,_fnConvertToWidth,_fnCalculateColumnWidths,_fnScrollingWidthAdjust,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnArrayCmp,_fnDetectType,_fnSettingsFromNode,_fnGetDataMaster,_fnGetTrNodes,_fnGetTdNodes,_fnEscapeRegex,_fnDeleteIndex,_fnReOrderIndex,_fnColumnOrdering,_fnLog,_fnClearTable,_fnSaveState,_fnLoadState,_fnCreateCookie,_fnReadCookie,_fnGetUniqueThs,_fnScrollBarWidth,_fnApplyToChildren,_fnMap*/
30
+
31
+ (function($, window, document) {
32
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
33
+ * Section - DataTables variables
34
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
35
+
36
+ /*
37
+ * Variable: dataTableSettings
38
+ * Purpose: Store the settings for each dataTables instance
39
+ * Scope: jQuery.fn
40
+ */
41
+ $.fn.dataTableSettings = [];
42
+ var _aoSettings = $.fn.dataTableSettings; /* Short reference for fast internal lookup */
43
+
44
+ /*
45
+ * Variable: dataTableExt
46
+ * Purpose: Container for customisable parts of DataTables
47
+ * Scope: jQuery.fn
48
+ */
49
+ $.fn.dataTableExt = {};
50
+ var _oExt = $.fn.dataTableExt;
51
+
52
+
53
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
54
+ * Section - DataTables extensible objects
55
+ *
56
+ * The _oExt object is used to provide an area where user dfined plugins can be
57
+ * added to DataTables. The following properties of the object are used:
58
+ * oApi - Plug-in API functions
59
+ * aTypes - Auto-detection of types
60
+ * oSort - Sorting functions used by DataTables (based on the type)
61
+ * oPagination - Pagination functions for different input styles
62
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
63
+
64
+ /*
65
+ * Variable: sVersion
66
+ * Purpose: Version string for plug-ins to check compatibility
67
+ * Scope: jQuery.fn.dataTableExt
68
+ * Notes: Allowed format is a.b.c.d.e where:
69
+ * a:int, b:int, c:int, d:string(dev|beta), e:int. d and e are optional
70
+ */
71
+ _oExt.sVersion = "1.7.4";
72
+
73
+ /*
74
+ * Variable: sErrMode
75
+ * Purpose: How should DataTables report an error. Can take the value 'alert' or 'throw'
76
+ * Scope: jQuery.fn.dataTableExt
77
+ */
78
+ _oExt.sErrMode = "alert";
79
+
80
+ /*
81
+ * Variable: iApiIndex
82
+ * Purpose: Index for what 'this' index API functions should use
83
+ * Scope: jQuery.fn.dataTableExt
84
+ */
85
+ _oExt.iApiIndex = 0;
86
+
87
+ /*
88
+ * Variable: oApi
89
+ * Purpose: Container for plugin API functions
90
+ * Scope: jQuery.fn.dataTableExt
91
+ */
92
+ _oExt.oApi = { };
93
+
94
+ /*
95
+ * Variable: aFiltering
96
+ * Purpose: Container for plugin filtering functions
97
+ * Scope: jQuery.fn.dataTableExt
98
+ */
99
+ _oExt.afnFiltering = [ ];
100
+
101
+ /*
102
+ * Variable: aoFeatures
103
+ * Purpose: Container for plugin function functions
104
+ * Scope: jQuery.fn.dataTableExt
105
+ * Notes: Array of objects with the following parameters:
106
+ * fnInit: Function for initialisation of Feature. Takes oSettings and returns node
107
+ * cFeature: Character that will be matched in sDom - case sensitive
108
+ * sFeature: Feature name - just for completeness :-)
109
+ */
110
+ _oExt.aoFeatures = [ ];
111
+
112
+ /*
113
+ * Variable: ofnSearch
114
+ * Purpose: Container for custom filtering functions
115
+ * Scope: jQuery.fn.dataTableExt
116
+ * Notes: This is an object (the name should match the type) for custom filtering function,
117
+ * which can be used for live DOM checking or formatted text filtering
118
+ */
119
+ _oExt.ofnSearch = { };
120
+
121
+ /*
122
+ * Variable: afnSortData
123
+ * Purpose: Container for custom sorting data source functions
124
+ * Scope: jQuery.fn.dataTableExt
125
+ * Notes: Array (associative) of functions which is run prior to a column of this
126
+ * 'SortDataType' being sorted upon.
127
+ * Function input parameters:
128
+ * object:oSettings- DataTables settings object
129
+ * int:iColumn - Target column number
130
+ * Return value: Array of data which exactly matched the full data set size for the column to
131
+ * be sorted upon
132
+ */
133
+ _oExt.afnSortData = [ ];
134
+
135
+ /*
136
+ * Variable: oStdClasses
137
+ * Purpose: Storage for the various classes that DataTables uses
138
+ * Scope: jQuery.fn.dataTableExt
139
+ */
140
+ _oExt.oStdClasses = {
141
+ /* Two buttons buttons */
142
+ "sPagePrevEnabled": "paginate_enabled_previous",
143
+ "sPagePrevDisabled": "paginate_disabled_previous",
144
+ "sPageNextEnabled": "paginate_enabled_next",
145
+ "sPageNextDisabled": "paginate_disabled_next",
146
+ "sPageJUINext": "",
147
+ "sPageJUIPrev": "",
148
+
149
+ /* Full numbers paging buttons */
150
+ "sPageButton": "paginate_button",
151
+ "sPageButtonActive": "paginate_active",
152
+ "sPageButtonStaticDisabled": "paginate_button",
153
+ "sPageFirst": "first",
154
+ "sPagePrevious": "previous",
155
+ "sPageNext": "next",
156
+ "sPageLast": "last",
157
+
158
+ /* Stripping classes */
159
+ "sStripOdd": "odd",
160
+ "sStripEven": "even",
161
+
162
+ /* Empty row */
163
+ "sRowEmpty": "dataTables_empty",
164
+
165
+ /* Features */
166
+ "sWrapper": "dataTables_wrapper",
167
+ "sFilter": "dataTables_filter",
168
+ "sInfo": "dataTables_info",
169
+ "sPaging": "dataTables_paginate paging_", /* Note that the type is postfixed */
170
+ "sLength": "dataTables_length",
171
+ "sProcessing": "dataTables_processing",
172
+
173
+ /* Sorting */
174
+ "sSortAsc": "sorting_asc",
175
+ "sSortDesc": "sorting_desc",
176
+ "sSortable": "sorting", /* Sortable in both directions */
177
+ "sSortableAsc": "sorting_asc_disabled",
178
+ "sSortableDesc": "sorting_desc_disabled",
179
+ "sSortableNone": "sorting_disabled",
180
+ "sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */
181
+ "sSortJUIAsc": "",
182
+ "sSortJUIDesc": "",
183
+ "sSortJUI": "",
184
+ "sSortJUIAscAllowed": "",
185
+ "sSortJUIDescAllowed": "",
186
+ "sSortJUIWrapper": "",
187
+
188
+ /* Scrolling */
189
+ "sScrollWrapper": "dataTables_scroll",
190
+ "sScrollHead": "dataTables_scrollHead",
191
+ "sScrollHeadInner": "dataTables_scrollHeadInner",
192
+ "sScrollBody": "dataTables_scrollBody",
193
+ "sScrollFoot": "dataTables_scrollFoot",
194
+ "sScrollFootInner": "dataTables_scrollFootInner",
195
+
196
+ /* Misc */
197
+ "sFooterTH": ""
198
+ };
199
+
200
+ /*
201
+ * Variable: oJUIClasses
202
+ * Purpose: Storage for the various classes that DataTables uses - jQuery UI suitable
203
+ * Scope: jQuery.fn.dataTableExt
204
+ */
205
+ _oExt.oJUIClasses = {
206
+ /* Two buttons buttons */
207
+ "sPagePrevEnabled": "fg-button ui-button ui-state-default ui-corner-left",
208
+ "sPagePrevDisabled": "fg-button ui-button ui-state-default ui-corner-left ui-state-disabled",
209
+ "sPageNextEnabled": "fg-button ui-button ui-state-default ui-corner-right",
210
+ "sPageNextDisabled": "fg-button ui-button ui-state-default ui-corner-right ui-state-disabled",
211
+ "sPageJUINext": "ui-icon ui-icon-circle-arrow-e",
212
+ "sPageJUIPrev": "ui-icon ui-icon-circle-arrow-w",
213
+
214
+ /* Full numbers paging buttons */
215
+ "sPageButton": "fg-button ui-button ui-state-default",
216
+ "sPageButtonActive": "fg-button ui-button ui-state-default ui-state-disabled",
217
+ "sPageButtonStaticDisabled": "fg-button ui-button ui-state-default ui-state-disabled",
218
+ "sPageFirst": "first ui-corner-tl ui-corner-bl",
219
+ "sPagePrevious": "previous",
220
+ "sPageNext": "next",
221
+ "sPageLast": "last ui-corner-tr ui-corner-br",
222
+
223
+ /* Stripping classes */
224
+ "sStripOdd": "odd",
225
+ "sStripEven": "even",
226
+
227
+ /* Empty row */
228
+ "sRowEmpty": "dataTables_empty",
229
+
230
+ /* Features */
231
+ "sWrapper": "dataTables_wrapper",
232
+ "sFilter": "dataTables_filter",
233
+ "sInfo": "dataTables_info",
234
+ "sPaging": "dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi "+
235
+ "ui-buttonset-multi paging_", /* Note that the type is postfixed */
236
+ "sLength": "dataTables_length",
237
+ "sProcessing": "dataTables_processing",
238
+
239
+ /* Sorting */
240
+ "sSortAsc": "ui-state-default",
241
+ "sSortDesc": "ui-state-default",
242
+ "sSortable": "ui-state-default",
243
+ "sSortableAsc": "ui-state-default",
244
+ "sSortableDesc": "ui-state-default",
245
+ "sSortableNone": "ui-state-default",
246
+ "sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */
247
+ "sSortJUIAsc": "css_right ui-icon ui-icon-triangle-1-n",
248
+ "sSortJUIDesc": "css_right ui-icon ui-icon-triangle-1-s",
249
+ "sSortJUI": "css_right ui-icon ui-icon-carat-2-n-s",
250
+ "sSortJUIAscAllowed": "css_right ui-icon ui-icon-carat-1-n",
251
+ "sSortJUIDescAllowed": "css_right ui-icon ui-icon-carat-1-s",
252
+ "sSortJUIWrapper": "DataTables_sort_wrapper",
253
+
254
+ /* Scrolling */
255
+ "sScrollWrapper": "dataTables_scroll",
256
+ "sScrollHead": "dataTables_scrollHead ui-state-default",
257
+ "sScrollHeadInner": "dataTables_scrollHeadInner",
258
+ "sScrollBody": "dataTables_scrollBody",
259
+ "sScrollFoot": "dataTables_scrollFoot ui-state-default",
260
+ "sScrollFootInner": "dataTables_scrollFootInner",
261
+
262
+ /* Misc */
263
+ "sFooterTH": "ui-state-default"
264
+ };
265
+
266
+ /*
267
+ * Variable: oPagination
268
+ * Purpose: Container for the various type of pagination that dataTables supports
269
+ * Scope: jQuery.fn.dataTableExt
270
+ */
271
+ _oExt.oPagination = {
272
+ /*
273
+ * Variable: two_button
274
+ * Purpose: Standard two button (forward/back) pagination
275
+ * Scope: jQuery.fn.dataTableExt.oPagination
276
+ */
277
+ "two_button": {
278
+ /*
279
+ * Function: oPagination.two_button.fnInit
280
+ * Purpose: Initalise dom elements required for pagination with forward/back buttons only
281
+ * Returns: -
282
+ * Inputs: object:oSettings - dataTables settings object
283
+ * node:nPaging - the DIV which contains this pagination control
284
+ * function:fnCallbackDraw - draw function which must be called on update
285
+ */
286
+ "fnInit": function ( oSettings, nPaging, fnCallbackDraw )
287
+ {
288
+ var nPrevious, nNext, nPreviousInner, nNextInner;
289
+
290
+ /* Store the next and previous elements in the oSettings object as they can be very
291
+ * usful for automation - particularly testing
292
+ */
293
+ if ( !oSettings.bJUI )
294
+ {
295
+ nPrevious = document.createElement( 'div' );
296
+ nNext = document.createElement( 'div' );
297
+ }
298
+ else
299
+ {
300
+ nPrevious = document.createElement( 'a' );
301
+ nNext = document.createElement( 'a' );
302
+
303
+ nNextInner = document.createElement('span');
304
+ nNextInner.className = oSettings.oClasses.sPageJUINext;
305
+ nNext.appendChild( nNextInner );
306
+
307
+ nPreviousInner = document.createElement('span');
308
+ nPreviousInner.className = oSettings.oClasses.sPageJUIPrev;
309
+ nPrevious.appendChild( nPreviousInner );
310
+ }
311
+
312
+ nPrevious.className = oSettings.oClasses.sPagePrevDisabled;
313
+ nNext.className = oSettings.oClasses.sPageNextDisabled;
314
+
315
+ nPrevious.title = oSettings.oLanguage.oPaginate.sPrevious;
316
+ nNext.title = oSettings.oLanguage.oPaginate.sNext;
317
+
318
+ nPaging.appendChild( nPrevious );
319
+ nPaging.appendChild( nNext );
320
+
321
+ $(nPrevious).click( function() {
322
+ if ( oSettings.oApi._fnPageChange( oSettings, "previous" ) )
323
+ {
324
+ /* Only draw when the page has actually changed */
325
+ fnCallbackDraw( oSettings );
326
+ }
327
+ } );
328
+
329
+ $(nNext).click( function() {
330
+ if ( oSettings.oApi._fnPageChange( oSettings, "next" ) )
331
+ {
332
+ fnCallbackDraw( oSettings );
333
+ }
334
+ } );
335
+
336
+ /* Take the brutal approach to cancelling text selection */
337
+ $(nPrevious).bind( 'selectstart', function () { return false; } );
338
+ $(nNext).bind( 'selectstart', function () { return false; } );
339
+
340
+ /* ID the first elements only */
341
+ if ( oSettings.sTableId !== '' && typeof oSettings.aanFeatures.p == "undefined" )
342
+ {
343
+ nPaging.setAttribute( 'id', oSettings.sTableId+'_paginate' );
344
+ nPrevious.setAttribute( 'id', oSettings.sTableId+'_previous' );
345
+ nNext.setAttribute( 'id', oSettings.sTableId+'_next' );
346
+ }
347
+ },
348
+
349
+ /*
350
+ * Function: oPagination.two_button.fnUpdate
351
+ * Purpose: Update the two button pagination at the end of the draw
352
+ * Returns: -
353
+ * Inputs: object:oSettings - dataTables settings object
354
+ * function:fnCallbackDraw - draw function to call on page change
355
+ */
356
+ "fnUpdate": function ( oSettings, fnCallbackDraw )
357
+ {
358
+ if ( !oSettings.aanFeatures.p )
359
+ {
360
+ return;
361
+ }
362
+
363
+ /* Loop over each instance of the pager */
364
+ var an = oSettings.aanFeatures.p;
365
+ for ( var i=0, iLen=an.length ; i<iLen ; i++ )
366
+ {
367
+ if ( an[i].childNodes.length !== 0 )
368
+ {
369
+ an[i].childNodes[0].className =
370
+ ( oSettings._iDisplayStart === 0 ) ?
371
+ oSettings.oClasses.sPagePrevDisabled : oSettings.oClasses.sPagePrevEnabled;
372
+
373
+ an[i].childNodes[1].className =
374
+ ( oSettings.fnDisplayEnd() == oSettings.fnRecordsDisplay() ) ?
375
+ oSettings.oClasses.sPageNextDisabled : oSettings.oClasses.sPageNextEnabled;
376
+ }
377
+ }
378
+ }
379
+ },
380
+
381
+
382
+ /*
383
+ * Variable: iFullNumbersShowPages
384
+ * Purpose: Change the number of pages which can be seen
385
+ * Scope: jQuery.fn.dataTableExt.oPagination
386
+ */
387
+ "iFullNumbersShowPages": 5,
388
+
389
+ /*
390
+ * Variable: full_numbers
391
+ * Purpose: Full numbers pagination
392
+ * Scope: jQuery.fn.dataTableExt.oPagination
393
+ */
394
+ "full_numbers": {
395
+ /*
396
+ * Function: oPagination.full_numbers.fnInit
397
+ * Purpose: Initalise dom elements required for pagination with a list of the pages
398
+ * Returns: -
399
+ * Inputs: object:oSettings - dataTables settings object
400
+ * node:nPaging - the DIV which contains this pagination control
401
+ * function:fnCallbackDraw - draw function which must be called on update
402
+ */
403
+ "fnInit": function ( oSettings, nPaging, fnCallbackDraw )
404
+ {
405
+ var nFirst = document.createElement( 'span' );
406
+ var nPrevious = document.createElement( 'span' );
407
+ var nList = document.createElement( 'span' );
408
+ var nNext = document.createElement( 'span' );
409
+ var nLast = document.createElement( 'span' );
410
+
411
+ nFirst.innerHTML = oSettings.oLanguage.oPaginate.sFirst;
412
+ nPrevious.innerHTML = oSettings.oLanguage.oPaginate.sPrevious;
413
+ nNext.innerHTML = oSettings.oLanguage.oPaginate.sNext;
414
+ nLast.innerHTML = oSettings.oLanguage.oPaginate.sLast;
415
+
416
+ var oClasses = oSettings.oClasses;
417
+ nFirst.className = oClasses.sPageButton+" "+oClasses.sPageFirst;
418
+ nPrevious.className = oClasses.sPageButton+" "+oClasses.sPagePrevious;
419
+ nNext.className= oClasses.sPageButton+" "+oClasses.sPageNext;
420
+ nLast.className = oClasses.sPageButton+" "+oClasses.sPageLast;
421
+
422
+ nPaging.appendChild( nFirst );
423
+ nPaging.appendChild( nPrevious );
424
+ nPaging.appendChild( nList );
425
+ nPaging.appendChild( nNext );
426
+ nPaging.appendChild( nLast );
427
+
428
+ $(nFirst).click( function () {
429
+ if ( oSettings.oApi._fnPageChange( oSettings, "first" ) )
430
+ {
431
+ fnCallbackDraw( oSettings );
432
+ }
433
+ } );
434
+
435
+ $(nPrevious).click( function() {
436
+ if ( oSettings.oApi._fnPageChange( oSettings, "previous" ) )
437
+ {
438
+ fnCallbackDraw( oSettings );
439
+ }
440
+ } );
441
+
442
+ $(nNext).click( function() {
443
+ if ( oSettings.oApi._fnPageChange( oSettings, "next" ) )
444
+ {
445
+ fnCallbackDraw( oSettings );
446
+ }
447
+ } );
448
+
449
+ $(nLast).click( function() {
450
+ if ( oSettings.oApi._fnPageChange( oSettings, "last" ) )
451
+ {
452
+ fnCallbackDraw( oSettings );
453
+ }
454
+ } );
455
+
456
+ /* Take the brutal approach to cancelling text selection */
457
+ $('span', nPaging)
458
+ .bind( 'mousedown', function () { return false; } )
459
+ .bind( 'selectstart', function () { return false; } );
460
+
461
+ /* ID the first elements only */
462
+ if ( oSettings.sTableId !== '' && typeof oSettings.aanFeatures.p == "undefined" )
463
+ {
464
+ nPaging.setAttribute( 'id', oSettings.sTableId+'_paginate' );
465
+ nFirst.setAttribute( 'id', oSettings.sTableId+'_first' );
466
+ nPrevious.setAttribute( 'id', oSettings.sTableId+'_previous' );
467
+ nNext.setAttribute( 'id', oSettings.sTableId+'_next' );
468
+ nLast.setAttribute( 'id', oSettings.sTableId+'_last' );
469
+ }
470
+ },
471
+
472
+ /*
473
+ * Function: oPagination.full_numbers.fnUpdate
474
+ * Purpose: Update the list of page buttons shows
475
+ * Returns: -
476
+ * Inputs: object:oSettings - dataTables settings object
477
+ * function:fnCallbackDraw - draw function to call on page change
478
+ */
479
+ "fnUpdate": function ( oSettings, fnCallbackDraw )
480
+ {
481
+ if ( !oSettings.aanFeatures.p )
482
+ {
483
+ return;
484
+ }
485
+
486
+ var iPageCount = _oExt.oPagination.iFullNumbersShowPages;
487
+ var iPageCountHalf = Math.floor(iPageCount / 2);
488
+ var iPages = Math.ceil((oSettings.fnRecordsDisplay()) / oSettings._iDisplayLength);
489
+ var iCurrentPage = Math.ceil(oSettings._iDisplayStart / oSettings._iDisplayLength) + 1;
490
+ var sList = "";
491
+ var iStartButton, iEndButton, i, iLen;
492
+ var oClasses = oSettings.oClasses;
493
+
494
+ /* Pages calculation */
495
+ if (iPages < iPageCount)
496
+ {
497
+ iStartButton = 1;
498
+ iEndButton = iPages;
499
+ }
500
+ else
501
+ {
502
+ if (iCurrentPage <= iPageCountHalf)
503
+ {
504
+ iStartButton = 1;
505
+ iEndButton = iPageCount;
506
+ }
507
+ else
508
+ {
509
+ if (iCurrentPage >= (iPages - iPageCountHalf))
510
+ {
511
+ iStartButton = iPages - iPageCount + 1;
512
+ iEndButton = iPages;
513
+ }
514
+ else
515
+ {
516
+ iStartButton = iCurrentPage - Math.ceil(iPageCount / 2) + 1;
517
+ iEndButton = iStartButton + iPageCount - 1;
518
+ }
519
+ }
520
+ }
521
+
522
+ /* Build the dynamic list */
523
+ for ( i=iStartButton ; i<=iEndButton ; i++ )
524
+ {
525
+ if ( iCurrentPage != i )
526
+ {
527
+ sList += '<span class="'+oClasses.sPageButton+'">'+i+'</span>';
528
+ }
529
+ else
530
+ {
531
+ sList += '<span class="'+oClasses.sPageButtonActive+'">'+i+'</span>';
532
+ }
533
+ }
534
+
535
+ /* Loop over each instance of the pager */
536
+ var an = oSettings.aanFeatures.p;
537
+ var anButtons, anStatic, nPaginateList;
538
+ var fnClick = function() {
539
+ /* Use the information in the element to jump to the required page */
540
+ var iTarget = (this.innerHTML * 1) - 1;
541
+ oSettings._iDisplayStart = iTarget * oSettings._iDisplayLength;
542
+ fnCallbackDraw( oSettings );
543
+ return false;
544
+ };
545
+ var fnFalse = function () { return false; };
546
+
547
+ for ( i=0, iLen=an.length ; i<iLen ; i++ )
548
+ {
549
+ if ( an[i].childNodes.length === 0 )
550
+ {
551
+ continue;
552
+ }
553
+
554
+ /* Build up the dynamic list forst - html and listeners */
555
+ var qjPaginateList = $('span:eq(2)', an[i]);
556
+ qjPaginateList.html( sList );
557
+ $('span', qjPaginateList).click( fnClick ).bind( 'mousedown', fnFalse )
558
+ .bind( 'selectstart', fnFalse );
559
+
560
+ /* Update the 'premanent botton's classes */
561
+ anButtons = an[i].getElementsByTagName('span');
562
+ anStatic = [
563
+ anButtons[0], anButtons[1],
564
+ anButtons[anButtons.length-2], anButtons[anButtons.length-1]
565
+ ];
566
+ $(anStatic).removeClass( oClasses.sPageButton+" "+oClasses.sPageButtonActive+" "+oClasses.sPageButtonStaticDisabled );
567
+ if ( iCurrentPage == 1 )
568
+ {
569
+ anStatic[0].className += " "+oClasses.sPageButtonStaticDisabled;
570
+ anStatic[1].className += " "+oClasses.sPageButtonStaticDisabled;
571
+ }
572
+ else
573
+ {
574
+ anStatic[0].className += " "+oClasses.sPageButton;
575
+ anStatic[1].className += " "+oClasses.sPageButton;
576
+ }
577
+
578
+ if ( iPages === 0 || iCurrentPage == iPages || oSettings._iDisplayLength == -1 )
579
+ {
580
+ anStatic[2].className += " "+oClasses.sPageButtonStaticDisabled;
581
+ anStatic[3].className += " "+oClasses.sPageButtonStaticDisabled;
582
+ }
583
+ else
584
+ {
585
+ anStatic[2].className += " "+oClasses.sPageButton;
586
+ anStatic[3].className += " "+oClasses.sPageButton;
587
+ }
588
+ }
589
+ }
590
+ }
591
+ };
592
+
593
+ /*
594
+ * Variable: oSort
595
+ * Purpose: Wrapper for the sorting functions that can be used in DataTables
596
+ * Scope: jQuery.fn.dataTableExt
597
+ * Notes: The functions provided in this object are basically standard javascript sort
598
+ * functions - they expect two inputs which they then compare and then return a priority
599
+ * result. For each sort method added, two functions need to be defined, an ascending sort and
600
+ * a descending sort.
601
+ */
602
+ _oExt.oSort = {
603
+ /*
604
+ * text sorting
605
+ */
606
+ "string-asc": function ( a, b )
607
+ {
608
+ var x = a.toLowerCase();
609
+ var y = b.toLowerCase();
610
+ return ((x < y) ? -1 : ((x > y) ? 1 : 0));
611
+ },
612
+
613
+ "string-desc": function ( a, b )
614
+ {
615
+ var x = a.toLowerCase();
616
+ var y = b.toLowerCase();
617
+ return ((x < y) ? 1 : ((x > y) ? -1 : 0));
618
+ },
619
+
620
+
621
+ /*
622
+ * html sorting (ignore html tags)
623
+ */
624
+ "html-asc": function ( a, b )
625
+ {
626
+ var x = a.replace( /<.*?>/g, "" ).toLowerCase();
627
+ var y = b.replace( /<.*?>/g, "" ).toLowerCase();
628
+ return ((x < y) ? -1 : ((x > y) ? 1 : 0));
629
+ },
630
+
631
+ "html-desc": function ( a, b )
632
+ {
633
+ var x = a.replace( /<.*?>/g, "" ).toLowerCase();
634
+ var y = b.replace( /<.*?>/g, "" ).toLowerCase();
635
+ return ((x < y) ? 1 : ((x > y) ? -1 : 0));
636
+ },
637
+
638
+
639
+ /*
640
+ * date sorting
641
+ */
642
+ "date-asc": function ( a, b )
643
+ {
644
+ var x = Date.parse( a );
645
+ var y = Date.parse( b );
646
+
647
+ if ( isNaN(x) || x==="" )
648
+ {
649
+ x = Date.parse( "01/01/1970 00:00:00" );
650
+ }
651
+ if ( isNaN(y) || y==="" )
652
+ {
653
+ y = Date.parse( "01/01/1970 00:00:00" );
654
+ }
655
+
656
+ return x - y;
657
+ },
658
+
659
+ "date-desc": function ( a, b )
660
+ {
661
+ var x = Date.parse( a );
662
+ var y = Date.parse( b );
663
+
664
+ if ( isNaN(x) || x==="" )
665
+ {
666
+ x = Date.parse( "01/01/1970 00:00:00" );
667
+ }
668
+ if ( isNaN(y) || y==="" )
669
+ {
670
+ y = Date.parse( "01/01/1970 00:00:00" );
671
+ }
672
+
673
+ return y - x;
674
+ },
675
+
676
+
677
+ /*
678
+ * numerical sorting
679
+ */
680
+ "numeric-asc": function ( a, b )
681
+ {
682
+ var x = (a=="-" || a==="") ? 0 : a*1;
683
+ var y = (b=="-" || b==="") ? 0 : b*1;
684
+ return x - y;
685
+ },
686
+
687
+ "numeric-desc": function ( a, b )
688
+ {
689
+ var x = (a=="-" || a==="") ? 0 : a*1;
690
+ var y = (b=="-" || b==="") ? 0 : b*1;
691
+ return y - x;
692
+ }
693
+ };
694
+
695
+
696
+ /*
697
+ * Variable: aTypes
698
+ * Purpose: Container for the various type of type detection that dataTables supports
699
+ * Scope: jQuery.fn.dataTableExt
700
+ * Notes: The functions in this array are expected to parse a string to see if it is a data
701
+ * type that it recognises. If so then the function should return the name of the type (a
702
+ * corresponding sort function should be defined!), if the type is not recognised then the
703
+ * function should return null such that the parser and move on to check the next type.
704
+ * Note that ordering is important in this array - the functions are processed linearly,
705
+ * starting at index 0.
706
+ * Note that the input for these functions is always a string! It cannot be any other data
707
+ * type
708
+ */
709
+ _oExt.aTypes = [
710
+ /*
711
+ * Function: -
712
+ * Purpose: Check to see if a string is numeric
713
+ * Returns: string:'numeric' or null
714
+ * Inputs: string:sText - string to check
715
+ */
716
+ function ( sData )
717
+ {
718
+ /* Allow zero length strings as a number */
719
+ if ( sData.length === 0 )
720
+ {
721
+ return 'numeric';
722
+ }
723
+
724
+ var sValidFirstChars = "0123456789-";
725
+ var sValidChars = "0123456789.";
726
+ var Char;
727
+ var bDecimal = false;
728
+
729
+ /* Check for a valid first char (no period and allow negatives) */
730
+ Char = sData.charAt(0);
731
+ if (sValidFirstChars.indexOf(Char) == -1)
732
+ {
733
+ return null;
734
+ }
735
+
736
+ /* Check all the other characters are valid */
737
+ for ( var i=1 ; i<sData.length ; i++ )
738
+ {
739
+ Char = sData.charAt(i);
740
+ if (sValidChars.indexOf(Char) == -1)
741
+ {
742
+ return null;
743
+ }
744
+
745
+ /* Only allowed one decimal place... */
746
+ if ( Char == "." )
747
+ {
748
+ if ( bDecimal )
749
+ {
750
+ return null;
751
+ }
752
+ bDecimal = true;
753
+ }
754
+ }
755
+
756
+ return 'numeric';
757
+ },
758
+
759
+ /*
760
+ * Function: -
761
+ * Purpose: Check to see if a string is actually a formatted date
762
+ * Returns: string:'date' or null
763
+ * Inputs: string:sText - string to check
764
+ */
765
+ function ( sData )
766
+ {
767
+ var iParse = Date.parse(sData);
768
+ if ( (iParse !== null && !isNaN(iParse)) || sData.length === 0 )
769
+ {
770
+ return 'date';
771
+ }
772
+ return null;
773
+ },
774
+
775
+ /*
776
+ * Function: -
777
+ * Purpose: Check to see if a string should be treated as an HTML string
778
+ * Returns: string:'html' or null
779
+ * Inputs: string:sText - string to check
780
+ */
781
+ function ( sData )
782
+ {
783
+ if ( sData.indexOf('<') != -1 && sData.indexOf('>') != -1 )
784
+ {
785
+ return 'html';
786
+ }
787
+ return null;
788
+ }
789
+ ];
790
+
791
+ /*
792
+ * Function: fnVersionCheck
793
+ * Purpose: Check a version string against this version of DataTables. Useful for plug-ins
794
+ * Returns: bool:true -this version of DataTables is greater or equal to the required version
795
+ * false -this version of DataTales is not suitable
796
+ * Inputs: string:sVersion - the version to check against. May be in the following formats:
797
+ * "a", "a.b" or "a.b.c"
798
+ * Notes: This function will only check the first three parts of a version string. It is
799
+ * assumed that beta and dev versions will meet the requirements. This might change in future
800
+ */
801
+ _oExt.fnVersionCheck = function( sVersion )
802
+ {
803
+ /* This is cheap, but very effective */
804
+ var fnZPad = function (Zpad, count)
805
+ {
806
+ while(Zpad.length < count) {
807
+ Zpad += '0';
808
+ }
809
+ return Zpad;
810
+ };
811
+ var aThis = _oExt.sVersion.split('.');
812
+ var aThat = sVersion.split('.');
813
+ var sThis = '', sThat = '';
814
+
815
+ for ( var i=0, iLen=aThat.length ; i<iLen ; i++ )
816
+ {
817
+ sThis += fnZPad( aThis[i], 3 );
818
+ sThat += fnZPad( aThat[i], 3 );
819
+ }
820
+
821
+ return parseInt(sThis, 10) >= parseInt(sThat, 10);
822
+ };
823
+
824
+ /*
825
+ * Variable: _oExternConfig
826
+ * Purpose: Store information for DataTables to access globally about other instances
827
+ * Scope: jQuery.fn.dataTableExt
828
+ */
829
+ _oExt._oExternConfig = {
830
+ /* int:iNextUnique - next unique number for an instance */
831
+ "iNextUnique": 0
832
+ };
833
+
834
+
835
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
836
+ * Section - DataTables prototype
837
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
838
+
839
+ /*
840
+ * Function: dataTable
841
+ * Purpose: DataTables information
842
+ * Returns: -
843
+ * Inputs: object:oInit - initalisation options for the table
844
+ */
845
+ $.fn.dataTable = function( oInit )
846
+ {
847
+ /*
848
+ * Function: classSettings
849
+ * Purpose: Settings container function for all 'class' properties which are required
850
+ * by dataTables
851
+ * Returns: -
852
+ * Inputs: -
853
+ */
854
+ function classSettings ()
855
+ {
856
+ this.fnRecordsTotal = function ()
857
+ {
858
+ if ( this.oFeatures.bServerSide ) {
859
+ return parseInt(this._iRecordsTotal, 10);
860
+ } else {
861
+ return this.aiDisplayMaster.length;
862
+ }
863
+ };
864
+
865
+ this.fnRecordsDisplay = function ()
866
+ {
867
+ if ( this.oFeatures.bServerSide ) {
868
+ return parseInt(this._iRecordsDisplay, 10);
869
+ } else {
870
+ return this.aiDisplay.length;
871
+ }
872
+ };
873
+
874
+ this.fnDisplayEnd = function ()
875
+ {
876
+ if ( this.oFeatures.bServerSide ) {
877
+ if ( this.oFeatures.bPaginate === false || this._iDisplayLength == -1 ) {
878
+ return this._iDisplayStart+this.aiDisplay.length;
879
+ } else {
880
+ return Math.min( this._iDisplayStart+this._iDisplayLength,
881
+ this._iRecordsDisplay );
882
+ }
883
+ } else {
884
+ return this._iDisplayEnd;
885
+ }
886
+ };
887
+
888
+ /*
889
+ * Variable: oInstance
890
+ * Purpose: The DataTables object for this table
891
+ * Scope: jQuery.dataTable.classSettings
892
+ */
893
+ this.oInstance = null;
894
+
895
+ /*
896
+ * Variable: sInstance
897
+ * Purpose: Unique idendifier for each instance of the DataTables object
898
+ * Scope: jQuery.dataTable.classSettings
899
+ */
900
+ this.sInstance = null;
901
+
902
+ /*
903
+ * Variable: oFeatures
904
+ * Purpose: Indicate the enablement of key dataTable features
905
+ * Scope: jQuery.dataTable.classSettings
906
+ */
907
+ this.oFeatures = {
908
+ "bPaginate": true,
909
+ "bLengthChange": true,
910
+ "bFilter": true,
911
+ "bSort": true,
912
+ "bInfo": true,
913
+ "bAutoWidth": true,
914
+ "bProcessing": false,
915
+ "bSortClasses": true,
916
+ "bStateSave": false,
917
+ "bServerSide": false
918
+ };
919
+
920
+ /*
921
+ * Variable: oScroll
922
+ * Purpose: Container for scrolling options
923
+ * Scope: jQuery.dataTable.classSettings
924
+ */
925
+ this.oScroll = {
926
+ "sX": "",
927
+ "sXInner": "",
928
+ "sY": "",
929
+ "bCollapse": false,
930
+ "bInfinite": false,
931
+ "iLoadGap": 100,
932
+ "iBarWidth": 0
933
+ };
934
+
935
+ /*
936
+ * Variable: aanFeatures
937
+ * Purpose: Array referencing the nodes which are used for the features
938
+ * Scope: jQuery.dataTable.classSettings
939
+ * Notes: The parameters of this object match what is allowed by sDom - i.e.
940
+ * 'l' - Length changing
941
+ * 'f' - Filtering input
942
+ * 't' - The table!
943
+ * 'i' - Information
944
+ * 'p' - Pagination
945
+ * 'r' - pRocessing
946
+ */
947
+ this.aanFeatures = [];
948
+
949
+ /*
950
+ * Variable: oLanguage
951
+ * Purpose: Store the language strings used by dataTables
952
+ * Scope: jQuery.dataTable.classSettings
953
+ * Notes: The words in the format _VAR_ are variables which are dynamically replaced
954
+ * by javascript
955
+ */
956
+ this.oLanguage = {
957
+ "sProcessing": "Processing...",
958
+ "sLengthMenu": "Show _MENU_ entries",
959
+ "sZeroRecords": "No matching records found",
960
+ "sEmptyTable": "No data available in table",
961
+ "sInfo": "Showing _START_ to _END_ of _TOTAL_ entries",
962
+ "sInfoEmpty": "Showing 0 to 0 of 0 entries",
963
+ "sInfoFiltered": "(filtered from _MAX_ total entries)",
964
+ "sInfoPostFix": "",
965
+ "sSearch": "Search:",
966
+ "sUrl": "",
967
+ "oPaginate": {
968
+ "sFirst": "First",
969
+ "sPrevious": "Previous",
970
+ "sNext": "Next",
971
+ "sLast": "Last"
972
+ },
973
+ "fnInfoCallback": null
974
+ };
975
+
976
+ /*
977
+ * Variable: aoData
978
+ * Purpose: Store data information
979
+ * Scope: jQuery.dataTable.classSettings
980
+ * Notes: This is an array of objects with the following parameters:
981
+ * int: _iId - internal id for tracking
982
+ * array: _aData - internal data - used for sorting / filtering etc
983
+ * node: nTr - display node
984
+ * array node: _anHidden - hidden TD nodes
985
+ * string: _sRowStripe
986
+ */
987
+ this.aoData = [];
988
+
989
+ /*
990
+ * Variable: aiDisplay
991
+ * Purpose: Array of indexes which are in the current display (after filtering etc)
992
+ * Scope: jQuery.dataTable.classSettings
993
+ */
994
+ this.aiDisplay = [];
995
+
996
+ /*
997
+ * Variable: aiDisplayMaster
998
+ * Purpose: Array of indexes for display - no filtering
999
+ * Scope: jQuery.dataTable.classSettings
1000
+ */
1001
+ this.aiDisplayMaster = [];
1002
+
1003
+ /*
1004
+ * Variable: aoColumns
1005
+ * Purpose: Store information about each column that is in use
1006
+ * Scope: jQuery.dataTable.classSettings
1007
+ */
1008
+ this.aoColumns = [];
1009
+
1010
+ /*
1011
+ * Variable: iNextId
1012
+ * Purpose: Store the next unique id to be used for a new row
1013
+ * Scope: jQuery.dataTable.classSettings
1014
+ */
1015
+ this.iNextId = 0;
1016
+
1017
+ /*
1018
+ * Variable: asDataSearch
1019
+ * Purpose: Search data array for regular expression searching
1020
+ * Scope: jQuery.dataTable.classSettings
1021
+ */
1022
+ this.asDataSearch = [];
1023
+
1024
+ /*
1025
+ * Variable: oPreviousSearch
1026
+ * Purpose: Store the previous search incase we want to force a re-search
1027
+ * or compare the old search to a new one
1028
+ * Scope: jQuery.dataTable.classSettings
1029
+ */
1030
+ this.oPreviousSearch = {
1031
+ "sSearch": "",
1032
+ "bRegex": false,
1033
+ "bSmart": true
1034
+ };
1035
+
1036
+ /*
1037
+ * Variable: aoPreSearchCols
1038
+ * Purpose: Store the previous search for each column
1039
+ * Scope: jQuery.dataTable.classSettings
1040
+ */
1041
+ this.aoPreSearchCols = [];
1042
+
1043
+ /*
1044
+ * Variable: aaSorting
1045
+ * Purpose: Sorting information
1046
+ * Scope: jQuery.dataTable.classSettings
1047
+ * Notes: Index 0 - column number
1048
+ * Index 1 - current sorting direction
1049
+ * Index 2 - index of asSorting for this column
1050
+ */
1051
+ this.aaSorting = [ [0, 'asc', 0] ];
1052
+
1053
+ /*
1054
+ * Variable: aaSortingFixed
1055
+ * Purpose: Sorting information that is always applied
1056
+ * Scope: jQuery.dataTable.classSettings
1057
+ */
1058
+ this.aaSortingFixed = null;
1059
+
1060
+ /*
1061
+ * Variable: asStripClasses
1062
+ * Purpose: Classes to use for the striping of a table
1063
+ * Scope: jQuery.dataTable.classSettings
1064
+ */
1065
+ this.asStripClasses = [];
1066
+
1067
+ /*
1068
+ * Variable: asDestoryStrips
1069
+ * Purpose: If restoring a table - we should restore it's striping classes as well
1070
+ * Scope: jQuery.dataTable.classSettings
1071
+ */
1072
+ this.asDestoryStrips = [];
1073
+
1074
+ /*
1075
+ * Variable: sDestroyWidth
1076
+ * Purpose: If restoring a table - we should restore it's width
1077
+ * Scope: jQuery.dataTable.classSettings
1078
+ */
1079
+ this.sDestroyWidth = 0;
1080
+
1081
+ /*
1082
+ * Variable: fnRowCallback
1083
+ * Purpose: Call this function every time a row is inserted (draw)
1084
+ * Scope: jQuery.dataTable.classSettings
1085
+ */
1086
+ this.fnRowCallback = null;
1087
+
1088
+ /*
1089
+ * Variable: fnHeaderCallback
1090
+ * Purpose: Callback function for the header on each draw
1091
+ * Scope: jQuery.dataTable.classSettings
1092
+ */
1093
+ this.fnHeaderCallback = null;
1094
+
1095
+ /*
1096
+ * Variable: fnFooterCallback
1097
+ * Purpose: Callback function for the footer on each draw
1098
+ * Scope: jQuery.dataTable.classSettings
1099
+ */
1100
+ this.fnFooterCallback = null;
1101
+
1102
+ /*
1103
+ * Variable: aoDrawCallback
1104
+ * Purpose: Array of callback functions for draw callback functions
1105
+ * Scope: jQuery.dataTable.classSettings
1106
+ * Notes: Each array element is an object with the following parameters:
1107
+ * function:fn - function to call
1108
+ * string:sName - name callback (feature). useful for arranging array
1109
+ */
1110
+ this.aoDrawCallback = [];
1111
+
1112
+ /*
1113
+ * Variable: fnInitComplete
1114
+ * Purpose: Callback function for when the table has been initalised
1115
+ * Scope: jQuery.dataTable.classSettings
1116
+ */
1117
+ this.fnInitComplete = null;
1118
+
1119
+ /*
1120
+ * Variable: sTableId
1121
+ * Purpose: Cache the table ID for quick access
1122
+ * Scope: jQuery.dataTable.classSettings
1123
+ */
1124
+ this.sTableId = "";
1125
+
1126
+ /*
1127
+ * Variable: nTable
1128
+ * Purpose: Cache the table node for quick access
1129
+ * Scope: jQuery.dataTable.classSettings
1130
+ */
1131
+ this.nTable = null;
1132
+
1133
+ /*
1134
+ * Variable: nTHead
1135
+ * Purpose: Permanent ref to the thead element
1136
+ * Scope: jQuery.dataTable.classSettings
1137
+ */
1138
+ this.nTHead = null;
1139
+
1140
+ /*
1141
+ * Variable: nTFoot
1142
+ * Purpose: Permanent ref to the tfoot element - if it exists
1143
+ * Scope: jQuery.dataTable.classSettings
1144
+ */
1145
+ this.nTFoot = null;
1146
+
1147
+ /*
1148
+ * Variable: nTBody
1149
+ * Purpose: Permanent ref to the tbody element
1150
+ * Scope: jQuery.dataTable.classSettings
1151
+ */
1152
+ this.nTBody = null;
1153
+
1154
+ /*
1155
+ * Variable: nTableWrapper
1156
+ * Purpose: Cache the wrapper node (contains all DataTables controlled elements)
1157
+ * Scope: jQuery.dataTable.classSettings
1158
+ */
1159
+ this.nTableWrapper = null;
1160
+
1161
+ /*
1162
+ * Variable: bInitialised
1163
+ * Purpose: Indicate if all required information has been read in
1164
+ * Scope: jQuery.dataTable.classSettings
1165
+ */
1166
+ this.bInitialised = false;
1167
+
1168
+ /*
1169
+ * Variable: aoOpenRows
1170
+ * Purpose: Information about open rows
1171
+ * Scope: jQuery.dataTable.classSettings
1172
+ * Notes: Has the parameters 'nTr' and 'nParent'
1173
+ */
1174
+ this.aoOpenRows = [];
1175
+
1176
+ /*
1177
+ * Variable: sDom
1178
+ * Purpose: Dictate the positioning that the created elements will take
1179
+ * Scope: jQuery.dataTable.classSettings
1180
+ * Notes:
1181
+ * The following options are allowed:
1182
+ * 'l' - Length changing
1183
+ * 'f' - Filtering input
1184
+ * 't' - The table!
1185
+ * 'i' - Information
1186
+ * 'p' - Pagination
1187
+ * 'r' - pRocessing
1188
+ * The following constants are allowed:
1189
+ * 'H' - jQueryUI theme "header" classes
1190
+ * 'F' - jQueryUI theme "footer" classes
1191
+ * The following syntax is expected:
1192
+ * '<' and '>' - div elements
1193
+ * '<"class" and '>' - div with a class
1194
+ * Examples:
1195
+ * '<"wrapper"flipt>', '<lf<t>ip>'
1196
+ */
1197
+ this.sDom = 'lfrtip';
1198
+
1199
+ /*
1200
+ * Variable: sPaginationType
1201
+ * Purpose: Note which type of sorting should be used
1202
+ * Scope: jQuery.dataTable.classSettings
1203
+ */
1204
+ this.sPaginationType = "two_button";
1205
+
1206
+ /*
1207
+ * Variable: iCookieDuration
1208
+ * Purpose: The cookie duration (for bStateSave) in seconds - default 2 hours
1209
+ * Scope: jQuery.dataTable.classSettings
1210
+ */
1211
+ this.iCookieDuration = 60 * 60 * 2;
1212
+
1213
+ /*
1214
+ * Variable: sCookiePrefix
1215
+ * Purpose: The cookie name prefix
1216
+ * Scope: jQuery.dataTable.classSettings
1217
+ */
1218
+ this.sCookiePrefix = "SpryMedia_DataTables_";
1219
+
1220
+ /*
1221
+ * Variable: fnCookieCallback
1222
+ * Purpose: Callback function for cookie creation
1223
+ * Scope: jQuery.dataTable.classSettings
1224
+ */
1225
+ this.fnCookieCallback = null;
1226
+
1227
+ /*
1228
+ * Variable: aoStateSave
1229
+ * Purpose: Array of callback functions for state saving
1230
+ * Scope: jQuery.dataTable.classSettings
1231
+ * Notes: Each array element is an object with the following parameters:
1232
+ * function:fn - function to call. Takes two parameters, oSettings and the JSON string to
1233
+ * save that has been thus far created. Returns a JSON string to be inserted into a
1234
+ * json object (i.e. '"param": [ 0, 1, 2]')
1235
+ * string:sName - name of callback
1236
+ */
1237
+ this.aoStateSave = [];
1238
+
1239
+ /*
1240
+ * Variable: aoStateLoad
1241
+ * Purpose: Array of callback functions for state loading
1242
+ * Scope: jQuery.dataTable.classSettings
1243
+ * Notes: Each array element is an object with the following parameters:
1244
+ * function:fn - function to call. Takes two parameters, oSettings and the object stored.
1245
+ * May return false to cancel state loading.
1246
+ * string:sName - name of callback
1247
+ */
1248
+ this.aoStateLoad = [];
1249
+
1250
+ /*
1251
+ * Variable: oLoadedState
1252
+ * Purpose: State that was loaded from the cookie. Useful for back reference
1253
+ * Scope: jQuery.dataTable.classSettings
1254
+ */
1255
+ this.oLoadedState = null;
1256
+
1257
+ /*
1258
+ * Variable: sAjaxSource
1259
+ * Purpose: Source url for AJAX data for the table
1260
+ * Scope: jQuery.dataTable.classSettings
1261
+ */
1262
+ this.sAjaxSource = null;
1263
+
1264
+ /*
1265
+ * Variable: bAjaxDataGet
1266
+ * Purpose: Note if draw should be blocked while getting data
1267
+ * Scope: jQuery.dataTable.classSettings
1268
+ */
1269
+ this.bAjaxDataGet = true;
1270
+
1271
+ /*
1272
+ * Variable: fnServerData
1273
+ * Purpose: Function to get the server-side data - can be overruled by the developer
1274
+ * Scope: jQuery.dataTable.classSettings
1275
+ */
1276
+ this.fnServerData = function ( url, data, callback ) {
1277
+ $.ajax( {
1278
+ "url": url,
1279
+ "data": data,
1280
+ "success": callback,
1281
+ "dataType": "json",
1282
+ "cache": false,
1283
+ "error": function (xhr, error, thrown) {
1284
+ if ( error == "parsererror" ) {
1285
+ alert( "DataTables warning: JSON data from server could not be parsed. "+
1286
+ "This is caused by a JSON formatting error." );
1287
+ }
1288
+ }
1289
+ } );
1290
+ };
1291
+
1292
+ /*
1293
+ * Variable: fnFormatNumber
1294
+ * Purpose: Format numbers for display
1295
+ * Scope: jQuery.dataTable.classSettings
1296
+ */
1297
+ this.fnFormatNumber = function ( iIn )
1298
+ {
1299
+ if ( iIn < 1000 )
1300
+ {
1301
+ /* A small optimisation for what is likely to be the vast majority of use cases */
1302
+ return iIn;
1303
+ }
1304
+ else
1305
+ {
1306
+ var s=(iIn+""), a=s.split(""), out="", iLen=s.length;
1307
+
1308
+ for ( var i=0 ; i<iLen ; i++ )
1309
+ {
1310
+ if ( i%3 === 0 && i !== 0 )
1311
+ {
1312
+ out = ','+out;
1313
+ }
1314
+ out = a[iLen-i-1]+out;
1315
+ }
1316
+ }
1317
+ return out;
1318
+ };
1319
+
1320
+ /*
1321
+ * Variable: aLengthMenu
1322
+ * Purpose: List of options that can be used for the user selectable length menu
1323
+ * Scope: jQuery.dataTable.classSettings
1324
+ * Note: This varaible can take for form of a 1D array, in which case the value and the
1325
+ * displayed value in the menu are the same, or a 2D array in which case the value comes
1326
+ * from the first array, and the displayed value to the end user comes from the second
1327
+ * array. 2D example: [ [ 10, 25, 50, 100, -1 ], [ 10, 25, 50, 100, 'All' ] ];
1328
+ */
1329
+ this.aLengthMenu = [ 10, 25, 50, 100 ];
1330
+
1331
+ /*
1332
+ * Variable: iDraw
1333
+ * Purpose: Counter for the draws that the table does. Also used as a tracker for
1334
+ * server-side processing
1335
+ * Scope: jQuery.dataTable.classSettings
1336
+ */
1337
+ this.iDraw = 0;
1338
+
1339
+ /*
1340
+ * Variable: bDrawing
1341
+ * Purpose: Indicate if a redraw is being done - useful for Ajax
1342
+ * Scope: jQuery.dataTable.classSettings
1343
+ */
1344
+ this.bDrawing = 0;
1345
+
1346
+ /*
1347
+ * Variable: iDrawError
1348
+ * Purpose: Last draw error
1349
+ * Scope: jQuery.dataTable.classSettings
1350
+ */
1351
+ this.iDrawError = -1;
1352
+
1353
+ /*
1354
+ * Variable: _iDisplayLength, _iDisplayStart, _iDisplayEnd
1355
+ * Purpose: Display length variables
1356
+ * Scope: jQuery.dataTable.classSettings
1357
+ * Notes: These variable must NOT be used externally to get the data length. Rather, use
1358
+ * the fnRecordsTotal() (etc) functions.
1359
+ */
1360
+ this._iDisplayLength = 10;
1361
+ this._iDisplayStart = 0;
1362
+ this._iDisplayEnd = 10;
1363
+
1364
+ /*
1365
+ * Variable: _iRecordsTotal, _iRecordsDisplay
1366
+ * Purpose: Display length variables used for server side processing
1367
+ * Scope: jQuery.dataTable.classSettings
1368
+ * Notes: These variable must NOT be used externally to get the data length. Rather, use
1369
+ * the fnRecordsTotal() (etc) functions.
1370
+ */
1371
+ this._iRecordsTotal = 0;
1372
+ this._iRecordsDisplay = 0;
1373
+
1374
+ /*
1375
+ * Variable: bJUI
1376
+ * Purpose: Should we add the markup needed for jQuery UI theming?
1377
+ * Scope: jQuery.dataTable.classSettings
1378
+ */
1379
+ this.bJUI = false;
1380
+
1381
+ /*
1382
+ * Variable: bJUI
1383
+ * Purpose: Should we add the markup needed for jQuery UI theming?
1384
+ * Scope: jQuery.dataTable.classSettings
1385
+ */
1386
+ this.oClasses = _oExt.oStdClasses;
1387
+
1388
+ /*
1389
+ * Variable: bFiltered and bSorted
1390
+ * Purpose: Flags to allow callback functions to see what actions have been performed
1391
+ * Scope: jQuery.dataTable.classSettings
1392
+ */
1393
+ this.bFiltered = false;
1394
+ this.bSorted = false;
1395
+
1396
+ /*
1397
+ * Variable: oInit
1398
+ * Purpose: Initialisation object that is used for the table
1399
+ * Scope: jQuery.dataTable.classSettings
1400
+ */
1401
+ this.oInit = null;
1402
+ }
1403
+
1404
+ /*
1405
+ * Variable: oApi
1406
+ * Purpose: Container for publicly exposed 'private' functions
1407
+ * Scope: jQuery.dataTable
1408
+ */
1409
+ this.oApi = {};
1410
+
1411
+
1412
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1413
+ * Section - API functions
1414
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
1415
+
1416
+ /*
1417
+ * Function: fnDraw
1418
+ * Purpose: Redraw the table
1419
+ * Returns: -
1420
+ * Inputs: bool:bComplete - Refilter and resort (if enabled) the table before the draw.
1421
+ * Optional: default - true
1422
+ */
1423
+ this.fnDraw = function( bComplete )
1424
+ {
1425
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1426
+ if ( typeof bComplete != 'undefined' && bComplete === false )
1427
+ {
1428
+ _fnCalculateEnd( oSettings );
1429
+ _fnDraw( oSettings );
1430
+ }
1431
+ else
1432
+ {
1433
+ _fnReDraw( oSettings );
1434
+ }
1435
+ };
1436
+
1437
+ /*
1438
+ * Function: fnFilter
1439
+ * Purpose: Filter the input based on data
1440
+ * Returns: -
1441
+ * Inputs: string:sInput - string to filter the table on
1442
+ * int:iColumn - optional - column to limit filtering to
1443
+ * bool:bRegex - optional - treat as regular expression or not - default false
1444
+ * bool:bSmart - optional - perform smart filtering or not - default true
1445
+ * bool:bShowGlobal - optional - show the input global filter in it's input box(es)
1446
+ * - default true
1447
+ */
1448
+ this.fnFilter = function( sInput, iColumn, bRegex, bSmart, bShowGlobal )
1449
+ {
1450
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1451
+
1452
+ if ( !oSettings.oFeatures.bFilter )
1453
+ {
1454
+ return;
1455
+ }
1456
+
1457
+ if ( typeof bRegex == 'undefined' )
1458
+ {
1459
+ bRegex = false;
1460
+ }
1461
+
1462
+ if ( typeof bSmart == 'undefined' )
1463
+ {
1464
+ bSmart = true;
1465
+ }
1466
+
1467
+ if ( typeof bShowGlobal == 'undefined' )
1468
+ {
1469
+ bShowGlobal = true;
1470
+ }
1471
+
1472
+ if ( typeof iColumn == "undefined" || iColumn === null )
1473
+ {
1474
+ /* Global filter */
1475
+ _fnFilterComplete( oSettings, {
1476
+ "sSearch":sInput,
1477
+ "bRegex": bRegex,
1478
+ "bSmart": bSmart
1479
+ }, 1 );
1480
+
1481
+ if ( bShowGlobal && typeof oSettings.aanFeatures.f != 'undefined' )
1482
+ {
1483
+ var n = oSettings.aanFeatures.f;
1484
+ for ( var i=0, iLen=n.length ; i<iLen ; i++ )
1485
+ {
1486
+ $('input', n[i]).val( sInput );
1487
+ }
1488
+ }
1489
+ }
1490
+ else
1491
+ {
1492
+ /* Single column filter */
1493
+ oSettings.aoPreSearchCols[ iColumn ].sSearch = sInput;
1494
+ oSettings.aoPreSearchCols[ iColumn ].bRegex = bRegex;
1495
+ oSettings.aoPreSearchCols[ iColumn ].bSmart = bSmart;
1496
+ _fnFilterComplete( oSettings, oSettings.oPreviousSearch, 1 );
1497
+ }
1498
+ };
1499
+
1500
+ /*
1501
+ * Function: fnSettings
1502
+ * Purpose: Get the settings for a particular table for extern. manipulation
1503
+ * Returns: -
1504
+ * Inputs: -
1505
+ */
1506
+ this.fnSettings = function( nNode )
1507
+ {
1508
+ return _fnSettingsFromNode( this[_oExt.iApiIndex] );
1509
+ };
1510
+
1511
+ /*
1512
+ * Function: fnVersionCheck
1513
+ * Notes: The function is the same as the 'static' function provided in the ext variable
1514
+ */
1515
+ this.fnVersionCheck = _oExt.fnVersionCheck;
1516
+
1517
+ /*
1518
+ * Function: fnSort
1519
+ * Purpose: Sort the table by a particular row
1520
+ * Returns: -
1521
+ * Inputs: int:iCol - the data index to sort on. Note that this will
1522
+ * not match the 'display index' if you have hidden data entries
1523
+ */
1524
+ this.fnSort = function( aaSort )
1525
+ {
1526
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1527
+ oSettings.aaSorting = aaSort;
1528
+ _fnSort( oSettings );
1529
+ };
1530
+
1531
+ /*
1532
+ * Function: fnSortListener
1533
+ * Purpose: Attach a sort listener to an element for a given column
1534
+ * Returns: -
1535
+ * Inputs: node:nNode - the element to attach the sort listener to
1536
+ * int:iColumn - the column that a click on this node will sort on
1537
+ * function:fnCallback - callback function when sort is run - optional
1538
+ */
1539
+ this.fnSortListener = function( nNode, iColumn, fnCallback )
1540
+ {
1541
+ _fnSortAttachListener( _fnSettingsFromNode( this[_oExt.iApiIndex] ), nNode, iColumn,
1542
+ fnCallback );
1543
+ };
1544
+
1545
+ /*
1546
+ * Function: fnAddData
1547
+ * Purpose: Add new row(s) into the table
1548
+ * Returns: array int: array of indexes (aoData) which have been added (zero length on error)
1549
+ * Inputs: array:mData - the data to be added. The length must match
1550
+ * the original data from the DOM
1551
+ * or
1552
+ * array array:mData - 2D array of data to be added
1553
+ * bool:bRedraw - redraw the table or not - default true
1554
+ * Notes: Warning - the refilter here will cause the table to redraw
1555
+ * starting at zero
1556
+ * Notes: Thanks to Yekimov Denis for contributing the basis for this function!
1557
+ */
1558
+ this.fnAddData = function( mData, bRedraw )
1559
+ {
1560
+ if ( mData.length === 0 )
1561
+ {
1562
+ return [];
1563
+ }
1564
+
1565
+ var aiReturn = [];
1566
+ var iTest;
1567
+
1568
+ /* Find settings from table node */
1569
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1570
+
1571
+ /* Check if we want to add multiple rows or not */
1572
+ if ( typeof mData[0] == "object" )
1573
+ {
1574
+ for ( var i=0 ; i<mData.length ; i++ )
1575
+ {
1576
+ iTest = _fnAddData( oSettings, mData[i] );
1577
+ if ( iTest == -1 )
1578
+ {
1579
+ return aiReturn;
1580
+ }
1581
+ aiReturn.push( iTest );
1582
+ }
1583
+ }
1584
+ else
1585
+ {
1586
+ iTest = _fnAddData( oSettings, mData );
1587
+ if ( iTest == -1 )
1588
+ {
1589
+ return aiReturn;
1590
+ }
1591
+ aiReturn.push( iTest );
1592
+ }
1593
+
1594
+ oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
1595
+
1596
+ if ( typeof bRedraw == 'undefined' || bRedraw )
1597
+ {
1598
+ _fnReDraw( oSettings );
1599
+ }
1600
+ return aiReturn;
1601
+ };
1602
+
1603
+ /*
1604
+ * Function: fnDeleteRow
1605
+ * Purpose: Remove a row for the table
1606
+ * Returns: array:aReturn - the row that was deleted
1607
+ * Inputs: mixed:mTarget -
1608
+ * int: - index of aoData to be deleted, or
1609
+ * node(TR): - TR element you want to delete
1610
+ * function:fnCallBack - callback function - default null
1611
+ * bool:bRedraw - redraw the table or not - default true
1612
+ */
1613
+ this.fnDeleteRow = function( mTarget, fnCallBack, bRedraw )
1614
+ {
1615
+ /* Find settings from table node */
1616
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1617
+ var i, iAODataIndex;
1618
+
1619
+ iAODataIndex = (typeof mTarget == 'object') ?
1620
+ _fnNodeToDataIndex(oSettings, mTarget) : mTarget;
1621
+
1622
+ /* Return the data array from this row */
1623
+ var oData = oSettings.aoData.splice( iAODataIndex, 1 );
1624
+
1625
+ /* Remove the target row from the search array */
1626
+ var iDisplayIndex = $.inArray( iAODataIndex, oSettings.aiDisplay );
1627
+ oSettings.asDataSearch.splice( iDisplayIndex, 1 );
1628
+
1629
+ /* Delete from the display arrays */
1630
+ _fnDeleteIndex( oSettings.aiDisplayMaster, iAODataIndex );
1631
+ _fnDeleteIndex( oSettings.aiDisplay, iAODataIndex );
1632
+
1633
+ /* If there is a user callback function - call it */
1634
+ if ( typeof fnCallBack == "function" )
1635
+ {
1636
+ fnCallBack.call( this, oSettings, oData );
1637
+ }
1638
+
1639
+ /* Check for an 'overflow' they case for dislaying the table */
1640
+ if ( oSettings._iDisplayStart >= oSettings.aiDisplay.length )
1641
+ {
1642
+ oSettings._iDisplayStart -= oSettings._iDisplayLength;
1643
+ if ( oSettings._iDisplayStart < 0 )
1644
+ {
1645
+ oSettings._iDisplayStart = 0;
1646
+ }
1647
+ }
1648
+
1649
+ if ( typeof bRedraw == 'undefined' || bRedraw )
1650
+ {
1651
+ _fnCalculateEnd( oSettings );
1652
+ _fnDraw( oSettings );
1653
+ }
1654
+
1655
+ return oData;
1656
+ };
1657
+
1658
+ /*
1659
+ * Function: fnClearTable
1660
+ * Purpose: Quickly and simply clear a table
1661
+ * Returns: -
1662
+ * Inputs: bool:bRedraw - redraw the table or not - default true
1663
+ * Notes: Thanks to Yekimov Denis for contributing the basis for this function!
1664
+ */
1665
+ this.fnClearTable = function( bRedraw )
1666
+ {
1667
+ /* Find settings from table node */
1668
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1669
+ _fnClearTable( oSettings );
1670
+
1671
+ if ( typeof bRedraw == 'undefined' || bRedraw )
1672
+ {
1673
+ _fnDraw( oSettings );
1674
+ }
1675
+ };
1676
+
1677
+ /*
1678
+ * Function: fnOpen
1679
+ * Purpose: Open a display row (append a row after the row in question)
1680
+ * Returns: node:nNewRow - the row opened
1681
+ * Inputs: node:nTr - the table row to 'open'
1682
+ * string:sHtml - the HTML to put into the row
1683
+ * string:sClass - class to give the new TD cell
1684
+ */
1685
+ this.fnOpen = function( nTr, sHtml, sClass )
1686
+ {
1687
+ /* Find settings from table node */
1688
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1689
+
1690
+ /* the old open one if there is one */
1691
+ this.fnClose( nTr );
1692
+
1693
+ var nNewRow = document.createElement("tr");
1694
+ var nNewCell = document.createElement("td");
1695
+ nNewRow.appendChild( nNewCell );
1696
+ nNewCell.className = sClass;
1697
+ nNewCell.colSpan = _fnVisbleColumns( oSettings );
1698
+ nNewCell.innerHTML = sHtml;
1699
+
1700
+ /* If the nTr isn't on the page at the moment - then we don't insert at the moment */
1701
+ var nTrs = $('tr', oSettings.nTBody);
1702
+ if ( $.inArray(nTr, nTrs) != -1 )
1703
+ {
1704
+ $(nNewRow).insertAfter(nTr);
1705
+ }
1706
+
1707
+ oSettings.aoOpenRows.push( {
1708
+ "nTr": nNewRow,
1709
+ "nParent": nTr
1710
+ } );
1711
+
1712
+ return nNewRow;
1713
+ };
1714
+
1715
+ /*
1716
+ * Function: fnClose
1717
+ * Purpose: Close a display row
1718
+ * Returns: int: 0 (success) or 1 (failed)
1719
+ * Inputs: node:nTr - the table row to 'close'
1720
+ */
1721
+ this.fnClose = function( nTr )
1722
+ {
1723
+ /* Find settings from table node */
1724
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1725
+
1726
+ for ( var i=0 ; i<oSettings.aoOpenRows.length ; i++ )
1727
+ {
1728
+ if ( oSettings.aoOpenRows[i].nParent == nTr )
1729
+ {
1730
+ var nTrParent = oSettings.aoOpenRows[i].nTr.parentNode;
1731
+ if ( nTrParent )
1732
+ {
1733
+ /* Remove it if it is currently on display */
1734
+ nTrParent.removeChild( oSettings.aoOpenRows[i].nTr );
1735
+ }
1736
+ oSettings.aoOpenRows.splice( i, 1 );
1737
+ return 0;
1738
+ }
1739
+ }
1740
+ return 1;
1741
+ };
1742
+
1743
+ /*
1744
+ * Function: fnGetData
1745
+ * Purpose: Return an array with the data which is used to make up the table
1746
+ * Returns: array array string: 2d data array ([row][column]) or array string: 1d data array
1747
+ * or
1748
+ * array string (if iRow specified)
1749
+ * Inputs: mixed:mRow - optional - if not present, then the full 2D array for the table
1750
+ * if given then:
1751
+ * int: - return 1D array for aoData entry of this index
1752
+ * node(TR): - return 1D array for this TR element
1753
+ * Inputs: int:iRow - optional - if present then the array returned will be the data for
1754
+ * the row with the index 'iRow'
1755
+ */
1756
+ this.fnGetData = function( mRow )
1757
+ {
1758
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1759
+
1760
+ if ( typeof mRow != 'undefined' )
1761
+ {
1762
+ var iRow = (typeof mRow == 'object') ?
1763
+ _fnNodeToDataIndex(oSettings, mRow) : mRow;
1764
+ return oSettings.aoData[iRow]._aData;
1765
+ }
1766
+ return _fnGetDataMaster( oSettings );
1767
+ };
1768
+
1769
+ /*
1770
+ * Function: fnGetNodes
1771
+ * Purpose: Return an array with the TR nodes used for drawing the table
1772
+ * Returns: array node: TR elements
1773
+ * or
1774
+ * node (if iRow specified)
1775
+ * Inputs: int:iRow - optional - if present then the array returned will be the node for
1776
+ * the row with the index 'iRow'
1777
+ */
1778
+ this.fnGetNodes = function( iRow )
1779
+ {
1780
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1781
+
1782
+ if ( typeof iRow != 'undefined' )
1783
+ {
1784
+ return oSettings.aoData[iRow].nTr;
1785
+ }
1786
+ return _fnGetTrNodes( oSettings );
1787
+ };
1788
+
1789
+ /*
1790
+ * Function: fnGetPosition
1791
+ * Purpose: Get the array indexes of a particular cell from it's DOM element
1792
+ * Returns: int: - row index, or array[ int, int, int ]: - row index, column index (visible)
1793
+ * and column index including hidden columns
1794
+ * Inputs: node:nNode - this can either be a TR or a TD in the table, the return is
1795
+ * dependent on this input
1796
+ */
1797
+ this.fnGetPosition = function( nNode )
1798
+ {
1799
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1800
+ var i;
1801
+
1802
+ if ( nNode.nodeName.toUpperCase() == "TR" )
1803
+ {
1804
+ return _fnNodeToDataIndex(oSettings, nNode);
1805
+ }
1806
+ else if ( nNode.nodeName.toUpperCase() == "TD" )
1807
+ {
1808
+ var iDataIndex = _fnNodeToDataIndex(oSettings, nNode.parentNode);
1809
+ var iCorrector = 0;
1810
+ for ( var j=0 ; j<oSettings.aoColumns.length ; j++ )
1811
+ {
1812
+ if ( oSettings.aoColumns[j].bVisible )
1813
+ {
1814
+ if ( oSettings.aoData[iDataIndex].nTr.getElementsByTagName('td')[j-iCorrector] == nNode )
1815
+ {
1816
+ return [ iDataIndex, j-iCorrector, j ];
1817
+ }
1818
+ }
1819
+ else
1820
+ {
1821
+ iCorrector++;
1822
+ }
1823
+ }
1824
+ }
1825
+ return null;
1826
+ };
1827
+
1828
+ /*
1829
+ * Function: fnUpdate
1830
+ * Purpose: Update a table cell or row
1831
+ * Returns: int: 0 okay, 1 error
1832
+ * Inputs: array string 'or' string:mData - data to update the cell/row with
1833
+ * mixed:mRow -
1834
+ * int: - index of aoData to be updated, or
1835
+ * node(TR): - TR element you want to update
1836
+ * int:iColumn - the column to update - optional (not used of mData is 2D)
1837
+ * bool:bRedraw - redraw the table or not - default true
1838
+ * bool:bAction - perform predraw actions or not (you will want this as 'true' if
1839
+ * you have bRedraw as true) - default true
1840
+ */
1841
+ this.fnUpdate = function( mData, mRow, iColumn, bRedraw, bAction )
1842
+ {
1843
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1844
+ var iVisibleColumn;
1845
+ var sDisplay;
1846
+ var iRow = (typeof mRow == 'object') ?
1847
+ _fnNodeToDataIndex(oSettings, mRow) : mRow;
1848
+
1849
+ if ( typeof mData != 'object' )
1850
+ {
1851
+ sDisplay = mData;
1852
+ oSettings.aoData[iRow]._aData[iColumn] = sDisplay;
1853
+
1854
+ if ( oSettings.aoColumns[iColumn].fnRender !== null )
1855
+ {
1856
+ sDisplay = oSettings.aoColumns[iColumn].fnRender( {
1857
+ "iDataRow": iRow,
1858
+ "iDataColumn": iColumn,
1859
+ "aData": oSettings.aoData[iRow]._aData,
1860
+ "oSettings": oSettings
1861
+ } );
1862
+
1863
+ if ( oSettings.aoColumns[iColumn].bUseRendered )
1864
+ {
1865
+ oSettings.aoData[iRow]._aData[iColumn] = sDisplay;
1866
+ }
1867
+ }
1868
+
1869
+ iVisibleColumn = _fnColumnIndexToVisible( oSettings, iColumn );
1870
+ if ( iVisibleColumn !== null )
1871
+ {
1872
+ oSettings.aoData[iRow].nTr.getElementsByTagName('td')[iVisibleColumn].innerHTML =
1873
+ sDisplay;
1874
+ }
1875
+ }
1876
+ else
1877
+ {
1878
+ if ( mData.length != oSettings.aoColumns.length )
1879
+ {
1880
+ _fnLog( oSettings, 0, 'An array passed to fnUpdate must have the same number of '+
1881
+ 'columns as the table in question - in this case '+oSettings.aoColumns.length );
1882
+ return 1;
1883
+ }
1884
+
1885
+ for ( var i=0 ; i<mData.length ; i++ )
1886
+ {
1887
+ sDisplay = mData[i];
1888
+ oSettings.aoData[iRow]._aData[i] = sDisplay;
1889
+
1890
+ if ( oSettings.aoColumns[i].fnRender !== null )
1891
+ {
1892
+ sDisplay = oSettings.aoColumns[i].fnRender( {
1893
+ "iDataRow": iRow,
1894
+ "iDataColumn": i,
1895
+ "aData": oSettings.aoData[iRow]._aData,
1896
+ "oSettings": oSettings
1897
+ } );
1898
+
1899
+ if ( oSettings.aoColumns[i].bUseRendered )
1900
+ {
1901
+ oSettings.aoData[iRow]._aData[i] = sDisplay;
1902
+ }
1903
+ }
1904
+
1905
+ iVisibleColumn = _fnColumnIndexToVisible( oSettings, i );
1906
+ if ( iVisibleColumn !== null )
1907
+ {
1908
+ oSettings.aoData[iRow].nTr.getElementsByTagName('td')[iVisibleColumn].innerHTML =
1909
+ sDisplay;
1910
+ }
1911
+ }
1912
+ }
1913
+
1914
+ /* Modify the search index for this row (strictly this is likely not needed, since fnReDraw
1915
+ * will rebuild the search array - however, the redraw might be disabled by the user)
1916
+ */
1917
+ var iDisplayIndex = $.inArray( iRow, oSettings.aiDisplay );
1918
+ oSettings.asDataSearch[iDisplayIndex] = _fnBuildSearchRow( oSettings,
1919
+ oSettings.aoData[iRow]._aData );
1920
+
1921
+ /* Perform pre-draw actions */
1922
+ if ( typeof bAction == 'undefined' || bAction )
1923
+ {
1924
+ _fnAjustColumnSizing( oSettings );
1925
+ }
1926
+
1927
+ /* Redraw the table */
1928
+ if ( typeof bRedraw == 'undefined' || bRedraw )
1929
+ {
1930
+ _fnReDraw( oSettings );
1931
+ }
1932
+ return 0;
1933
+ };
1934
+
1935
+
1936
+ /*
1937
+ * Function: fnShowColoumn
1938
+ * Purpose: Show a particular column
1939
+ * Returns: -
1940
+ * Inputs: int:iCol - the column whose display should be changed
1941
+ * bool:bShow - show (true) or hide (false) the column
1942
+ * bool:bRedraw - redraw the table or not - default true
1943
+ */
1944
+ this.fnSetColumnVis = function ( iCol, bShow, bRedraw )
1945
+ {
1946
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
1947
+ var i, iLen;
1948
+ var iColumns = oSettings.aoColumns.length;
1949
+ var nTd, anTds;
1950
+
1951
+ /* No point in doing anything if we are requesting what is already true */
1952
+ if ( oSettings.aoColumns[iCol].bVisible == bShow )
1953
+ {
1954
+ return;
1955
+ }
1956
+
1957
+ var nTrHead = $('>tr', oSettings.nTHead)[0];
1958
+ var nTrFoot = $('>tr', oSettings.nTFoot)[0];
1959
+ var anTheadTh = [];
1960
+ var anTfootTh = [];
1961
+ for ( i=0 ; i<iColumns ; i++ )
1962
+ {
1963
+ anTheadTh.push( oSettings.aoColumns[i].nTh );
1964
+ anTfootTh.push( oSettings.aoColumns[i].nTf );
1965
+ }
1966
+
1967
+ /* Show the column */
1968
+ if ( bShow )
1969
+ {
1970
+ var iInsert = 0;
1971
+ for ( i=0 ; i<iCol ; i++ )
1972
+ {
1973
+ if ( oSettings.aoColumns[i].bVisible )
1974
+ {
1975
+ iInsert++;
1976
+ }
1977
+ }
1978
+
1979
+ /* Need to decide if we should use appendChild or insertBefore */
1980
+ if ( iInsert >= _fnVisbleColumns( oSettings ) )
1981
+ {
1982
+ nTrHead.appendChild( anTheadTh[iCol] );
1983
+ if ( nTrFoot )
1984
+ {
1985
+ nTrFoot.appendChild( anTfootTh[iCol] );
1986
+ }
1987
+
1988
+ for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
1989
+ {
1990
+ nTd = oSettings.aoData[i]._anHidden[iCol];
1991
+ oSettings.aoData[i].nTr.appendChild( nTd );
1992
+ }
1993
+ }
1994
+ else
1995
+ {
1996
+ /* Which coloumn should we be inserting before? */
1997
+ var iBefore;
1998
+ for ( i=iCol ; i<iColumns ; i++ )
1999
+ {
2000
+ iBefore = _fnColumnIndexToVisible( oSettings, i );
2001
+ if ( iBefore !== null )
2002
+ {
2003
+ break;
2004
+ }
2005
+ }
2006
+
2007
+ nTrHead.insertBefore( anTheadTh[iCol], nTrHead.getElementsByTagName('th')[iBefore] );
2008
+ if ( nTrFoot )
2009
+ {
2010
+ nTrFoot.insertBefore( anTfootTh[iCol], nTrFoot.getElementsByTagName('th')[iBefore] );
2011
+ }
2012
+
2013
+ anTds = _fnGetTdNodes( oSettings );
2014
+ for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
2015
+ {
2016
+ nTd = oSettings.aoData[i]._anHidden[iCol];
2017
+ oSettings.aoData[i].nTr.insertBefore( nTd, $('>td:eq('+iBefore+')',
2018
+ oSettings.aoData[i].nTr)[0] );
2019
+ }
2020
+ }
2021
+
2022
+ oSettings.aoColumns[iCol].bVisible = true;
2023
+ }
2024
+ else
2025
+ {
2026
+ /* Remove a column from display */
2027
+ nTrHead.removeChild( anTheadTh[iCol] );
2028
+ if ( nTrFoot )
2029
+ {
2030
+ nTrFoot.removeChild( anTfootTh[iCol] );
2031
+ }
2032
+
2033
+ anTds = _fnGetTdNodes( oSettings );
2034
+ for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
2035
+ {
2036
+ nTd = anTds[ ( i*oSettings.aoColumns.length) + (iCol*1) ];
2037
+ oSettings.aoData[i]._anHidden[iCol] = nTd;
2038
+ nTd.parentNode.removeChild( nTd );
2039
+ }
2040
+
2041
+ oSettings.aoColumns[iCol].bVisible = false;
2042
+ }
2043
+
2044
+ /* If there are any 'open' rows, then we need to alter the colspan for this col change */
2045
+ for ( i=0, iLen=oSettings.aoOpenRows.length ; i<iLen ; i++ )
2046
+ {
2047
+ oSettings.aoOpenRows[i].nTr.colSpan = _fnVisbleColumns( oSettings );
2048
+ }
2049
+
2050
+ /* Do a redraw incase anything depending on the table columns needs it
2051
+ * (built-in: scrolling)
2052
+ */
2053
+ if ( typeof bRedraw == 'undefined' || bRedraw )
2054
+ {
2055
+ _fnAjustColumnSizing( oSettings );
2056
+ _fnDraw( oSettings );
2057
+ }
2058
+
2059
+ _fnSaveState( oSettings );
2060
+ };
2061
+
2062
+ /*
2063
+ * Function: fnPageChange
2064
+ * Purpose: Change the pagination
2065
+ * Returns: -
2066
+ * Inputs: string:sAction - paging action to take: "first", "previous", "next" or "last"
2067
+ * bool:bRedraw - redraw the table or not - optional - default true
2068
+ */
2069
+ this.fnPageChange = function ( sAction, bRedraw )
2070
+ {
2071
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
2072
+ _fnPageChange( oSettings, sAction );
2073
+ _fnCalculateEnd( oSettings );
2074
+
2075
+ if ( typeof bRedraw == 'undefined' || bRedraw )
2076
+ {
2077
+ _fnDraw( oSettings );
2078
+ }
2079
+ };
2080
+
2081
+ /*
2082
+ * Function: fnDestroy
2083
+ * Purpose: Destructor for the DataTable
2084
+ * Returns: -
2085
+ * Inputs: -
2086
+ */
2087
+ this.fnDestroy = function ( )
2088
+ {
2089
+ var oSettings = _fnSettingsFromNode( this[_oExt.iApiIndex] );
2090
+ var nOrig = oSettings.nTableWrapper.parentNode;
2091
+ var nBody = oSettings.nTBody;
2092
+ var i, iLen;
2093
+
2094
+ /* Flag to note that the table is currently being destoryed - no action should be taken */
2095
+ oSettings.bDestroying = true;
2096
+
2097
+ /* Restore hidden columns */
2098
+ for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
2099
+ {
2100
+ if ( oSettings.aoColumns[i].bVisible === false )
2101
+ {
2102
+ this.fnSetColumnVis( i, true );
2103
+ }
2104
+ }
2105
+
2106
+ /* If there is an 'empty' indicator row, remove it */
2107
+ $('tbody>tr>td.'+oSettings.oClasses.sRowEmpty, oSettings.nTable).parent().remove();
2108
+
2109
+ /* When scrolling we had to break the table up - restore it */
2110
+ if ( oSettings.nTable != oSettings.nTHead.parentNode )
2111
+ {
2112
+ $('>thead', oSettings.nTable).remove();
2113
+ oSettings.nTable.appendChild( oSettings.nTHead );
2114
+ }
2115
+
2116
+ if ( oSettings.nTFoot && oSettings.nTable != oSettings.nTFoot.parentNode )
2117
+ {
2118
+ $('>tfoot', oSettings.nTable).remove();
2119
+ oSettings.nTable.appendChild( oSettings.nTFoot );
2120
+ }
2121
+
2122
+ /* Remove the DataTables generated nodes, events and classes */
2123
+ oSettings.nTable.parentNode.removeChild( oSettings.nTable );
2124
+ $(oSettings.nTableWrapper).remove();
2125
+
2126
+ oSettings.aaSorting = [];
2127
+ oSettings.aaSortingFixed = [];
2128
+ _fnSortingClasses( oSettings );
2129
+
2130
+ $(_fnGetTrNodes( oSettings )).removeClass( oSettings.asStripClasses.join(' ') );
2131
+
2132
+ if ( !oSettings.bJUI )
2133
+ {
2134
+ $('th', oSettings.nTHead).removeClass( [ _oExt.oStdClasses.sSortable,
2135
+ _oExt.oStdClasses.sSortableAsc,
2136
+ _oExt.oStdClasses.sSortableDesc,
2137
+ _oExt.oStdClasses.sSortableNone ].join(' ')
2138
+ );
2139
+ }
2140
+ else
2141
+ {
2142
+ $('th', oSettings.nTHead).removeClass( [ _oExt.oStdClasses.sSortable,
2143
+ _oExt.oJUIClasses.sSortableAsc,
2144
+ _oExt.oJUIClasses.sSortableDesc,
2145
+ _oExt.oJUIClasses.sSortableNone ].join(' ')
2146
+ );
2147
+ $('th span', oSettings.nTHead).remove();
2148
+ }
2149
+
2150
+ /* Add the TR elements back into the table in their original order */
2151
+ nOrig.appendChild( oSettings.nTable );
2152
+ for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
2153
+ {
2154
+ nBody.appendChild( oSettings.aoData[i].nTr );
2155
+ }
2156
+
2157
+ /* Restore the width of the original table */
2158
+ oSettings.nTable.style.width = _fnStringToCss(oSettings.sDestroyWidth);
2159
+
2160
+ /* If the were originally odd/even type classes - then we add them back here. Note
2161
+ * this is not fool proof (for example if not all rows as odd/even classes - but
2162
+ * it's a good effort without getting carried away
2163
+ */
2164
+ $('>tr:even', nBody).addClass( oSettings.asDestoryStrips[0] );
2165
+ $('>tr:odd', nBody).addClass( oSettings.asDestoryStrips[1] );
2166
+
2167
+ /* Remove the settings object from the settings array */
2168
+ for ( i=0, iLen=_aoSettings.length ; i<iLen ; i++ )
2169
+ {
2170
+ if ( _aoSettings[i] == oSettings )
2171
+ {
2172
+ _aoSettings.splice( i, 1 );
2173
+ }
2174
+ }
2175
+
2176
+ /* End it all */
2177
+ oSettings = null;
2178
+ };
2179
+
2180
+ /*
2181
+ * Function: _fnAjustColumnSizing
2182
+ * Purpose: Update tale sizing based on content. This would most likely be used for scrolling
2183
+ * and will typically need a redraw after it.
2184
+ * Returns: -
2185
+ * Inputs: bool:bRedraw - redraw the table or not, you will typically want to - default true
2186
+ */
2187
+ this.fnAdjustColumnSizing = function ( bRedraw )
2188
+ {
2189
+ _fnAjustColumnSizing( _fnSettingsFromNode( this[_oExt.iApiIndex] ) );
2190
+
2191
+ if ( typeof bRedraw == 'undefined' || bRedraw )
2192
+ {
2193
+ this.fnDraw( false );
2194
+ }
2195
+ };
2196
+
2197
+ /*
2198
+ * Plugin API functions
2199
+ *
2200
+ * This call will add the functions which are defined in _oExt.oApi to the
2201
+ * DataTables object, providing a rather nice way to allow plug-in API functions. Note that
2202
+ * this is done here, so that API function can actually override the built in API functions if
2203
+ * required for a particular purpose.
2204
+ */
2205
+
2206
+ /*
2207
+ * Function: _fnExternApiFunc
2208
+ * Purpose: Create a wrapper function for exporting an internal func to an external API func
2209
+ * Returns: function: - wrapped function
2210
+ * Inputs: string:sFunc - API function name
2211
+ */
2212
+ function _fnExternApiFunc (sFunc)
2213
+ {
2214
+ return function() {
2215
+ var aArgs = [_fnSettingsFromNode(this[_oExt.iApiIndex])].concat(
2216
+ Array.prototype.slice.call(arguments) );
2217
+ return _oExt.oApi[sFunc].apply( this, aArgs );
2218
+ };
2219
+ }
2220
+
2221
+ for ( var sFunc in _oExt.oApi )
2222
+ {
2223
+ if ( sFunc )
2224
+ {
2225
+ /*
2226
+ * Function: anon
2227
+ * Purpose: Wrap the plug-in API functions in order to provide the settings as 1st arg
2228
+ * and execute in this scope
2229
+ * Returns: -
2230
+ * Inputs: -
2231
+ */
2232
+ this[sFunc] = _fnExternApiFunc(sFunc);
2233
+ }
2234
+ }
2235
+
2236
+
2237
+
2238
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2239
+ * Section - Local functions
2240
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2241
+
2242
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2243
+ * Section - Initalisation
2244
+ */
2245
+
2246
+ /*
2247
+ * Function: _fnInitalise
2248
+ * Purpose: Draw the table for the first time, adding all required features
2249
+ * Returns: -
2250
+ * Inputs: object:oSettings - dataTables settings object
2251
+ */
2252
+ function _fnInitalise ( oSettings )
2253
+ {
2254
+ var i, iLen;
2255
+
2256
+ /* Ensure that the table data is fully initialised */
2257
+ if ( oSettings.bInitialised === false )
2258
+ {
2259
+ setTimeout( function(){ _fnInitalise( oSettings ); }, 200 );
2260
+ return;
2261
+ }
2262
+
2263
+ /* Show the display HTML options */
2264
+ _fnAddOptionsHtml( oSettings );
2265
+
2266
+ /* Draw the headers for the table */
2267
+ _fnDrawHead( oSettings );
2268
+
2269
+ /* Okay to show that something is going on now */
2270
+ _fnProcessingDisplay( oSettings, true );
2271
+
2272
+ /* Calculate sizes for columns */
2273
+ if ( oSettings.oFeatures.bAutoWidth )
2274
+ {
2275
+ _fnCalculateColumnWidths( oSettings );
2276
+ }
2277
+
2278
+ for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
2279
+ {
2280
+ if ( oSettings.aoColumns[i].sWidth !== null )
2281
+ {
2282
+ oSettings.aoColumns[i].nTh.style.width = _fnStringToCss( oSettings.aoColumns[i].sWidth );
2283
+ }
2284
+ }
2285
+
2286
+ /* If there is default sorting required - let's do it. The sort function
2287
+ * will do the drawing for us. Otherwise we draw the table
2288
+ */
2289
+ if ( oSettings.oFeatures.bSort )
2290
+ {
2291
+ _fnSort( oSettings );
2292
+ }
2293
+ else
2294
+ {
2295
+ oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
2296
+ _fnCalculateEnd( oSettings );
2297
+ _fnDraw( oSettings );
2298
+ }
2299
+
2300
+ /* if there is an ajax source */
2301
+ if ( oSettings.sAjaxSource !== null && !oSettings.oFeatures.bServerSide )
2302
+ {
2303
+ oSettings.fnServerData.call( oSettings.oInstance, oSettings.sAjaxSource, [], function(json) {
2304
+ /* Got the data - add it to the table */
2305
+ for ( i=0 ; i<json.aaData.length ; i++ )
2306
+ {
2307
+ _fnAddData( oSettings, json.aaData[i] );
2308
+ }
2309
+
2310
+ /* Reset the init display for cookie saving. We've already done a filter, and
2311
+ * therefore cleared it before. So we need to make it appear 'fresh'
2312
+ */
2313
+ oSettings.iInitDisplayStart = oSettings._iDisplayStart;
2314
+
2315
+ if ( oSettings.oFeatures.bSort )
2316
+ {
2317
+ _fnSort( oSettings );
2318
+ }
2319
+ else
2320
+ {
2321
+ oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
2322
+ _fnCalculateEnd( oSettings );
2323
+ _fnDraw( oSettings );
2324
+ }
2325
+
2326
+ _fnProcessingDisplay( oSettings, false );
2327
+
2328
+ /* Run the init callback if there is one - done here for ajax source for json obj */
2329
+ if ( typeof oSettings.fnInitComplete == 'function' )
2330
+ {
2331
+ oSettings.fnInitComplete.call( oSettings.oInstance, oSettings, json );
2332
+ }
2333
+ } );
2334
+ return;
2335
+ }
2336
+
2337
+ if ( !oSettings.oFeatures.bServerSide )
2338
+ {
2339
+ _fnProcessingDisplay( oSettings, false );
2340
+ }
2341
+ }
2342
+
2343
+ /*
2344
+ * Function: _fnLanguageProcess
2345
+ * Purpose: Copy language variables from remote object to a local one
2346
+ * Returns: -
2347
+ * Inputs: object:oSettings - dataTables settings object
2348
+ * object:oLanguage - Language information
2349
+ * bool:bInit - init once complete
2350
+ */
2351
+ function _fnLanguageProcess( oSettings, oLanguage, bInit )
2352
+ {
2353
+ _fnMap( oSettings.oLanguage, oLanguage, 'sProcessing' );
2354
+ _fnMap( oSettings.oLanguage, oLanguage, 'sLengthMenu' );
2355
+ _fnMap( oSettings.oLanguage, oLanguage, 'sEmptyTable' );
2356
+ _fnMap( oSettings.oLanguage, oLanguage, 'sZeroRecords' );
2357
+ _fnMap( oSettings.oLanguage, oLanguage, 'sInfo' );
2358
+ _fnMap( oSettings.oLanguage, oLanguage, 'sInfoEmpty' );
2359
+ _fnMap( oSettings.oLanguage, oLanguage, 'sInfoFiltered' );
2360
+ _fnMap( oSettings.oLanguage, oLanguage, 'sInfoPostFix' );
2361
+ _fnMap( oSettings.oLanguage, oLanguage, 'sSearch' );
2362
+
2363
+ if ( typeof oLanguage.oPaginate != 'undefined' )
2364
+ {
2365
+ _fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sFirst' );
2366
+ _fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sPrevious' );
2367
+ _fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sNext' );
2368
+ _fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sLast' );
2369
+ }
2370
+
2371
+ /* Backwards compatibility - if there is no sEmptyTable given, then use the same as
2372
+ * sZeroRecords - assuming that is given.
2373
+ */
2374
+ if ( typeof oLanguage.sEmptyTable == 'undefined' &&
2375
+ typeof oLanguage.sZeroRecords != 'undefined' )
2376
+ {
2377
+ _fnMap( oSettings.oLanguage, oLanguage, 'sZeroRecords', 'sEmptyTable' );
2378
+ }
2379
+
2380
+ if ( bInit )
2381
+ {
2382
+ _fnInitalise( oSettings );
2383
+ }
2384
+ }
2385
+
2386
+ /*
2387
+ * Function: _fnAddColumn
2388
+ * Purpose: Add a column to the list used for the table with default values
2389
+ * Returns: -
2390
+ * Inputs: object:oSettings - dataTables settings object
2391
+ * node:nTh - the th element for this column
2392
+ */
2393
+ function _fnAddColumn( oSettings, nTh )
2394
+ {
2395
+ oSettings.aoColumns[ oSettings.aoColumns.length++ ] = {
2396
+ "sType": null,
2397
+ "_bAutoType": true,
2398
+ "bVisible": true,
2399
+ "bSearchable": true,
2400
+ "bSortable": true,
2401
+ "asSorting": [ 'asc', 'desc' ],
2402
+ "sSortingClass": oSettings.oClasses.sSortable,
2403
+ "sSortingClassJUI": oSettings.oClasses.sSortJUI,
2404
+ "sTitle": nTh ? nTh.innerHTML : '',
2405
+ "sName": '',
2406
+ "sWidth": null,
2407
+ "sWidthOrig": null,
2408
+ "sClass": null,
2409
+ "fnRender": null,
2410
+ "bUseRendered": true,
2411
+ "iDataSort": oSettings.aoColumns.length-1,
2412
+ "sSortDataType": 'std',
2413
+ "nTh": nTh ? nTh : document.createElement('th'),
2414
+ "nTf": null
2415
+ };
2416
+
2417
+ var iCol = oSettings.aoColumns.length-1;
2418
+ var oCol = oSettings.aoColumns[ iCol ];
2419
+
2420
+ /* Add a column specific filter */
2421
+ if ( typeof oSettings.aoPreSearchCols[ iCol ] == 'undefined' ||
2422
+ oSettings.aoPreSearchCols[ iCol ] === null )
2423
+ {
2424
+ oSettings.aoPreSearchCols[ iCol ] = {
2425
+ "sSearch": "",
2426
+ "bRegex": false,
2427
+ "bSmart": true
2428
+ };
2429
+ }
2430
+ else
2431
+ {
2432
+ /* Don't require that the user must specify bRegex and / or bSmart */
2433
+ if ( typeof oSettings.aoPreSearchCols[ iCol ].bRegex == 'undefined' )
2434
+ {
2435
+ oSettings.aoPreSearchCols[ iCol ].bRegex = true;
2436
+ }
2437
+
2438
+ if ( typeof oSettings.aoPreSearchCols[ iCol ].bSmart == 'undefined' )
2439
+ {
2440
+ oSettings.aoPreSearchCols[ iCol ].bSmart = true;
2441
+ }
2442
+ }
2443
+
2444
+ /* Use the column options function to initialise classes etc */
2445
+ _fnColumnOptions( oSettings, iCol, null );
2446
+ }
2447
+
2448
+ /*
2449
+ * Function: _fnColumnOptions
2450
+ * Purpose: Apply options for a column
2451
+ * Returns: -
2452
+ * Inputs: object:oSettings - dataTables settings object
2453
+ * int:iCol - column index to consider
2454
+ * object:oOptions - object with sType, bVisible and bSearchable
2455
+ */
2456
+ function _fnColumnOptions( oSettings, iCol, oOptions )
2457
+ {
2458
+ var oCol = oSettings.aoColumns[ iCol ];
2459
+
2460
+ /* User specified column options */
2461
+ if ( typeof oOptions != 'undefined' && oOptions !== null )
2462
+ {
2463
+ if ( typeof oOptions.sType != 'undefined' )
2464
+ {
2465
+ oCol.sType = oOptions.sType;
2466
+ oCol._bAutoType = false;
2467
+ }
2468
+
2469
+ _fnMap( oCol, oOptions, "bVisible" );
2470
+ _fnMap( oCol, oOptions, "bSearchable" );
2471
+ _fnMap( oCol, oOptions, "bSortable" );
2472
+ _fnMap( oCol, oOptions, "sTitle" );
2473
+ _fnMap( oCol, oOptions, "sName" );
2474
+ _fnMap( oCol, oOptions, "sWidth" );
2475
+ _fnMap( oCol, oOptions, "sWidth", "sWidthOrig" );
2476
+ _fnMap( oCol, oOptions, "sClass" );
2477
+ _fnMap( oCol, oOptions, "fnRender" );
2478
+ _fnMap( oCol, oOptions, "bUseRendered" );
2479
+ _fnMap( oCol, oOptions, "iDataSort" );
2480
+ _fnMap( oCol, oOptions, "asSorting" );
2481
+ _fnMap( oCol, oOptions, "sSortDataType" );
2482
+ }
2483
+
2484
+ /* Feature sorting overrides column specific when off */
2485
+ if ( !oSettings.oFeatures.bSort )
2486
+ {
2487
+ oCol.bSortable = false;
2488
+ }
2489
+
2490
+ /* Check that the class assignment is correct for sorting */
2491
+ if ( !oCol.bSortable ||
2492
+ ($.inArray('asc', oCol.asSorting) == -1 && $.inArray('desc', oCol.asSorting) == -1) )
2493
+ {
2494
+ oCol.sSortingClass = oSettings.oClasses.sSortableNone;
2495
+ oCol.sSortingClassJUI = "";
2496
+ }
2497
+ else if ( $.inArray('asc', oCol.asSorting) != -1 && $.inArray('desc', oCol.asSorting) == -1 )
2498
+ {
2499
+ oCol.sSortingClass = oSettings.oClasses.sSortableAsc;
2500
+ oCol.sSortingClassJUI = oSettings.oClasses.sSortJUIAscAllowed;
2501
+ }
2502
+ else if ( $.inArray('asc', oCol.asSorting) == -1 && $.inArray('desc', oCol.asSorting) != -1 )
2503
+ {
2504
+ oCol.sSortingClass = oSettings.oClasses.sSortableDesc;
2505
+ oCol.sSortingClassJUI = oSettings.oClasses.sSortJUIDescAllowed;
2506
+ }
2507
+ }
2508
+
2509
+ /*
2510
+ * Function: _fnAddData
2511
+ * Purpose: Add a data array to the table, creating DOM node etc
2512
+ * Returns: int: - >=0 if successful (index of new aoData entry), -1 if failed
2513
+ * Inputs: object:oSettings - dataTables settings object
2514
+ * array:aData - data array to be added
2515
+ * Notes: There are two basic methods for DataTables to get data to display - a JS array
2516
+ * (which is dealt with by this function), and the DOM, which has it's own optimised
2517
+ * function (_fnGatherData). Be careful to make the same changes here as there and vice-versa
2518
+ */
2519
+ function _fnAddData ( oSettings, aDataSupplied )
2520
+ {
2521
+ /* Sanity check the length of the new array */
2522
+ if ( aDataSupplied.length != oSettings.aoColumns.length &&
2523
+ oSettings.iDrawError != oSettings.iDraw )
2524
+ {
2525
+ _fnLog( oSettings, 0, "Added data (size "+aDataSupplied.length+") does not match known "+
2526
+ "number of columns ("+oSettings.aoColumns.length+")" );
2527
+ oSettings.iDrawError = oSettings.iDraw;
2528
+ return -1;
2529
+ }
2530
+
2531
+
2532
+ /* Create the object for storing information about this new row */
2533
+ var aData = aDataSupplied.slice();
2534
+ var iThisIndex = oSettings.aoData.length;
2535
+ oSettings.aoData.push( {
2536
+ "nTr": document.createElement('tr'),
2537
+ "_iId": oSettings.iNextId++,
2538
+ "_aData": aData,
2539
+ "_anHidden": [],
2540
+ "_sRowStripe": ''
2541
+ } );
2542
+
2543
+ /* Create the cells */
2544
+ var nTd, sThisType;
2545
+ for ( var i=0 ; i<aData.length ; i++ )
2546
+ {
2547
+ nTd = document.createElement('td');
2548
+
2549
+ /* Allow null data (from a data array) - simply deal with it as a blank string */
2550
+ if ( aData[i] === null )
2551
+ {
2552
+ aData[i] = '';
2553
+ }
2554
+
2555
+ if ( typeof oSettings.aoColumns[i].fnRender == 'function' )
2556
+ {
2557
+ var sRendered = oSettings.aoColumns[i].fnRender( {
2558
+ "iDataRow": iThisIndex,
2559
+ "iDataColumn": i,
2560
+ "aData": aData,
2561
+ "oSettings": oSettings
2562
+ } );
2563
+ nTd.innerHTML = sRendered;
2564
+ if ( oSettings.aoColumns[i].bUseRendered )
2565
+ {
2566
+ /* Use the rendered data for filtering/sorting */
2567
+ oSettings.aoData[iThisIndex]._aData[i] = sRendered;
2568
+ }
2569
+ }
2570
+ else
2571
+ {
2572
+ nTd.innerHTML = aData[i];
2573
+ }
2574
+
2575
+ /* Cast everything as a string - so we can treat everything equally when sorting */
2576
+ if ( typeof aData[i] != 'string' )
2577
+ {
2578
+ aData[i] += "";
2579
+ }
2580
+ aData[i] = $.trim(aData[i]);
2581
+
2582
+ /* Add user defined class */
2583
+ if ( oSettings.aoColumns[i].sClass !== null )
2584
+ {
2585
+ nTd.className = oSettings.aoColumns[i].sClass;
2586
+ }
2587
+
2588
+ /* See if we should auto-detect the column type */
2589
+ if ( oSettings.aoColumns[i]._bAutoType && oSettings.aoColumns[i].sType != 'string' )
2590
+ {
2591
+ /* Attempt to auto detect the type - same as _fnGatherData() */
2592
+ sThisType = _fnDetectType( oSettings.aoData[iThisIndex]._aData[i] );
2593
+ if ( oSettings.aoColumns[i].sType === null )
2594
+ {
2595
+ oSettings.aoColumns[i].sType = sThisType;
2596
+ }
2597
+ else if ( oSettings.aoColumns[i].sType != sThisType )
2598
+ {
2599
+ /* String is always the 'fallback' option */
2600
+ oSettings.aoColumns[i].sType = 'string';
2601
+ }
2602
+ }
2603
+
2604
+ if ( oSettings.aoColumns[i].bVisible )
2605
+ {
2606
+ oSettings.aoData[iThisIndex].nTr.appendChild( nTd );
2607
+ oSettings.aoData[iThisIndex]._anHidden[i] = null;
2608
+ }
2609
+ else
2610
+ {
2611
+ oSettings.aoData[iThisIndex]._anHidden[i] = nTd;
2612
+ }
2613
+ }
2614
+
2615
+ /* Add to the display array */
2616
+ oSettings.aiDisplayMaster.push( iThisIndex );
2617
+ return iThisIndex;
2618
+ }
2619
+
2620
+ /*
2621
+ * Function: _fnGatherData
2622
+ * Purpose: Read in the data from the target table from the DOM
2623
+ * Returns: -
2624
+ * Inputs: object:oSettings - dataTables settings object
2625
+ * Notes: This is a optimised version of _fnAddData (more or less) for reading information
2626
+ * from the DOM. The basic actions must be identical in the two functions.
2627
+ */
2628
+ function _fnGatherData( oSettings )
2629
+ {
2630
+ var iLoop, i, iLen, j, jLen, jInner,
2631
+ nTds, nTrs, nTd, aLocalData, iThisIndex,
2632
+ iRow, iRows, iColumn, iColumns;
2633
+
2634
+ /*
2635
+ * Process by row first
2636
+ * Add the data object for the whole table - storing the tr node. Note - no point in getting
2637
+ * DOM based data if we are going to go and replace it with Ajax source data.
2638
+ */
2639
+ if ( oSettings.sAjaxSource === null )
2640
+ {
2641
+ nTrs = oSettings.nTBody.childNodes;
2642
+ for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
2643
+ {
2644
+ if ( nTrs[i].nodeName.toUpperCase() == "TR" )
2645
+ {
2646
+ iThisIndex = oSettings.aoData.length;
2647
+ oSettings.aoData.push( {
2648
+ "nTr": nTrs[i],
2649
+ "_iId": oSettings.iNextId++,
2650
+ "_aData": [],
2651
+ "_anHidden": [],
2652
+ "_sRowStripe": ''
2653
+ } );
2654
+
2655
+ oSettings.aiDisplayMaster.push( iThisIndex );
2656
+
2657
+ aLocalData = oSettings.aoData[iThisIndex]._aData;
2658
+ nTds = nTrs[i].childNodes;
2659
+ jInner = 0;
2660
+
2661
+ for ( j=0, jLen=nTds.length ; j<jLen ; j++ )
2662
+ {
2663
+ if ( nTds[j].nodeName.toUpperCase() == "TD" )
2664
+ {
2665
+ aLocalData[jInner] = $.trim(nTds[j].innerHTML);
2666
+ jInner++;
2667
+ }
2668
+ }
2669
+ }
2670
+ }
2671
+ }
2672
+
2673
+ /* Gather in the TD elements of the Table - note that this is basically the same as
2674
+ * fnGetTdNodes, but that function takes account of hidden columns, which we haven't yet
2675
+ * setup!
2676
+ */
2677
+ nTrs = _fnGetTrNodes( oSettings );
2678
+ nTds = [];
2679
+ for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
2680
+ {
2681
+ for ( j=0, jLen=nTrs[i].childNodes.length ; j<jLen ; j++ )
2682
+ {
2683
+ nTd = nTrs[i].childNodes[j];
2684
+ if ( nTd.nodeName.toUpperCase() == "TD" )
2685
+ {
2686
+ nTds.push( nTd );
2687
+ }
2688
+ }
2689
+ }
2690
+
2691
+ /* Sanity check */
2692
+ if ( nTds.length != nTrs.length * oSettings.aoColumns.length )
2693
+ {
2694
+ _fnLog( oSettings, 1, "Unexpected number of TD elements. Expected "+
2695
+ (nTrs.length * oSettings.aoColumns.length)+" and got "+nTds.length+". DataTables does "+
2696
+ "not support rowspan / colspan in the table body, and there must be one cell for each "+
2697
+ "row/column combination." );
2698
+ }
2699
+
2700
+ /* Now process by column */
2701
+ for ( iColumn=0, iColumns=oSettings.aoColumns.length ; iColumn<iColumns ; iColumn++ )
2702
+ {
2703
+ /* Get the title of the column - unless there is a user set one */
2704
+ if ( oSettings.aoColumns[iColumn].sTitle === null )
2705
+ {
2706
+ oSettings.aoColumns[iColumn].sTitle = oSettings.aoColumns[iColumn].nTh.innerHTML;
2707
+ }
2708
+
2709
+ var
2710
+ bAutoType = oSettings.aoColumns[iColumn]._bAutoType,
2711
+ bRender = typeof oSettings.aoColumns[iColumn].fnRender == 'function',
2712
+ bClass = oSettings.aoColumns[iColumn].sClass !== null,
2713
+ bVisible = oSettings.aoColumns[iColumn].bVisible,
2714
+ nCell, sThisType, sRendered;
2715
+
2716
+ /* A single loop to rule them all (and be more efficient) */
2717
+ if ( bAutoType || bRender || bClass || !bVisible )
2718
+ {
2719
+ for ( iRow=0, iRows=oSettings.aoData.length ; iRow<iRows ; iRow++ )
2720
+ {
2721
+ nCell = nTds[ (iRow*iColumns) + iColumn ];
2722
+
2723
+ /* Type detection */
2724
+ if ( bAutoType )
2725
+ {
2726
+ if ( oSettings.aoColumns[iColumn].sType != 'string' )
2727
+ {
2728
+ sThisType = _fnDetectType( oSettings.aoData[iRow]._aData[iColumn] );
2729
+ if ( oSettings.aoColumns[iColumn].sType === null )
2730
+ {
2731
+ oSettings.aoColumns[iColumn].sType = sThisType;
2732
+ }
2733
+ else if ( oSettings.aoColumns[iColumn].sType != sThisType )
2734
+ {
2735
+ /* String is always the 'fallback' option */
2736
+ oSettings.aoColumns[iColumn].sType = 'string';
2737
+ }
2738
+ }
2739
+ }
2740
+
2741
+ /* Rendering */
2742
+ if ( bRender )
2743
+ {
2744
+ sRendered = oSettings.aoColumns[iColumn].fnRender( {
2745
+ "iDataRow": iRow,
2746
+ "iDataColumn": iColumn,
2747
+ "aData": oSettings.aoData[iRow]._aData,
2748
+ "oSettings": oSettings
2749
+ } );
2750
+ nCell.innerHTML = sRendered;
2751
+ if ( oSettings.aoColumns[iColumn].bUseRendered )
2752
+ {
2753
+ /* Use the rendered data for filtering/sorting */
2754
+ oSettings.aoData[iRow]._aData[iColumn] = sRendered;
2755
+ }
2756
+ }
2757
+
2758
+ /* Classes */
2759
+ if ( bClass )
2760
+ {
2761
+ nCell.className += ' '+oSettings.aoColumns[iColumn].sClass;
2762
+ }
2763
+
2764
+ /* Column visability */
2765
+ if ( !bVisible )
2766
+ {
2767
+ oSettings.aoData[iRow]._anHidden[iColumn] = nCell;
2768
+ nCell.parentNode.removeChild( nCell );
2769
+ }
2770
+ else
2771
+ {
2772
+ oSettings.aoData[iRow]._anHidden[iColumn] = null;
2773
+ }
2774
+ }
2775
+ }
2776
+ }
2777
+ }
2778
+
2779
+
2780
+
2781
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2782
+ * Section - Drawing functions
2783
+ */
2784
+
2785
+ /*
2786
+ * Function: _fnDrawHead
2787
+ * Purpose: Create the HTML header for the table
2788
+ * Returns: -
2789
+ * Inputs: object:oSettings - dataTables settings object
2790
+ */
2791
+ function _fnDrawHead( oSettings )
2792
+ {
2793
+ var i, nTh, iLen, j, jLen;
2794
+ var iThs = oSettings.nTHead.getElementsByTagName('th').length;
2795
+ var iCorrector = 0;
2796
+
2797
+ /* If there is a header in place - then use it - otherwise it's going to get nuked... */
2798
+ if ( iThs !== 0 )
2799
+ {
2800
+ /* We've got a thead from the DOM, so remove hidden columns and apply width to vis cols */
2801
+ for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
2802
+ {
2803
+ nTh = oSettings.aoColumns[i].nTh;
2804
+
2805
+ if ( oSettings.aoColumns[i].bVisible )
2806
+ {
2807
+ /* Set the title of the column if it is user defined (not what was auto detected) */
2808
+ if ( oSettings.aoColumns[i].sTitle != nTh.innerHTML )
2809
+ {
2810
+ nTh.innerHTML = oSettings.aoColumns[i].sTitle;
2811
+ }
2812
+ }
2813
+ else
2814
+ {
2815
+ nTh.parentNode.removeChild( nTh );
2816
+ iCorrector++;
2817
+ }
2818
+ }
2819
+ }
2820
+ else
2821
+ {
2822
+ /* We don't have a header in the DOM - so we are going to have to create one */
2823
+ var nTr = document.createElement( "tr" );
2824
+
2825
+ for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
2826
+ {
2827
+ nTh = oSettings.aoColumns[i].nTh;
2828
+ nTh.innerHTML = oSettings.aoColumns[i].sTitle;
2829
+
2830
+ if ( oSettings.aoColumns[i].bVisible )
2831
+ {
2832
+ if ( oSettings.aoColumns[i].sClass !== null )
2833
+ {
2834
+ nTh.className = oSettings.aoColumns[i].sClass;
2835
+ }
2836
+
2837
+ nTr.appendChild( nTh );
2838
+ }
2839
+ }
2840
+ $(oSettings.nTHead).html( '' )[0].appendChild( nTr );
2841
+ }
2842
+
2843
+ /* Add the extra markup needed by jQuery UI's themes */
2844
+ if ( oSettings.bJUI )
2845
+ {
2846
+ for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
2847
+ {
2848
+ nTh = oSettings.aoColumns[i].nTh;
2849
+
2850
+ var nDiv = document.createElement('div');
2851
+ nDiv.className = oSettings.oClasses.sSortJUIWrapper;
2852
+ $(nTh).contents().appendTo(nDiv);
2853
+
2854
+ nDiv.appendChild( document.createElement('span') );
2855
+ nTh.appendChild( nDiv );
2856
+ }
2857
+ }
2858
+
2859
+ /* Add sort listener */
2860
+ var fnNoSelect = function (e) {
2861
+ this.onselectstart = function() { return false; };
2862
+ return false;
2863
+ };
2864
+
2865
+ if ( oSettings.oFeatures.bSort )
2866
+ {
2867
+ for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
2868
+ {
2869
+ if ( oSettings.aoColumns[i].bSortable !== false )
2870
+ {
2871
+ _fnSortAttachListener( oSettings, oSettings.aoColumns[i].nTh, i );
2872
+
2873
+ /* Take the brutal approach to cancelling text selection in header */
2874
+ $(oSettings.aoColumns[i].nTh).mousedown( fnNoSelect );
2875
+ }
2876
+ else
2877
+ {
2878
+ $(oSettings.aoColumns[i].nTh).addClass( oSettings.oClasses.sSortableNone );
2879
+ }
2880
+ }
2881
+ }
2882
+
2883
+ /* Cache the footer elements */
2884
+ if ( oSettings.nTFoot !== null )
2885
+ {
2886
+ iCorrector = 0;
2887
+ var nTfs = oSettings.nTFoot.getElementsByTagName('th');
2888
+ for ( i=0, iLen=nTfs.length ; i<iLen ; i++ )
2889
+ {
2890
+ if ( typeof oSettings.aoColumns[i] != 'undefined' )
2891
+ {
2892
+ oSettings.aoColumns[i].nTf = nTfs[i-iCorrector];
2893
+
2894
+ if ( oSettings.oClasses.sFooterTH !== "" )
2895
+ {
2896
+ oSettings.aoColumns[i].nTf.className += " "+oSettings.oClasses.sFooterTH;
2897
+ }
2898
+
2899
+ if ( !oSettings.aoColumns[i].bVisible )
2900
+ {
2901
+ nTfs[i-iCorrector].parentNode.removeChild( nTfs[i-iCorrector] );
2902
+ iCorrector++;
2903
+ }
2904
+ }
2905
+ }
2906
+ }
2907
+ }
2908
+
2909
+ /*
2910
+ * Function: _fnDraw
2911
+ * Purpose: Insert the required TR nodes into the table for display
2912
+ * Returns: -
2913
+ * Inputs: object:oSettings - dataTables settings object
2914
+ */
2915
+ function _fnDraw( oSettings )
2916
+ {
2917
+ var i, iLen;
2918
+ var anRows = [];
2919
+ var iRowCount = 0;
2920
+ var bRowError = false;
2921
+ var iStrips = oSettings.asStripClasses.length;
2922
+ var iOpenRows = oSettings.aoOpenRows.length;
2923
+
2924
+ oSettings.bDrawing = true;
2925
+
2926
+ /* Check and see if we have an initial draw position from state saving */
2927
+ if ( typeof oSettings.iInitDisplayStart != 'undefined' && oSettings.iInitDisplayStart != -1 )
2928
+ {
2929
+ if ( oSettings.oFeatures.bServerSide )
2930
+ {
2931
+ oSettings._iDisplayStart = oSettings.iInitDisplayStart;
2932
+ }
2933
+ else
2934
+ {
2935
+ oSettings._iDisplayStart = (oSettings.iInitDisplayStart >= oSettings.fnRecordsDisplay()) ?
2936
+ 0 : oSettings.iInitDisplayStart;
2937
+ }
2938
+ oSettings.iInitDisplayStart = -1;
2939
+ _fnCalculateEnd( oSettings );
2940
+ }
2941
+
2942
+ /* If we are dealing with Ajax - do it here */
2943
+ if ( oSettings.oFeatures.bServerSide &&
2944
+ !_fnAjaxUpdate( oSettings ) )
2945
+ {
2946
+ return;
2947
+ }
2948
+ else if ( !oSettings.oFeatures.bServerSide )
2949
+ {
2950
+ oSettings.iDraw++;
2951
+ }
2952
+
2953
+ if ( oSettings.aiDisplay.length !== 0 )
2954
+ {
2955
+ var iStart = oSettings._iDisplayStart;
2956
+ var iEnd = oSettings._iDisplayEnd;
2957
+
2958
+ if ( oSettings.oFeatures.bServerSide )
2959
+ {
2960
+ iStart = 0;
2961
+ iEnd = oSettings.aoData.length;
2962
+ }
2963
+
2964
+ for ( var j=iStart ; j<iEnd ; j++ )
2965
+ {
2966
+ var aoData = oSettings.aoData[ oSettings.aiDisplay[j] ];
2967
+ var nRow = aoData.nTr;
2968
+
2969
+ /* Remove the old stripping classes and then add the new one */
2970
+ if ( iStrips !== 0 )
2971
+ {
2972
+ var sStrip = oSettings.asStripClasses[ iRowCount % iStrips ];
2973
+ if ( aoData._sRowStripe != sStrip )
2974
+ {
2975
+ $(nRow).removeClass( aoData._sRowStripe ).addClass( sStrip );
2976
+ aoData._sRowStripe = sStrip;
2977
+ }
2978
+ }
2979
+
2980
+ /* Custom row callback function - might want to manipule the row */
2981
+ if ( typeof oSettings.fnRowCallback == "function" )
2982
+ {
2983
+ nRow = oSettings.fnRowCallback.call( oSettings.oInstance, nRow,
2984
+ oSettings.aoData[ oSettings.aiDisplay[j] ]._aData, iRowCount, j );
2985
+ if ( !nRow && !bRowError )
2986
+ {
2987
+ _fnLog( oSettings, 0, "A node was not returned by fnRowCallback" );
2988
+ bRowError = true;
2989
+ }
2990
+ }
2991
+
2992
+ anRows.push( nRow );
2993
+ iRowCount++;
2994
+
2995
+ /* If there is an open row - and it is attached to this parent - attach it on redraw */
2996
+ if ( iOpenRows !== 0 )
2997
+ {
2998
+ for ( var k=0 ; k<iOpenRows ; k++ )
2999
+ {
3000
+ if ( nRow == oSettings.aoOpenRows[k].nParent )
3001
+ {
3002
+ anRows.push( oSettings.aoOpenRows[k].nTr );
3003
+ }
3004
+ }
3005
+ }
3006
+ }
3007
+ }
3008
+ else
3009
+ {
3010
+ /* Table is empty - create a row with an empty message in it */
3011
+ anRows[ 0 ] = document.createElement( 'tr' );
3012
+
3013
+ if ( typeof oSettings.asStripClasses[0] != 'undefined' )
3014
+ {
3015
+ anRows[ 0 ].className = oSettings.asStripClasses[0];
3016
+ }
3017
+
3018
+ var nTd = document.createElement( 'td' );
3019
+ nTd.setAttribute( 'valign', "top" );
3020
+ nTd.colSpan = _fnVisbleColumns( oSettings );
3021
+ nTd.className = oSettings.oClasses.sRowEmpty;
3022
+ if ( typeof oSettings.oLanguage.sEmptyTable != 'undefined' &&
3023
+ oSettings.fnRecordsTotal() === 0 )
3024
+ {
3025
+ nTd.innerHTML = oSettings.oLanguage.sEmptyTable;
3026
+ }
3027
+ else
3028
+ {
3029
+ nTd.innerHTML = oSettings.oLanguage.sZeroRecords.replace(
3030
+ '_MAX_', oSettings.fnFormatNumber(oSettings.fnRecordsTotal()) );
3031
+ }
3032
+
3033
+ anRows[ iRowCount ].appendChild( nTd );
3034
+ }
3035
+
3036
+ /* Callback the header and footer custom funcation if there is one */
3037
+ if ( typeof oSettings.fnHeaderCallback == 'function' )
3038
+ {
3039
+ oSettings.fnHeaderCallback.call( oSettings.oInstance, $('>tr', oSettings.nTHead)[0],
3040
+ _fnGetDataMaster( oSettings ), oSettings._iDisplayStart, oSettings.fnDisplayEnd(),
3041
+ oSettings.aiDisplay );
3042
+ }
3043
+
3044
+ if ( typeof oSettings.fnFooterCallback == 'function' )
3045
+ {
3046
+ oSettings.fnFooterCallback.call( oSettings.oInstance, $('>tr', oSettings.nTFoot)[0],
3047
+ _fnGetDataMaster( oSettings ), oSettings._iDisplayStart, oSettings.fnDisplayEnd(),
3048
+ oSettings.aiDisplay );
3049
+ }
3050
+
3051
+ /*
3052
+ * Need to remove any old row from the display - note we can't just empty the tbody using
3053
+ * $().html('') since this will unbind the jQuery event handlers (even although the node
3054
+ * still exists!) - equally we can't use innerHTML, since IE throws an exception.
3055
+ */
3056
+ var
3057
+ nAddFrag = document.createDocumentFragment(),
3058
+ nRemoveFrag = document.createDocumentFragment(),
3059
+ nBodyPar, nTrs;
3060
+
3061
+ if ( oSettings.nTBody )
3062
+ {
3063
+ nBodyPar = oSettings.nTBody.parentNode;
3064
+ nRemoveFrag.appendChild( oSettings.nTBody );
3065
+
3066
+ /* When doing infinite scrolling, only remove child rows when sorting, filtering or start
3067
+ * up. When not infinite scroll, always do it.
3068
+ */
3069
+ if ( !oSettings.oScroll.bInfinite || !oSettings._bInitComplete ||
3070
+ oSettings.bSorted || oSettings.bFiltered )
3071
+ {
3072
+ nTrs = oSettings.nTBody.childNodes;
3073
+ for ( i=nTrs.length-1 ; i>=0 ; i-- )
3074
+ {
3075
+ nTrs[i].parentNode.removeChild( nTrs[i] );
3076
+ }
3077
+ }
3078
+
3079
+ /* Put the draw table into the dom */
3080
+ for ( i=0, iLen=anRows.length ; i<iLen ; i++ )
3081
+ {
3082
+ nAddFrag.appendChild( anRows[i] );
3083
+ }
3084
+
3085
+ oSettings.nTBody.appendChild( nAddFrag );
3086
+ if ( nBodyPar !== null )
3087
+ {
3088
+ nBodyPar.appendChild( oSettings.nTBody );
3089
+ }
3090
+ }
3091
+
3092
+ /* Call all required callback functions for the end of a draw */
3093
+ for ( i=0, iLen=oSettings.aoDrawCallback.length ; i<iLen ; i++ )
3094
+ {
3095
+ oSettings.aoDrawCallback[i].fn.call( oSettings.oInstance, oSettings );
3096
+ }
3097
+
3098
+ /* Draw is complete, sorting and filtering must be as well */
3099
+ oSettings.bSorted = false;
3100
+ oSettings.bFiltered = false;
3101
+ oSettings.bDrawing = false;
3102
+
3103
+ /* On first draw, initilaisation is now complete */
3104
+ if ( typeof oSettings._bInitComplete == "undefined" )
3105
+ {
3106
+ oSettings._bInitComplete = true;
3107
+
3108
+ if ( typeof oSettings.fnInitComplete == 'function' &&
3109
+ (oSettings.oFeatures.bServerSide || oSettings.sAjaxSource === null) )
3110
+ {
3111
+ oSettings.fnInitComplete.call( oSettings.oInstance, oSettings );
3112
+ }
3113
+ }
3114
+ }
3115
+
3116
+ /*
3117
+ * Function: _fnReDraw
3118
+ * Purpose: Redraw the table - taking account of the various features which are enabled
3119
+ * Returns: -
3120
+ * Inputs: object:oSettings - dataTables settings object
3121
+ */
3122
+ function _fnReDraw( oSettings )
3123
+ {
3124
+ if ( oSettings.oFeatures.bSort )
3125
+ {
3126
+ /* Sorting will refilter and draw for us */
3127
+ _fnSort( oSettings, oSettings.oPreviousSearch );
3128
+ }
3129
+ else if ( oSettings.oFeatures.bFilter )
3130
+ {
3131
+ /* Filtering will redraw for us */
3132
+ _fnFilterComplete( oSettings, oSettings.oPreviousSearch );
3133
+ }
3134
+ else
3135
+ {
3136
+ _fnCalculateEnd( oSettings );
3137
+ _fnDraw( oSettings );
3138
+ }
3139
+ }
3140
+
3141
+ /*
3142
+ * Function: _fnAjaxUpdate
3143
+ * Purpose: Update the table using an Ajax call
3144
+ * Returns: bool: block the table drawing or not
3145
+ * Inputs: object:oSettings - dataTables settings object
3146
+ */
3147
+ function _fnAjaxUpdate( oSettings )
3148
+ {
3149
+ if ( oSettings.bAjaxDataGet )
3150
+ {
3151
+ _fnProcessingDisplay( oSettings, true );
3152
+ var iColumns = oSettings.aoColumns.length;
3153
+ var aoData = [];
3154
+ var i;
3155
+
3156
+ /* Paging and general */
3157
+ oSettings.iDraw++;
3158
+ aoData.push( { "name": "sEcho", "value": oSettings.iDraw } );
3159
+ aoData.push( { "name": "iColumns", "value": iColumns } );
3160
+ aoData.push( { "name": "sColumns", "value": _fnColumnOrdering(oSettings) } );
3161
+ aoData.push( { "name": "iDisplayStart", "value": oSettings._iDisplayStart } );
3162
+ aoData.push( { "name": "iDisplayLength", "value": oSettings.oFeatures.bPaginate !== false ?
3163
+ oSettings._iDisplayLength : -1 } );
3164
+
3165
+ /* Column names */
3166
+ var aNames = [];
3167
+ for ( i=0 ; i<iColumns ; i++ )
3168
+ {
3169
+ aNames.push( oSettings.aoColumns[i].sName );
3170
+ }
3171
+ aoData.push( { "name": "sNames", "value": aNames.join(',') } );
3172
+
3173
+ /* Filtering */
3174
+ if ( oSettings.oFeatures.bFilter !== false )
3175
+ {
3176
+ aoData.push( { "name": "sSearch", "value": oSettings.oPreviousSearch.sSearch } );
3177
+ aoData.push( { "name": "bRegex", "value": oSettings.oPreviousSearch.bRegex } );
3178
+ for ( i=0 ; i<iColumns ; i++ )
3179
+ {
3180
+ aoData.push( { "name": "sSearch_"+i, "value": oSettings.aoPreSearchCols[i].sSearch } );
3181
+ aoData.push( { "name": "bRegex_"+i, "value": oSettings.aoPreSearchCols[i].bRegex } );
3182
+ aoData.push( { "name": "bSearchable_"+i, "value": oSettings.aoColumns[i].bSearchable } );
3183
+ }
3184
+ }
3185
+
3186
+ /* Sorting */
3187
+ if ( oSettings.oFeatures.bSort !== false )
3188
+ {
3189
+ var iFixed = oSettings.aaSortingFixed !== null ? oSettings.aaSortingFixed.length : 0;
3190
+ var iUser = oSettings.aaSorting.length;
3191
+ aoData.push( { "name": "iSortingCols", "value": iFixed+iUser } );
3192
+ for ( i=0 ; i<iFixed ; i++ )
3193
+ {
3194
+ aoData.push( { "name": "iSortCol_"+i, "value": oSettings.aaSortingFixed[i][0] } );
3195
+ aoData.push( { "name": "sSortDir_"+i, "value": oSettings.aaSortingFixed[i][1] } );
3196
+ }
3197
+
3198
+ for ( i=0 ; i<iUser ; i++ )
3199
+ {
3200
+ aoData.push( { "name": "iSortCol_"+(i+iFixed), "value": oSettings.aaSorting[i][0] } );
3201
+ aoData.push( { "name": "sSortDir_"+(i+iFixed), "value": oSettings.aaSorting[i][1] } );
3202
+ }
3203
+
3204
+ for ( i=0 ; i<iColumns ; i++ )
3205
+ {
3206
+ aoData.push( { "name": "bSortable_"+i, "value": oSettings.aoColumns[i].bSortable } );
3207
+ }
3208
+ }
3209
+
3210
+ oSettings.fnServerData.call( oSettings.oInstance, oSettings.sAjaxSource, aoData,
3211
+ function(json) {
3212
+ _fnAjaxUpdateDraw( oSettings, json );
3213
+ } );
3214
+ return false;
3215
+ }
3216
+ else
3217
+ {
3218
+ return true;
3219
+ }
3220
+ }
3221
+
3222
+ /*
3223
+ * Function: _fnAjaxUpdateDraw
3224
+ * Purpose: Data the data from the server (nuking the old) and redraw the table
3225
+ * Returns: -
3226
+ * Inputs: object:oSettings - dataTables settings object
3227
+ * object:json - json data return from the server.
3228
+ * The following must be defined:
3229
+ * iTotalRecords, iTotalDisplayRecords, aaData
3230
+ * The following may be defined:
3231
+ * sColumns
3232
+ */
3233
+ function _fnAjaxUpdateDraw ( oSettings, json )
3234
+ {
3235
+ if ( typeof json.sEcho != 'undefined' )
3236
+ {
3237
+ /* Protect against old returns over-writing a new one. Possible when you get
3238
+ * very fast interaction, and later queires are completed much faster
3239
+ */
3240
+ if ( json.sEcho*1 < oSettings.iDraw )
3241
+ {
3242
+ return;
3243
+ }
3244
+ else
3245
+ {
3246
+ oSettings.iDraw = json.sEcho * 1;
3247
+ }
3248
+ }
3249
+
3250
+ if ( !oSettings.oScroll.bInfinite ||
3251
+ (oSettings.oScroll.bInfinite && (oSettings.bSorted || oSettings.bFiltered)) )
3252
+ {
3253
+ _fnClearTable( oSettings );
3254
+ }
3255
+ oSettings._iRecordsTotal = json.iTotalRecords;
3256
+ oSettings._iRecordsDisplay = json.iTotalDisplayRecords;
3257
+
3258
+ /* Determine if reordering is required */
3259
+ var sOrdering = _fnColumnOrdering(oSettings);
3260
+ var bReOrder = (typeof json.sColumns != 'undefined' && sOrdering !== "" && json.sColumns != sOrdering );
3261
+ if ( bReOrder )
3262
+ {
3263
+ var aiIndex = _fnReOrderIndex( oSettings, json.sColumns );
3264
+ }
3265
+
3266
+ for ( var i=0, iLen=json.aaData.length ; i<iLen ; i++ )
3267
+ {
3268
+ if ( bReOrder )
3269
+ {
3270
+ /* If we need to re-order, then create a new array with the correct order and add it */
3271
+ var aData = [];
3272
+ for ( var j=0, jLen=oSettings.aoColumns.length ; j<jLen ; j++ )
3273
+ {
3274
+ aData.push( json.aaData[i][ aiIndex[j] ] );
3275
+ }
3276
+ _fnAddData( oSettings, aData );
3277
+ }
3278
+ else
3279
+ {
3280
+ /* No re-order required, sever got it "right" - just straight add */
3281
+ _fnAddData( oSettings, json.aaData[i] );
3282
+ }
3283
+ }
3284
+ oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
3285
+
3286
+ oSettings.bAjaxDataGet = false;
3287
+ _fnDraw( oSettings );
3288
+ oSettings.bAjaxDataGet = true;
3289
+ _fnProcessingDisplay( oSettings, false );
3290
+ }
3291
+
3292
+
3293
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
3294
+ * Section - Options (features) HTML
3295
+ */
3296
+
3297
+ /*
3298
+ * Function: _fnAddOptionsHtml
3299
+ * Purpose: Add the options to the page HTML for the table
3300
+ * Returns: -
3301
+ * Inputs: object:oSettings - dataTables settings object
3302
+ */
3303
+ function _fnAddOptionsHtml ( oSettings )
3304
+ {
3305
+ /*
3306
+ * Create a temporary, empty, div which we can later on replace with what we have generated
3307
+ * we do it this way to rendering the 'options' html offline - speed :-)
3308
+ */
3309
+ var nHolding = document.createElement( 'div' );
3310
+ oSettings.nTable.parentNode.insertBefore( nHolding, oSettings.nTable );
3311
+
3312
+ /*
3313
+ * All DataTables are wrapped in a div - this is not currently optional - backwards
3314
+ * compatability. It can be removed if you don't want it.
3315
+ */
3316
+ oSettings.nTableWrapper = document.createElement( 'div' );
3317
+ oSettings.nTableWrapper.className = oSettings.oClasses.sWrapper;
3318
+ if ( oSettings.sTableId !== '' )
3319
+ {
3320
+ oSettings.nTableWrapper.setAttribute( 'id', oSettings.sTableId+'_wrapper' );
3321
+ }
3322
+
3323
+ /* Track where we want to insert the option */
3324
+ var nInsertNode = oSettings.nTableWrapper;
3325
+
3326
+ /* Loop over the user set positioning and place the elements as needed */
3327
+ var aDom = oSettings.sDom.split('');
3328
+ var nTmp, iPushFeature, cOption, nNewNode, cNext, sAttr, j;
3329
+ for ( var i=0 ; i<aDom.length ; i++ )
3330
+ {
3331
+ iPushFeature = 0;
3332
+ cOption = aDom[i];
3333
+
3334
+ if ( cOption == '<' )
3335
+ {
3336
+ /* New container div */
3337
+ nNewNode = document.createElement( 'div' );
3338
+
3339
+ /* Check to see if we should append an id and/or a class name to the container */
3340
+ cNext = aDom[i+1];
3341
+ if ( cNext == "'" || cNext == '"' )
3342
+ {
3343
+ sAttr = "";
3344
+ j = 2;
3345
+ while ( aDom[i+j] != cNext )
3346
+ {
3347
+ sAttr += aDom[i+j];
3348
+ j++;
3349
+ }
3350
+
3351
+ /* Replace jQuery UI constants */
3352
+ if ( sAttr == "H" )
3353
+ {
3354
+ sAttr = "fg-toolbar ui-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix";
3355
+ }
3356
+ else if ( sAttr == "F" )
3357
+ {
3358
+ sAttr = "fg-toolbar ui-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix";
3359
+ }
3360
+
3361
+ /* The attribute can be in the format of "#id.class", "#id" or "class" This logic
3362
+ * breaks the string into parts and applies them as needed
3363
+ */
3364
+ if ( sAttr.indexOf('.') != -1 )
3365
+ {
3366
+ var aSplit = sAttr.split('.');
3367
+ nNewNode.setAttribute('id', aSplit[0].substr(1, aSplit[0].length-1) );
3368
+ nNewNode.className = aSplit[1];
3369
+ }
3370
+ else if ( sAttr.charAt(0) == "#" )
3371
+ {
3372
+ nNewNode.setAttribute('id', sAttr.substr(1, sAttr.length-1) );
3373
+ }
3374
+ else
3375
+ {
3376
+ nNewNode.className = sAttr;
3377
+ }
3378
+
3379
+ i += j; /* Move along the position array */
3380
+ }
3381
+
3382
+ nInsertNode.appendChild( nNewNode );
3383
+ nInsertNode = nNewNode;
3384
+ }
3385
+ else if ( cOption == '>' )
3386
+ {
3387
+ /* End container div */
3388
+ nInsertNode = nInsertNode.parentNode;
3389
+ }
3390
+ else if ( cOption == 'l' && oSettings.oFeatures.bPaginate && oSettings.oFeatures.bLengthChange )
3391
+ {
3392
+ /* Length */
3393
+ nTmp = _fnFeatureHtmlLength( oSettings );
3394
+ iPushFeature = 1;
3395
+ }
3396
+ else if ( cOption == 'f' && oSettings.oFeatures.bFilter )
3397
+ {
3398
+ /* Filter */
3399
+ nTmp = _fnFeatureHtmlFilter( oSettings );
3400
+ iPushFeature = 1;
3401
+ }
3402
+ else if ( cOption == 'r' && oSettings.oFeatures.bProcessing )
3403
+ {
3404
+ /* pRocessing */
3405
+ nTmp = _fnFeatureHtmlProcessing( oSettings );
3406
+ iPushFeature = 1;
3407
+ }
3408
+ else if ( cOption == 't' )
3409
+ {
3410
+ /* Table */
3411
+ nTmp = _fnFeatureHtmlTable( oSettings );
3412
+ iPushFeature = 1;
3413
+ }
3414
+ else if ( cOption == 'i' && oSettings.oFeatures.bInfo )
3415
+ {
3416
+ /* Info */
3417
+ nTmp = _fnFeatureHtmlInfo( oSettings );
3418
+ iPushFeature = 1;
3419
+ }
3420
+ else if ( cOption == 'p' && oSettings.oFeatures.bPaginate )
3421
+ {
3422
+ /* Pagination */
3423
+ nTmp = _fnFeatureHtmlPaginate( oSettings );
3424
+ iPushFeature = 1;
3425
+ }
3426
+ else if ( _oExt.aoFeatures.length !== 0 )
3427
+ {
3428
+ /* Plug-in features */
3429
+ var aoFeatures = _oExt.aoFeatures;
3430
+ for ( var k=0, kLen=aoFeatures.length ; k<kLen ; k++ )
3431
+ {
3432
+ if ( cOption == aoFeatures[k].cFeature )
3433
+ {
3434
+ nTmp = aoFeatures[k].fnInit( oSettings );
3435
+ if ( nTmp )
3436
+ {
3437
+ iPushFeature = 1;
3438
+ }
3439
+ break;
3440
+ }
3441
+ }
3442
+ }
3443
+
3444
+ /* Add to the 2D features array */
3445
+ if ( iPushFeature == 1 && nTmp !== null )
3446
+ {
3447
+ if ( typeof oSettings.aanFeatures[cOption] != 'object' )
3448
+ {
3449
+ oSettings.aanFeatures[cOption] = [];
3450
+ }
3451
+ oSettings.aanFeatures[cOption].push( nTmp );
3452
+ nInsertNode.appendChild( nTmp );
3453
+ }
3454
+ }
3455
+
3456
+ /* Built our DOM structure - replace the holding div with what we want */
3457
+ nHolding.parentNode.replaceChild( oSettings.nTableWrapper, nHolding );
3458
+ }
3459
+
3460
+
3461
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
3462
+ * Section - Feature: Filtering
3463
+ */
3464
+
3465
+ /*
3466
+ * Function: _fnFeatureHtmlTable
3467
+ * Purpose: Add any control elements for the table - specifically scrolling
3468
+ * Returns: node: - Node to add to the DOM
3469
+ * Inputs: object:oSettings - dataTables settings object
3470
+ */
3471
+ function _fnFeatureHtmlTable ( oSettings )
3472
+ {
3473
+ /* Chack if scrolling is enabled or not - if not then leave the DOM unaltered */
3474
+ if ( oSettings.oScroll.sX === "" && oSettings.oScroll.sY === "" )
3475
+ {
3476
+ return oSettings.nTable;
3477
+ }
3478
+
3479
+ /*
3480
+ * The HTML structure that we want to generate in this function is:
3481
+ * div - nScroller
3482
+ * div - nScrollHead
3483
+ * div - nScrollHeadInner
3484
+ * table - nScrollHeadTable
3485
+ * thead - nThead
3486
+ * div - nScrollBody
3487
+ * table - oSettings.nTable
3488
+ * thead - nTheadSize
3489
+ * tbody - nTbody
3490
+ * div - nScrollFoot
3491
+ * div - nScrollFootInner
3492
+ * table - nScrollFootTable
3493
+ * tfoot - nTfoot
3494
+ */
3495
+ var
3496
+ nScroller = document.createElement('div'),
3497
+ nScrollHead = document.createElement('div'),
3498
+ nScrollHeadInner = document.createElement('div'),
3499
+ nScrollBody = document.createElement('div'),
3500
+ nScrollFoot = document.createElement('div'),
3501
+ nScrollFootInner = document.createElement('div'),
3502
+ nScrollHeadTable = oSettings.nTable.cloneNode(false),
3503
+ nScrollFootTable = oSettings.nTable.cloneNode(false),
3504
+ nThead = oSettings.nTable.getElementsByTagName('thead')[0],
3505
+ nTfoot = oSettings.nTable.getElementsByTagName('tfoot').length === 0 ? null :
3506
+ oSettings.nTable.getElementsByTagName('tfoot')[0],
3507
+ oClasses = (typeof oInit.bJQueryUI != 'undefined' && oInit.bJQueryUI) ?
3508
+ _oExt.oJUIClasses : _oExt.oStdClasses;
3509
+
3510
+ nScrollHead.appendChild( nScrollHeadInner );
3511
+ nScrollFoot.appendChild( nScrollFootInner );
3512
+ nScrollBody.appendChild( oSettings.nTable );
3513
+ nScroller.appendChild( nScrollHead );
3514
+ nScroller.appendChild( nScrollBody );
3515
+ nScrollHeadInner.appendChild( nScrollHeadTable );
3516
+ nScrollHeadTable.appendChild( nThead );
3517
+ if ( nTfoot !== null )
3518
+ {
3519
+ nScroller.appendChild( nScrollFoot );
3520
+ nScrollFootInner.appendChild( nScrollFootTable );
3521
+ nScrollFootTable.appendChild( nTfoot );
3522
+ }
3523
+
3524
+ nScroller.className = oClasses.sScrollWrapper;
3525
+ nScrollHead.className = oClasses.sScrollHead;
3526
+ nScrollHeadInner.className = oClasses.sScrollHeadInner;
3527
+ nScrollBody.className = oClasses.sScrollBody;
3528
+ nScrollFoot.className = oClasses.sScrollFoot;
3529
+ nScrollFootInner.className = oClasses.sScrollFootInner;
3530
+
3531
+ nScrollHead.style.overflow = "hidden";
3532
+ nScrollHead.style.position = "relative";
3533
+ nScrollFoot.style.overflow = "hidden";
3534
+ nScrollBody.style.overflow = "auto";
3535
+ nScrollHead.style.border = "0";
3536
+ nScrollFoot.style.border = "0";
3537
+ nScrollHeadInner.style.width = "150%"; /* will be overwritten */
3538
+
3539
+ /* Modify attributes to respect the clones */
3540
+ nScrollHeadTable.removeAttribute('id');
3541
+ nScrollHeadTable.style.marginLeft = "0";
3542
+ oSettings.nTable.style.marginLeft = "0";
3543
+ if ( nTfoot !== null )
3544
+ {
3545
+ nScrollFootTable.removeAttribute('id');
3546
+ nScrollFootTable.style.marginLeft = "0";
3547
+ }
3548
+
3549
+ /* Move any caption elements from the body to the header */
3550
+ var nCaptions = $('>caption', oSettings.nTable);
3551
+ for ( var i=0, iLen=nCaptions.length ; i<iLen ; i++ )
3552
+ {
3553
+ nScrollHeadTable.appendChild( nCaptions[i] );
3554
+ }
3555
+
3556
+ /*
3557
+ * Sizing
3558
+ */
3559
+ /* When xscrolling add the width and a scroller to move the header with the body */
3560
+ if ( oSettings.oScroll.sX !== "" )
3561
+ {
3562
+ nScrollHead.style.width = _fnStringToCss( oSettings.oScroll.sX );
3563
+ nScrollBody.style.width = _fnStringToCss( oSettings.oScroll.sX );
3564
+
3565
+ if ( nTfoot !== null )
3566
+ {
3567
+ nScrollFoot.style.width = _fnStringToCss( oSettings.oScroll.sX );
3568
+ }
3569
+
3570
+ /* When the body is scrolled, then we also want to scroll the headers */
3571
+ $(nScrollBody).scroll( function (e) {
3572
+ nScrollHead.scrollLeft = this.scrollLeft;
3573
+
3574
+ if ( nTfoot !== null )
3575
+ {
3576
+ nScrollFoot.scrollLeft = this.scrollLeft;
3577
+ }
3578
+ } );
3579
+ }
3580
+
3581
+ /* When yscrolling, add the height */
3582
+ if ( oSettings.oScroll.sY !== "" )
3583
+ {
3584
+ nScrollBody.style.height = _fnStringToCss( oSettings.oScroll.sY );
3585
+ }
3586
+
3587
+ /* Redraw - align columns across the tables */
3588
+ oSettings.aoDrawCallback.push( {
3589
+ "fn": _fnScrollDraw,
3590
+ "sName": "scrolling"
3591
+ } );
3592
+
3593
+ /* Infinite scrolling event handlers */
3594
+ if ( oSettings.oScroll.bInfinite )
3595
+ {
3596
+ $(nScrollBody).scroll( function() {
3597
+ /* Use a blocker to stop scrolling from loading more data while other data is still loading */
3598
+ if ( !oSettings.bDrawing )
3599
+ {
3600
+ /* Check if we should load the next data set */
3601
+ if ( $(this).scrollTop() + $(this).height() >
3602
+ $(oSettings.nTable).height() - oSettings.oScroll.iLoadGap )
3603
+ {
3604
+ /* Only do the redraw if we have to - we might be at the end of the data */
3605
+ if ( oSettings.fnDisplayEnd() < oSettings.fnRecordsDisplay() )
3606
+ {
3607
+ _fnPageChange( oSettings, 'next' );
3608
+ _fnCalculateEnd( oSettings );
3609
+ _fnDraw( oSettings );
3610
+ }
3611
+ }
3612
+ }
3613
+ } );
3614
+ }
3615
+
3616
+ oSettings.nScrollHead = nScrollHead;
3617
+ oSettings.nScrollFoot = nScrollFoot;
3618
+
3619
+ return nScroller;
3620
+ }
3621
+
3622
+ /*
3623
+ * Function: _fnScrollDraw
3624
+ * Purpose: Update the various tables for resizing
3625
+ * Returns: node: - Node to add to the DOM
3626
+ * Inputs: object:o - dataTables settings object
3627
+ * Notes: It's a bit of a pig this function, but basically the idea to:
3628
+ * 1. Re-create the table inside the scrolling div
3629
+ * 2. Take live measurements from the DOM
3630
+ * 3. Apply the measurements
3631
+ * 4. Clean up
3632
+ */
3633
+ function _fnScrollDraw ( o )
3634
+ {
3635
+ var
3636
+ nScrollHeadInner = o.nScrollHead.getElementsByTagName('div')[0],
3637
+ nScrollHeadTable = nScrollHeadInner.getElementsByTagName('table')[0],
3638
+ nScrollBody = o.nTable.parentNode,
3639
+ i, iLen, j, jLen, anHeadToSize, anHeadSizers, anFootSizers, anFootToSize, oStyle, iVis,
3640
+ iWidth, aApplied=[], iSanityWidth;
3641
+
3642
+ /*
3643
+ * 1. Re-create the table inside the scrolling div
3644
+ */
3645
+
3646
+ /* Remove the old minimised thead and tfoot elements in the inner table */
3647
+ var nTheadSize = o.nTable.getElementsByTagName('thead');
3648
+ if ( nTheadSize.length > 0 )
3649
+ {
3650
+ o.nTable.removeChild( nTheadSize[0] );
3651
+ }
3652
+
3653
+ if ( o.nTFoot !== null )
3654
+ {
3655
+ /* Remove the old minimised footer element in the cloned header */
3656
+ var nTfootSize = o.nTable.getElementsByTagName('tfoot');
3657
+ if ( nTfootSize.length > 0 )
3658
+ {
3659
+ o.nTable.removeChild( nTfootSize[0] );
3660
+ }
3661
+ }
3662
+
3663
+ /* Clone the current header and footer elements and then place it into the inner table */
3664
+ nTheadSize = o.nTHead.cloneNode(true);
3665
+ o.nTable.insertBefore( nTheadSize, o.nTable.childNodes[0] );
3666
+
3667
+ if ( o.nTFoot !== null )
3668
+ {
3669
+ nTfootSize = o.nTFoot.cloneNode(true);
3670
+ o.nTable.insertBefore( nTfootSize, o.nTable.childNodes[1] );
3671
+ }
3672
+
3673
+ /*
3674
+ * 2. Take live measurements from the DOM - do not alter the DOM itself!
3675
+ */
3676
+
3677
+ /* Remove old sizing and apply the calculated column widths
3678
+ * Get the unique column headers in the newly created (cloned) header. We want to apply the
3679
+ * calclated sizes to this header
3680
+ */
3681
+ var nThs = _fnGetUniqueThs( nTheadSize );
3682
+ for ( i=0, iLen=nThs.length ; i<iLen ; i++ )
3683
+ {
3684
+ iVis = _fnVisibleToColumnIndex( o, i );
3685
+ nThs[i].style.width = o.aoColumns[iVis].sWidth;
3686
+ }
3687
+
3688
+ if ( o.nTFoot !== null )
3689
+ {
3690
+ _fnApplyToChildren( function(n) {
3691
+ n.style.width = "";
3692
+ }, nTfootSize.getElementsByTagName('tr') );
3693
+ }
3694
+
3695
+ /* Size the table as a whole */
3696
+ iSanityWidth = $(o.nTable).outerWidth();
3697
+ if ( o.oScroll.sX === "" )
3698
+ {
3699
+ /* No x scrolling */
3700
+ o.nTable.style.width = "100%";
3701
+
3702
+ /* I know this is rubbish - but IE7 will make the width of the table when 100% include
3703
+ * the scrollbar - which is shouldn't. This needs feature detection in future - to do
3704
+ */
3705
+ if ( $.browser.msie && $.browser.version <= 7 )
3706
+ {
3707
+ o.nTable.style.width = _fnStringToCss( $(o.nTable).outerWidth()-o.oScroll.iBarWidth );
3708
+ }
3709
+ }
3710
+ else
3711
+ {
3712
+ if ( o.oScroll.sXInner !== "" )
3713
+ {
3714
+ /* x scroll inner has been given - use it */
3715
+ o.nTable.style.width = _fnStringToCss(o.oScroll.sXInner);
3716
+ }
3717
+ else if ( iSanityWidth == $(nScrollBody).width() &&
3718
+ $(nScrollBody).height() < $(o.nTable).height() )
3719
+ {
3720
+ /* There is y-scrolling - try to take account of the y scroll bar */
3721
+ o.nTable.style.width = _fnStringToCss( iSanityWidth-o.oScroll.iBarWidth );
3722
+ if ( $(o.nTable).outerWidth() > iSanityWidth-o.oScroll.iBarWidth )
3723
+ {
3724
+ /* Not possible to take account of it */
3725
+ o.nTable.style.width = _fnStringToCss( iSanityWidth );
3726
+ }
3727
+ }
3728
+ else
3729
+ {
3730
+ /* All else fails */
3731
+ o.nTable.style.width = _fnStringToCss( iSanityWidth );
3732
+ }
3733
+ }
3734
+
3735
+ /* Recalculate the sanity width - now that we've applied the required width, before it was
3736
+ * a temporary variable. This is required because the column width calculation is done
3737
+ * before this table DOM is created.
3738
+ */
3739
+ iSanityWidth = $(o.nTable).outerWidth();
3740
+
3741
+ /* We want the hidden header to have zero height, so remove padding and borders. Then
3742
+ * set the width based on the real headers
3743
+ */
3744
+ anHeadToSize = o.nTHead.getElementsByTagName('tr');
3745
+ anHeadSizers = nTheadSize.getElementsByTagName('tr');
3746
+
3747
+ _fnApplyToChildren( function(nSizer, nToSize) {
3748
+ oStyle = nSizer.style;
3749
+ oStyle.paddingTop = "0";
3750
+ oStyle.paddingBottom = "0";
3751
+ oStyle.borderTopWidth = "0";
3752
+ oStyle.borderBottomWidth = "0";
3753
+ oStyle.height = 0;
3754
+
3755
+ iWidth = $(nSizer).width();
3756
+ nToSize.style.width = _fnStringToCss( iWidth );
3757
+ aApplied.push( iWidth );
3758
+ }, anHeadSizers, anHeadToSize );
3759
+ $(anHeadSizers).height(0);
3760
+
3761
+ if ( o.nTFoot !== null )
3762
+ {
3763
+ /* Clone the current footer and then place it into the body table as a "hidden header" */
3764
+ anFootSizers = nTfootSize.getElementsByTagName('tr');
3765
+ anFootToSize = o.nTFoot.getElementsByTagName('tr');
3766
+
3767
+ _fnApplyToChildren( function(nSizer, nToSize) {
3768
+ oStyle = nSizer.style;
3769
+ oStyle.paddingTop = "0";
3770
+ oStyle.paddingBottom = "0";
3771
+ oStyle.borderTopWidth = "0";
3772
+ oStyle.borderBottomWidth = "0";
3773
+
3774
+ iWidth = $(nSizer).width();
3775
+ nToSize.style.width = _fnStringToCss( iWidth );
3776
+ aApplied.push( iWidth );
3777
+ }, anFootSizers, anFootToSize );
3778
+ $(anFootSizers).height(0);
3779
+ }
3780
+
3781
+ /*
3782
+ * 3. Apply the measurements
3783
+ */
3784
+
3785
+ /* "Hide" the header and footer that we used for the sizing. We want to also fix their width
3786
+ * to what they currently are
3787
+ */
3788
+ _fnApplyToChildren( function(nSizer) {
3789
+ nSizer.innerHTML = "";
3790
+ nSizer.style.width = _fnStringToCss( aApplied.shift() );
3791
+ }, anHeadSizers );
3792
+
3793
+ if ( o.nTFoot !== null )
3794
+ {
3795
+ _fnApplyToChildren( function(nSizer) {
3796
+ nSizer.innerHTML = "";
3797
+ nSizer.style.width = _fnStringToCss( aApplied.shift() );
3798
+ }, anFootSizers );
3799
+ }
3800
+
3801
+ /* Sanity check that the table is of a sensible width. If not then we are going to get
3802
+ * misalignment
3803
+ */
3804
+ if ( $(o.nTable).outerWidth() < iSanityWidth )
3805
+ {
3806
+ if ( o.oScroll.sX === "" )
3807
+ {
3808
+ _fnLog( o, 1, "The table cannot fit into the current element which will cause column"+
3809
+ " misalignment. It is suggested that you enable x-scrolling or increase the width"+
3810
+ " the table has in which to be drawn" );
3811
+ }
3812
+ else if ( o.oScroll.sXInner !== "" )
3813
+ {
3814
+ _fnLog( o, 1, "The table cannot fit into the current element which will cause column"+
3815
+ " misalignment. It is suggested that you increase the sScrollXInner property to"+
3816
+ " allow it to draw in a larger area, or simply remove that parameter to allow"+
3817
+ " automatic calculation" );
3818
+ }
3819
+ }
3820
+
3821
+
3822
+ /*
3823
+ * 4. Clean up
3824
+ */
3825
+
3826
+ if ( o.oScroll.sY === "" )
3827
+ {
3828
+ /* IE7< puts a vertical scrollbar in place (when it shouldn't be) due to subtracting
3829
+ * the scrollbar height from the visible display, rather than adding it on. We need to
3830
+ * set the height in order to sort this. Don't want to do it in any other browsers.
3831
+ */
3832
+ if ( $.browser.msie && $.browser.version <= 7 )
3833
+ {
3834
+ nScrollBody.style.height = _fnStringToCss( o.nTable.offsetHeight+o.oScroll.iBarWidth );
3835
+ }
3836
+ }
3837
+
3838
+ if ( o.oScroll.sY !== "" && o.oScroll.bCollapse )
3839
+ {
3840
+ nScrollBody.style.height = _fnStringToCss( o.oScroll.sY );
3841
+
3842
+ var iExtra = (o.oScroll.sX !== "" && o.nTable.offsetWidth > nScrollBody.offsetWidth) ?
3843
+ o.oScroll.iBarWidth : 0;
3844
+ if ( o.nTable.offsetHeight < nScrollBody.offsetHeight )
3845
+ {
3846
+ nScrollBody.style.height = _fnStringToCss( $(o.nTable).height()+iExtra );
3847
+ }
3848
+ }
3849
+
3850
+ /* Finally set the width's of the header and footer tables */
3851
+ var iOuterWidth = $(o.nTable).outerWidth();
3852
+ nScrollHeadTable.style.width = _fnStringToCss( iOuterWidth );
3853
+ nScrollHeadInner.style.width = _fnStringToCss( iOuterWidth+o.oScroll.iBarWidth );
3854
+ nScrollHeadInner.parentNode.style.width = _fnStringToCss( $(nScrollBody).width() );
3855
+
3856
+ if ( o.nTFoot !== null )
3857
+ {
3858
+ var
3859
+ nScrollFootInner = o.nScrollFoot.getElementsByTagName('div')[0],
3860
+ nScrollFootTable = nScrollFootInner.getElementsByTagName('table')[0];
3861
+
3862
+ nScrollFootInner.style.width = _fnStringToCss( o.nTable.offsetWidth+o.oScroll.iBarWidth );
3863
+ nScrollFootTable.style.width = _fnStringToCss( o.nTable.offsetWidth );
3864
+ }
3865
+
3866
+ /* If sorting or filtering has occured, jump the scrolling back to the top */
3867
+ if ( o.bSorted || o.bFiltered )
3868
+ {
3869
+ nScrollBody.scrollTop = 0;
3870
+ }
3871
+ }
3872
+
3873
+ /*
3874
+ * Function: _fnAjustColumnSizing
3875
+ * Purpose: Ajust the table column widths for new data
3876
+ * Returns: -
3877
+ * Inputs: object:oSettings - dataTables settings object
3878
+ * Notes: You would probably want to do a redraw after calling this function!
3879
+ */
3880
+ function _fnAjustColumnSizing ( oSettings )
3881
+ {
3882
+ /* Not interested in doing column width calculation if autowidth is disabled */
3883
+ if ( oSettings.oFeatures.bAutoWidth === false )
3884
+ {
3885
+ return false;
3886
+ }
3887
+
3888
+ _fnCalculateColumnWidths( oSettings );
3889
+ for ( var i=0 , iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
3890
+ {
3891
+ oSettings.aoColumns[i].nTh.style.width = oSettings.aoColumns[i].sWidth;
3892
+ }
3893
+ }
3894
+
3895
+
3896
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
3897
+ * Section - Feature: Filtering
3898
+ */
3899
+
3900
+ /*
3901
+ * Function: _fnFeatureHtmlFilter
3902
+ * Purpose: Generate the node required for filtering text
3903
+ * Returns: node
3904
+ * Inputs: object:oSettings - dataTables settings object
3905
+ */
3906
+ function _fnFeatureHtmlFilter ( oSettings )
3907
+ {
3908
+ var nFilter = document.createElement( 'div' );
3909
+ if ( oSettings.sTableId !== '' && typeof oSettings.aanFeatures.f == "undefined" )
3910
+ {
3911
+ nFilter.setAttribute( 'id', oSettings.sTableId+'_filter' );
3912
+ }
3913
+ nFilter.className = oSettings.oClasses.sFilter;
3914
+ var sSpace = oSettings.oLanguage.sSearch==="" ? "" : " ";
3915
+ nFilter.innerHTML = oSettings.oLanguage.sSearch+sSpace+'<input type="text" />';
3916
+
3917
+ var jqFilter = $("input", nFilter);
3918
+ jqFilter.val( oSettings.oPreviousSearch.sSearch.replace('"','&quot;') );
3919
+ jqFilter.keyup( function(e) {
3920
+ /* Update all other filter input elements for the new display */
3921
+ var n = oSettings.aanFeatures.f;
3922
+ for ( var i=0, iLen=n.length ; i<iLen ; i++ )
3923
+ {
3924
+ if ( n[i] != this.parentNode )
3925
+ {
3926
+ $('input', n[i]).val( this.value );
3927
+ }
3928
+ }
3929
+
3930
+ /* Now do the filter */
3931
+ if ( this.value != oSettings.oPreviousSearch.sSearch )
3932
+ {
3933
+ _fnFilterComplete( oSettings, {
3934
+ "sSearch": this.value,
3935
+ "bRegex": oSettings.oPreviousSearch.bRegex,
3936
+ "bSmart": oSettings.oPreviousSearch.bSmart
3937
+ } );
3938
+ }
3939
+ } );
3940
+
3941
+ jqFilter.keypress( function(e) {
3942
+ /* Prevent default */
3943
+ if ( e.keyCode == 13 )
3944
+ {
3945
+ return false;
3946
+ }
3947
+ } );
3948
+
3949
+ return nFilter;
3950
+ }
3951
+
3952
+ /*
3953
+ * Function: _fnFilterComplete
3954
+ * Purpose: Filter the table using both the global filter and column based filtering
3955
+ * Returns: -
3956
+ * Inputs: object:oSettings - dataTables settings object
3957
+ * object:oSearch: search information
3958
+ * int:iForce - optional - force a research of the master array (1) or not (undefined or 0)
3959
+ */
3960
+ function _fnFilterComplete ( oSettings, oInput, iForce )
3961
+ {
3962
+ /* Filter on everything */
3963
+ _fnFilter( oSettings, oInput.sSearch, iForce, oInput.bRegex, oInput.bSmart );
3964
+
3965
+ /* Now do the individual column filter */
3966
+ for ( var i=0 ; i<oSettings.aoPreSearchCols.length ; i++ )
3967
+ {
3968
+ _fnFilterColumn( oSettings, oSettings.aoPreSearchCols[i].sSearch, i,
3969
+ oSettings.aoPreSearchCols[i].bRegex, oSettings.aoPreSearchCols[i].bSmart );
3970
+ }
3971
+
3972
+ /* Custom filtering */
3973
+ if ( _oExt.afnFiltering.length !== 0 )
3974
+ {
3975
+ _fnFilterCustom( oSettings );
3976
+ }
3977
+
3978
+ /* Tell the draw function we have been filtering */
3979
+ oSettings.bFiltered = true;
3980
+
3981
+ /* Redraw the table */
3982
+ oSettings._iDisplayStart = 0;
3983
+ _fnCalculateEnd( oSettings );
3984
+ _fnDraw( oSettings );
3985
+
3986
+ /* Rebuild search array 'offline' */
3987
+ _fnBuildSearchArray( oSettings, 0 );
3988
+ }
3989
+
3990
+ /*
3991
+ * Function: _fnFilterCustom
3992
+ * Purpose: Apply custom filtering functions
3993
+ * Returns: -
3994
+ * Inputs: object:oSettings - dataTables settings object
3995
+ */
3996
+ function _fnFilterCustom( oSettings )
3997
+ {
3998
+ var afnFilters = _oExt.afnFiltering;
3999
+ for ( var i=0, iLen=afnFilters.length ; i<iLen ; i++ )
4000
+ {
4001
+ var iCorrector = 0;
4002
+ for ( var j=0, jLen=oSettings.aiDisplay.length ; j<jLen ; j++ )
4003
+ {
4004
+ var iDisIndex = oSettings.aiDisplay[j-iCorrector];
4005
+
4006
+ /* Check if we should use this row based on the filtering function */
4007
+ if ( !afnFilters[i]( oSettings, oSettings.aoData[iDisIndex]._aData, iDisIndex ) )
4008
+ {
4009
+ oSettings.aiDisplay.splice( j-iCorrector, 1 );
4010
+ iCorrector++;
4011
+ }
4012
+ }
4013
+ }
4014
+ }
4015
+
4016
+ /*
4017
+ * Function: _fnFilterColumn
4018
+ * Purpose: Filter the table on a per-column basis
4019
+ * Returns: -
4020
+ * Inputs: object:oSettings - dataTables settings object
4021
+ * string:sInput - string to filter on
4022
+ * int:iColumn - column to filter
4023
+ * bool:bRegex - treat search string as a regular expression or not
4024
+ * bool:bSmart - use smart filtering or not
4025
+ */
4026
+ function _fnFilterColumn ( oSettings, sInput, iColumn, bRegex, bSmart )
4027
+ {
4028
+ if ( sInput === "" )
4029
+ {
4030
+ return;
4031
+ }
4032
+
4033
+ var iIndexCorrector = 0;
4034
+ var rpSearch = _fnFilterCreateSearch( sInput, bRegex, bSmart );
4035
+
4036
+ for ( var i=oSettings.aiDisplay.length-1 ; i>=0 ; i-- )
4037
+ {
4038
+ var sData = _fnDataToSearch( oSettings.aoData[ oSettings.aiDisplay[i] ]._aData[iColumn],
4039
+ oSettings.aoColumns[iColumn].sType );
4040
+ if ( ! rpSearch.test( sData ) )
4041
+ {
4042
+ oSettings.aiDisplay.splice( i, 1 );
4043
+ iIndexCorrector++;
4044
+ }
4045
+ }
4046
+ }
4047
+
4048
+ /*
4049
+ * Function: _fnFilter
4050
+ * Purpose: Filter the data table based on user input and draw the table
4051
+ * Returns: -
4052
+ * Inputs: object:oSettings - dataTables settings object
4053
+ * string:sInput - string to filter on
4054
+ * int:iForce - optional - force a research of the master array (1) or not (undefined or 0)
4055
+ * bool:bRegex - treat as a regular expression or not
4056
+ * bool:bSmart - perform smart filtering or not
4057
+ */
4058
+ function _fnFilter( oSettings, sInput, iForce, bRegex, bSmart )
4059
+ {
4060
+ var i;
4061
+ var rpSearch = _fnFilterCreateSearch( sInput, bRegex, bSmart );
4062
+
4063
+ /* Check if we are forcing or not - optional parameter */
4064
+ if ( typeof iForce == 'undefined' || iForce === null )
4065
+ {
4066
+ iForce = 0;
4067
+ }
4068
+
4069
+ /* Need to take account of custom filtering functions - always filter */
4070
+ if ( _oExt.afnFiltering.length !== 0 )
4071
+ {
4072
+ iForce = 1;
4073
+ }
4074
+
4075
+ /*
4076
+ * If the input is blank - we want the full data set
4077
+ */
4078
+ if ( sInput.length <= 0 )
4079
+ {
4080
+ oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length);
4081
+ oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
4082
+ }
4083
+ else
4084
+ {
4085
+ /*
4086
+ * We are starting a new search or the new search string is smaller
4087
+ * then the old one (i.e. delete). Search from the master array
4088
+ */
4089
+ if ( oSettings.aiDisplay.length == oSettings.aiDisplayMaster.length ||
4090
+ oSettings.oPreviousSearch.sSearch.length > sInput.length || iForce == 1 ||
4091
+ sInput.indexOf(oSettings.oPreviousSearch.sSearch) !== 0 )
4092
+ {
4093
+ /* Nuke the old display array - we are going to rebuild it */
4094
+ oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length);
4095
+
4096
+ /* Force a rebuild of the search array */
4097
+ _fnBuildSearchArray( oSettings, 1 );
4098
+
4099
+ /* Search through all records to populate the search array
4100
+ * The the oSettings.aiDisplayMaster and asDataSearch arrays have 1 to 1
4101
+ * mapping
4102
+ */
4103
+ for ( i=0 ; i<oSettings.aiDisplayMaster.length ; i++ )
4104
+ {
4105
+ if ( rpSearch.test(oSettings.asDataSearch[i]) )
4106
+ {
4107
+ oSettings.aiDisplay.push( oSettings.aiDisplayMaster[i] );
4108
+ }
4109
+ }
4110
+ }
4111
+ else
4112
+ {
4113
+ /* Using old search array - refine it - do it this way for speed
4114
+ * Don't have to search the whole master array again
4115
+ */
4116
+ var iIndexCorrector = 0;
4117
+
4118
+ /* Search the current results */
4119
+ for ( i=0 ; i<oSettings.asDataSearch.length ; i++ )
4120
+ {
4121
+ if ( ! rpSearch.test(oSettings.asDataSearch[i]) )
4122
+ {
4123
+ oSettings.aiDisplay.splice( i-iIndexCorrector, 1 );
4124
+ iIndexCorrector++;
4125
+ }
4126
+ }
4127
+ }
4128
+ }
4129
+ oSettings.oPreviousSearch.sSearch = sInput;
4130
+ oSettings.oPreviousSearch.bRegex = bRegex;
4131
+ oSettings.oPreviousSearch.bSmart = bSmart;
4132
+ }
4133
+
4134
+ /*
4135
+ * Function: _fnBuildSearchArray
4136
+ * Purpose: Create an array which can be quickly search through
4137
+ * Returns: -
4138
+ * Inputs: object:oSettings - dataTables settings object
4139
+ * int:iMaster - use the master data array - optional
4140
+ */
4141
+ function _fnBuildSearchArray ( oSettings, iMaster )
4142
+ {
4143
+ /* Clear out the old data */
4144
+ oSettings.asDataSearch.splice( 0, oSettings.asDataSearch.length );
4145
+
4146
+ var aArray = (typeof iMaster != 'undefined' && iMaster == 1) ?
4147
+ oSettings.aiDisplayMaster : oSettings.aiDisplay;
4148
+
4149
+ for ( var i=0, iLen=aArray.length ; i<iLen ; i++ )
4150
+ {
4151
+ oSettings.asDataSearch[i] = _fnBuildSearchRow( oSettings,
4152
+ oSettings.aoData[ aArray[i] ]._aData );
4153
+ }
4154
+ }
4155
+
4156
+ /*
4157
+ * Function: _fnBuildSearchRow
4158
+ * Purpose: Create a searchable string from a single data row
4159
+ * Returns: -
4160
+ * Inputs: object:oSettings - dataTables settings object
4161
+ * array:aData - aoData[]._aData array to use for the data to search
4162
+ */
4163
+ function _fnBuildSearchRow( oSettings, aData )
4164
+ {
4165
+ var sSearch = '';
4166
+ var nTmp = document.createElement('div');
4167
+
4168
+ for ( var j=0, jLen=oSettings.aoColumns.length ; j<jLen ; j++ )
4169
+ {
4170
+ if ( oSettings.aoColumns[j].bSearchable )
4171
+ {
4172
+ var sData = aData[j];
4173
+ sSearch += _fnDataToSearch( sData, oSettings.aoColumns[j].sType )+' ';
4174
+ }
4175
+ }
4176
+
4177
+ /* If it looks like there is an HTML entity in the string, attempt to decode it */
4178
+ if ( sSearch.indexOf('&') !== -1 )
4179
+ {
4180
+ nTmp.innerHTML = sSearch;
4181
+ sSearch = nTmp.textContent ? nTmp.textContent : nTmp.innerText;
4182
+
4183
+ /* IE and Opera appear to put an newline where there is a <br> tag - remove it */
4184
+ sSearch = sSearch.replace(/\n/g," ").replace(/\r/g,"");
4185
+ }
4186
+
4187
+ return sSearch;
4188
+ }
4189
+
4190
+ /*
4191
+ * Function: _fnFilterCreateSearch
4192
+ * Purpose: Build a regular expression object suitable for searching a table
4193
+ * Returns: RegExp: - constructed object
4194
+ * Inputs: string:sSearch - string to search for
4195
+ * bool:bRegex - treat as a regular expression or not
4196
+ * bool:bSmart - perform smart filtering or not
4197
+ */
4198
+ function _fnFilterCreateSearch( sSearch, bRegex, bSmart )
4199
+ {
4200
+ var asSearch, sRegExpString;
4201
+
4202
+ if ( bSmart )
4203
+ {
4204
+ /* Generate the regular expression to use. Something along the lines of:
4205
+ * ^(?=.*?\bone\b)(?=.*?\btwo\b)(?=.*?\bthree\b).*$
4206
+ */
4207
+ asSearch = bRegex ? sSearch.split( ' ' ) : _fnEscapeRegex( sSearch ).split( ' ' );
4208
+ sRegExpString = '^(?=.*?'+asSearch.join( ')(?=.*?' )+').*$';
4209
+ return new RegExp( sRegExpString, "i" );
4210
+ }
4211
+ else
4212
+ {
4213
+ sSearch = bRegex ? sSearch : _fnEscapeRegex( sSearch );
4214
+ return new RegExp( sSearch, "i" );
4215
+ }
4216
+ }
4217
+
4218
+ /*
4219
+ * Function: _fnDataToSearch
4220
+ * Purpose: Convert raw data into something that the user can search on
4221
+ * Returns: string: - search string
4222
+ * Inputs: string:sData - data to be modified
4223
+ * string:sType - data type
4224
+ */
4225
+ function _fnDataToSearch ( sData, sType )
4226
+ {
4227
+ if ( typeof _oExt.ofnSearch[sType] == "function" )
4228
+ {
4229
+ return _oExt.ofnSearch[sType]( sData );
4230
+ }
4231
+ else if ( sType == "html" )
4232
+ {
4233
+ return sData.replace(/\n/g," ").replace( /<.*?>/g, "" );
4234
+ }
4235
+ else if ( typeof sData == "string" )
4236
+ {
4237
+ return sData.replace(/\n/g," ");
4238
+ }
4239
+ return sData;
4240
+ }
4241
+
4242
+
4243
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
4244
+ * Section - Feature: Sorting
4245
+ */
4246
+
4247
+ /*
4248
+ * Function: _fnSort
4249
+ * Purpose: Change the order of the table
4250
+ * Returns: -
4251
+ * Inputs: object:oSettings - dataTables settings object
4252
+ * bool:bApplyClasses - optional - should we apply classes or not
4253
+ * Notes: We always sort the master array and then apply a filter again
4254
+ * if it is needed. This probably isn't optimal - but atm I can't think
4255
+ * of any other way which is (each has disadvantages). we want to sort aiDisplayMaster -
4256
+ * but according to aoData[]._aData
4257
+ */
4258
+ function _fnSort ( oSettings, bApplyClasses )
4259
+ {
4260
+ var aaSort = [];
4261
+ var oSort = _oExt.oSort;
4262
+ var aoData = oSettings.aoData;
4263
+ var iDataSort;
4264
+ var iDataType;
4265
+ var i, j, jLen;
4266
+
4267
+ /* No sorting required if server-side or no sorting array */
4268
+ if ( !oSettings.oFeatures.bServerSide &&
4269
+ (oSettings.aaSorting.length !== 0 || oSettings.aaSortingFixed !== null) )
4270
+ {
4271
+ if ( oSettings.aaSortingFixed !== null )
4272
+ {
4273
+ aaSort = oSettings.aaSortingFixed.concat( oSettings.aaSorting );
4274
+ }
4275
+ else
4276
+ {
4277
+ aaSort = oSettings.aaSorting.slice();
4278
+ }
4279
+
4280
+ /* If there is a sorting data type, and a fuction belonging to it, then we need to
4281
+ * get the data from the developer's function and apply it for this column
4282
+ */
4283
+ for ( i=0 ; i<aaSort.length ; i++ )
4284
+ {
4285
+ var iColumn = aaSort[i][0];
4286
+ var iVisColumn = _fnColumnIndexToVisible( oSettings, iColumn );
4287
+ var sDataType = oSettings.aoColumns[ iColumn ].sSortDataType;
4288
+ if ( typeof _oExt.afnSortData[sDataType] != 'undefined' )
4289
+ {
4290
+ var aData = _oExt.afnSortData[sDataType]( oSettings, iColumn, iVisColumn );
4291
+ for ( j=0, jLen=aoData.length ; j<jLen ; j++ )
4292
+ {
4293
+ aoData[j]._aData[iColumn] = aData[j];
4294
+ }
4295
+ }
4296
+ }
4297
+
4298
+ /* DataTables offers two different methods for doing the 2D array sorting over multiple
4299
+ * columns. The first is to construct a function dynamically, and then evaluate and run
4300
+ * the function, while the second has no need for evalulation, but is a little bit slower.
4301
+ * This is used for environments which do not allow eval() for code execuation such as AIR
4302
+ */
4303
+ if ( !window.runtime )
4304
+ {
4305
+ /* Dynamically created sorting function. Based on the information that we have, we can
4306
+ * create a sorting function as if it were specifically written for this sort. Here we
4307
+ * want to build a function something like (for two column sorting):
4308
+ * fnLocalSorting = function(a,b){
4309
+ * var iTest;
4310
+ * iTest = oSort['string-asc']('data11', 'data12');
4311
+ * if (iTest === 0)
4312
+ * iTest = oSort['numeric-desc']('data21', 'data22');
4313
+ * if (iTest === 0)
4314
+ * return oSort['numeric-desc'](1,2);
4315
+ * return iTest;
4316
+ * }
4317
+ * So basically we have a test for each column, and if that column matches, test the
4318
+ * next one. If all columns match, then we use a numeric sort on the position the two
4319
+ * row have in the original data array in order to provide a stable sort. In order to
4320
+ * get the position for the numeric stablisation, we need to take a clone of the current
4321
+ * display array and then get the position of the sorting value from that during the
4322
+ * sort.
4323
+ *
4324
+ * Note that for use with the Closure compiler, we need to be very careful how we deal
4325
+ * with this eval. Closure will rename all of our local variables, resutling in breakage
4326
+ * if the variables in the eval don't also reflect this. For this reason, we need to use
4327
+ * 'this' to store the variables we need in the eval, so we can control them. A little
4328
+ * nasty, but well worth it for using Closure.
4329
+ */
4330
+ this.ClosureDataTables = {
4331
+ "fn": function(){},
4332
+ "data": aoData,
4333
+ "sort": _oExt.oSort,
4334
+ "master": oSettings.aiDisplayMaster.slice()
4335
+ };
4336
+ var sDynamicSort = "this.ClosureDataTables.fn = function(a,b){"+
4337
+ "var iTest, oSort=this.ClosureDataTables.sort, "+
4338
+ "aoData=this.ClosureDataTables.data, "+
4339
+ "aiOrig=this.ClosureDataTables.master;";
4340
+
4341
+ for ( i=0 ; i<aaSort.length-1 ; i++ )
4342
+ {
4343
+ iDataSort = oSettings.aoColumns[ aaSort[i][0] ].iDataSort;
4344
+ iDataType = oSettings.aoColumns[ iDataSort ].sType;
4345
+ sDynamicSort += "iTest = oSort['"+iDataType+"-"+aaSort[i][1]+"']"+
4346
+ "( aoData[a]._aData["+iDataSort+"], aoData[b]._aData["+iDataSort+"] ); if ( iTest === 0 )";
4347
+ }
4348
+
4349
+ if ( aaSort.length > 0 )
4350
+ {
4351
+ iDataSort = oSettings.aoColumns[ aaSort[aaSort.length-1][0] ].iDataSort;
4352
+ iDataType = oSettings.aoColumns[ iDataSort ].sType;
4353
+ sDynamicSort += "iTest = oSort['"+iDataType+"-"+aaSort[aaSort.length-1][1]+"']"+
4354
+ "( aoData[a]._aData["+iDataSort+"], aoData[b]._aData["+iDataSort+"] );"+
4355
+ "if (iTest===0) "+
4356
+ "return oSort['numeric-asc'](jQuery.inArray(a,aiOrig), jQuery.inArray(b,aiOrig)); "+
4357
+ "return iTest;}";
4358
+
4359
+ /* The eval has to be done to a variable for IE */
4360
+ eval( sDynamicSort );
4361
+ oSettings.aiDisplayMaster.sort( this.ClosureDataTables.fn );
4362
+ }
4363
+ this.ClosureDataTables = undefined;
4364
+ }
4365
+ else
4366
+ {
4367
+ /*
4368
+ * Non-eval() sorting (AIR and other environments which doesn't allow code in eval()
4369
+ * Note that for reasonable sized data sets this method is around 1.5 times slower than
4370
+ * the eval above (hence why it is not used all the time). Oddly enough, it is ever so
4371
+ * slightly faster for very small sets (presumably the eval has overhead).
4372
+ * Single column (1083 records) - eval: 32mS AIR: 38mS
4373
+ * Two columns (1083 records) - eval: 55mS AIR: 66mS
4374
+ */
4375
+
4376
+ /* Build a cached array so the sort doesn't have to process this stuff on every call */
4377
+ var aAirSort = [];
4378
+ var iLen = aaSort.length;
4379
+ for ( i=0 ; i<iLen ; i++ )
4380
+ {
4381
+ iDataSort = oSettings.aoColumns[ aaSort[i][0] ].iDataSort;
4382
+ aAirSort.push( [
4383
+ iDataSort,
4384
+ oSettings.aoColumns[ iDataSort ].sType+'-'+aaSort[i][1]
4385
+ ] );
4386
+ }
4387
+
4388
+ oSettings.aiDisplayMaster.sort( function (a,b) {
4389
+ var iTest;
4390
+ for ( var i=0 ; i<iLen ; i++ )
4391
+ {
4392
+ iTest = oSort[ aAirSort[i][1] ]( aoData[a]._aData[aAirSort[i][0]], aoData[b]._aData[aAirSort[i][0]] );
4393
+ if ( iTest !== 0 )
4394
+ {
4395
+ return iTest;
4396
+ }
4397
+ }
4398
+ return 0;
4399
+ } );
4400
+ }
4401
+ }
4402
+
4403
+ /* Alter the sorting classes to take account of the changes */
4404
+ if ( typeof bApplyClasses == 'undefined' || bApplyClasses )
4405
+ {
4406
+ _fnSortingClasses( oSettings );
4407
+ }
4408
+
4409
+ /* Tell the draw function that we have sorted the data */
4410
+ oSettings.bSorted = true;
4411
+
4412
+ /* Copy the master data into the draw array and re-draw */
4413
+ if ( oSettings.oFeatures.bFilter )
4414
+ {
4415
+ /* _fnFilter() will redraw the table for us */
4416
+ _fnFilterComplete( oSettings, oSettings.oPreviousSearch, 1 );
4417
+ }
4418
+ else
4419
+ {
4420
+ oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
4421
+ oSettings._iDisplayStart = 0; /* reset display back to page 0 */
4422
+ _fnCalculateEnd( oSettings );
4423
+ _fnDraw( oSettings );
4424
+ }
4425
+ }
4426
+
4427
+ /*
4428
+ * Function: _fnSortAttachListener
4429
+ * Purpose: Attach a sort handler (click) to a node
4430
+ * Returns: -
4431
+ * Inputs: object:oSettings - dataTables settings object
4432
+ * node:nNode - node to attach the handler to
4433
+ * int:iDataIndex - column sorting index
4434
+ * function:fnCallback - callback function - optional
4435
+ */
4436
+ function _fnSortAttachListener ( oSettings, nNode, iDataIndex, fnCallback )
4437
+ {
4438
+ $(nNode).click( function (e) {
4439
+ /* If the column is not sortable - don't to anything */
4440
+ if ( oSettings.aoColumns[iDataIndex].bSortable === false )
4441
+ {
4442
+ return;
4443
+ }
4444
+
4445
+ /*
4446
+ * This is a little bit odd I admit... I declare a temporary function inside the scope of
4447
+ * _fnDrawHead and the click handler in order that the code presented here can be used
4448
+ * twice - once for when bProcessing is enabled, and another time for when it is
4449
+ * disabled, as we need to perform slightly different actions.
4450
+ * Basically the issue here is that the Javascript engine in modern browsers don't
4451
+ * appear to allow the rendering engine to update the display while it is still excuting
4452
+ * it's thread (well - it does but only after long intervals). This means that the
4453
+ * 'processing' display doesn't appear for a table sort. To break the js thread up a bit
4454
+ * I force an execution break by using setTimeout - but this breaks the expected
4455
+ * thread continuation for the end-developer's point of view (their code would execute
4456
+ * too early), so we on;y do it when we absolutely have to.
4457
+ */
4458
+ var fnInnerSorting = function () {
4459
+ var iColumn, iNextSort;
4460
+
4461
+ /* If the shift key is pressed then we are multipe column sorting */
4462
+ if ( e.shiftKey )
4463
+ {
4464
+ /* Are we already doing some kind of sort on this column? */
4465
+ var bFound = false;
4466
+ for ( var i=0 ; i<oSettings.aaSorting.length ; i++ )
4467
+ {
4468
+ if ( oSettings.aaSorting[i][0] == iDataIndex )
4469
+ {
4470
+ bFound = true;
4471
+ iColumn = oSettings.aaSorting[i][0];
4472
+ iNextSort = oSettings.aaSorting[i][2]+1;
4473
+
4474
+ if ( typeof oSettings.aoColumns[iColumn].asSorting[iNextSort] == 'undefined' )
4475
+ {
4476
+ /* Reached the end of the sorting options, remove from multi-col sort */
4477
+ oSettings.aaSorting.splice( i, 1 );
4478
+ }
4479
+ else
4480
+ {
4481
+ /* Move onto next sorting direction */
4482
+ oSettings.aaSorting[i][1] = oSettings.aoColumns[iColumn].asSorting[iNextSort];
4483
+ oSettings.aaSorting[i][2] = iNextSort;
4484
+ }
4485
+ break;
4486
+ }
4487
+ }
4488
+
4489
+ /* No sort yet - add it in */
4490
+ if ( bFound === false )
4491
+ {
4492
+ oSettings.aaSorting.push( [ iDataIndex,
4493
+ oSettings.aoColumns[iDataIndex].asSorting[0], 0 ] );
4494
+ }
4495
+ }
4496
+ else
4497
+ {
4498
+ /* If no shift key then single column sort */
4499
+ if ( oSettings.aaSorting.length == 1 && oSettings.aaSorting[0][0] == iDataIndex )
4500
+ {
4501
+ iColumn = oSettings.aaSorting[0][0];
4502
+ iNextSort = oSettings.aaSorting[0][2]+1;
4503
+ if ( typeof oSettings.aoColumns[iColumn].asSorting[iNextSort] == 'undefined' )
4504
+ {
4505
+ iNextSort = 0;
4506
+ }
4507
+ oSettings.aaSorting[0][1] = oSettings.aoColumns[iColumn].asSorting[iNextSort];
4508
+ oSettings.aaSorting[0][2] = iNextSort;
4509
+ }
4510
+ else
4511
+ {
4512
+ oSettings.aaSorting.splice( 0, oSettings.aaSorting.length );
4513
+ oSettings.aaSorting.push( [ iDataIndex,
4514
+ oSettings.aoColumns[iDataIndex].asSorting[0], 0 ] );
4515
+ }
4516
+ }
4517
+
4518
+ /* Run the sort */
4519
+ _fnSort( oSettings );
4520
+ }; /* /fnInnerSorting */
4521
+
4522
+ if ( !oSettings.oFeatures.bProcessing )
4523
+ {
4524
+ fnInnerSorting();
4525
+ }
4526
+ else
4527
+ {
4528
+ _fnProcessingDisplay( oSettings, true );
4529
+ setTimeout( function() {
4530
+ fnInnerSorting();
4531
+ if ( !oSettings.oFeatures.bServerSide )
4532
+ {
4533
+ _fnProcessingDisplay( oSettings, false );
4534
+ }
4535
+ }, 0 );
4536
+ }
4537
+
4538
+ /* Call the user specified callback function - used for async user interaction */
4539
+ if ( typeof fnCallback == 'function' )
4540
+ {
4541
+ fnCallback( oSettings );
4542
+ }
4543
+ } );
4544
+ }
4545
+
4546
+ /*
4547
+ * Function: _fnSortingClasses
4548
+ * Purpose: Set the sortting classes on the header
4549
+ * Returns: -
4550
+ * Inputs: object:oSettings - dataTables settings object
4551
+ * Notes: It is safe to call this function when bSort and bSortClasses are false
4552
+ */
4553
+ function _fnSortingClasses( oSettings )
4554
+ {
4555
+ var i, iLen, j, jLen, iFound;
4556
+ var aaSort, sClass;
4557
+ var iColumns = oSettings.aoColumns.length;
4558
+ var oClasses = oSettings.oClasses;
4559
+
4560
+ for ( i=0 ; i<iColumns ; i++ )
4561
+ {
4562
+ if ( oSettings.aoColumns[i].bSortable )
4563
+ {
4564
+ $(oSettings.aoColumns[i].nTh).removeClass( oClasses.sSortAsc +" "+ oClasses.sSortDesc +
4565
+ " "+ oSettings.aoColumns[i].sSortingClass );
4566
+ }
4567
+ }
4568
+
4569
+ if ( oSettings.aaSortingFixed !== null )
4570
+ {
4571
+ aaSort = oSettings.aaSortingFixed.concat( oSettings.aaSorting );
4572
+ }
4573
+ else
4574
+ {
4575
+ aaSort = oSettings.aaSorting.slice();
4576
+ }
4577
+
4578
+ /* Apply the required classes to the header */
4579
+ for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
4580
+ {
4581
+ if ( oSettings.aoColumns[i].bSortable )
4582
+ {
4583
+ sClass = oSettings.aoColumns[i].sSortingClass;
4584
+ iFound = -1;
4585
+ for ( j=0 ; j<aaSort.length ; j++ )
4586
+ {
4587
+ if ( aaSort[j][0] == i )
4588
+ {
4589
+ sClass = ( aaSort[j][1] == "asc" ) ?
4590
+ oClasses.sSortAsc : oClasses.sSortDesc;
4591
+ iFound = j;
4592
+ break;
4593
+ }
4594
+ }
4595
+ $(oSettings.aoColumns[i].nTh).addClass( sClass );
4596
+
4597
+ if ( oSettings.bJUI )
4598
+ {
4599
+ /* jQuery UI uses extra markup */
4600
+ var jqSpan = $("span", oSettings.aoColumns[i].nTh);
4601
+ jqSpan.removeClass(oClasses.sSortJUIAsc +" "+ oClasses.sSortJUIDesc +" "+
4602
+ oClasses.sSortJUI +" "+ oClasses.sSortJUIAscAllowed +" "+ oClasses.sSortJUIDescAllowed );
4603
+
4604
+ var sSpanClass;
4605
+ if ( iFound == -1 )
4606
+ {
4607
+ sSpanClass = oSettings.aoColumns[i].sSortingClassJUI;
4608
+ }
4609
+ else if ( aaSort[iFound][1] == "asc" )
4610
+ {
4611
+ sSpanClass = oClasses.sSortJUIAsc;
4612
+ }
4613
+ else
4614
+ {
4615
+ sSpanClass = oClasses.sSortJUIDesc;
4616
+ }
4617
+
4618
+ jqSpan.addClass( sSpanClass );
4619
+ }
4620
+ }
4621
+ else
4622
+ {
4623
+ /* No sorting on this column, so add the base class. This will have been assigned by
4624
+ * _fnAddColumn
4625
+ */
4626
+ $(oSettings.aoColumns[i].nTh).addClass( oSettings.aoColumns[i].sSortingClass );
4627
+ }
4628
+ }
4629
+
4630
+ /*
4631
+ * Apply the required classes to the table body
4632
+ * Note that this is given as a feature switch since it can significantly slow down a sort
4633
+ * on large data sets (adding and removing of classes is always slow at the best of times..)
4634
+ * Further to this, note that this code is admitadly fairly ugly. It could be made a lot
4635
+ * simpiler using jQuery selectors and add/removeClass, but that is significantly slower
4636
+ * (on the order of 5 times slower) - hence the direct DOM manipulation here.
4637
+ */
4638
+ sClass = oClasses.sSortColumn;
4639
+
4640
+ if ( oSettings.oFeatures.bSort && oSettings.oFeatures.bSortClasses )
4641
+ {
4642
+ var nTds = _fnGetTdNodes( oSettings );
4643
+
4644
+ /* Remove the old classes */
4645
+ if ( nTds.length >= iColumns )
4646
+ {
4647
+ for ( i=0 ; i<iColumns ; i++ )
4648
+ {
4649
+ if ( nTds[i].className.indexOf(sClass+"1") != -1 )
4650
+ {
4651
+ for ( j=0, jLen=(nTds.length/iColumns) ; j<jLen ; j++ )
4652
+ {
4653
+ nTds[(iColumns*j)+i].className =
4654
+ $.trim( nTds[(iColumns*j)+i].className.replace( sClass+"1", "" ) );
4655
+ }
4656
+ }
4657
+ else if ( nTds[i].className.indexOf(sClass+"2") != -1 )
4658
+ {
4659
+ for ( j=0, jLen=(nTds.length/iColumns) ; j<jLen ; j++ )
4660
+ {
4661
+ nTds[(iColumns*j)+i].className =
4662
+ $.trim( nTds[(iColumns*j)+i].className.replace( sClass+"2", "" ) );
4663
+ }
4664
+ }
4665
+ else if ( nTds[i].className.indexOf(sClass+"3") != -1 )
4666
+ {
4667
+ for ( j=0, jLen=(nTds.length/iColumns) ; j<jLen ; j++ )
4668
+ {
4669
+ nTds[(iColumns*j)+i].className =
4670
+ $.trim( nTds[(iColumns*j)+i].className.replace( " "+sClass+"3", "" ) );
4671
+ }
4672
+ }
4673
+ }
4674
+ }
4675
+
4676
+ /* Add the new classes to the table */
4677
+ var iClass = 1, iTargetCol;
4678
+ for ( i=0 ; i<aaSort.length ; i++ )
4679
+ {
4680
+ iTargetCol = parseInt( aaSort[i][0], 10 );
4681
+ for ( j=0, jLen=(nTds.length/iColumns) ; j<jLen ; j++ )
4682
+ {
4683
+ nTds[(iColumns*j)+iTargetCol].className += " "+sClass+iClass;
4684
+ }
4685
+
4686
+ if ( iClass < 3 )
4687
+ {
4688
+ iClass++;
4689
+ }
4690
+ }
4691
+ }
4692
+ }
4693
+
4694
+
4695
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
4696
+ * Section - Feature: Pagination. Note that most of the paging logic is done in
4697
+ * _oExt.oPagination
4698
+ */
4699
+
4700
+ /*
4701
+ * Function: _fnFeatureHtmlPaginate
4702
+ * Purpose: Generate the node required for default pagination
4703
+ * Returns: node
4704
+ * Inputs: object:oSettings - dataTables settings object
4705
+ */
4706
+ function _fnFeatureHtmlPaginate ( oSettings )
4707
+ {
4708
+ if ( oSettings.oScroll.bInfinite )
4709
+ {
4710
+ return null;
4711
+ }
4712
+
4713
+ var nPaginate = document.createElement( 'div' );
4714
+ nPaginate.className = oSettings.oClasses.sPaging+oSettings.sPaginationType;
4715
+
4716
+ _oExt.oPagination[ oSettings.sPaginationType ].fnInit( oSettings, nPaginate,
4717
+ function( oSettings ) {
4718
+ _fnCalculateEnd( oSettings );
4719
+ _fnDraw( oSettings );
4720
+ }
4721
+ );
4722
+
4723
+ /* Add a draw callback for the pagination on first instance, to update the paging display */
4724
+ if ( typeof oSettings.aanFeatures.p == "undefined" )
4725
+ {
4726
+ oSettings.aoDrawCallback.push( {
4727
+ "fn": function( oSettings ) {
4728
+ _oExt.oPagination[ oSettings.sPaginationType ].fnUpdate( oSettings, function( oSettings ) {
4729
+ _fnCalculateEnd( oSettings );
4730
+ _fnDraw( oSettings );
4731
+ } );
4732
+ },
4733
+ "sName": "pagination"
4734
+ } );
4735
+ }
4736
+ return nPaginate;
4737
+ }
4738
+
4739
+ /*
4740
+ * Function: _fnPageChange
4741
+ * Purpose: Alter the display settings to change the page
4742
+ * Returns: bool:true - page has changed, false - no change (no effect) eg 'first' on page 1
4743
+ * Inputs: object:oSettings - dataTables settings object
4744
+ * string:sAction - paging action to take: "first", "previous", "next" or "last"
4745
+ */
4746
+ function _fnPageChange ( oSettings, sAction )
4747
+ {
4748
+ var iOldStart = oSettings._iDisplayStart;
4749
+
4750
+ if ( sAction == "first" )
4751
+ {
4752
+ oSettings._iDisplayStart = 0;
4753
+ }
4754
+ else if ( sAction == "previous" )
4755
+ {
4756
+ oSettings._iDisplayStart = oSettings._iDisplayLength>=0 ?
4757
+ oSettings._iDisplayStart - oSettings._iDisplayLength :
4758
+ 0;
4759
+
4760
+ /* Correct for underrun */
4761
+ if ( oSettings._iDisplayStart < 0 )
4762
+ {
4763
+ oSettings._iDisplayStart = 0;
4764
+ }
4765
+ }
4766
+ else if ( sAction == "next" )
4767
+ {
4768
+ if ( oSettings._iDisplayLength >= 0 )
4769
+ {
4770
+ /* Make sure we are not over running the display array */
4771
+ if ( oSettings._iDisplayStart + oSettings._iDisplayLength < oSettings.fnRecordsDisplay() )
4772
+ {
4773
+ oSettings._iDisplayStart += oSettings._iDisplayLength;
4774
+ }
4775
+ }
4776
+ else
4777
+ {
4778
+ oSettings._iDisplayStart = 0;
4779
+ }
4780
+ }
4781
+ else if ( sAction == "last" )
4782
+ {
4783
+ if ( oSettings._iDisplayLength >= 0 )
4784
+ {
4785
+ var iPages = parseInt( (oSettings.fnRecordsDisplay()-1) / oSettings._iDisplayLength, 10 ) + 1;
4786
+ oSettings._iDisplayStart = (iPages-1) * oSettings._iDisplayLength;
4787
+ }
4788
+ else
4789
+ {
4790
+ oSettings._iDisplayStart = 0;
4791
+ }
4792
+ }
4793
+ else
4794
+ {
4795
+ _fnLog( oSettings, 0, "Unknown paging action: "+sAction );
4796
+ }
4797
+
4798
+ return iOldStart != oSettings._iDisplayStart;
4799
+ }
4800
+
4801
+
4802
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
4803
+ * Section - Feature: HTML info
4804
+ */
4805
+
4806
+ /*
4807
+ * Function: _fnFeatureHtmlInfo
4808
+ * Purpose: Generate the node required for the info display
4809
+ * Returns: node
4810
+ * Inputs: object:oSettings - dataTables settings object
4811
+ */
4812
+ function _fnFeatureHtmlInfo ( oSettings )
4813
+ {
4814
+ var nInfo = document.createElement( 'div' );
4815
+ nInfo.className = oSettings.oClasses.sInfo;
4816
+
4817
+ /* Actions that are to be taken once only for this feature */
4818
+ if ( typeof oSettings.aanFeatures.i == "undefined" )
4819
+ {
4820
+ /* Add draw callback */
4821
+ oSettings.aoDrawCallback.push( {
4822
+ "fn": _fnUpdateInfo,
4823
+ "sName": "information"
4824
+ } );
4825
+
4826
+ /* Add id */
4827
+ if ( oSettings.sTableId !== '' )
4828
+ {
4829
+ nInfo.setAttribute( 'id', oSettings.sTableId+'_info' );
4830
+ }
4831
+ }
4832
+
4833
+ return nInfo;
4834
+ }
4835
+
4836
+ /*
4837
+ * Function: _fnUpdateInfo
4838
+ * Purpose: Update the information elements in the display
4839
+ * Returns: -
4840
+ * Inputs: object:oSettings - dataTables settings object
4841
+ */
4842
+ function _fnUpdateInfo ( oSettings )
4843
+ {
4844
+ /* Show information about the table */
4845
+ if ( !oSettings.oFeatures.bInfo || oSettings.aanFeatures.i.length === 0 )
4846
+ {
4847
+ return;
4848
+ }
4849
+
4850
+ var
4851
+ iStart = oSettings._iDisplayStart+1, iEnd = oSettings.fnDisplayEnd(),
4852
+ iMax = oSettings.fnRecordsTotal(), iTotal = oSettings.fnRecordsDisplay(),
4853
+ sStart = oSettings.fnFormatNumber( iStart ), sEnd = oSettings.fnFormatNumber( iEnd ),
4854
+ sMax = oSettings.fnFormatNumber( iMax ), sTotal = oSettings.fnFormatNumber( iTotal ),
4855
+ sOut;
4856
+
4857
+ /* When infinite scrolling, we are always starting at 1. _iDisplayStart is used only
4858
+ * internally
4859
+ */
4860
+ if ( oSettings.oScroll.bInfinite )
4861
+ {
4862
+ sStart = oSettings.fnFormatNumber( 1 );
4863
+ }
4864
+
4865
+ if ( oSettings.fnRecordsDisplay() === 0 &&
4866
+ oSettings.fnRecordsDisplay() == oSettings.fnRecordsTotal() )
4867
+ {
4868
+ /* Empty record set */
4869
+ sOut = oSettings.oLanguage.sInfoEmpty+ oSettings.oLanguage.sInfoPostFix;
4870
+ }
4871
+ else if ( oSettings.fnRecordsDisplay() === 0 )
4872
+ {
4873
+ /* Rmpty record set after filtering */
4874
+ sOut = oSettings.oLanguage.sInfoEmpty +' '+
4875
+ oSettings.oLanguage.sInfoFiltered.replace('_MAX_', sMax)+
4876
+ oSettings.oLanguage.sInfoPostFix;
4877
+ }
4878
+ else if ( oSettings.fnRecordsDisplay() == oSettings.fnRecordsTotal() )
4879
+ {
4880
+ /* Normal record set */
4881
+ sOut = oSettings.oLanguage.sInfo.
4882
+ replace('_START_', sStart).
4883
+ replace('_END_', sEnd).
4884
+ replace('_TOTAL_', sTotal)+
4885
+ oSettings.oLanguage.sInfoPostFix;
4886
+ }
4887
+ else
4888
+ {
4889
+ /* Record set after filtering */
4890
+ sOut = oSettings.oLanguage.sInfo.
4891
+ replace('_START_', sStart).
4892
+ replace('_END_', sEnd).
4893
+ replace('_TOTAL_', sTotal) +' '+
4894
+ oSettings.oLanguage.sInfoFiltered.replace('_MAX_',
4895
+ oSettings.fnFormatNumber(oSettings.fnRecordsTotal()))+
4896
+ oSettings.oLanguage.sInfoPostFix;
4897
+ }
4898
+
4899
+ if ( oSettings.oLanguage.fnInfoCallback !== null )
4900
+ {
4901
+ sOut = oSettings.oLanguage.fnInfoCallback( oSettings, iStart, iEnd, iMax, iTotal, sOut );
4902
+ }
4903
+
4904
+ var n = oSettings.aanFeatures.i;
4905
+ for ( var i=0, iLen=n.length ; i<iLen ; i++ )
4906
+ {
4907
+ $(n[i]).html( sOut );
4908
+ }
4909
+ }
4910
+
4911
+
4912
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
4913
+ * Section - Feature: Length change
4914
+ */
4915
+
4916
+ /*
4917
+ * Function: _fnFeatureHtmlLength
4918
+ * Purpose: Generate the node required for user display length changing
4919
+ * Returns: node
4920
+ * Inputs: object:oSettings - dataTables settings object
4921
+ */
4922
+ function _fnFeatureHtmlLength ( oSettings )
4923
+ {
4924
+ if ( oSettings.oScroll.bInfinite )
4925
+ {
4926
+ return null;
4927
+ }
4928
+
4929
+ /* This can be overruled by not using the _MENU_ var/macro in the language variable */
4930
+ var sName = (oSettings.sTableId === "") ? "" : 'name="'+oSettings.sTableId+'_length"';
4931
+ var sStdMenu = '<select size="1" '+sName+'>';
4932
+ var i, iLen;
4933
+
4934
+ if ( oSettings.aLengthMenu.length == 2 && typeof oSettings.aLengthMenu[0] == 'object' &&
4935
+ typeof oSettings.aLengthMenu[1] == 'object' )
4936
+ {
4937
+ for ( i=0, iLen=oSettings.aLengthMenu[0].length ; i<iLen ; i++ )
4938
+ {
4939
+ sStdMenu += '<option value="'+oSettings.aLengthMenu[0][i]+'">'+
4940
+ oSettings.aLengthMenu[1][i]+'</option>';
4941
+ }
4942
+ }
4943
+ else
4944
+ {
4945
+ for ( i=0, iLen=oSettings.aLengthMenu.length ; i<iLen ; i++ )
4946
+ {
4947
+ sStdMenu += '<option value="'+oSettings.aLengthMenu[i]+'">'+
4948
+ oSettings.aLengthMenu[i]+'</option>';
4949
+ }
4950
+ }
4951
+ sStdMenu += '</select>';
4952
+
4953
+ var nLength = document.createElement( 'div' );
4954
+ if ( oSettings.sTableId !== '' && typeof oSettings.aanFeatures.l == "undefined" )
4955
+ {
4956
+ nLength.setAttribute( 'id', oSettings.sTableId+'_length' );
4957
+ }
4958
+ nLength.className = oSettings.oClasses.sLength;
4959
+ nLength.innerHTML = oSettings.oLanguage.sLengthMenu.replace( '_MENU_', sStdMenu );
4960
+
4961
+ /*
4962
+ * Set the length to the current display length - thanks to Andrea Pavlovic for this fix,
4963
+ * and Stefan Skopnik for fixing the fix!
4964
+ */
4965
+ $('select option[value="'+oSettings._iDisplayLength+'"]',nLength).attr("selected",true);
4966
+
4967
+ $('select', nLength).change( function(e) {
4968
+ var iVal = $(this).val();
4969
+
4970
+ /* Update all other length options for the new display */
4971
+ var n = oSettings.aanFeatures.l;
4972
+ for ( i=0, iLen=n.length ; i<iLen ; i++ )
4973
+ {
4974
+ if ( n[i] != this.parentNode )
4975
+ {
4976
+ $('select', n[i]).val( iVal );
4977
+ }
4978
+ }
4979
+
4980
+ /* Redraw the table */
4981
+ oSettings._iDisplayLength = parseInt(iVal, 10);
4982
+ _fnCalculateEnd( oSettings );
4983
+
4984
+ /* If we have space to show extra rows (backing up from the end point - then do so */
4985
+ if ( oSettings.fnDisplayEnd() == oSettings.fnRecordsDisplay() )
4986
+ {
4987
+ oSettings._iDisplayStart = oSettings.fnDisplayEnd() - oSettings._iDisplayLength;
4988
+ if ( oSettings._iDisplayStart < 0 )
4989
+ {
4990
+ oSettings._iDisplayStart = 0;
4991
+ }
4992
+ }
4993
+
4994
+ if ( oSettings._iDisplayLength == -1 )
4995
+ {
4996
+ oSettings._iDisplayStart = 0;
4997
+ }
4998
+
4999
+ _fnDraw( oSettings );
5000
+ } );
5001
+
5002
+ return nLength;
5003
+ }
5004
+
5005
+
5006
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
5007
+ * Section - Feature: Processing incidator
5008
+ */
5009
+
5010
+ /*
5011
+ * Function: _fnFeatureHtmlProcessing
5012
+ * Purpose: Generate the node required for the processing node
5013
+ * Returns: node
5014
+ * Inputs: object:oSettings - dataTables settings object
5015
+ */
5016
+ function _fnFeatureHtmlProcessing ( oSettings )
5017
+ {
5018
+ var nProcessing = document.createElement( 'div' );
5019
+
5020
+ if ( oSettings.sTableId !== '' && typeof oSettings.aanFeatures.r == "undefined" )
5021
+ {
5022
+ nProcessing.setAttribute( 'id', oSettings.sTableId+'_processing' );
5023
+ }
5024
+ nProcessing.innerHTML = oSettings.oLanguage.sProcessing;
5025
+ nProcessing.className = oSettings.oClasses.sProcessing;
5026
+ oSettings.nTable.parentNode.insertBefore( nProcessing, oSettings.nTable );
5027
+
5028
+ return nProcessing;
5029
+ }
5030
+
5031
+ /*
5032
+ * Function: _fnProcessingDisplay
5033
+ * Purpose: Display or hide the processing indicator
5034
+ * Returns: -
5035
+ * Inputs: object:oSettings - dataTables settings object
5036
+ * bool:
5037
+ * true - show the processing indicator
5038
+ * false - don't show
5039
+ */
5040
+ function _fnProcessingDisplay ( oSettings, bShow )
5041
+ {
5042
+ if ( oSettings.oFeatures.bProcessing )
5043
+ {
5044
+ var an = oSettings.aanFeatures.r;
5045
+ for ( var i=0, iLen=an.length ; i<iLen ; i++ )
5046
+ {
5047
+ an[i].style.visibility = bShow ? "visible" : "hidden";
5048
+ }
5049
+ }
5050
+ }
5051
+
5052
+
5053
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
5054
+ * Section - Support functions
5055
+ */
5056
+
5057
+ /*
5058
+ * Function: _fnVisibleToColumnIndex
5059
+ * Purpose: Covert the index of a visible column to the index in the data array (take account
5060
+ * of hidden columns)
5061
+ * Returns: int:i - the data index
5062
+ * Inputs: object:oSettings - dataTables settings object
5063
+ */
5064
+ function _fnVisibleToColumnIndex( oSettings, iMatch )
5065
+ {
5066
+ var iColumn = -1;
5067
+
5068
+ for ( var i=0 ; i<oSettings.aoColumns.length ; i++ )
5069
+ {
5070
+ if ( oSettings.aoColumns[i].bVisible === true )
5071
+ {
5072
+ iColumn++;
5073
+ }
5074
+
5075
+ if ( iColumn == iMatch )
5076
+ {
5077
+ return i;
5078
+ }
5079
+ }
5080
+
5081
+ return null;
5082
+ }
5083
+
5084
+ /*
5085
+ * Function: _fnColumnIndexToVisible
5086
+ * Purpose: Covert the index of an index in the data array and convert it to the visible
5087
+ * column index (take account of hidden columns)
5088
+ * Returns: int:i - the data index
5089
+ * Inputs: object:oSettings - dataTables settings object
5090
+ */
5091
+ function _fnColumnIndexToVisible( oSettings, iMatch )
5092
+ {
5093
+ var iVisible = -1;
5094
+ for ( var i=0 ; i<oSettings.aoColumns.length ; i++ )
5095
+ {
5096
+ if ( oSettings.aoColumns[i].bVisible === true )
5097
+ {
5098
+ iVisible++;
5099
+ }
5100
+
5101
+ if ( i == iMatch )
5102
+ {
5103
+ return oSettings.aoColumns[i].bVisible === true ? iVisible : null;
5104
+ }
5105
+ }
5106
+
5107
+ return null;
5108
+ }
5109
+
5110
+
5111
+ /*
5112
+ * Function: _fnNodeToDataIndex
5113
+ * Purpose: Take a TR element and convert it to an index in aoData
5114
+ * Returns: int:i - index if found, null if not
5115
+ * Inputs: object:s - dataTables settings object
5116
+ * node:n - the TR element to find
5117
+ */
5118
+ function _fnNodeToDataIndex( s, n )
5119
+ {
5120
+ var i, iLen;
5121
+
5122
+ /* Optimisation - see if the nodes which are currently visible match, since that is
5123
+ * the most likely node to be asked for (a selector or event for example)
5124
+ */
5125
+ for ( i=s._iDisplayStart, iLen=s._iDisplayEnd ; i<iLen ; i++ )
5126
+ {
5127
+ if ( s.aoData[ s.aiDisplay[i] ].nTr == n )
5128
+ {
5129
+ return s.aiDisplay[i];
5130
+ }
5131
+ }
5132
+
5133
+ /* Otherwise we are in for a slog through the whole data cache */
5134
+ for ( i=0, iLen=s.aoData.length ; i<iLen ; i++ )
5135
+ {
5136
+ if ( s.aoData[i].nTr == n )
5137
+ {
5138
+ return i;
5139
+ }
5140
+ }
5141
+ return null;
5142
+ }
5143
+
5144
+ /*
5145
+ * Function: _fnVisbleColumns
5146
+ * Purpose: Get the number of visible columns
5147
+ * Returns: int:i - the number of visible columns
5148
+ * Inputs: object:oS - dataTables settings object
5149
+ */
5150
+ function _fnVisbleColumns( oS )
5151
+ {
5152
+ var iVis = 0;
5153
+ for ( var i=0 ; i<oS.aoColumns.length ; i++ )
5154
+ {
5155
+ if ( oS.aoColumns[i].bVisible === true )
5156
+ {
5157
+ iVis++;
5158
+ }
5159
+ }
5160
+ return iVis;
5161
+ }
5162
+
5163
+ /*
5164
+ * Function: _fnCalculateEnd
5165
+ * Purpose: Rcalculate the end point based on the start point
5166
+ * Returns: -
5167
+ * Inputs: object:oSettings - dataTables settings object
5168
+ */
5169
+ function _fnCalculateEnd( oSettings )
5170
+ {
5171
+ if ( oSettings.oFeatures.bPaginate === false )
5172
+ {
5173
+ oSettings._iDisplayEnd = oSettings.aiDisplay.length;
5174
+ }
5175
+ else
5176
+ {
5177
+ /* Set the end point of the display - based on how many elements there are
5178
+ * still to display
5179
+ */
5180
+ if ( oSettings._iDisplayStart + oSettings._iDisplayLength > oSettings.aiDisplay.length ||
5181
+ oSettings._iDisplayLength == -1 )
5182
+ {
5183
+ oSettings._iDisplayEnd = oSettings.aiDisplay.length;
5184
+ }
5185
+ else
5186
+ {
5187
+ oSettings._iDisplayEnd = oSettings._iDisplayStart + oSettings._iDisplayLength;
5188
+ }
5189
+ }
5190
+ }
5191
+
5192
+ /*
5193
+ * Function: _fnConvertToWidth
5194
+ * Purpose: Convert a CSS unit width to pixels (e.g. 2em)
5195
+ * Returns: int:iWidth - width in pixels
5196
+ * Inputs: string:sWidth - width to be converted
5197
+ * node:nParent - parent to get the with for (required for
5198
+ * relative widths) - optional
5199
+ */
5200
+ function _fnConvertToWidth ( sWidth, nParent )
5201
+ {
5202
+ if ( !sWidth || sWidth === null || sWidth === '' )
5203
+ {
5204
+ return 0;
5205
+ }
5206
+
5207
+ if ( typeof nParent == "undefined" )
5208
+ {
5209
+ nParent = document.getElementsByTagName('body')[0];
5210
+ }
5211
+
5212
+ var iWidth;
5213
+ var nTmp = document.createElement( "div" );
5214
+ nTmp.style.width = sWidth;
5215
+
5216
+ nParent.appendChild( nTmp );
5217
+ iWidth = nTmp.offsetWidth;
5218
+ nParent.removeChild( nTmp );
5219
+
5220
+ return ( iWidth );
5221
+ }
5222
+
5223
+ /*
5224
+ * Function: _fnCalculateColumnWidths
5225
+ * Purpose: Calculate the width of columns for the table
5226
+ * Returns: -
5227
+ * Inputs: object:oSettings - dataTables settings object
5228
+ */
5229
+ function _fnCalculateColumnWidths ( oSettings )
5230
+ {
5231
+ var iTableWidth = oSettings.nTable.offsetWidth;
5232
+ var iUserInputs = 0;
5233
+ var iTmpWidth;
5234
+ var iVisibleColumns = 0;
5235
+ var iColums = oSettings.aoColumns.length;
5236
+ var i;
5237
+ var oHeaders = $('th', oSettings.nTHead);
5238
+
5239
+ /* Convert any user input sizes into pixel sizes */
5240
+ for ( i=0 ; i<iColums ; i++ )
5241
+ {
5242
+ if ( oSettings.aoColumns[i].bVisible )
5243
+ {
5244
+ iVisibleColumns++;
5245
+
5246
+ if ( oSettings.aoColumns[i].sWidth !== null )
5247
+ {
5248
+ iTmpWidth = _fnConvertToWidth( oSettings.aoColumns[i].sWidthOrig,
5249
+ oSettings.nTable.parentNode );
5250
+ if ( iTmpWidth !== null )
5251
+ {
5252
+ oSettings.aoColumns[i].sWidth = _fnStringToCss( iTmpWidth );
5253
+ }
5254
+
5255
+ iUserInputs++;
5256
+ }
5257
+ }
5258
+ }
5259
+
5260
+ /* If the number of columns in the DOM equals the number that we have to process in
5261
+ * DataTables, then we can use the offsets that are created by the web-browser. No custom
5262
+ * sizes can be set in order for this to happen, nor scrolling used
5263
+ */
5264
+ if ( iColums == oHeaders.length && iUserInputs === 0 && iVisibleColumns == iColums &&
5265
+ oSettings.oScroll.sX === "" && oSettings.oScroll.sY === "" )
5266
+ {
5267
+ for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
5268
+ {
5269
+ iTmpWidth = $(oHeaders[i]).width();
5270
+ if ( iTmpWidth !== null )
5271
+ {
5272
+ oSettings.aoColumns[i].sWidth = _fnStringToCss( iTmpWidth );
5273
+ }
5274
+ }
5275
+ }
5276
+ else
5277
+ {
5278
+ /* Otherwise we are going to have to do some calculations to get the width of each column.
5279
+ * Construct a 1 row table with the widest node in the data, and any user defined widths,
5280
+ * then insert it into the DOM and allow the browser to do all the hard work of
5281
+ * calculating table widths.
5282
+ */
5283
+ var
5284
+ nCalcTmp = oSettings.nTable.cloneNode( false ),
5285
+ nBody = document.createElement( 'tbody' ),
5286
+ nTr = document.createElement( 'tr' ),
5287
+ nDivSizing;
5288
+
5289
+ nCalcTmp.removeAttribute( "id" );
5290
+ nCalcTmp.appendChild( oSettings.nTHead.cloneNode(true) );
5291
+ if ( oSettings.nTFoot !== null )
5292
+ {
5293
+ nCalcTmp.appendChild( oSettings.nTFoot.cloneNode(true) );
5294
+ _fnApplyToChildren( function(n) {
5295
+ n.style.width = "";
5296
+ }, nCalcTmp.getElementsByTagName('tr') );
5297
+ }
5298
+
5299
+ nCalcTmp.appendChild( nBody );
5300
+ nBody.appendChild( nTr );
5301
+
5302
+ /* Remove any sizing that was previously applied by the styles */
5303
+ var jqColSizing = $('thead th', nCalcTmp);
5304
+ if ( jqColSizing.length === 0 )
5305
+ {
5306
+ jqColSizing = $('tbody tr:eq(0)>td', nCalcTmp);
5307
+ }
5308
+ jqColSizing.each( function (i) {
5309
+ this.style.width = "";
5310
+
5311
+ var iIndex = _fnVisibleToColumnIndex( oSettings, i );
5312
+ if ( iIndex !== null && oSettings.aoColumns[iIndex].sWidthOrig !== "" )
5313
+ {
5314
+ this.style.width = oSettings.aoColumns[iIndex].sWidthOrig;
5315
+ }
5316
+ } );
5317
+
5318
+ /* Find the biggest td for each column and put it into the table */
5319
+ for ( i=0 ; i<iColums ; i++ )
5320
+ {
5321
+ if ( oSettings.aoColumns[i].bVisible )
5322
+ {
5323
+ var nTd = _fnGetWidestNode( oSettings, i );
5324
+ if ( nTd !== null )
5325
+ {
5326
+ nTd = nTd.cloneNode(true);
5327
+ nTr.appendChild( nTd );
5328
+ }
5329
+ }
5330
+ }
5331
+
5332
+ /* Build the table and 'display' it */
5333
+ var nWrapper = oSettings.nTable.parentNode;
5334
+ nWrapper.appendChild( nCalcTmp );
5335
+
5336
+ /* When scrolling (X or Y) we want to set the width of the table as appropriate. However,
5337
+ * when not scrolling leave the table width as it is. This results in slightly different,
5338
+ * but I think correct behaviour
5339
+ */
5340
+ if ( oSettings.oScroll.sX !== "" && oSettings.oScroll.sXInner !== "" )
5341
+ {
5342
+ nCalcTmp.style.width = _fnStringToCss(oSettings.oScroll.sXInner);
5343
+ }
5344
+ else if ( oSettings.oScroll.sX !== "" )
5345
+ {
5346
+ nCalcTmp.style.width = "";
5347
+ if ( $(nCalcTmp).width() < nWrapper.offsetWidth )
5348
+ {
5349
+ nCalcTmp.style.width = _fnStringToCss( nWrapper.offsetWidth );
5350
+ }
5351
+ }
5352
+ else if ( oSettings.oScroll.sY !== "" )
5353
+ {
5354
+ nCalcTmp.style.width = _fnStringToCss( nWrapper.offsetWidth );
5355
+ }
5356
+ nCalcTmp.style.visibility = "hidden";
5357
+
5358
+ /* Scrolling considerations */
5359
+ _fnScrollingWidthAdjust( oSettings, nCalcTmp );
5360
+
5361
+ /* Read the width's calculated by the browser and store them for use by the caller. We
5362
+ * first of all try to use the elements in the body, but it is possible that there are
5363
+ * no elements there, under which circumstances we use the header elements
5364
+ */
5365
+ var oNodes = $("tbody tr:eq(0)>td", nCalcTmp);
5366
+ if ( oNodes.length === 0 )
5367
+ {
5368
+ oNodes = $("thead tr:eq(0)>th", nCalcTmp);
5369
+ }
5370
+
5371
+ var iIndex, iCorrector = 0, iWidth;
5372
+ for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
5373
+ {
5374
+ if ( oSettings.aoColumns[i].bVisible )
5375
+ {
5376
+ iWidth = $(oNodes[iCorrector]).width();
5377
+ if ( iWidth !== null && iWidth > 0 )
5378
+ {
5379
+ oSettings.aoColumns[i].sWidth = _fnStringToCss( iWidth );
5380
+ }
5381
+ iCorrector++;
5382
+ }
5383
+ }
5384
+
5385
+ oSettings.nTable.style.width = _fnStringToCss( $(nCalcTmp).outerWidth() );
5386
+ nCalcTmp.parentNode.removeChild( nCalcTmp );
5387
+ }
5388
+ }
5389
+
5390
+ /*
5391
+ * Function: _fnScrollingWidthAdjust
5392
+ * Purpose: Adjust a table's width to take account of scrolling
5393
+ * Returns: -
5394
+ * Inputs: object:oSettings - dataTables settings object
5395
+ * node:n - table node
5396
+ */
5397
+ function _fnScrollingWidthAdjust ( oSettings, n )
5398
+ {
5399
+ if ( oSettings.oScroll.sX === "" && oSettings.oScroll.sY !== "" )
5400
+ {
5401
+ /* When y-scrolling only, we want to remove the width of the scroll bar so the table
5402
+ * + scroll bar will fit into the area avaialble.
5403
+ */
5404
+ var iOrigWidth = $(n).width();
5405
+ n.style.width = _fnStringToCss( $(n).outerWidth()-oSettings.oScroll.iBarWidth );
5406
+ }
5407
+ else if ( oSettings.oScroll.sX !== "" )
5408
+ {
5409
+ /* When x-scrolling both ways, fix the table at it's current size, without adjusting */
5410
+ n.style.width = _fnStringToCss( $(n).outerWidth() );
5411
+ }
5412
+ }
5413
+
5414
+ /*
5415
+ * Function: _fnGetWidestNode
5416
+ * Purpose: Get the widest node
5417
+ * Returns: string: - max strlens for each column
5418
+ * Inputs: object:oSettings - dataTables settings object
5419
+ * int:iCol - column of interest
5420
+ * boolean:bFast - Should we use fast (but non-accurate) calculation - optional,
5421
+ * default true
5422
+ * Notes: This operation is _expensive_ (!!!). It requires a lot of DOM interaction, but
5423
+ * this is the only way to reliably get the widest string. For example 'mmm' would be wider
5424
+ * than 'iiii' so we can't just ocunt characters. If this can be optimised it would be good
5425
+ * to do so!
5426
+ */
5427
+ function _fnGetWidestNode( oSettings, iCol, bFast )
5428
+ {
5429
+ /* Use fast not non-accurate calculate based on the strlen */
5430
+ if ( typeof bFast == 'undefined' || bFast )
5431
+ {
5432
+ var iMaxLen = _fnGetMaxLenString( oSettings, iCol );
5433
+ var iFastVis = _fnColumnIndexToVisible( oSettings, iCol);
5434
+ if ( iMaxLen < 0 )
5435
+ {
5436
+ return null;
5437
+ }
5438
+ return oSettings.aoData[iMaxLen].nTr.getElementsByTagName('td')[iFastVis];
5439
+ }
5440
+
5441
+ /* Use the slow approach, but get high quality answers - note that this code is not actually
5442
+ * used by DataTables by default. If you want to use it you can alter the call to
5443
+ * _fnGetWidestNode to pass 'false' as the third argument
5444
+ */
5445
+ var
5446
+ iMax = -1, i, iLen,
5447
+ iMaxIndex = -1,
5448
+ n = document.createElement('div');
5449
+
5450
+ n.style.visibility = "hidden";
5451
+ n.style.position = "absolute";
5452
+ document.body.appendChild( n );
5453
+
5454
+ for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
5455
+ {
5456
+ n.innerHTML = oSettings.aoData[i]._aData[iCol];
5457
+ if ( n.offsetWidth > iMax )
5458
+ {
5459
+ iMax = n.offsetWidth;
5460
+ iMaxIndex = i;
5461
+ }
5462
+ }
5463
+ document.body.removeChild( n );
5464
+
5465
+ if ( iMaxIndex >= 0 )
5466
+ {
5467
+ var iVis = _fnColumnIndexToVisible( oSettings, iCol);
5468
+ var nRet = oSettings.aoData[iMaxIndex].nTr.getElementsByTagName('td')[iVis];
5469
+ if ( nRet )
5470
+ {
5471
+ return nRet;
5472
+ }
5473
+ }
5474
+ return null;
5475
+ }
5476
+
5477
+ /*
5478
+ * Function: _fnGetMaxLenString
5479
+ * Purpose: Get the maximum strlen for each data column
5480
+ * Returns: string: - max strlens for each column
5481
+ * Inputs: object:oSettings - dataTables settings object
5482
+ * int:iCol - column of interest
5483
+ */
5484
+ function _fnGetMaxLenString( oSettings, iCol )
5485
+ {
5486
+ var iMax = -1;
5487
+ var iMaxIndex = -1;
5488
+
5489
+ for ( var i=0 ; i<oSettings.aoData.length ; i++ )
5490
+ {
5491
+ var s = oSettings.aoData[i]._aData[iCol];
5492
+ if ( s.length > iMax )
5493
+ {
5494
+ iMax = s.length;
5495
+ iMaxIndex = i;
5496
+ }
5497
+ }
5498
+
5499
+ return iMaxIndex;
5500
+ }
5501
+
5502
+ /*
5503
+ * Function: _fnStringToCss
5504
+ * Purpose: Append a CSS unit (only if required) to a string
5505
+ * Returns: 0 if match, 1 if length is different, 2 if no match
5506
+ * Inputs: array:aArray1 - first array
5507
+ * array:aArray2 - second array
5508
+ */
5509
+ function _fnStringToCss( s )
5510
+ {
5511
+ if ( s === null )
5512
+ {
5513
+ return "0px";
5514
+ }
5515
+
5516
+ if ( typeof s == 'number' )
5517
+ {
5518
+ if ( s < 0 )
5519
+ {
5520
+ return "0px";
5521
+ }
5522
+ return s+"px";
5523
+ }
5524
+
5525
+ /* Check if the last character is not 0-9 */
5526
+ var c = s.charCodeAt( s.length-1 );
5527
+ if (c < 0x30 || c > 0x39)
5528
+ {
5529
+ return s;
5530
+ }
5531
+ return s+"px";
5532
+ }
5533
+
5534
+ /*
5535
+ * Function: _fnArrayCmp
5536
+ * Purpose: Compare two arrays
5537
+ * Returns: 0 if match, 1 if length is different, 2 if no match
5538
+ * Inputs: array:aArray1 - first array
5539
+ * array:aArray2 - second array
5540
+ */
5541
+ function _fnArrayCmp( aArray1, aArray2 )
5542
+ {
5543
+ if ( aArray1.length != aArray2.length )
5544
+ {
5545
+ return 1;
5546
+ }
5547
+
5548
+ for ( var i=0 ; i<aArray1.length ; i++ )
5549
+ {
5550
+ if ( aArray1[i] != aArray2[i] )
5551
+ {
5552
+ return 2;
5553
+ }
5554
+ }
5555
+
5556
+ return 0;
5557
+ }
5558
+
5559
+ /*
5560
+ * Function: _fnDetectType
5561
+ * Purpose: Get the sort type based on an input string
5562
+ * Returns: string: - type (defaults to 'string' if no type can be detected)
5563
+ * Inputs: string:sData - data we wish to know the type of
5564
+ * Notes: This function makes use of the DataTables plugin objct _oExt
5565
+ * (.aTypes) such that new types can easily be added.
5566
+ */
5567
+ function _fnDetectType( sData )
5568
+ {
5569
+ var aTypes = _oExt.aTypes;
5570
+ var iLen = aTypes.length;
5571
+
5572
+ for ( var i=0 ; i<iLen ; i++ )
5573
+ {
5574
+ var sType = aTypes[i]( sData );
5575
+ if ( sType !== null )
5576
+ {
5577
+ return sType;
5578
+ }
5579
+ }
5580
+
5581
+ return 'string';
5582
+ }
5583
+
5584
+ /*
5585
+ * Function: _fnSettingsFromNode
5586
+ * Purpose: Return the settings object for a particular table
5587
+ * Returns: object: Settings object - or null if not found
5588
+ * Inputs: node:nTable - table we are using as a dataTable
5589
+ */
5590
+ function _fnSettingsFromNode ( nTable )
5591
+ {
5592
+ for ( var i=0 ; i<_aoSettings.length ; i++ )
5593
+ {
5594
+ if ( _aoSettings[i].nTable == nTable )
5595
+ {
5596
+ return _aoSettings[i];
5597
+ }
5598
+ }
5599
+
5600
+ return null;
5601
+ }
5602
+
5603
+ /*
5604
+ * Function: _fnGetDataMaster
5605
+ * Purpose: Return an array with the full table data
5606
+ * Returns: array array:aData - Master data array
5607
+ * Inputs: object:oSettings - dataTables settings object
5608
+ */
5609
+ function _fnGetDataMaster ( oSettings )
5610
+ {
5611
+ var aData = [];
5612
+ var iLen = oSettings.aoData.length;
5613
+ for ( var i=0 ; i<iLen; i++ )
5614
+ {
5615
+ aData.push( oSettings.aoData[i]._aData );
5616
+ }
5617
+ return aData;
5618
+ }
5619
+
5620
+ /*
5621
+ * Function: _fnGetTrNodes
5622
+ * Purpose: Return an array with the TR nodes for the table
5623
+ * Returns: array: - TR array
5624
+ * Inputs: object:oSettings - dataTables settings object
5625
+ */
5626
+ function _fnGetTrNodes ( oSettings )
5627
+ {
5628
+ var aNodes = [];
5629
+ var iLen = oSettings.aoData.length;
5630
+ for ( var i=0 ; i<iLen ; i++ )
5631
+ {
5632
+ aNodes.push( oSettings.aoData[i].nTr );
5633
+ }
5634
+ return aNodes;
5635
+ }
5636
+
5637
+ /*
5638
+ * Function: _fnGetTdNodes
5639
+ * Purpose: Return an array with the TD nodes for the table
5640
+ * Returns: array: - TD array
5641
+ * Inputs: object:oSettings - dataTables settings object
5642
+ */
5643
+ function _fnGetTdNodes ( oSettings )
5644
+ {
5645
+ var nTrs = _fnGetTrNodes( oSettings );
5646
+ var nTds = [], nTd;
5647
+ var anReturn = [];
5648
+ var iCorrector;
5649
+ var iRow, iRows, iColumn, iColumns;
5650
+
5651
+ for ( iRow=0, iRows=nTrs.length ; iRow<iRows ; iRow++ )
5652
+ {
5653
+ nTds = [];
5654
+ for ( iColumn=0, iColumns=nTrs[iRow].childNodes.length ; iColumn<iColumns ; iColumn++ )
5655
+ {
5656
+ nTd = nTrs[iRow].childNodes[iColumn];
5657
+ if ( nTd.nodeName.toUpperCase() == "TD" )
5658
+ {
5659
+ nTds.push( nTd );
5660
+ }
5661
+ }
5662
+
5663
+ iCorrector = 0;
5664
+ for ( iColumn=0, iColumns=oSettings.aoColumns.length ; iColumn<iColumns ; iColumn++ )
5665
+ {
5666
+ if ( oSettings.aoColumns[iColumn].bVisible )
5667
+ {
5668
+ anReturn.push( nTds[iColumn-iCorrector] );
5669
+ }
5670
+ else
5671
+ {
5672
+ anReturn.push( oSettings.aoData[iRow]._anHidden[iColumn] );
5673
+ iCorrector++;
5674
+ }
5675
+ }
5676
+ }
5677
+ return anReturn;
5678
+ }
5679
+
5680
+ /*
5681
+ * Function: _fnEscapeRegex
5682
+ * Purpose: scape a string stuch that it can be used in a regular expression
5683
+ * Returns: string: - escaped string
5684
+ * Inputs: string:sVal - string to escape
5685
+ */
5686
+ function _fnEscapeRegex ( sVal )
5687
+ {
5688
+ var acEscape = [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^' ];
5689
+ var reReplace = new RegExp( '(\\' + acEscape.join('|\\') + ')', 'g' );
5690
+ return sVal.replace(reReplace, '\\$1');
5691
+ }
5692
+
5693
+ /*
5694
+ * Function: _fnDeleteIndex
5695
+ * Purpose: Take an array of integers (index array) and remove a target integer (value - not
5696
+ * the key!)
5697
+ * Returns: -
5698
+ * Inputs: a:array int - Index array to target
5699
+ * int:iTarget - value to find
5700
+ */
5701
+ function _fnDeleteIndex( a, iTarget )
5702
+ {
5703
+ var iTargetIndex = -1;
5704
+
5705
+ for ( var i=0, iLen=a.length ; i<iLen ; i++ )
5706
+ {
5707
+ if ( a[i] == iTarget )
5708
+ {
5709
+ iTargetIndex = i;
5710
+ }
5711
+ else if ( a[i] > iTarget )
5712
+ {
5713
+ a[i]--;
5714
+ }
5715
+ }
5716
+
5717
+ if ( iTargetIndex != -1 )
5718
+ {
5719
+ a.splice( iTargetIndex, 1 );
5720
+ }
5721
+ }
5722
+
5723
+ /*
5724
+ * Function: _fnReOrderIndex
5725
+ * Purpose: Figure out how to reorder a display list
5726
+ * Returns: array int:aiReturn - index list for reordering
5727
+ * Inputs: object:oSettings - dataTables settings object
5728
+ */
5729
+ function _fnReOrderIndex ( oSettings, sColumns )
5730
+ {
5731
+ var aColumns = sColumns.split(',');
5732
+ var aiReturn = [];
5733
+
5734
+ for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
5735
+ {
5736
+ for ( var j=0 ; j<iLen ; j++ )
5737
+ {
5738
+ if ( oSettings.aoColumns[i].sName == aColumns[j] )
5739
+ {
5740
+ aiReturn.push( j );
5741
+ break;
5742
+ }
5743
+ }
5744
+ }
5745
+
5746
+ return aiReturn;
5747
+ }
5748
+
5749
+ /*
5750
+ * Function: _fnColumnOrdering
5751
+ * Purpose: Get the column ordering that DataTables expects
5752
+ * Returns: string: - comma separated list of names
5753
+ * Inputs: object:oSettings - dataTables settings object
5754
+ */
5755
+ function _fnColumnOrdering ( oSettings )
5756
+ {
5757
+ var sNames = '';
5758
+ for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
5759
+ {
5760
+ sNames += oSettings.aoColumns[i].sName+',';
5761
+ }
5762
+ if ( sNames.length == iLen )
5763
+ {
5764
+ return "";
5765
+ }
5766
+ return sNames.slice(0, -1);
5767
+ }
5768
+
5769
+ /*
5770
+ * Function: _fnLog
5771
+ * Purpose: Log an error message
5772
+ * Returns: -
5773
+ * Inputs: int:iLevel - log error messages, or display them to the user
5774
+ * string:sMesg - error message
5775
+ */
5776
+ function _fnLog( oSettings, iLevel, sMesg )
5777
+ {
5778
+ var sAlert = oSettings.sTableId === "" ?
5779
+ "DataTables warning: " +sMesg :
5780
+ "DataTables warning (table id = '"+oSettings.sTableId+"'): " +sMesg;
5781
+
5782
+ if ( iLevel === 0 )
5783
+ {
5784
+ if ( _oExt.sErrMode == 'alert' )
5785
+ {
5786
+ alert( sAlert );
5787
+ }
5788
+ else
5789
+ {
5790
+ throw sAlert;
5791
+ }
5792
+ return;
5793
+ }
5794
+ else if ( typeof console != 'undefined' && typeof console.log != 'undefined' )
5795
+ {
5796
+ console.log( sAlert );
5797
+ }
5798
+ }
5799
+
5800
+ /*
5801
+ * Function: _fnClearTable
5802
+ * Purpose: Nuke the table
5803
+ * Returns: -
5804
+ * Inputs: object:oSettings - dataTables settings object
5805
+ */
5806
+ function _fnClearTable( oSettings )
5807
+ {
5808
+ oSettings.aoData.splice( 0, oSettings.aoData.length );
5809
+ oSettings.aiDisplayMaster.splice( 0, oSettings.aiDisplayMaster.length );
5810
+ oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length );
5811
+ _fnCalculateEnd( oSettings );
5812
+ }
5813
+
5814
+ /*
5815
+ * Function: _fnSaveState
5816
+ * Purpose: Save the state of a table in a cookie such that the page can be reloaded
5817
+ * Returns: -
5818
+ * Inputs: object:oSettings - dataTables settings object
5819
+ */
5820
+ function _fnSaveState ( oSettings )
5821
+ {
5822
+ if ( !oSettings.oFeatures.bStateSave || typeof oSettings.bDestroying != 'undefined' )
5823
+ {
5824
+ return;
5825
+ }
5826
+
5827
+ /* Store the interesting variables */
5828
+ var i, iLen, sTmp;
5829
+ var sValue = "{";
5830
+ sValue += '"iCreate":'+ new Date().getTime()+',';
5831
+ sValue += '"iStart":'+ oSettings._iDisplayStart+',';
5832
+ sValue += '"iEnd":'+ oSettings._iDisplayEnd+',';
5833
+ sValue += '"iLength":'+ oSettings._iDisplayLength+',';
5834
+ sValue += '"sFilter":"'+ encodeURIComponent(oSettings.oPreviousSearch.sSearch)+'",';
5835
+ sValue += '"sFilterEsc":'+ !oSettings.oPreviousSearch.bRegex+',';
5836
+
5837
+ sValue += '"aaSorting":[ ';
5838
+ for ( i=0 ; i<oSettings.aaSorting.length ; i++ )
5839
+ {
5840
+ sValue += '['+oSettings.aaSorting[i][0]+',"'+oSettings.aaSorting[i][1]+'"],';
5841
+ }
5842
+ sValue = sValue.substring(0, sValue.length-1);
5843
+ sValue += "],";
5844
+
5845
+ sValue += '"aaSearchCols":[ ';
5846
+ for ( i=0 ; i<oSettings.aoPreSearchCols.length ; i++ )
5847
+ {
5848
+ sValue += '["'+encodeURIComponent(oSettings.aoPreSearchCols[i].sSearch)+
5849
+ '",'+!oSettings.aoPreSearchCols[i].bRegex+'],';
5850
+ }
5851
+ sValue = sValue.substring(0, sValue.length-1);
5852
+ sValue += "],";
5853
+
5854
+ sValue += '"abVisCols":[ ';
5855
+ for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
5856
+ {
5857
+ sValue += oSettings.aoColumns[i].bVisible+",";
5858
+ }
5859
+ sValue = sValue.substring(0, sValue.length-1);
5860
+ sValue += "]";
5861
+
5862
+ /* Save state from any plug-ins */
5863
+ for ( i=0, iLen=oSettings.aoStateSave.length ; i<iLen ; i++ )
5864
+ {
5865
+ sTmp = oSettings.aoStateSave[i].fn( oSettings, sValue );
5866
+ if ( sTmp !== "" )
5867
+ {
5868
+ sValue = sTmp;
5869
+ }
5870
+ }
5871
+
5872
+ sValue += "}";
5873
+
5874
+ _fnCreateCookie( oSettings.sCookiePrefix+oSettings.sInstance, sValue,
5875
+ oSettings.iCookieDuration, oSettings.sCookiePrefix, oSettings.fnCookieCallback );
5876
+ }
5877
+
5878
+ /*
5879
+ * Function: _fnLoadState
5880
+ * Purpose: Attempt to load a saved table state from a cookie
5881
+ * Returns: -
5882
+ * Inputs: object:oSettings - dataTables settings object
5883
+ * object:oInit - DataTables init object so we can override settings
5884
+ */
5885
+ function _fnLoadState ( oSettings, oInit )
5886
+ {
5887
+ if ( !oSettings.oFeatures.bStateSave )
5888
+ {
5889
+ return;
5890
+ }
5891
+
5892
+ var oData, i, iLen;
5893
+ var sData = _fnReadCookie( oSettings.sCookiePrefix+oSettings.sInstance );
5894
+ if ( sData !== null && sData !== '' )
5895
+ {
5896
+ /* Try/catch the JSON eval - if it is bad then we ignore it - note that 1.7.0 and before
5897
+ * incorrectly used single quotes for some strings - hence the replace below
5898
+ */
5899
+ try
5900
+ {
5901
+ oData = (typeof $.parseJSON == 'function') ?
5902
+ $.parseJSON( sData.replace(/'/g, '"') ) : eval( '('+sData+')' );
5903
+ }
5904
+ catch( e )
5905
+ {
5906
+ return;
5907
+ }
5908
+
5909
+ /* Allow custom and plug-in manipulation functions to alter the data set which was
5910
+ * saved, and also reject any saved state by returning false
5911
+ */
5912
+ for ( i=0, iLen=oSettings.aoStateLoad.length ; i<iLen ; i++ )
5913
+ {
5914
+ if ( !oSettings.aoStateLoad[i].fn( oSettings, oData ) )
5915
+ {
5916
+ return;
5917
+ }
5918
+ }
5919
+
5920
+ /* Store the saved state so it might be accessed at any time (particualrly a plug-in */
5921
+ oSettings.oLoadedState = $.extend( true, {}, oData );
5922
+
5923
+ /* Restore key features */
5924
+ oSettings._iDisplayStart = oData.iStart;
5925
+ oSettings.iInitDisplayStart = oData.iStart;
5926
+ oSettings._iDisplayEnd = oData.iEnd;
5927
+ oSettings._iDisplayLength = oData.iLength;
5928
+ oSettings.oPreviousSearch.sSearch = decodeURIComponent(oData.sFilter);
5929
+ oSettings.aaSorting = oData.aaSorting.slice();
5930
+ oSettings.saved_aaSorting = oData.aaSorting.slice();
5931
+
5932
+ /*
5933
+ * Search filtering - global reference added in 1.4.1
5934
+ * Note that we use a 'not' for the value of the regular expression indicator to maintain
5935
+ * compatibility with pre 1.7 versions, where this was basically inverted. Added in 1.7.0
5936
+ */
5937
+ if ( typeof oData.sFilterEsc != 'undefined' )
5938
+ {
5939
+ oSettings.oPreviousSearch.bRegex = !oData.sFilterEsc;
5940
+ }
5941
+
5942
+ /* Column filtering - added in 1.5.0 beta 6 */
5943
+ if ( typeof oData.aaSearchCols != 'undefined' )
5944
+ {
5945
+ for ( i=0 ; i<oData.aaSearchCols.length ; i++ )
5946
+ {
5947
+ oSettings.aoPreSearchCols[i] = {
5948
+ "sSearch": decodeURIComponent(oData.aaSearchCols[i][0]),
5949
+ "bRegex": !oData.aaSearchCols[i][1]
5950
+ };
5951
+ }
5952
+ }
5953
+
5954
+ /* Column visibility state - added in 1.5.0 beta 10 */
5955
+ if ( typeof oData.abVisCols != 'undefined' )
5956
+ {
5957
+ /* Pass back visibiliy settings to the init handler, but to do not here override
5958
+ * the init object that the user might have passed in
5959
+ */
5960
+ oInit.saved_aoColumns = [];
5961
+ for ( i=0 ; i<oData.abVisCols.length ; i++ )
5962
+ {
5963
+ oInit.saved_aoColumns[i] = {};
5964
+ oInit.saved_aoColumns[i].bVisible = oData.abVisCols[i];
5965
+ }
5966
+ }
5967
+ }
5968
+ }
5969
+
5970
+ /*
5971
+ * Function: _fnCreateCookie
5972
+ * Purpose: Create a new cookie with a value to store the state of a table
5973
+ * Returns: -
5974
+ * Inputs: string:sName - name of the cookie to create
5975
+ * string:sValue - the value the cookie should take
5976
+ * int:iSecs - duration of the cookie
5977
+ * string:sBaseName - sName is made up of the base + file name - this is the base
5978
+ * function:fnCallback - User definable function to modify the cookie
5979
+ */
5980
+ function _fnCreateCookie ( sName, sValue, iSecs, sBaseName, fnCallback )
5981
+ {
5982
+ var date = new Date();
5983
+ date.setTime( date.getTime()+(iSecs*1000) );
5984
+
5985
+ /*
5986
+ * Shocking but true - it would appear IE has major issues with having the path not having
5987
+ * a trailing slash on it. We need the cookie to be available based on the path, so we
5988
+ * have to append the file name to the cookie name. Appalling. Thanks to vex for adding the
5989
+ * patch to use at least some of the path
5990
+ */
5991
+ var aParts = window.location.pathname.split('/');
5992
+ var sNameFile = sName + '_' + aParts.pop().replace(/[\/:]/g,"").toLowerCase();
5993
+ var sFullCookie, oData;
5994
+
5995
+ if ( fnCallback !== null )
5996
+ {
5997
+ oData = (typeof $.parseJSON == 'function') ?
5998
+ $.parseJSON( sValue ) : eval( '('+sValue+')' );
5999
+ sFullCookie = fnCallback( sNameFile, oData, date.toGMTString(),
6000
+ aParts.join('/')+"/" );
6001
+ }
6002
+ else
6003
+ {
6004
+ sFullCookie = sNameFile + "=" + encodeURIComponent(sValue) +
6005
+ "; expires=" + date.toGMTString() +"; path=" + aParts.join('/')+"/";
6006
+ }
6007
+
6008
+ /* Are we going to go over the cookie limit of 4KiB? If so, try to delete a cookies
6009
+ * belonging to DataTables. This is FAR from bullet proof
6010
+ */
6011
+ var sOldName="", iOldTime=9999999999999;
6012
+ var iLength = _fnReadCookie( sNameFile )!==null ? document.cookie.length :
6013
+ sFullCookie.length + document.cookie.length;
6014
+
6015
+ if ( iLength+10 > 4096 ) /* Magic 10 for padding */
6016
+ {
6017
+ var aCookies =document.cookie.split(';');
6018
+ for ( var i=0, iLen=aCookies.length ; i<iLen ; i++ )
6019
+ {
6020
+ if ( aCookies[i].indexOf( sBaseName ) != -1 )
6021
+ {
6022
+ /* It's a DataTables cookie, so eval it and check the time stamp */
6023
+ var aSplitCookie = aCookies[i].split('=');
6024
+ try { oData = eval( '('+decodeURIComponent(aSplitCookie[1])+')' ); }
6025
+ catch( e ) { continue; }
6026
+
6027
+ if ( typeof oData.iCreate != 'undefined' && oData.iCreate < iOldTime )
6028
+ {
6029
+ sOldName = aSplitCookie[0];
6030
+ iOldTime = oData.iCreate;
6031
+ }
6032
+ }
6033
+ }
6034
+
6035
+ if ( sOldName !== "" )
6036
+ {
6037
+ document.cookie = sOldName+"=; expires=Thu, 01-Jan-1970 00:00:01 GMT; path="+
6038
+ aParts.join('/') + "/";
6039
+ }
6040
+ }
6041
+
6042
+ document.cookie = sFullCookie;
6043
+ }
6044
+
6045
+ /*
6046
+ * Function: _fnReadCookie
6047
+ * Purpose: Read an old cookie to get a cookie with an old table state
6048
+ * Returns: string: - contents of the cookie - or null if no cookie with that name found
6049
+ * Inputs: string:sName - name of the cookie to read
6050
+ */
6051
+ function _fnReadCookie ( sName )
6052
+ {
6053
+ var
6054
+ aParts = window.location.pathname.split('/'),
6055
+ sNameEQ = sName + '_' + aParts[aParts.length-1].replace(/[\/:]/g,"").toLowerCase() + '=',
6056
+ sCookieContents = document.cookie.split(';');
6057
+
6058
+ for( var i=0 ; i<sCookieContents.length ; i++ )
6059
+ {
6060
+ var c = sCookieContents[i];
6061
+
6062
+ while (c.charAt(0)==' ')
6063
+ {
6064
+ c = c.substring(1,c.length);
6065
+ }
6066
+
6067
+ if (c.indexOf(sNameEQ) === 0)
6068
+ {
6069
+ return decodeURIComponent( c.substring(sNameEQ.length,c.length) );
6070
+ }
6071
+ }
6072
+ return null;
6073
+ }
6074
+
6075
+ /*
6076
+ * Function: _fnGetUniqueThs
6077
+ * Purpose: Get an array of unique th elements, one for each column
6078
+ * Returns: array node:aReturn - list of unique ths
6079
+ * Inputs: node:nThead - The thead element for the table
6080
+ */
6081
+ function _fnGetUniqueThs ( nThead )
6082
+ {
6083
+ var nTrs = nThead.getElementsByTagName('tr');
6084
+
6085
+ /* Nice simple case */
6086
+ if ( nTrs.length == 1 )
6087
+ {
6088
+ return nTrs[0].getElementsByTagName('th');
6089
+ }
6090
+
6091
+ /* Otherwise we need to figure out the layout array to get the nodes */
6092
+ var aLayout = [], aReturn = [];
6093
+ var ROWSPAN = 2, COLSPAN = 3, TDELEM = 4;
6094
+ var i, j, k, iLen, jLen, iColumnShifted;
6095
+ var fnShiftCol = function ( a, i, j ) {
6096
+ while ( typeof a[i][j] != 'undefined' ) {
6097
+ j++;
6098
+ }
6099
+ return j;
6100
+ };
6101
+ var fnAddRow = function ( i ) {
6102
+ if ( typeof aLayout[i] == 'undefined' ) {
6103
+ aLayout[i] = [];
6104
+ }
6105
+ };
6106
+
6107
+ /* Calculate a layout array */
6108
+ for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
6109
+ {
6110
+ fnAddRow( i );
6111
+ var iColumn = 0;
6112
+ var nTds = [];
6113
+
6114
+ for ( j=0, jLen=nTrs[i].childNodes.length ; j<jLen ; j++ )
6115
+ {
6116
+ if ( nTrs[i].childNodes[j].nodeName.toUpperCase() == "TD" ||
6117
+ nTrs[i].childNodes[j].nodeName.toUpperCase() == "TH" )
6118
+ {
6119
+ nTds.push( nTrs[i].childNodes[j] );
6120
+ }
6121
+ }
6122
+
6123
+ for ( j=0, jLen=nTds.length ; j<jLen ; j++ )
6124
+ {
6125
+ var iColspan = nTds[j].getAttribute('colspan') * 1;
6126
+ var iRowspan = nTds[j].getAttribute('rowspan') * 1;
6127
+
6128
+ if ( !iColspan || iColspan===0 || iColspan===1 )
6129
+ {
6130
+ iColumnShifted = fnShiftCol( aLayout, i, iColumn );
6131
+ aLayout[i][iColumnShifted] = (nTds[j].nodeName.toUpperCase()=="TD") ? TDELEM : nTds[j];
6132
+ if ( iRowspan || iRowspan===0 || iRowspan===1 )
6133
+ {
6134
+ for ( k=1 ; k<iRowspan ; k++ )
6135
+ {
6136
+ fnAddRow( i+k );
6137
+ aLayout[i+k][iColumnShifted] = ROWSPAN;
6138
+ }
6139
+ }
6140
+ iColumn++;
6141
+ }
6142
+ else
6143
+ {
6144
+ iColumnShifted = fnShiftCol( aLayout, i, iColumn );
6145
+ for ( k=0 ; k<iColspan ; k++ )
6146
+ {
6147
+ aLayout[i][iColumnShifted+k] = COLSPAN;
6148
+ }
6149
+ iColumn += iColspan;
6150
+ }
6151
+ }
6152
+ }
6153
+
6154
+ /* Convert the layout array into a node array */
6155
+ for ( i=0, iLen=aLayout.length ; i<iLen ; i++ )
6156
+ {
6157
+ for ( j=0, jLen=aLayout[i].length ; j<jLen ; j++ )
6158
+ {
6159
+ if ( typeof aLayout[i][j] == 'object' )
6160
+ {
6161
+ aReturn[j] = aLayout[i][j];
6162
+ }
6163
+ }
6164
+ }
6165
+
6166
+ return aReturn;
6167
+ }
6168
+
6169
+ /*
6170
+ * Function: _fnScrollBarWidth
6171
+ * Purpose: Get the width of a scroll bar in this browser being used
6172
+ * Returns: int: - width in pixels
6173
+ * Inputs: -
6174
+ * Notes: All credit for this function belongs to Alexandre Gomes. Thanks for sharing!
6175
+ * http://www.alexandre-gomes.com/?p=115
6176
+ */
6177
+ function _fnScrollBarWidth ()
6178
+ {
6179
+ var inner = document.createElement('p');
6180
+ var style = inner.style;
6181
+ style.width = "100%";
6182
+ style.height = "200px";
6183
+
6184
+ var outer = document.createElement('div');
6185
+ style = outer.style;
6186
+ style.position = "absolute";
6187
+ style.top = "0px";
6188
+ style.left = "0px";
6189
+ style.visibility = "hidden";
6190
+ style.width = "200px";
6191
+ style.height = "150px";
6192
+ style.overflow = "hidden";
6193
+ outer.appendChild(inner);
6194
+
6195
+ document.body.appendChild(outer);
6196
+ var w1 = inner.offsetWidth;
6197
+ outer.style.overflow = 'scroll';
6198
+ var w2 = inner.offsetWidth;
6199
+ if ( w1 == w2 )
6200
+ {
6201
+ w2 = outer.clientWidth;
6202
+ }
6203
+
6204
+ document.body.removeChild(outer);
6205
+ return (w1 - w2);
6206
+ }
6207
+
6208
+ /*
6209
+ * Function: _fnApplyToChildren
6210
+ * Purpose: Apply a given function to the display child nodes of an element array (typically
6211
+ * TD children of TR rows
6212
+ * Returns: - (done by reference)
6213
+ * Inputs: function:fn - Method to apply to the objects
6214
+ * array nodes:an1 - List of elements to look through for display children
6215
+ * array nodes:an2 - Another list (identical structure to the first) - optional
6216
+ */
6217
+ function _fnApplyToChildren( fn, an1, an2 )
6218
+ {
6219
+ for ( var i=0, iLen=an1.length ; i<iLen ; i++ )
6220
+ {
6221
+ for ( var j=0, jLen=an1[i].childNodes.length ; j<jLen ; j++ )
6222
+ {
6223
+ if ( an1[i].childNodes[j].nodeType == 1 )
6224
+ {
6225
+ if ( typeof an2 != 'undefined' )
6226
+ {
6227
+ fn( an1[i].childNodes[j], an2[i].childNodes[j] );
6228
+ }
6229
+ else
6230
+ {
6231
+ fn( an1[i].childNodes[j] );
6232
+ }
6233
+ }
6234
+ }
6235
+ }
6236
+ }
6237
+
6238
+ /*
6239
+ * Function: _fnMap
6240
+ * Purpose: See if a property is defined on one object, if so assign it to the other object
6241
+ * Returns: - (done by reference)
6242
+ * Inputs: object:oRet - target object
6243
+ * object:oSrc - source object
6244
+ * string:sName - property
6245
+ * string:sMappedName - name to map too - optional, sName used if not given
6246
+ */
6247
+ function _fnMap( oRet, oSrc, sName, sMappedName )
6248
+ {
6249
+ if ( typeof sMappedName == 'undefined' )
6250
+ {
6251
+ sMappedName = sName;
6252
+ }
6253
+ if ( typeof oSrc[sName] != 'undefined' )
6254
+ {
6255
+ oRet[sMappedName] = oSrc[sName];
6256
+ }
6257
+ }
6258
+
6259
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
6260
+ * Section - API
6261
+ *
6262
+ * I'm not overly happy with this solution - I'd much rather that there was a way of getting
6263
+ * a list of all the private functions and do what we need to dynamically - but that doesn't
6264
+ * appear to be possible. Bonkers. A better solution would be to provide a 'bind' type object
6265
+ * To do - bind type method in DTs 2.x.
6266
+ */
6267
+ this.oApi._fnExternApiFunc = _fnExternApiFunc;
6268
+ this.oApi._fnInitalise = _fnInitalise;
6269
+ this.oApi._fnLanguageProcess = _fnLanguageProcess;
6270
+ this.oApi._fnAddColumn = _fnAddColumn;
6271
+ this.oApi._fnColumnOptions = _fnColumnOptions;
6272
+ this.oApi._fnAddData = _fnAddData;
6273
+ this.oApi._fnGatherData = _fnGatherData;
6274
+ this.oApi._fnDrawHead = _fnDrawHead;
6275
+ this.oApi._fnDraw = _fnDraw;
6276
+ this.oApi._fnReDraw = _fnReDraw;
6277
+ this.oApi._fnAjaxUpdate = _fnAjaxUpdate;
6278
+ this.oApi._fnAjaxUpdateDraw = _fnAjaxUpdateDraw;
6279
+ this.oApi._fnAddOptionsHtml = _fnAddOptionsHtml;
6280
+ this.oApi._fnFeatureHtmlTable = _fnFeatureHtmlTable;
6281
+ this.oApi._fnScrollDraw = _fnScrollDraw;
6282
+ this.oApi._fnAjustColumnSizing = _fnAjustColumnSizing;
6283
+ this.oApi._fnFeatureHtmlFilter = _fnFeatureHtmlFilter;
6284
+ this.oApi._fnFilterComplete = _fnFilterComplete;
6285
+ this.oApi._fnFilterCustom = _fnFilterCustom;
6286
+ this.oApi._fnFilterColumn = _fnFilterColumn;
6287
+ this.oApi._fnFilter = _fnFilter;
6288
+ this.oApi._fnBuildSearchArray = _fnBuildSearchArray;
6289
+ this.oApi._fnBuildSearchRow = _fnBuildSearchRow;
6290
+ this.oApi._fnFilterCreateSearch = _fnFilterCreateSearch;
6291
+ this.oApi._fnDataToSearch = _fnDataToSearch;
6292
+ this.oApi._fnSort = _fnSort;
6293
+ this.oApi._fnSortAttachListener = _fnSortAttachListener;
6294
+ this.oApi._fnSortingClasses = _fnSortingClasses;
6295
+ this.oApi._fnFeatureHtmlPaginate = _fnFeatureHtmlPaginate;
6296
+ this.oApi._fnPageChange = _fnPageChange;
6297
+ this.oApi._fnFeatureHtmlInfo = _fnFeatureHtmlInfo;
6298
+ this.oApi._fnUpdateInfo = _fnUpdateInfo;
6299
+ this.oApi._fnFeatureHtmlLength = _fnFeatureHtmlLength;
6300
+ this.oApi._fnFeatureHtmlProcessing = _fnFeatureHtmlProcessing;
6301
+ this.oApi._fnProcessingDisplay = _fnProcessingDisplay;
6302
+ this.oApi._fnVisibleToColumnIndex = _fnVisibleToColumnIndex;
6303
+ this.oApi._fnColumnIndexToVisible = _fnColumnIndexToVisible;
6304
+ this.oApi._fnNodeToDataIndex = _fnNodeToDataIndex;
6305
+ this.oApi._fnVisbleColumns = _fnVisbleColumns;
6306
+ this.oApi._fnCalculateEnd = _fnCalculateEnd;
6307
+ this.oApi._fnConvertToWidth = _fnConvertToWidth;
6308
+ this.oApi._fnCalculateColumnWidths = _fnCalculateColumnWidths;
6309
+ this.oApi._fnScrollingWidthAdjust = _fnScrollingWidthAdjust;
6310
+ this.oApi._fnGetWidestNode = _fnGetWidestNode;
6311
+ this.oApi._fnGetMaxLenString = _fnGetMaxLenString;
6312
+ this.oApi._fnStringToCss = _fnStringToCss;
6313
+ this.oApi._fnArrayCmp = _fnArrayCmp;
6314
+ this.oApi._fnDetectType = _fnDetectType;
6315
+ this.oApi._fnSettingsFromNode = _fnSettingsFromNode;
6316
+ this.oApi._fnGetDataMaster = _fnGetDataMaster;
6317
+ this.oApi._fnGetTrNodes = _fnGetTrNodes;
6318
+ this.oApi._fnGetTdNodes = _fnGetTdNodes;
6319
+ this.oApi._fnEscapeRegex = _fnEscapeRegex;
6320
+ this.oApi._fnDeleteIndex = _fnDeleteIndex;
6321
+ this.oApi._fnReOrderIndex = _fnReOrderIndex;
6322
+ this.oApi._fnColumnOrdering = _fnColumnOrdering;
6323
+ this.oApi._fnLog = _fnLog;
6324
+ this.oApi._fnClearTable = _fnClearTable;
6325
+ this.oApi._fnSaveState = _fnSaveState;
6326
+ this.oApi._fnLoadState = _fnLoadState;
6327
+ this.oApi._fnCreateCookie = _fnCreateCookie;
6328
+ this.oApi._fnReadCookie = _fnReadCookie;
6329
+ this.oApi._fnGetUniqueThs = _fnGetUniqueThs;
6330
+ this.oApi._fnScrollBarWidth = _fnScrollBarWidth;
6331
+ this.oApi._fnApplyToChildren = _fnApplyToChildren;
6332
+ this.oApi._fnMap = _fnMap;
6333
+
6334
+
6335
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
6336
+ * Section - Constructor
6337
+ */
6338
+
6339
+ /* Want to be able to reference "this" inside the this.each function */
6340
+ var _that = this;
6341
+ return this.each(function()
6342
+ {
6343
+ var i=0, iLen, j, jLen, k, kLen;
6344
+
6345
+ /* Check to see if we are re-initalising a table */
6346
+ for ( i=0, iLen=_aoSettings.length ; i<iLen ; i++ )
6347
+ {
6348
+ /* Base check on table node */
6349
+ if ( _aoSettings[i].nTable == this )
6350
+ {
6351
+ if ( typeof oInit == 'undefined' ||
6352
+ ( typeof oInit.bRetrieve != 'undefined' && oInit.bRetrieve === true ) )
6353
+ {
6354
+ return _aoSettings[i].oInstance;
6355
+ }
6356
+ else if ( typeof oInit.bDestroy != 'undefined' && oInit.bDestroy === true )
6357
+ {
6358
+ _aoSettings[i].oInstance.fnDestroy();
6359
+ break;
6360
+ }
6361
+ else
6362
+ {
6363
+ _fnLog( _aoSettings[i], 0, "Cannot reinitialise DataTable.\n\n"+
6364
+ "To retrieve the DataTables object for this table, please pass either no arguments "+
6365
+ "to the dataTable() function, or set bRetrieve to true. Alternatively, to destory "+
6366
+ "the old table and create a new one, set bDestroy to true (note that a lot of "+
6367
+ "changes to the configuration can be made through the API which is usually much "+
6368
+ "faster)." );
6369
+ return;
6370
+ }
6371
+ }
6372
+
6373
+ /* If the element we are initialising has the same ID as a table which was previously
6374
+ * initialised, but the table nodes don't match (from before) then we destory the old
6375
+ * instance by simply deleting it. This is under the assumption that the table has been
6376
+ * destroyed by other methods. Anyone using non-id selectors will need to do this manually
6377
+ */
6378
+ if ( _aoSettings[i].sTableId !== "" && _aoSettings[i].sTableId == this.getAttribute('id') )
6379
+ {
6380
+ _aoSettings.splice( i, 1 );
6381
+ break;
6382
+ }
6383
+ }
6384
+
6385
+ /* Make a complete and independent copy of the settings object */
6386
+ var oSettings = new classSettings();
6387
+ _aoSettings.push( oSettings );
6388
+
6389
+ var bInitHandedOff = false;
6390
+ var bUsePassedData = false;
6391
+
6392
+ /* Set the id */
6393
+ var sId = this.getAttribute( 'id' );
6394
+ if ( sId !== null )
6395
+ {
6396
+ oSettings.sTableId = sId;
6397
+ oSettings.sInstance = sId;
6398
+ }
6399
+ else
6400
+ {
6401
+ oSettings.sInstance = _oExt._oExternConfig.iNextUnique ++;
6402
+ }
6403
+
6404
+ /* Sanity check */
6405
+ if ( this.nodeName.toLowerCase() != 'table' )
6406
+ {
6407
+ _fnLog( oSettings, 0, "Attempted to initialise DataTables on a node which is not a "+
6408
+ "table: "+this.nodeName );
6409
+ return;
6410
+ }
6411
+
6412
+ /* Store 'this' in the settings object for later retrieval */
6413
+ oSettings.oInstance = _that;
6414
+
6415
+ /* Set the table node */
6416
+ oSettings.nTable = this;
6417
+
6418
+ /* Bind the API functions to the settings, so we can perform actions whenever oSettings is
6419
+ * available
6420
+ */
6421
+ oSettings.oApi = _that.oApi;
6422
+
6423
+ /* State the table's width for if a destroy is called at a later time */
6424
+ oSettings.sDestroyWidth = $(this).width();
6425
+
6426
+ /* Store the features that we have available */
6427
+ if ( typeof oInit != 'undefined' && oInit !== null )
6428
+ {
6429
+ oSettings.oInit = oInit;
6430
+ _fnMap( oSettings.oFeatures, oInit, "bPaginate" );
6431
+ _fnMap( oSettings.oFeatures, oInit, "bLengthChange" );
6432
+ _fnMap( oSettings.oFeatures, oInit, "bFilter" );
6433
+ _fnMap( oSettings.oFeatures, oInit, "bSort" );
6434
+ _fnMap( oSettings.oFeatures, oInit, "bInfo" );
6435
+ _fnMap( oSettings.oFeatures, oInit, "bProcessing" );
6436
+ _fnMap( oSettings.oFeatures, oInit, "bAutoWidth" );
6437
+ _fnMap( oSettings.oFeatures, oInit, "bSortClasses" );
6438
+ _fnMap( oSettings.oFeatures, oInit, "bServerSide" );
6439
+ _fnMap( oSettings.oScroll, oInit, "sScrollX", "sX" );
6440
+ _fnMap( oSettings.oScroll, oInit, "sScrollXInner", "sXInner" );
6441
+ _fnMap( oSettings.oScroll, oInit, "sScrollY", "sY" );
6442
+ _fnMap( oSettings.oScroll, oInit, "bScrollCollapse", "bCollapse" );
6443
+ _fnMap( oSettings.oScroll, oInit, "bScrollInfinite", "bInfinite" );
6444
+ _fnMap( oSettings.oScroll, oInit, "iScrollLoadGap", "iLoadGap" );
6445
+ _fnMap( oSettings, oInit, "asStripClasses" );
6446
+ _fnMap( oSettings, oInit, "fnRowCallback" );
6447
+ _fnMap( oSettings, oInit, "fnHeaderCallback" );
6448
+ _fnMap( oSettings, oInit, "fnFooterCallback" );
6449
+ _fnMap( oSettings, oInit, "fnCookieCallback" );
6450
+ _fnMap( oSettings, oInit, "fnInitComplete" );
6451
+ _fnMap( oSettings, oInit, "fnServerData" );
6452
+ _fnMap( oSettings, oInit, "fnFormatNumber" );
6453
+ _fnMap( oSettings, oInit, "aaSorting" );
6454
+ _fnMap( oSettings, oInit, "aaSortingFixed" );
6455
+ _fnMap( oSettings, oInit, "aLengthMenu" );
6456
+ _fnMap( oSettings, oInit, "sPaginationType" );
6457
+ _fnMap( oSettings, oInit, "sAjaxSource" );
6458
+ _fnMap( oSettings, oInit, "iCookieDuration" );
6459
+ _fnMap( oSettings, oInit, "sCookiePrefix" );
6460
+ _fnMap( oSettings, oInit, "sDom" );
6461
+ _fnMap( oSettings, oInit, "oSearch", "oPreviousSearch" );
6462
+ _fnMap( oSettings, oInit, "aoSearchCols", "aoPreSearchCols" );
6463
+ _fnMap( oSettings, oInit, "iDisplayLength", "_iDisplayLength" );
6464
+ _fnMap( oSettings, oInit, "bJQueryUI", "bJUI" );
6465
+ _fnMap( oSettings.oLanguage, oInit, "fnInfoCallback" );
6466
+
6467
+ /* Callback functions which are array driven */
6468
+ if ( typeof oInit.fnDrawCallback == 'function' )
6469
+ {
6470
+ oSettings.aoDrawCallback.push( {
6471
+ "fn": oInit.fnDrawCallback,
6472
+ "sName": "user"
6473
+ } );
6474
+ }
6475
+
6476
+ if ( typeof oInit.fnStateSaveCallback == 'function' )
6477
+ {
6478
+ oSettings.aoStateSave.push( {
6479
+ "fn": oInit.fnStateSaveCallback,
6480
+ "sName": "user"
6481
+ } );
6482
+ }
6483
+
6484
+ if ( typeof oInit.fnStateLoadCallback == 'function' )
6485
+ {
6486
+ oSettings.aoStateLoad.push( {
6487
+ "fn": oInit.fnStateLoadCallback,
6488
+ "sName": "user"
6489
+ } );
6490
+ }
6491
+
6492
+ if ( oSettings.oFeatures.bServerSide && oSettings.oFeatures.bSort &&
6493
+ oSettings.oFeatures.bSortClasses )
6494
+ {
6495
+ /* Enable sort classes for server-side processing. Safe to do it here, since server-side
6496
+ * processing must be enabled by the developer
6497
+ */
6498
+ oSettings.aoDrawCallback.push( {
6499
+ "fn": _fnSortingClasses,
6500
+ "sName": "server_side_sort_classes"
6501
+ } );
6502
+ }
6503
+
6504
+ if ( typeof oInit.bJQueryUI != 'undefined' && oInit.bJQueryUI )
6505
+ {
6506
+ /* Use the JUI classes object for display. You could clone the oStdClasses object if
6507
+ * you want to have multiple tables with multiple independent classes
6508
+ */
6509
+ oSettings.oClasses = _oExt.oJUIClasses;
6510
+
6511
+ if ( typeof oInit.sDom == 'undefined' )
6512
+ {
6513
+ /* Set the DOM to use a layout suitable for jQuery UI's theming */
6514
+ oSettings.sDom = '<"H"lfr>t<"F"ip>';
6515
+ }
6516
+ }
6517
+
6518
+ /* Calculate the scroll bar width and cache it for use later on */
6519
+ if ( oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "" )
6520
+ {
6521
+ oSettings.oScroll.iBarWidth = _fnScrollBarWidth();
6522
+ }
6523
+
6524
+ if ( typeof oInit.iDisplayStart != 'undefined' &&
6525
+ typeof oSettings.iInitDisplayStart == 'undefined' )
6526
+ {
6527
+ /* Display start point, taking into account the save saving */
6528
+ oSettings.iInitDisplayStart = oInit.iDisplayStart;
6529
+ oSettings._iDisplayStart = oInit.iDisplayStart;
6530
+ }
6531
+
6532
+ /* Must be done after everything which can be overridden by a cookie! */
6533
+ if ( typeof oInit.bStateSave != 'undefined' )
6534
+ {
6535
+ oSettings.oFeatures.bStateSave = oInit.bStateSave;
6536
+ _fnLoadState( oSettings, oInit );
6537
+ oSettings.aoDrawCallback.push( {
6538
+ "fn": _fnSaveState,
6539
+ "sName": "state_save"
6540
+ } );
6541
+ }
6542
+
6543
+ if ( typeof oInit.aaData != 'undefined' )
6544
+ {
6545
+ bUsePassedData = true;
6546
+ }
6547
+
6548
+ /* Backwards compatability */
6549
+ /* aoColumns / aoData - remove at some point... */
6550
+ if ( typeof oInit != 'undefined' && typeof oInit.aoData != 'undefined' )
6551
+ {
6552
+ oInit.aoColumns = oInit.aoData;
6553
+ }
6554
+
6555
+ /* Language definitions */
6556
+ if ( typeof oInit.oLanguage != 'undefined' )
6557
+ {
6558
+ if ( typeof oInit.oLanguage.sUrl != 'undefined' && oInit.oLanguage.sUrl !== "" )
6559
+ {
6560
+ /* Get the language definitions from a file */
6561
+ oSettings.oLanguage.sUrl = oInit.oLanguage.sUrl;
6562
+ $.getJSON( oSettings.oLanguage.sUrl, null, function( json ) {
6563
+ _fnLanguageProcess( oSettings, json, true ); } );
6564
+ bInitHandedOff = true;
6565
+ }
6566
+ else
6567
+ {
6568
+ _fnLanguageProcess( oSettings, oInit.oLanguage, false );
6569
+ }
6570
+ }
6571
+ /* Warning: The _fnLanguageProcess function is async to the remainder of this function due
6572
+ * to the XHR. We use _bInitialised in _fnLanguageProcess() to check this the processing
6573
+ * below is complete. The reason for spliting it like this is optimisation - we can fire
6574
+ * off the XHR (if needed) and then continue processing the data.
6575
+ */
6576
+ }
6577
+ else
6578
+ {
6579
+ /* Create a dummy object for quick manipulation later on. */
6580
+ oInit = {};
6581
+ }
6582
+
6583
+ /*
6584
+ * Stripes
6585
+ * Add the strip classes now that we know which classes to apply - unless overruled
6586
+ */
6587
+ if ( typeof oInit.asStripClasses == 'undefined' )
6588
+ {
6589
+ oSettings.asStripClasses.push( oSettings.oClasses.sStripOdd );
6590
+ oSettings.asStripClasses.push( oSettings.oClasses.sStripEven );
6591
+ }
6592
+
6593
+ /* Remove row stripe classes if they are already on the table row */
6594
+ var bStripeRemove = false;
6595
+ var anRows = $('tbody>tr', this);
6596
+ for ( i=0, iLen=oSettings.asStripClasses.length ; i<iLen ; i++ )
6597
+ {
6598
+ if ( anRows.filter(":lt(2)").hasClass( oSettings.asStripClasses[i]) )
6599
+ {
6600
+ bStripeRemove = true;
6601
+ break;
6602
+ }
6603
+ }
6604
+
6605
+ if ( bStripeRemove )
6606
+ {
6607
+ /* Store the classes which we are about to remove so they can be readded on destory */
6608
+ oSettings.asDestoryStrips = [ '', '' ];
6609
+ if ( $(anRows[0]).hasClass(oSettings.oClasses.sStripOdd) )
6610
+ {
6611
+ oSettings.asDestoryStrips[0] += oSettings.oClasses.sStripOdd+" ";
6612
+ }
6613
+ if ( $(anRows[0]).hasClass(oSettings.oClasses.sStripEven) )
6614
+ {
6615
+ oSettings.asDestoryStrips[0] += oSettings.oClasses.sStripEven;
6616
+ }
6617
+ if ( $(anRows[1]).hasClass(oSettings.oClasses.sStripOdd) )
6618
+ {
6619
+ oSettings.asDestoryStrips[1] += oSettings.oClasses.sStripOdd+" ";
6620
+ }
6621
+ if ( $(anRows[1]).hasClass(oSettings.oClasses.sStripEven) )
6622
+ {
6623
+ oSettings.asDestoryStrips[1] += oSettings.oClasses.sStripEven;
6624
+ }
6625
+
6626
+ anRows.removeClass( oSettings.asStripClasses.join(' ') );
6627
+ }
6628
+
6629
+ /*
6630
+ * Columns
6631
+ * See if we should load columns automatically or use defined ones
6632
+ */
6633
+ var nThead = this.getElementsByTagName('thead');
6634
+ var anThs = nThead.length===0 ? [] : _fnGetUniqueThs( nThead[0] );
6635
+ var aoColumnsInit;
6636
+
6637
+ /* If not given a column array, generate one with nulls */
6638
+ if ( typeof oInit.aoColumns == 'undefined' )
6639
+ {
6640
+ aoColumnsInit = [];
6641
+ for ( i=0, iLen=anThs.length ; i<iLen ; i++ )
6642
+ {
6643
+ aoColumnsInit.push( null );
6644
+ }
6645
+ }
6646
+ else
6647
+ {
6648
+ aoColumnsInit = oInit.aoColumns;
6649
+ }
6650
+
6651
+ /* Add the columns */
6652
+ for ( i=0, iLen=aoColumnsInit.length ; i<iLen ; i++ )
6653
+ {
6654
+ /* Check if we have column visibilty state to restore */
6655
+ if ( typeof oInit.saved_aoColumns != 'undefined' && oInit.saved_aoColumns.length == iLen )
6656
+ {
6657
+ if ( aoColumnsInit[i] === null )
6658
+ {
6659
+ aoColumnsInit[i] = {};
6660
+ }
6661
+ aoColumnsInit[i].bVisible = oInit.saved_aoColumns[i].bVisible;
6662
+ }
6663
+
6664
+ _fnAddColumn( oSettings, anThs ? anThs[i] : null );
6665
+ }
6666
+
6667
+ /* Add options from column definations */
6668
+ if ( typeof oInit.aoColumnDefs != 'undefined' )
6669
+ {
6670
+ /* Loop over the column defs array - loop in reverse so first instace has priority */
6671
+ for ( i=oInit.aoColumnDefs.length-1 ; i>=0 ; i-- )
6672
+ {
6673
+ /* Each column def can target multiple columns, as it is an array */
6674
+ var aTargets = oInit.aoColumnDefs[i].aTargets;
6675
+ if ( !$.isArray( aTargets ) )
6676
+ {
6677
+ _fnLog( oSettings, 1, 'aTargets must be an array of targets, not a '+(typeof aTargets) );
6678
+ }
6679
+ for ( j=0, jLen=aTargets.length ; j<jLen ; j++ )
6680
+ {
6681
+ if ( typeof aTargets[j] == 'number' && aTargets[j] >= 0 )
6682
+ {
6683
+ /* 0+ integer, left to right column counting. We add columns which are unknown
6684
+ * automatically. Is this the right behaviour for this? We should at least
6685
+ * log it in future. We cannot do this for the negative or class targets, only here.
6686
+ */
6687
+ while( oSettings.aoColumns.length <= aTargets[j] )
6688
+ {
6689
+ _fnAddColumn( oSettings );
6690
+ }
6691
+ _fnColumnOptions( oSettings, aTargets[j], oInit.aoColumnDefs[i] );
6692
+ }
6693
+ else if ( typeof aTargets[j] == 'number' && aTargets[j] < 0 )
6694
+ {
6695
+ /* Negative integer, right to left column counting */
6696
+ _fnColumnOptions( oSettings, oSettings.aoColumns.length+aTargets[j],
6697
+ oInit.aoColumnDefs[i] );
6698
+ }
6699
+ else if ( typeof aTargets[j] == 'string' )
6700
+ {
6701
+ /* Class name matching on TH element */
6702
+ for ( k=0, kLen=oSettings.aoColumns.length ; k<kLen ; k++ )
6703
+ {
6704
+ if ( aTargets[j] == "_all" ||
6705
+ oSettings.aoColumns[k].nTh.className.indexOf( aTargets[j] ) != -1 )
6706
+ {
6707
+ _fnColumnOptions( oSettings, k, oInit.aoColumnDefs[i] );
6708
+ }
6709
+ }
6710
+ }
6711
+ }
6712
+ }
6713
+ }
6714
+
6715
+ /* Add options from column array - after the defs array so this has priority */
6716
+ if ( typeof aoColumnsInit != 'undefined' )
6717
+ {
6718
+ for ( i=0, iLen=aoColumnsInit.length ; i<iLen ; i++ )
6719
+ {
6720
+ _fnColumnOptions( oSettings, i, aoColumnsInit[i] );
6721
+ }
6722
+ }
6723
+
6724
+ /*
6725
+ * Sorting
6726
+ * Check the aaSorting array
6727
+ */
6728
+ for ( i=0, iLen=oSettings.aaSorting.length ; i<iLen ; i++ )
6729
+ {
6730
+ if ( oSettings.aaSorting[i][0] >= oSettings.aoColumns.length )
6731
+ {
6732
+ oSettings.aaSorting[i][0] = 0;
6733
+ }
6734
+ var oColumn = oSettings.aoColumns[ oSettings.aaSorting[i][0] ];
6735
+
6736
+ /* Add a default sorting index */
6737
+ if ( typeof oSettings.aaSorting[i][2] == 'undefined' )
6738
+ {
6739
+ oSettings.aaSorting[i][2] = 0;
6740
+ }
6741
+
6742
+ /* If aaSorting is not defined, then we use the first indicator in asSorting */
6743
+ if ( typeof oInit.aaSorting == "undefined" &&
6744
+ typeof oSettings.saved_aaSorting == "undefined" )
6745
+ {
6746
+ oSettings.aaSorting[i][1] = oColumn.asSorting[0];
6747
+ }
6748
+
6749
+ /* Set the current sorting index based on aoColumns.asSorting */
6750
+ for ( j=0, jLen=oColumn.asSorting.length ; j<jLen ; j++ )
6751
+ {
6752
+ if ( oSettings.aaSorting[i][1] == oColumn.asSorting[j] )
6753
+ {
6754
+ oSettings.aaSorting[i][2] = j;
6755
+ break;
6756
+ }
6757
+ }
6758
+ }
6759
+
6760
+ /* Do a first pass on the sorting classes (allows any size changes to be taken into
6761
+ * account, and also will apply sorting disabled classes if disabled
6762
+ */
6763
+ _fnSortingClasses( oSettings );
6764
+
6765
+ /*
6766
+ * Final init
6767
+ * Sanity check that there is a thead and tbody. If not let's just create them
6768
+ */
6769
+ if ( this.getElementsByTagName('thead').length === 0 )
6770
+ {
6771
+ this.appendChild( document.createElement( 'thead' ) );
6772
+ }
6773
+
6774
+ if ( this.getElementsByTagName('tbody').length === 0 )
6775
+ {
6776
+ this.appendChild( document.createElement( 'tbody' ) );
6777
+ }
6778
+
6779
+ oSettings.nTHead = this.getElementsByTagName('thead')[0];
6780
+ oSettings.nTBody = this.getElementsByTagName('tbody')[0];
6781
+ if ( this.getElementsByTagName('tfoot').length > 0 )
6782
+ {
6783
+ oSettings.nTFoot = this.getElementsByTagName('tfoot')[0];
6784
+ }
6785
+
6786
+ /* Check if there is data passing into the constructor */
6787
+ if ( bUsePassedData )
6788
+ {
6789
+ for ( i=0 ; i<oInit.aaData.length ; i++ )
6790
+ {
6791
+ _fnAddData( oSettings, oInit.aaData[ i ] );
6792
+ }
6793
+ }
6794
+ else
6795
+ {
6796
+ /* Grab the data from the page */
6797
+ _fnGatherData( oSettings );
6798
+ }
6799
+
6800
+ /* Copy the data index array */
6801
+ oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
6802
+
6803
+ /* Initialisation complete - table can be drawn */
6804
+ oSettings.bInitialised = true;
6805
+
6806
+ /* Check if we need to initialise the table (it might not have been handed off to the
6807
+ * language processor)
6808
+ */
6809
+ if ( bInitHandedOff === false )
6810
+ {
6811
+ _fnInitalise( oSettings );
6812
+ }
6813
+ });
6814
+ };
6815
+ })(jQuery, window, document);
readme.txt CHANGED
@@ -4,7 +4,7 @@ Donate link: http://www.taylorlovett.com
4
  Tags: contact form, web form, custom contact form, custom forms, captcha form, contact fields, form mailers
5
  Requires at least: 2.8.1
6
  Tested up to: 3.0.1
7
- Stable tag: 4.0.1
8
 
9
  Gauranteed to be the most customizable and intuitive contact form plugin for Wordpress.
10
 
@@ -101,6 +101,10 @@ Visit http://www.taylorlovett.com/wordpress-plugins for screenshots. Right now a
101
 
102
  == Changelog ==
103
 
 
 
 
 
104
  = 4.0.1 =
105
  * custom-contact-forms.php
106
  * custom-contact-forms-admin.php - support for multiple form destination emails added
4
  Tags: contact form, web form, custom contact form, custom forms, captcha form, contact fields, form mailers
5
  Requires at least: 2.8.1
6
  Tested up to: 3.0.1
7
+ Stable tag: 4.0.2
8
 
9
  Gauranteed to be the most customizable and intuitive contact form plugin for Wordpress.
10
 
101
 
102
  == Changelog ==
103
 
104
+ = 4.0.2 =
105
+ * custom-contact-forms-front.php - Field instructions bug fixed
106
+ * custom-contact-forms-admin.php - Display change
107
+
108
  = 4.0.1 =
109
  * custom-contact-forms.php
110
  * custom-contact-forms-admin.php - support for multiple form destination emails added