You are here

slickgrid.module in Slickgrid 6

Same filename and directory in other branches
  1. 7.2 slickgrid.module
  2. 7 slickgrid.module

File

slickgrid.module
View source
<?php

/*********************************************************************************************
 * 
 * CONSTANTS
 * 
 ********************************************************************************************/
define('SLICKGRID_CALLBACK_PATH', 'slickgrid/callback');

/*********************************************************************************************
 * 
 * HOOKS
 * 
 ********************************************************************************************/

/** 
 * Implementation of hook_init().
 * The full lightbox2 does not work with jquery 1.4.3, so need to turn it off for slickgrid pages
 */
function slickgrid_init() {
  global $conf;
  if (module_exists('lightbox2')) {

    // Is this a slickgrid callback?
    // JS files are added via ajax_load so need to set lightbox2_lite here too
    if (arg(0) == 'slickgrid' && arg(1) == 'callback') {
      $conf['lightbox2_lite'] = TRUE;
    }
    else {
      $menu_item = menu_get_item();

      // Is this a slickgrid view page?
      if ($menu_item['page_callback'] == 'views_page') {
        $view = views_get_view($menu_item['page_arguments'][0]);
        if ($view->display['default']->display_options['style_plugin'] == 'slickgrid') {

          // If it is set lightbox2_lite = true so the full lightbox JS won't get added to the page
          $conf['lightbox2_lite'] = TRUE;
        }
      }
    }
  }
}

/** 
 * Implementation of hook_menu(). 
 */
function slickgrid_menu() {

  // Menu callbacks
  $items[SLICKGRID_CALLBACK_PATH . '/%'] = array(
    'page callback' => 'slickgrid_callback',
    'page arguments' => array(
      2,
    ),
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'slickgrid.callbacks.inc',
  );
  return $items;
}

/** 
 * Implementation of hook_menu_alter(). 
 */
function slickgrid_menu_alter(&$items) {

  // JSON parsing is much stricter is jquery 1.4 so using drupal_to_js() in AHAH callbacks will result in a parse error
  // Rewrite the callbacks to use json_encode()
  // Content add more
  $items['content/js_add_more']['page callback'] = 'slickgrid_content_add_more_js';
  $items['content/js_add_more']['file'] = 'slickgrid.ahah.inc';
  $items['content/js_add_more']['file path'] = drupal_get_path('module', 'slickgrid');

  // File field
  $items['filefield/ahah/%/%/%']['page callback'] = 'slickgrid_filefield_js';
  $items['filefield/ahah/%/%/%']['file'] = 'slickgrid.ahah.inc';
  $items['filefield/ahah/%/%/%']['file path'] = drupal_get_path('module', 'slickgrid');

  // Node reference autocomplete
  $items['nodereference/autocomplete']['page callback'] = 'slickgrid_nodereference_autocomplete';
  $items['nodereference/autocomplete']['file'] = 'slickgrid.ahah.inc';
  $items['nodereference/autocomplete']['file path'] = drupal_get_path('module', 'slickgrid');
}

/**
 * Implementation of hook_views_api
 */
function slickgrid_views_api() {
  return array(
    'api' => 2,
  );
}

/**
 * Implementation of hook_views_pre_view
 * Slickgrid handles the paging internally so need to override 
 * @param object $view 
 * @return void
 * @author Ben Scott
 */
function slickgrid_views_pre_view(&$view, $display_id) {
  global $conf;

  // TODO - Can this be moved into the views style plugin? See plugins.inc
  // Is this view a slickgrid view
  if ($view->display_handler
    ->get_option('style_plugin') == 'slickgrid') {

    // Really hacky, but ajax_load chokes on agrregated JS so I need to turn it off
    // TODO There must be a better way of working round this!
    $conf['preprocess_js'] = 0;
    $style_options = $view->display_handler
      ->get_option('style_options');
    if ($view->display_handler
      ->get_option('use_pager')) {

      // turn off views paging
      $view->display_handler
        ->set_option('use_pager', FALSE);

      // let slickgrid know we want to use it's own paging system by setting it as a view style option
      $style_options['pager'] = TRUE;
    }
    $view->display_handler
      ->set_option('style_options', $style_options);

    // If this is using a collapsible taxonomy field, we need to ensure the nodes are in the right order, and the parent TID is available
    // Much faster to do it now than in a preprocesser after the view data has been constructed
    if ($style_options['collapsible_taxonomy_field']) {

      // Define the parent relationship
      $parent_relationship = array(
        'label' => t('Slickgrid parent'),
        'required' => 0,
        'id' => 'parent',
        'table' => 'term_hierarchy',
        'field' => 'parent',
      );
      $view
        ->set_item($display_id, 'relationship', 'slickgrid_parent_relationship', $parent_relationship);

      // Ensure the tid of the parent term is available but hidden from the view
      $parent_tid_field = array(
        'exclude' => 1,
        'id' => 'slickgrid_parent_tid',
        'table' => 'term_data',
        'field' => 'tid',
        'relationship' => 'slickgrid_parent_relationship',
      );
      $view
        ->set_item($display_id, 'field', 'slickgrid_parent_tid', $parent_tid_field);

      // Ensure the tid of the actual term is available but hidden from the view
      $tid_field = array(
        'exclude' => 1,
        'id' => 'slickgrid_tid',
        'table' => 'term_data',
        'field' => 'tid',
      );
      $view
        ->set_item($display_id, 'field', 'slickgrid_tid', $tid_field);

      // The taxonomy sorts need to come first - so remove the exisitng sorts & re-add them after my own
      $existing_sorts = $view
        ->get_items('sort');

      // Unset all the existing sorts
      foreach (array_keys($existing_sorts) as $existing_sort_id) {
        $view
          ->set_item($display_id, 'sort', $existing_sort_id, null);
      }

      // Add two sorts to the view - one for the parent term, one for the child
      foreach (array(
        'slickgrid_parent_relationship',
        'none',
      ) as $relationship) {
        $id = 'slickgrid_sort_' . $relationship;
        $sort = array(
          'order' => 'ASC',
          'id' => $relationship,
          'table' => 'term_data',
          'field' => 'weight',
          'relationship' => $relationship,
        );
        $view
          ->set_item($display_id, 'sort', $id, $sort);
      }

      // Re add the original sorts
      // Unset all the existing sorts
      foreach ($existing_sorts as $existing_sort_id => $existing_sort) {
        $view
          ->set_item($display_id, 'sort', $existing_sort_id, $existing_sort);
      }
    }
  }
}

/** 
 * Implementation of hook_theme(). 
 */
function slickgrid_theme() {
  return array(
    // Display the matrix editor
    'slickgrid' => array(
      'arguments' => array(
        'options' => array(),
        'columns' => array(),
        'data' => array(),
      ),
    ),
    'slickgrid_views_plugin_table' => array(
      'arguments' => array(
        'form' => array(),
      ),
    ),
  );
}

/**
 * Implementation of hook_slickgrid_plugins().
 */
function slickgrid_slickgrid_plugins() {
  $path = drupal_get_path('module', 'slickgrid');
  $plugins = array(
    'TextCellEditor' => array(
      'description' => t('Text cell'),
      'js' => $path . '/js/slickgrid.editors.js',
      'css' => $path . '/css/slickgrid.editors.css',
      'field_type' => array(
        'node_title',
        'date_text',
      ),
      // Need to only allow for certain field types.
      'plugin_type' => 'editor',
    ),
    'LongTextCellEditor' => array(
      'description' => t('Simple textarea'),
      'js' => $path . '/js/slickgrid.editors.js',
      'css' => $path . '/css/slickgrid.editors.css',
      'field_type' => array(
        'text_textarea',
        'node_revisions_body',
      ),
      'plugin_type' => 'editor',
    ),
    'nodeFormEditor' => array(
      'description' => t('Popup node form'),
      'js' => $path . '/js/slickgrid.editors.js',
      'css' => $path . '/css/slickgrid.editors.css',
      'plugin_type' => 'editor',
    ),
    // Validators
    'requiredFieldValidator' => array(
      'description' => t('Required field'),
      'js' => $path . '/js/slickgrid.validators.js',
      'plugin_type' => 'validator',
    ),
    //    	'field_type' => array() // Works for all fields types
    // Filters
    'textFilter' => array(
      'description' => t('Text filter'),
      'js' => $path . '/js/slickgrid.filters.js',
      'plugin_type' => 'filter',
    ),
  );
  return $plugins;
}

/** 
 * Implementation of hook_user(). 
 */
function slickgrid_user($op, &$edit, &$account, $category = NULL) {
  switch ($op) {
    case 'delete':

      // User is being deleted - remove any slickgrid settings
      slickgrid_delete_settings(array(
        'uid' => $account->uid,
      ));
      break;
  }
}

/**
 * Implementation of hook_form_alter().
 */
function slickgrid_form_alter(&$form, $form_state, $form_id) {
  global $user;
  switch ($form_id) {
    case 'views_ui_rearrange_form':

      // We want to reset field visiabilty & order when views fields are rearranged
      // Add another submit handler so we can get a handle on this
      $form['buttons']['submit']['#submit'][] = 'slickgrid_views_ui_rearrange_form_submit';
      break;
  }

  // Is this a node type form being displayed in a pop up?
  if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] . '_node_form' == $form_id && arg(0) == 'slickgrid') {
    $form['#pre_render'][] = 'slickgrid_node_form_pre_render';
  }
}

/*********************************************************************************************
 * 
 * PREPROCESSOR FUNCTIONS
 * 
 ********************************************************************************************/

/**
 * Preprocessor for views-view-slickgrid.tpl.php
 * Repurpose the views result for slickgrid
 */
function slickgrid_preprocess_views_view_slickgrid(&$vars) {
  global $user;

  // We'll need node.pages so the node forms can be pseudo-rendered
  module_load_include('inc', 'node', 'node.pages');
  $view = $vars['view'];

  // Get any view settings
  $view->style_plugin->options['settings'] = (array) slickgrid_get_settings($user->uid, $view->name) + (array) $view->style_plugin->options['settings'];

  // Set header height default
  $view->style_plugin->options['headerHeight'] = 42;

  // Add some extra slickgrid options here
  $view->style_plugin->options['enableCellNavigation'] = $view->style_plugin->options['editable'];
  $view->style_plugin->options['enableAddRow'] = false;

  // Overide view height option if it's been set by user
  if (is_numeric($view->style_plugin->options['settings']['viewport_height'])) {
    $view->style_plugin->options['viewport_height'] = $view->style_plugin->options['settings']['viewport_height'];
  }

  // Overide forceFitColumns option if it's been set by user
  if (isset($view->style_plugin->options['settings']['forceFitColumns'])) {
    $view->style_plugin->options['forceFitColumns'] = (int) $view->style_plugin->options['settings']['forceFitColumns'];
  }
  $result = $vars['rows'];
  $handler = $view->style_plugin;
  $handlers = $view->style_plugin->display->handler
    ->get_handlers('field');
  $fields =& $view->field;
  $ordered_columns = array();
  $unordered_columns = array();

  // Build the slickgrid columns
  foreach ($handlers as $field => $handler) {

    // If this field is ecluded from the display, continue to the next one
    if ($handler->options['exclude']) {
      continue;
    }
    if (!($name = $handler
      ->label())) {
      $name = $handler
        ->ui_name();
    }
    $handler->content_field['field_name'] ? $field_name = $handler->content_field['field_name'] : ($field_name = $field);

    // Get the column width
    if ($view->style_plugin->options['settings']['column_width'][$field]) {

      // UI column resized
      $column_width = $view->style_plugin->options['settings']['column_width'][$field];
    }
    elseif ($view->style_plugin->options['columns'][$field]['width']) {

      // width set from within the view
      $column_width = $view->style_plugin->options['columns'][$field]['width'];
    }
    else {
      $column_width = 100;

      // default width for all unset columns
    }
    $column = array(
      'id' => $field,
      // The ID of the field from the view
      'name' => $name,
      // Column title / label
      'field' => $field_name,
      // The CCK / Node field name - data values match the field
      'width' => $column_width,
      'cssClass' => 'cell-title',
      'resizable' => $view->style_plugin->options['enableColumnResize'] ? 1 : 0,
    );

    // Is this a taxonomy term field?
    if (is_array($handler->options['vids']) && ($vid = slickgrid_taxonomy_field_get_vid($handler))) {
      $column['vid'] = $vid;
    }

    // Is this field being filtered?
    // If any fields require filtering, we need to let the JS know
    if ($view->style_plugin->options['columns'][$field]['filter']) {
      $view->style_plugin->options['has_filters'] = TRUE;

      // Add a header row for the filters
      // Don't add a headerRow if the filter is set on a taxonomy filter field - this won't require a header row
      if ($view->style_plugin->options['collapsible_taxonomy_field'] != $field) {
        $view->style_plugin->options['showHeaderRow'] = TRUE;
      }
    }

    // Loop through all the plugin types and see if it's been set for this column
    foreach (array_keys(slickgrid_get_plugin_types()) as $plugin_type) {

      // Is there an plugin defined for this column
      if ($view->style_plugin->options['columns'][$field][$plugin_type]) {
        $column[$plugin_type] = $view->style_plugin->options['columns'][$field][$plugin_type];

        // If just one column is editable, turn on editable & cell navigation for the grid
        if ($plugin_type == 'editor') {
          $view->style_plugin->options['editable'] = TRUE;
          $view->style_plugin->options['enableCellNavigation'] = TRUE;
        }
      }
    }

    // Is this field sortable?
    // If any fields are sortable, set style option so we can access it in the js
    // DO NOT turn on sortable columns if collapsible_taxonomy_field is set - they are not compatible
    if ($view->style_plugin->options['columns'][$field]['sortable'] & !$view->style_plugin->options['collapsible_taxonomy_field']) {
      $view->style_plugin->options['sortable_columns'] = TRUE;
      $column['sortable'] = 1;
    }

    // Is this using the collapsible tree formatter plugin?
    if ($view->style_plugin->options['collapsible_taxonomy_field'] == $field) {

      // Set the filters & formatters for the collapsible field column
      $view->style_plugin->options['columns'][$field]['filter'] = $column['filter'] = 'collapsibleFilter';
      $view->style_plugin->options['columns'][$field]['formatter'] = $column['formatter'] = 'collapsibleFormatter';
      $view->style_plugin->options['has_filters'] = true;
    }

    // We need to know the column label for the group by function in JS
    // Add it to the views plugin options so we can access it efficiantly (otherwise I'll need to loop thru columns array)
    $view->style_plugin->options['columns'][$field]['label'] = $name;
    if (is_array($view->style_plugin->options['settings']['ordered_columns']) && is_numeric($ordered_column_position = array_search($field, $view->style_plugin->options['settings']['ordered_columns']))) {

      // This is an ordered column
      $ordered_columns[$ordered_column_position] = $column;
    }
    else {

      // There is no ordering for this column
      $unordered_columns[] = $column;
    }
  }
  ksort($ordered_columns);

  // Merge ordered & unordered columns
  // Any unordered columns are added to the end to allow for new columns added through the view
  $columns = array_merge($ordered_columns, $unordered_columns);

  // Allow other modules to change the columns before they're output as a slickgrid
  drupal_alter('slickgrid', $data, 'columns', $view);

  // Construct the slickgrid data array
  $keys = array_keys($view->field);
  $parents = array();

  // array of tid => array(nid => nid, indent => indent) so we can retrive the nid & indentation of a parent node
  $data = array();
  if (is_array($result)) {

    // Keep a record of all ids & trigger an error on duplication
    $ids = array();
    foreach ($result as $count => $row) {

      // Has this already been added to the data grid?
      if (in_array($row->nid, $ids)) {
        drupal_set_message(t('There are duplicate nodes in this grid - each row must be unique.'), 'error');
        watchdog('slickgrid', t('Duplicate nodes - @id is not unique.'), array(
          '@id' => $row->{$view->base_field},
        ), WATCHDOG_ERROR);
        break;
      }
      else {

        // Add the data fields
        foreach ($columns as $column) {
          if (!$view->field[$column['field']]->options['exclude']) {
            $data[$count][$column['field']] = $view->field[$column['id']]
              ->theme($row);
          }
        }
        $data[$count]['id'] = $row->nid;

        // If this has collapsible_taxonomy_field set, we will have added slickgrid_parent_tid & slickgrid_tid in hook_views_pre_view
        // These are aliased in $row so get the field aliases
        if (property_exists($row, $view->field['slickgrid_tid']->field_alias) && ($slickgrid_tid = $row->{$view->field['slickgrid_tid']->field_alias})) {
          if ($parent = $parents[$row->{$view->field['slickgrid_parent_tid']->field_alias}]) {

            // If we can find a parent, set the parent nid & indentation++
            $data[$count]['indent'] = $parent['indent'] + 1;
            $data[$count]['parent'] = $parent['nid'];
          }
          else {

            // Otherwise set indent & parent to 0 - this breaks if left blank
            $data[$count]['indent'] = 0;
            $data[$count]['parent'] = 0;
          }

          // Store the indent & nid so then can be used to calculate child term indentation
          $parents[$slickgrid_tid] = array(
            'nid' => $row->nid,
            'indent' => $data[$count]['indent'],
          );
        }
      }
    }
  }

  // Allow other modules to change the data before it's output as a slickgrid
  drupal_alter('slickgrid', $data, 'data', $view);
  $vars['slickgrid'] = theme('slickgrid', $view->style_plugin->options, $columns, $data, $view);
}

/*********************************************************************************************
 * 
 * THEME FUNCTIONS
 * 
 ********************************************************************************************/

/**
 * The main theme function for the slickgrid
 * @param array $options
 * @param array $columns
 * @param array $data
 * @param array $view_name
 */
function theme_slickgrid($options, $columns, $data, $view) {
  $output = '';

  // Add all the CSS & JS
  _slickgrid_add_files($options, $columns);
  $js = array();
  $js[] = 'var options = ' . drupal_to_js($options) . ';';
  $js[] = 'var data = [];';
  if (count($data)) {
    $js[] = 'data = ' . drupal_to_js($data) . ';';
  }
  $js[] = 'var columns = ' . _slickgrid_drupal_to_js($columns) . ';';
  $js[] = 'var slickgrid;';
  $js[] = '$(function() {';
  $js[] = 'slickgrid = new Slickgrid("#slickgrid", "' . $view->name . '", "' . $view->current_display . '", "' . url(SLICKGRID_CALLBACK_PATH) . '");';
  $js[] = '})';
  drupal_add_js(implode("\n", $js), 'inline');

  // Adjust hieght to allow for scrollbar
  // output a placeholder div for the slickgrid
  $output .= '<div id="slickgrid" style="width:100%;height:' . $options['viewport_height'] . 'px;" class="hideCols hideRows ' . ($options['editable'] ? 'editable' : '') . '"></div>';
  $output .= '<div id="controls">';
  if ($options['pager']) {
    $output .= '<div id="slickgrid-pager"></div>';
  }
  if ($options['undo']) {
    $output .= '<div id="slickgrid-undo" title="Undo"></div>';
  }
  $output .= '<div id="slickgrid-status"></div>';
  $output .= '<div id="slickgrid-loading-bar"></div>';
  $output .= '</div>';
  if ($options['delete_nodes']) {
    $output .= '<div id="slickgrid-delete" style="display:none;position:absolute"><h6>Delete node</h6><p>Are you sure you want to delete this node? This action cannot be undone!</p><p class="buttons"><input type="button" value="Delete" id="slickgrid-delete-button" /><input type="button" value="Cancel" id="slickgrid-cancel-delete" /></p></div>';
  }
  return $output;
}

/**
 * Theme the form for the matrix style plugin
 */
function theme_slickgrid_views_plugin_table($form) {
  $output = drupal_render($form['description_markup']);
  $header = array(
    t('Field'),
    t('Width'),
    t('Sortable'),
  );
  $rows = array();
  $plugin_types = slickgrid_get_plugin_types();
  $header += $plugin_types;
  foreach (element_children($form['columns']) as $id) {
    $row = array();
    $row[] = drupal_render($form['columns'][$id]['name']);
    $row[] = drupal_render($form['columns'][$id]['width']);
    $row[] = drupal_render($form['columns'][$id]['sortable']);
    foreach (array_keys($plugin_types) as $plugin_type) {
      $row[] = drupal_render($form['columns'][$id][$plugin_type]);
    }
    $rows[] = $row;
  }
  $output .= theme('table', $header, $rows);
  $output .= drupal_render($form);
  return $output;
}

/*********************************************************************************************
 * 
 * DAO GET / SETTERS
 * 
 ********************************************************************************************/
function slickgrid_set_settings($uid, $view_name, $settings) {
  $record = new stdClass();
  $record->uid = $uid;
  $record->view_name = $view_name;
  if ($record->settings = slickgrid_get_settings($uid, $view_name)) {
    $update = array(
      'uid',
      'view_name',
    );
  }
  else {
    $update = array();
  }
  foreach ($settings as $setting => $value) {
    $record->settings[$setting] = $value;
  }
  $record->settings = serialize($record->settings);
  drupal_write_record('slickgrid', $record, $update);
}

/**
 * 
 * Get settings from the DB
 * Pass in $setting to retrieve a particular setting, NULL to get akll for a UID / View
 * @param string $uid
 * @param string $view_name
 * @param string $setting
 */
function slickgrid_get_settings($uid, $view_name, $setting = null) {
  $settings = unserialize(db_result(db_query("SELECT settings FROM {slickgrid} WHERE uid = %d AND view_name = '%s'", $uid, $view_name)));
  if ($setting) {
    return $settings[$setting];
  }
  else {
    return $settings;
  }
}

/**
 * Delete settings from the database
 * @param array $params
 * Can be any key value pair where key is the table field, value is the value (like UID, view name) to retrieve
 */
function slickgrid_delete_settings($params) {
  $conj = '';
  if (count($params)) {
    $sql = 'DELETE FROM {slickgrid} WHERE ';
    foreach ($params as $field => $param) {
      $sql .= $conj . $field . ' = "%s"';
      $conj = ' AND ';
    }
    db_query($sql, $params);
  }
}

/*********************************************************************************************
 * 
 * OTHER FUNCTIONS
 * 
 ********************************************************************************************/
function _slickgrid_add_files($options, $columns) {
  $path = drupal_get_path('module', 'slickgrid');
  drupal_add_js($path . '/js/slickgrid/lib/jquery-1.4.3.min.js');

  // Add slickgrid dependencies
  drupal_add_js($path . '/js/slickgrid/lib/firebugx.js');
  drupal_add_js($path . '/js/slickgrid/lib/jquery.event.drag-2.0.min.js');

  // Add the slickgrid files
  drupal_add_js($path . '/js/slickgrid/slick.core.js');
  drupal_add_js($path . '/js/slickgrid/slick.dataview.js');
  drupal_add_js($path . '/js/slickgrid/slick.grid.js');
  drupal_add_css($path . '/js/slickgrid/slick.grid.css');

  // Add jQuery UI files
  // Uses the UI files included in slickgrid to ensure compatibility
  drupal_add_js($path . '/js/slickgrid/lib/jquery-ui-1.8.5.custom.min.js');
  drupal_add_css($path . '/js/slickgrid/css/smoothness/jquery-ui-1.8.5.custom.css');

  // Depending on options slected for the view, enable slickgrid plugins
  // Is check box select column enabled?
  drupal_add_js($path . '/js/slickgrid/plugins/slick.checkboxselectcolumn.js');
  drupal_add_js($path . '/js/slickgrid/plugins/slick.rowselectionmodel.js');

  // Depending on options selected, enable controls (pager, show / hide column headers)
  if ($options['pager']) {
    drupal_add_js($path . '/js/slickgrid/controls/slick.pager.js');
    drupal_add_css($path . '/js/slickgrid/controls/slick.pager.css');
  }

  // Can user select which columns to display
  if ($options['select_columns']) {
    drupal_add_js($path . '/js/slickgrid/controls/slick.columnpicker.js');
    drupal_add_css($path . '/js/slickgrid/controls/slick.columnpicker.css');
  }

  // Add groups UI js file - adds buttons to expand / collpase all
  if ($options['grouping_field']) {
    drupal_add_js($path . '/js/slickgrid.groups-ui.js');
  }
  if ($options['collapsible_taxonomy_field']) {
    drupal_add_js($path . '/js/slickgrid.collapsible.js');
  }
  $slickgrid_plugins = slickgrid_get_plugins();

  // Need to add all the files associated with plugins
  // Loop through all the columns
  foreach ($columns as $column) {
    foreach (array_keys(slickgrid_get_plugin_types()) as $plugin_type) {

      // Does this column have this type of plugin defined? And is there as JS file?
      if (isset($column[$plugin_type])) {
        if (isset($slickgrid_plugins[$column[$plugin_type]]['js'])) {
          drupal_add_js($slickgrid_plugins[$column[$plugin_type]]['js']);
        }
        if (isset($slickgrid_plugins[$column[$plugin_type]]['css'])) {
          drupal_add_css($slickgrid_plugins[$column[$plugin_type]]['css']);
        }
      }
    }
  }

  // Add bespoke slickgrid js/css
  drupal_add_js($path . '/js/slickgrid.js');
  drupal_add_css($path . '/css/slickgrid.css');

  // Add beautytips
  beautytips_add_beautytips();

  // ajax_load borks on wysiwyg so load all the profiles on page load to add all the js / css files
  // Get the filter formats
  if (module_exists('wysiwyg')) {
    $filter_formats = filter_formats();
    foreach ($filter_formats as $filter_format) {
      wysiwyg_get_profile($filter_format->format);
    }
  }
}

/** 
 * Amended version of drupal_to_js()
 * Convert a php array into a js string, but allowing for function names (not wrapped in "")
 */
function _slickgrid_drupal_to_js($elements, $additional_function_names = array()) {
  $function_names = array(
    'formatter',
    'validator',
    'editor',
    'setValueHandler',
    'resizable',
  );
  if (count($additional_function_names)) {
    $function_names += $additional_function_names;
  }
  if (count($elements)) {
    $js_string = '[';
    foreach ($elements as $element) {
      $js_string .= $outer_conjunction . '{';
      foreach ($element as $element_name => $element_value) {
        $js_string .= $inner_conjunction;
        if (in_array($element_name, $function_names) || is_numeric($element_value) || is_bool($element_value)) {
          $js_string .= $element_name . ': ' . $element_value;
        }
        else {
          $js_string .= $element_name . ': ' . drupal_to_js($element_value);
        }
        $inner_conjunction = ', ';
      }

      // End of foreach($element)
      $js_string .= '}';
      $inner_conjunction = ' ';
      $outer_conjunction = ', ';
    }
    $js_string .= ']';
    return $js_string;
  }
}

/**
 * Discover all action functions by invoking hook_action_info().
 *
 * @code
 * mymodule_action_info() {
 * return array(
 * 'mymodule_functiondescription_action' => array(
 * 'type' => 'node',
 * 'description' => t('Save node'),
 * 'configurable' => FALSE,
 * 'hooks' => array(
 * 'nodeapi' => array('delete', 'insert', 'update', 'view'),
 * 'comment' => array('delete', 'insert', 'update', 'view'),
 * )
 * )
 * );
 * }
 * @endcode
 *
 * The description is used in presenting possible actions to the user for
 * configuration. The type is used to present these actions in a logical
 * grouping and to denote context. Some types are 'node', 'user', 'comment',
 * and 'system'. If an action is configurable it will provide form,
 * validation and submission functions. The hooks the action supports
 * are declared in the 'hooks' array.
 *
 * @param $reset
 * Reset the action info static cache.
 *
 * @return
 * An associative array keyed on function name. The value of each key is
 * an array containing information about the action, such as type of
 * action and description of the action, e.g.,
 *
 * @code
 * $actions['node_publish_action'] = array(
 * 'type' => 'node',
 * 'description' => t('Publish post'),
 * 'configurable' => FALSE,
 * 'hooks' => array(
 * 'nodeapi' => array('presave', 'insert', 'update', 'view'),
 * 'comment' => array('delete', 'insert', 'update', 'view'),
 * ),
 * );
 * @endcode
 */
function slickgrid_plugins_list($reset = FALSE) {
  static $plugins;
  if (!isset($plugins) || $reset) {
    $editors = module_invoke_all('slickgrid_plugins');
    drupal_alter('slickgrid_plugins', $editors);
  }
  return (array) $editors;
}

/**
 * 
 * Enter description here ...
 * @param array $filters
 * Array of filters to filter list of plugins 
 * eg: array('plugin_type' => 'editor' will return a list of all editors 
 */
function slickgrid_get_plugins($filters = array()) {

  // Get a list of all available plugins
  $plugins = slickgrid_plugins_list();

  // If they need to be filtered...
  if (is_array($filters)) {
    $plugins = slickgrid_filter_plugins($plugins, $filters);
  }
  return $plugins;
}

/**
 * 
 * Return all plugins matching the filters
 * @param array $plugins
 * @param array $filters	
 */
function slickgrid_filter_plugins($plugins, $filters) {

  // Loop through all of the plugins
  foreach ($plugins as $plugin_type => $plugin) {

    // Loop through the plugins
    foreach ($filters as $property => $value) {

      // If the plugin has a property that doesn't match the filter...
      if (is_array($plugin[$property]) && !in_array($value, $plugin[$property]) || is_string($plugin[$property]) && $plugin[$property] != $value) {

        // Remove the plugin
        unset($plugins[$plugin_type]);

        // And move onto the next plugin
        break;
      }
    }
  }
  return $plugins;
}

/**
 * 
 * Array of all allowed plugin types
 */
function slickgrid_get_plugin_types() {
  return array(
    'formatter' => t('Formatter'),
    'editor' => t('Editor'),
    'validator' => t('Validator'),
    'filter' => t('Filter'),
  );
}

/**
 * Return data in JSON format.
 * Using drupal_json() results in a parse error client side on some html content
 * So use json_encode instead
 * 
 * @param $var
 * (optional) If set, the variable will be converted to JSON and output.
 */
function slickgrid_json($var = NULL) {
  drupal_set_header('Content-Type: text/javascript; charset=utf-8');
  if (isset($var)) {
    echo json_encode($var);
  }
  exit;
}

/**
 * 
 * Get the vid from a taxonomy field
 * @param object $handler
 * @return mixed
 * returns numeric vid if field is editable (only one vid is selected), 
 * returns false if none or multiple vids (so not editable)
 */
function slickgrid_taxonomy_field_get_vid($handler) {
  $vids = array_filter($handler->options['vids']);

  // We can only use this as an editable field if one VID is selected
  if (count($vids) == 1) {
    return current($vids);
  }
  return FALSE;
}

/**
 * 
 * Additional submit handler for views_ui_rearrange_form
 * Delete any user settings for the view which will prevent reraranged fields from displaying properly 
 * @param array $form
 * @param array $form_state
 */
function slickgrid_views_ui_rearrange_form_submit($form, $form_state) {
  global $user;

  // Get any settings for this view
  $settings = slickgrid_get_settings($user->uid, $form_state['view']->name);

  // If there are settings, wipe hidden_columns & ordered_columns
  if (is_array($settings)) {
    slickgrid_set_settings($user->uid, $form_state['view']->name, array(
      'hidden_columns' => null,
      'ordered_columns' => null,
    ));
  }
}
function slickgrid_node_form_pre_render(&$form, &$form_state) {
  if ($_POST['vid']) {
    $vid = $_POST['vid'];

    // Load the vocabulary so we know whether this vocabulary uses tags
    $vocabularies = taxonomy_get_vocabularies($form['#node']->type);
    if ($vocabularies[$vid]->tags) {
      $form['#field_name'] = 'taxonomy[tags][' . $vid . ']';
    }
    else {
      $form['#field_name'] = 'taxonomy[' . $vid . ']';
    }
  }
  elseif ($_POST['field_name'] == 'group_nid') {
    $form['#field_name'] = 'og_groups';
  }
  elseif ($_POST['field_name'] == 'body') {
    $form['#field_name'] = 'body_field';
  }
  else {
    $form['#field_name'] = $_POST['field_name'];
  }

  // Some modules might need to play around with the form / field name to get the correct part of the form
  drupal_alter('slickgrid_field', $form, $form['#field_name']);
  if (!slickgrid_callback_get_form_element($form, $form['#field_name'])) {
    drupal_set_message(t('Field not found'), 'error');
  }
  return $form;
}
function slickgrid_callback_get_form_element(&$form, $field_name) {
  static $element_exists;
  foreach (element_children($form) as $key) {

    // If this isn't the field we're looking for it needs to be removed from the form
    if ($key === $field_name || $form[$key]['#name'] == $field_name) {

      // tidy up the form a bit by removing fieldsets if the field is in one
      if ($form['#type'] == 'fieldset') {
        unset($form['#type']);
      }
      $element_exists = true;
    }
    else {
      if (count(element_children($form[$key]))) {

        // If this item has children, call again
        slickgrid_callback_get_form_element($form[$key], $field_name);
      }
      if (!count(element_children($form[$key]))) {

        // If there are no children & it's not a requried field, unset it
        // Keep all hidden elements
        // these will containf form build id etc.,
        if (!in_array($key, array(
          'form_build_id',
          'form_id',
        )) || is_numeric($key)) {
          unset($form[$key]);
        }
      }
    }
  }
  return $element_exists;
}

Functions

Namesort descending Description
slickgrid_callback_get_form_element
slickgrid_delete_settings Delete settings from the database
slickgrid_filter_plugins Return all plugins matching the filters
slickgrid_form_alter Implementation of hook_form_alter().
slickgrid_get_plugins Enter description here ...
slickgrid_get_plugin_types Array of all allowed plugin types
slickgrid_get_settings Get settings from the DB Pass in $setting to retrieve a particular setting, NULL to get akll for a UID / View
slickgrid_init Implementation of hook_init(). The full lightbox2 does not work with jquery 1.4.3, so need to turn it off for slickgrid pages
slickgrid_json Return data in JSON format. Using drupal_json() results in a parse error client side on some html content So use json_encode instead
slickgrid_menu Implementation of hook_menu().
slickgrid_menu_alter Implementation of hook_menu_alter().
slickgrid_node_form_pre_render
slickgrid_plugins_list Discover all action functions by invoking hook_action_info().
slickgrid_preprocess_views_view_slickgrid Preprocessor for views-view-slickgrid.tpl.php Repurpose the views result for slickgrid
slickgrid_set_settings
slickgrid_slickgrid_plugins Implementation of hook_slickgrid_plugins().
slickgrid_taxonomy_field_get_vid Get the vid from a taxonomy field
slickgrid_theme Implementation of hook_theme().
slickgrid_user Implementation of hook_user().
slickgrid_views_api Implementation of hook_views_api
slickgrid_views_pre_view Implementation of hook_views_pre_view Slickgrid handles the paging internally so need to override
slickgrid_views_ui_rearrange_form_submit Additional submit handler for views_ui_rearrange_form Delete any user settings for the view which will prevent reraranged fields from displaying properly
theme_slickgrid The main theme function for the slickgrid
theme_slickgrid_views_plugin_table Theme the form for the matrix style plugin
_slickgrid_add_files
_slickgrid_drupal_to_js Amended version of drupal_to_js() Convert a php array into a js string, but allowing for function names (not wrapped in "")

Constants