You are here

table_trash.module in Table Trash 7

table_trash.module

Uses DataTables javascript library and third party extensions to it to add client-side bells and whistles to your HTML tables.

File

table_trash.module
View source
<?php

/**
 * @file
 * table_trash.module
 *
 * Uses DataTables javascript library and third party extensions to it to add
 * client-side bells and whistles to your HTML tables.
 */
define('TABLE_TRASH_DEFAULT_PAGE_INCLUSIONS', '');
define('TABLE_TRASH_DEFAULT_PAGE_EXCLUSIONS', "admin/reports/status\nadmin/modules*");
define('TABLE_TRASH_DEFAULT_TABLE_SELECTOR', 'table');
define('TABLE_TRASH_DEFAULT_BREAKPOINT_PHONE', 480);
define('TABLE_TRASH_DEFAULT_BREAKPOINT_TABLET', 1024);
define('TT_DATATABLES_JS', 'https://cdn.datatables.net/s/dt/jszip-2.5.0,pdfmake-0.1.18,dt-1.10.10,b-1.1.0,b-colvis-1.1.0,b-html5-1.1.0,b-print-1.1.0,cr-1.3.0,fc-3.2.0,fh-3.1.0,r-2.0.0/datatables.min.js');
define('TT_DATATABLES_CSS', 'https://cdn.datatables.net/s/dt/jszip-2.5.0,pdfmake-0.1.18,dt-1.10.10,b-1.1.0,b-colvis-1.1.0,b-html5-1.1.0,b-print-1.1.0,cr-1.3.0,fc-3.2.0,fh-3.1.0,r-2.0.0/datatables.min.css');

/**
 * Implements hook_ctools_plugin_directory().
 */
function table_trash_ctools_plugin_directory($module, $type) {
  if ($type == 'export_ui') {
    return 'plugins/export_ui';
  }
}

/**
 * Implements hook_menu().
 */
function table_trash_menu() {

  // Global settings page.
  $items['admin/config/content/table_trash/global_settings'] = array(
    'title' => 'Global Settings',
    'description' => 'Configure table decorations and global settings.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'table_trash_admin_global_settings_form',
    ),
    'access arguments' => array(
      'configure table decorations',
    ),
    'file' => 'table_trash.admin.inc',
    'type' => MENU_LOCAL_TASK,
  );

  // Update path page.
  $items['admin/config/content/table_trash/update_config'] = array(
    'title' => 'Update',
    'description' => 'Update configuration.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'table_trash_admin_update_config_form',
    ),
    'access callback' => 'table_trash_update_config_access_callback',
    'file' => 'table_trash.admin.inc',
    'type' => MENU_LOCAL_TASK,
  );
  return $items;
}

/**
 * Access callback to determine if the update page should be shown.
 */
function table_trash_update_config_access_callback() {
  $perm = user_access('configure table decorations');
  $var = variable_get('table_trash_decorations');
  return $perm && $var;
}

/**
 * Implements hook_preprocess_page().
 */
function table_trash_preprocess_html($variables) {
  ctools_include('export');
  $decorations = ctools_export_crud_load_all('table_trash');
  $path = current_path();
  $path_alias = drupal_get_path_alias($path);
  $settings = array();
  foreach ($decorations as $decoration) {
    if (isset($decorations->disabled) && $decorations->disabled == TRUE) {
      continue;
    }
    if (!empty($decoration->data['selectors'])) {
      $selectors = $decoration->data['selectors'];
      if (drupal_match_path($path, $selectors['included_pages']) || drupal_match_path($path_alias, $selectors['included_pages'])) {
        if (!drupal_match_path($path, $selectors['excluded_pages']) && !drupal_match_path($path_alias, $selectors['excluded_pages'])) {
          $selector = key($decoration->data['decoration_json']);
          $settings[$selector] = $decoration->data['decoration_json'][$selector];

          // @see http://webdesign.tutsplus.com/tutorials/htmlcss-tutorials/quick-tip-dont-forget-the-viewport-meta-tag
          // It's ok to call this more than once
          table_trash_add_html_head();

          // @TODO Can we insert <thead> for <table>s that do not have it?
        }
      }
    }
  }
  if ($settings) {
    $global_settings = variable_get('table_trash_global_settings');
    drupal_add_js(drupal_get_path('module', 'table_trash') . "/js/table_trash.js");
    drupal_add_js(array(
      'table_trash' => $settings,
    ), array(
      'type' => 'setting',
    ));
    if ($global_settings['use_table_trash_css']) {
      drupal_add_css(drupal_get_path('module', 'table_trash') . "/css/table_trash.css");
    }

    // Load the library based on the chosen loading method.
    switch ($global_settings['load_from']) {
      case 'module':
        $library = libraries_load('datatables');
        if (!empty($library['error'])) {
          drupal_set_message($library['error message'], 'warning');
        }
        if ($global_settings['use_datatables_css']) {
          drupal_add_css(drupal_get_path('module', 'table_trash') . "/library/DataTables/datatables.min.css");
        }
        break;
      case 'cdn':
        drupal_add_js(TT_DATATABLES_JS, 'external');
        if ($global_settings['use_datatables_css']) {
          drupal_add_css(TT_DATATABLES_CSS, 'external');
        }
        break;
      case 'alternate':
        drupal_add_js($global_settings['alternate_cdn_js'], 'external');
        if ($global_settings['use_datatables_css']) {
          drupal_add_css($global_settings['alternate_cdn_css'], 'external');
        }
        break;
    }
  }
}

/**
 * Implements hook_permission().
 */
function table_trash_permission() {
  return array(
    'configure table decorations' => array(
      'title' => t('Add and configure table decorations'),
    ),
  );
}

/**
 * Implements hook_help().
 */
function table_trash_help($path, $arg) {
  switch ($path) {
    case 'admin/help#table_trash':
      $t = t('Configuration instructions and tips are in this <a target="readme" href="@README">README</a> file.<br/>Known issues and solutions may be found on the <a taget="project" href="@table_trash">Table Trash</a> project page.', array(
        '@README' => url(drupal_get_path('module', 'table_trash') . '/README.txt'),
        '@table_trash' => url('http://drupal.org/project/table_trash'),
      ));
      break;
    case 'admin/config/content/table_trash':
      $t = t('A <strong>table decoration</strong> consists of a set of table features, selected below, to be added to one or more tables on this site. Apart from the features you wish to include in each decoration, you specify the pages and tables the decoration applies to.');
      break;
  }
  return empty($t) ? '' : '<p>' . $t . '</p>';
}

/**
 * Implements hook_libraries_info().
 */
function table_trash_libraries_info() {
  $libraries['datatables'] = array(
    'name' => 'Datatables',
    'vendor url' => 'https://www.datatables.net',
    'download url' => 'http://datatables.net/download/index',
    'library path' => drupal_get_path('module', 'table_trash') . '/library/DataTables',
    'version arguments' => array(
      'file' => 'datatables.js',
      'pattern' => '@(?:DataTables)\\s?([0-9\\.]+)@',
    ),
    'files' => array(
      'js' => array(
        'datatables.min.js',
      ),
    ),
    'variants' => array(
      'minified' => array(
        'files' => array(
          'js' => array(
            'datatables.min.js',
          ),
        ),
      ),
      'source' => array(
        'files' => array(
          'js' => array(
            'datatables.js',
          ),
        ),
      ),
    ),
  );
  return $libraries;
}

/**
 * Adds a <meta viewport> tag to the <head>.
 *
 * Add inside the <head> tag the following meta-tag essential for mobiles:
 * <meta name="viewport" content="initial-scale=1" />
 */
function table_trash_add_html_head() {
  $data = array(
    '#tag' => 'meta',
    '#attributes' => array(
      'name' => 'viewport',
      'content' => 'initial-scale=1',
    ),
  );
  drupal_add_html_head($data, 'system_meta_viewport');
}

/**
 * Setup the array of settings for the decoration.
 *
 * @param array $decoration
 *   An array of DataTable settings.
 */
function table_trash_process_decoration_config($decoration) {
  $table_selector = empty($decoration->data['selectors']['included_css_tables']) ? TABLE_TRASH_DEFAULT_TABLE_SELECTOR : $decoration->data['selectors']['included_css_tables'];
  $decoration_config = empty($decoration->data['decoration_config']) ? array() : $decoration->data['decoration_config'];
  $not_orderable = isset($decoration_config['not_orderable']) ? trim($decoration_config['not_orderable']) : '';

  /* 'dom' is used to specify where in the DOM to inject the various controls
   * DataTables adds to the page. For example you might want the pagination
   * controls at the top of the table. The following order is the default:
   *
   *    'l' - length changing
   *    'f' - filtering input
   *    'r' - processing
   *    't' - the table
   *    'i' - information
   *    'p' - pagination
   *
   * @see https://datatables.net/reference/option/dom
   */
  $settings[$table_selector] = array(
    'dom' => 'lfrtip',
    // Searching
    // https://datatables.net/reference/option/searching
    'searching' => isset($decoration_config['searching']) ? $decoration_config['searching'] : FALSE,
    // Ordering
    // https://datatables.net/reference/option/ordering
    'ordering' => $not_orderable !== '0' ? TRUE : FALSE,
    // Paging
    // https://datatables.net/reference/option/paging
    'paging' => !empty($decoration_config['paging_type']) && !empty($decoration_config['page_length']),
    'pagingType' => isset($decoration_config['paging_type']) ? $decoration_config['paging_type'] : '',
    'pageLength' => empty($decoration_config['page_length']) ? 0 : (int) $decoration_config['page_length'],
    // @TODO support lengthChange
    'lengthChange' => FALSE,
    // Retrieve
    // https://datatables.net/reference/option/retrieve
    'retrieve' => !empty($decoration_config['retrieve']),
    // Destroy
    // https://datatables.net/reference/option/destroy
    'destroy' => TRUE,
    // Force sort buttons to top row
    // https://datatables.net/reference/option/orderCellsTop
    'orderCellsTop' => TRUE,
    // Info
    // https://datatables.net/reference/option/info
    'info' => isset($decoration_config['info']) ? $decoration_config['info'] : FALSE,
  );

  // Column Sorting
  // https://datatables.net/reference/option/columns.orderable
  if (!empty($not_orderable)) {
    $not_orderable = array_map('intval', explode(',', $not_orderable));
    $settings[$table_selector]['columnDefs'][] = array(
      "orderable" => FALSE,
      "targets" => $not_orderable,
    );
  }

  // Horizontal Scrolling
  // https://datatables.net/reference/option/scrollX
  if (!empty($decoration_config['scrollX'])) {
    $settings[$table_selector]["scrollX"] = $decoration_config['scrollX'] ? TRUE : FALSE;
  }

  // Vertical Scrolling
  // https://datatables.net/reference/option/scrollY
  if (!empty($decoration_config['scrollY'])) {
    $settings[$table_selector]["scrollY"] = $decoration_config['scrollY'];
  }

  // Vertical Collapsing
  // https://datatables.net/reference/option/scrollCollapse
  if (!empty($decoration_config['scrollCollapse'])) {
    $settings[$table_selector]["scrollCollapse"] = $decoration_config['scrollCollapse'];
  }

  // Column Reordering
  // https://datatables.net/reference/option/colReorder
  $settings[$table_selector]['colReorder'] = $decoration_config['col_reorder'] ? TRUE : FALSE;

  // Fixed Columns
  // https://datatables.net/reference/option/fixedColumns
  if ($decoration_config['fixed_columns_left']) {
    $settings[$table_selector]['fixedColumns'] = array(
      'leftColumns' => (int) $decoration_config['fixed_columns_left'],
      'rightColumns' => (int) $decoration_config['fixed_columns_right'],
    );
  }

  // Fixed Header and Footer
  // https://datatables.net/reference/option/fixedHeader
  $settings[$table_selector]['fixedHeader'] = array(
    'header' => !empty($decoration_config['fixed_header']),
    'footer' => !empty($decoration_config['fixed_footer']),
  );

  // Export buttons.
  // @TODO support column visibility
  // https://datatables.net/extensions/buttons/
  if (!empty($decoration_config['buttons_export'])) {
    $settings[$table_selector]['dom'] = 'B' . $settings[$table_selector]['dom'];
    $settings[$table_selector]['buttons'] = array_keys($decoration_config['buttons_export']);
  }

  // Responsive Extension
  // https://datatables.net/extensions/responsive/
  if (!empty($decoration_config['responsive_config']['responsive_control_column'])) {
    $responsive_config = $decoration_config['responsive_config'];
    $responsiveConfig = array();
    $responsiveConfig['details'] = array(
      "type" => "inline",
    );

    // Set the column where the control for the child row will be
    // We start at 1, dataTables starts at 0.
    $settings[$table_selector]['columnDefs'][] = array(
      "className" => 'control',
      'orderable' => false,
      'targets' => array(
        $responsive_config['responsive_control_column'] - 1,
      ),
    );

    // Set the breakpoints.
    // @TODO support more then 2 break points?
    $phoneBP = empty($global_settings['responsive']['breakpoint_phone']) ? TABLE_TRASH_DEFAULT_BREAKPOINT_PHONE : (int) $global_settings['responsive_config']['breakpoint_phone'];
    $tabletBP = empty($global_settings['responsive']['breakpoint_tablet']) ? TABLE_TRASH_DEFAULT_BREAKPOINT_TABLET : (int) $global_settings['responsive_config']['breakpoint_tablet'];
    $responsiveConfig['breakpoints'] = array(
      array(
        "name" => "mobile",
        "width" => $phoneBP,
      ),
      array(
        "name" => "tablet",
        "width" => $tabletBP,
      ),
    );

    // Apply the classes to hide columns.
    if (!empty($responsive_config['responsive_phone_columns'])) {
      $columns = array_map('intval', explode(',', $responsive_config['responsive_phone_columns']));
      $settings[$table_selector]['columnDefs'][] = array(
        "className" => 'mobile',
        'targets' => $columns,
      );
    }
    if (!empty($responsive_config['responsive_tablet_columns'])) {
      $columns = array_map('intval', explode(',', $responsive_config['responsive_tablet_columns']));
      $settings[$table_selector]['columnDefs'][] = array(
        "className" => 'tablet',
        'targets' => $columns,
      );
    }
    $settings[$table_selector]['responsive'] = $responsiveConfig;
  }
  $decoration->data['decoration_json'] = $settings;
}