You are here

views_megarow.module in Views Megarow 7

Same filename and directory in other branches
  1. 8 views_megarow.module

File

views_megarow.module
View source
<?php

// @TODO
//  * Manage create.

/**
 * Implement hook_menu().
 */
function views_megarow_menu() {
  $items['views_megarow/refresh/%/%/%'] = array(
    'page callback' => 'views_megarow_get_row',
    'page arguments' => array(
      2,
      3,
      4,
    ),
    'access callback' => 'views_megarow_access',
    'access arguments' => array(
      2,
      3,
    ),
    'type' => MENU_CALLBACK,
  );
  $items['display_megarow/%/%'] = array(
    'page callback' => 'views_megarow_generic_render',
    'page arguments' => array(
      1,
      2,
    ),
    'delivery callback' => 'ajax_deliver',
    'access callback' => TRUE,
    'theme callback' => 'views_megarow_get_page_theme',
  );
  $items['admin/config/user-interface/views_megarow'] = array(
    'title' => 'Views Megarow configuration',
    'description' => t('Options for Views Megarow settings'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'views_megarow_admin_settings_form',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'views_megarow.admin.inc',
  );
  return $items;
}

/**
 * Access callback. Verify that the user can access the request view.
 *
 * @param $view_name
 *   Name of the view to check the access to.
 * @param $display_id
 *   ID of the display to serve.
 *
 * @return bool
 *   Returns TRUE is the user can access the page, FALSE otherwise.
 */
function views_megarow_access($view_name, $display_id) {
  $view = views_get_view($view_name);
  $view
    ->set_display($display_id);
  return $view
    ->access(array(
    $display_id,
  ));
}

/**
 * Determine which theme should be used to render the megarow.
 *
 * Since the rendering of this page is triggered on click by a user, our only
 * way to figure out the original page is to rely on the HTTP_REFERER.
 * With this information we can determine if the origin page was an admin path
 * or not and display the appropriate accordingly.
 */
function views_megarow_get_page_theme() {

  // Find the path of the parent page.
  $url = parse_url($_SERVER['HTTP_REFERER']);

  // If the Drupal install is in a subdirectory, discard that part.
  $path = substr($url['path'], strlen($GLOBALS['base_path']));

  // Use the admin theme if it's an admin page.
  if (user_access('view the administration theme') && path_is_admin($path)) {
    return variable_get('admin_theme', 'seven');
  }
  else {

    // Otherwise use the default theme.
    return variable_get('theme_default', 'bartik');
  }
}

/**
 * Page callback: Returns a view limited to a single row, used by the
 * "megarow_refresh_parent" ajax command.
 *
 * @param $view_name
 *  The machine name of the view to load.
 *
 * @param $display
 *  The display_id for the view.
 *
 * @param $args
 *  Arguments to be passed to the view, formatted in JSON.
 *
 * @todo This code forces the active display to have a base field argument.
 *  Perhaps try supporting a custom display only for refreshing, so that
 *  the user can use arguments for something else.
 */
function views_megarow_get_row($view_name, $display, $args) {
  $output = '';
  $view = views_get_view($view_name);
  if ($view) {
    $output = $view
      ->preview($display, drupal_json_decode($args));
  }
  return $output;
}

/**
 * Use a generic menu callback to display non megarow tailored pages.
 */
function views_megarow_generic_render($entity_id) {

  // Get the arguments passed to the render callback, it's basicaly
  // a split path.
  $args = func_get_args();

  // Remove the entity_id argument.
  array_shift($args);

  // Clean the values and implode them to generate a clean path.
  $path = implode('/', array_filter($args, 'strlen'));

  // Get the output of the called page.
  $output = menu_execute_active_handler($path, FALSE);

  // Allow errors to bubble up. The error is always one of: MENU_SITE_OFFLINE,
  // MENU_ACCESS_DENIED, MENU_NOT_FOUND
  if (is_int($output)) {
    return $output;
  }

  // The output is valid, display it through AJAX and voilà!
  // Render our output if we get a renderable array.
  if (is_array($output)) {

    // Let's double check if the output isn't already a ajax command.
    if (isset($output['#type']) && $output['#type'] == 'ajax') {
      return $output;
    }
    $arguments = array(
      'display' => 'error',
    );
    $messages = theme('status_messages', $arguments);

    // Pass the result of this to the modal to display it.
    $content = array(
      array(
        '#markup' => $messages,
      ),
      $output,
    );
    $output = drupal_render($content);
  }

  // @TODO Add command to slide to top if errors.
  $commands = array();
  $commands[] = views_megarow_command_display(variable_get('views_megarow_title', t('Megarow content')), $output, $entity_id);
  return array(
    '#type' => 'ajax',
    '#commands' => $commands,
  );
}

/**
 * Implements hook_views_api().
 */
function views_megarow_views_api() {
  return array(
    'api' => 3,
    'path' => drupal_get_path('module', 'views_megarow') . '/includes/views',
  );
}

/**
 * Implements hook_preprocess_views_view_table().
 *
 * This preprocess needs to run after the views one which defines 'row_classes'.
 */
function views_megarow_preprocess_views_view_table(&$vars) {

  // Add the custom classes and attributes that identify each entity row.
  if ($vars['view']->plugin_name == 'table_megarows') {
    $vars['attributes_array']['data-view-name'] = $vars['view']->name;
    $vars['attributes_array']['data-view-display'] = $vars['view']->current_display;
    $result_entities = $vars['view']->query
      ->get_result_entities($vars['view']->result);
    foreach ($vars['result'] as $count => $result) {
      $entity = $result_entities[1][$count];
      list($entity_id) = entity_extract_ids($result_entities[0], $entity);
      $vars['row_classes'][$count][] = 'item-' . $entity_id;
    }
  }
}

/**
 * Implements hook_views_pre_render().
 * Adds the CSS and JS needed for the functioning of the megarow.
 */
function views_megarow_views_pre_render(&$view) {
  static $done = FALSE;
  if ($view->plugin_name == 'table_megarows' && !$done) {

    // Store if the row should be automatically closed when the form are
    // processed.
    $_SESSION['views_megarow']['autoclose'] = $view->style_plugin->options['autoclose'];

    // Preserve initial destination URL query parameter.
    $destination = drupal_get_destination();
    $settings = array(
      'ViewsMegarow' => array(
        'loadingText' => $view->style_plugin->options['loading_text'],
        'scrollEnabled' => $view->style_plugin->options['enable_scroll'],
        'scrollPadding' => $view->style_plugin->options['scroll_padding'],
        'close' => $view->style_plugin->options['close'],
        'destination' => $destination['destination'],
        // An image can also be used, like this:

        //'close' => theme('image', array(

        //  'path' => ctools_image_path('icon-close-window.png'),
        //  'title' => t('Close window'),
        //  'alt' => t('Close window'),

        //)),
        'throbber' => theme('image', array(
          'path' => ctools_image_path('ajax-loader.gif', 'ctools_ajax_sample'),
          'title' => $view->style_plugin->options['loading_text'],
          'alt' => $view->style_plugin->options['loading_text'],
        )),
      ),
    );
    drupal_add_js($settings, 'setting');
    drupal_add_library('system', 'jquery.form');
    drupal_add_library('system', 'drupal.progress');
    drupal_add_library('system', 'drupal.ajax');
    drupal_add_js(drupal_get_path('module', 'views_megarow') . "/views_megarow.js");
    drupal_add_css(drupal_get_path('module', 'views_megarow') . "/views_megarow.css");

    // Add the Views dropbutton CSS so that links provided by
    // views_handler_field_megarow_links are styled properly.
    // @todo Remove this once http://drupal.org/node/1557662 gets committed.
    drupal_add_css(drupal_get_path('module', 'views') . '/css/views-admin.ctools.css');
    drupal_add_css(drupal_get_path('module', 'views') . '/css/views-admin.seven.css');
    $done = TRUE;
  }
}

/**
 * This callback is just a testing wrapper to display an ajaxified form
 * or its fallback if it's not called through AJAX.
 */
function views_megarow_display_form_wrapper($js, $form_id, $entity, $entity_type) {
  if ($js) {
    list($entity_id) = entity_extract_ids($entity_type, $entity);
    $form_state = array(
      'ajax' => TRUE,
      'build_info' => array(
        'args' => array(
          $entity_type,
          $entity,
        ),
      ),
    );
    $commands = views_megarow_form_wrapper($form_id, $form_state, $entity_id);
    if (!empty($form_state['executed'])) {

      // We'll just overwrite the form output if it was successful.
      $messages = theme('status_messages');
      $message = 'Submission completed. You can now close this row. ' . $messages;
      $commands = array();
      $commands[] = views_megarow_command_display('Submit OK', $message, $entity_id);

      // Invoke a custom event that refresh the table row of this item.
      $commands[] = ajax_command_invoke('.item-' . $entity_id, 'trigger', array(
        'refreshRow',
      ));
    }
    print ajax_render($commands);
    exit;
  }
  else {
    return drupal_get_form($form_id, $entity
      ->entityType(), $entity, $form_mode);
  }
}

/**
 * Wrap a form so that we can use it properly with AJAX. Essentially if the
 * form wishes to render, it automatically does that, otherwise it returns
 * so we can see submission results.
 *
 * @return
 *   The output of the form, if it was rendered. If $form_state['ajax']
 *   is set, this will use ctools_megarow_form_render so it will be
 *   a $command object suitable for ajax_render already.
 *
 *   The return will be NULL if the form was successfully submitted unless
 *   you specifically set re_render = TRUE. If ajax is set the
 *   form will never be redirected.
 */
function views_megarow_form_wrapper($form_id, &$form_state, $entity_id) {
  $form_state += array(
    're_render' => FALSE,
    'no_redirect' => !empty($form_state['ajax']),
  );
  $form = drupal_build_form($form_id, $form_state);

  // Add a callback to let the megarow being closed automatically if enabled.
  $form['#submit'][] = 'views_megarow_autoclose_megarow';
  if (!empty($form_state['ajax']) && empty($form_state['executed'])) {
    $output = drupal_render($form);
    $title = empty($form_state['title']) ? drupal_get_title() : $form_state['title'];
    return views_megarow_display($title, $output, $entity_id);
  }
  return $output;
}

/**
 * Displays the provided output in a megarow.
 *
 * Works by returning an array that ajax_deliver() can understand.
 */
function views_megarow_display($title, $output, $entity_id) {

  // Add the messages in the renderable array if the source is an array or
  // render them if we already have markup in order to display potential error
  // messages or confirmation messages to the user.
  if (is_array($output)) {
    $renderable_output = array(
      array(
        '#theme' => 'status_messages',
      ),
      $output,
    );
  }
  else {
    $renderable_output = theme('status_messages') . $output;
  }
  $commands = array();
  $commands[] = views_megarow_command_display($title, $renderable_output, $entity_id);
  return array(
    '#type' => 'ajax',
    '#commands' => $commands,
  );
}

/**
 * Place HTML within the megarow.
 *
 * @param $title
 *   The title of the megarow.
 * @param $html
 *   The html to place within the megarow.
 */
function views_megarow_command_display($title, $html, $entity_id) {
  if (is_array($html)) {
    $html = drupal_render($html);
  }
  return array(
    'command' => 'megarow_display',
    'entity_id' => $entity_id,
    'title' => $title,
    'output' => $html,
  );
}

/**
 * Dismiss the megarow.
 */
function views_megarow_command_dismiss($entity_id) {
  return array(
    'command' => 'megarow_dismiss',
    'entity_id' => $entity_id,
  );
}

/**
 * Refresh the parent row of a megarow.
 *
 * @param $entity_id
 *  The unique id of the base table entity being displayed whose data needs
 *  to be refreshed.
 *
 * @param $display_id
 *  The display_id of the view where the refresh target is displayed.
 *
 * @param $args
 *  An array of arguments the view needs to function.
 *
 */
function views_megarow_command_refresh_parent($entity_id, $display_id, $args = array()) {
  drupal_add_js(array(
    'ViewsMegarow' => array(
      'display_id' => $display_id,
    ),
  ), 'setting');

  // If no arguments are passed, default to the entity_id
  if (empty($args)) {
    $args = array(
      $entity_id,
    );
  }
  drupal_add_js(array(
    'ViewsMegarow' => array(
      'args' => drupal_json_encode($args),
    ),
  ), 'setting');
  return array(
    'command' => 'megarow_refresh_parent',
    'entity_id' => $entity_id,
  );
}

/**
 * Render an image as a button link. This will automatically apply an AJAX class
 * to the link and add the appropriate javascript to make this happen.
 *
 * @param $image
 *   The path to an image to use that will be sent to theme('image') for rendering.
 * @param $dest
 *   The destination of the link.
 * @param $alt
 *   The alt text of the link.
 * @param $class
 *   Any class to apply to the link. @todo this should be a options array.
 */
function views_megarow_image_button($image, $dest, $alt, $class = '') {
  return ctools_ajax_text_button(theme('image', array(
    'path' => $image,
  )), $dest, $alt, $class, 'views-megarow-open');
}

/**
 * Render text as a link. This will automatically apply an AJAX class
 * to the link and add the appropriate javascript to make this happen.
 *
 * Note: 'html' => true so be sure any text is vetted! Chances are these kinds of buttons will
 * not use user input so this is a very minor concern.
 *
 * @param $text
 *   The text that will be displayed as the link.
 * @param $dest
 *   The destination of the link.
 * @param $alt
 *   The alt text of the link.
 * @param $class
 *   Any class to apply to the link. @todo this should be a options array.
 */
function views_megarow_text_button($text, $dest, $alt, $class = '') {
  return ctools_ajax_text_button($text, $dest, $alt, $class, 'views-megarow-open');
}

/**
 * Implements hook_form_alter().
 */
function views_megarow_form_alter(&$form, &$form_state, $form_id) {

  // Look for node forms, entity forms should be ok.
  if (strstr($form_id, '_node_form')) {

    // Use module_load_include so that caches and stuff can know to load this.
    form_load_include($form_state, 'inc', 'node', 'node.pages');

    // Prevent Drupal from redirecting the user to the node being edited if
    // we are doing the edition action from a megarow.
    if (isset($form['#node']->from_megarow) && $form['#node']->from_megarow == TRUE) {
      $form_state['programmed'] = TRUE;

      // Trigger an extra submit callback offering the possibility to have
      // the megarow automatically closed when the node is submitted.
      $form['actions']['submit']['#submit'][] = 'views_megarow_autoclose_megarow';
    }
  }
  else {
    if ($form_id == 'user_profile_form') {

      // Trigger an extra submit callback offering the possibility to have
      // the megarow automatically closed when the user is submitted if
      // we are doing the edition action from a megarow.
      if (isset($form['#user']->from_megarow) && $form['#user']->from_megarow == TRUE) {
        $form['#submit'][] = 'views_megarow_autoclose_megarow';
      }
    }
  }
}

/**
 * Submit callback to trigger the megarow closing after submitting the form.
 */
function views_megarow_autoclose_megarow($form, &$form_state) {

  // Check if the user enabled the feature to automatically close the megarow
  // once a form is submitted.
  if (isset($_SESSION['views_megarow']) && $_SESSION['views_megarow']['autoclose']) {
    if (strstr($form['#form_id'], '_node_form')) {
      $output = views_megarow_command_dismiss($form_state['node']->nid);
    }
    else {
      if ($form['#form_id'] == 'user_profile_form') {
        $output = views_megarow_command_dismiss($form_state['user']->uid);
      }
      else {
        if (isset($form_state['#entity_id'])) {
          $output = views_megarow_command_dismiss($form_state['#entity_id']);
        }
      }
    }

    // Turn off the light before leaving.
    print ajax_render(array(
      $output,
    ));
    drupal_exit();
  }
}

/**
 * Implements hook_menu_alter().
 */
function views_megarow_menu_alter(&$items) {

  // For compatibility reasons with panels mainly let's check if the callbacks
  // should be overriden.
  // We need to wrap some logic around the default node edition page and user
  // edition page in order to be able to tell the system that we are editing
  // a node from a megarow context.
  if (variable_get('views_megarow_override_node_edit', TRUE)) {
    $items['node/%node/edit']['page callback'] = 'views_megarow_node_page_edit';
  }
  if (variable_get('views_megarow_override_user_edit', TRUE)) {
    $items['user/%user/edit']['page callback'] = 'views_megarow_user_page_edit';
  }
}

/**
 * Overrides the node edit callback.
 *
 * @see node_page_edit().
 */
function views_megarow_node_page_edit($node) {

  // If the url executing the node edit menu item is from a megarow,
  // let's hook into megarow form rendering instead of the classic
  // output.
  if (preg_match('`^display_megarow/[0-9]+/node/[0-9]+/edit$`', $_GET['q'])) {

    // Add a flag for the node edition form.
    $node->from_megarow = TRUE;

    // This is a duplication of the default behavior.
    $type_name = node_type_get_name($node);
    $title = t('<em>Edit @type</em> @title', array(
      '@type' => $type_name,
      '@title' => $node->title,
    ));
    drupal_set_title($title, PASS_THROUGH);
    $output = drupal_get_form($node->type . '_node_form', $node);

    // Instead of return the form, pass it to views megarow to display it.
    return views_megarow_display($title, $output, $node->nid);
  }
  else {

    // Otherwise fallback on the default behavior.
    $node->from_megarow = FALSE;
    return node_page_edit($node);
  }
}

/**
 * Wrap the default user edition form.
 *
 * @see user_profile_form().
 */
function views_megarow_user_page_edit($form_id, $user) {

  // If the url executing the user edit menu item is from a megarow,
  // let's hook into megarow form rendering instead of the classic
  // output.
  if (preg_match('`^display_megarow/[0-9]+/user/[0-9]+/edit$`', $_GET['q'])) {

    // Add a flag for the user edition form.
    $user->from_megarow = TRUE;
    $output = drupal_get_form('user_profile_form', $user);

    // Instead of return the form, pass it to views megarow to display it.
    return views_megarow_display(t('Edit'), $output, $user->uid);
  }
  else {

    // Otherwise fallback on the default behavior.
    $user->from_megarow = FALSE;
    return drupal_get_form('user_profile_form', $user);
  }
}

Functions

Namesort descending Description
views_megarow_access Access callback. Verify that the user can access the request view.
views_megarow_autoclose_megarow Submit callback to trigger the megarow closing after submitting the form.
views_megarow_command_dismiss Dismiss the megarow.
views_megarow_command_display Place HTML within the megarow.
views_megarow_command_refresh_parent Refresh the parent row of a megarow.
views_megarow_display Displays the provided output in a megarow.
views_megarow_display_form_wrapper This callback is just a testing wrapper to display an ajaxified form or its fallback if it's not called through AJAX.
views_megarow_form_alter Implements hook_form_alter().
views_megarow_form_wrapper Wrap a form so that we can use it properly with AJAX. Essentially if the form wishes to render, it automatically does that, otherwise it returns so we can see submission results.
views_megarow_generic_render Use a generic menu callback to display non megarow tailored pages.
views_megarow_get_page_theme Determine which theme should be used to render the megarow.
views_megarow_get_row Page callback: Returns a view limited to a single row, used by the "megarow_refresh_parent" ajax command.
views_megarow_image_button Render an image as a button link. This will automatically apply an AJAX class to the link and add the appropriate javascript to make this happen.
views_megarow_menu Implement hook_menu().
views_megarow_menu_alter Implements hook_menu_alter().
views_megarow_node_page_edit Overrides the node edit callback.
views_megarow_preprocess_views_view_table Implements hook_preprocess_views_view_table().
views_megarow_text_button Render text as a link. This will automatically apply an AJAX class to the link and add the appropriate javascript to make this happen.
views_megarow_user_page_edit Wrap the default user edition form.
views_megarow_views_api Implements hook_views_api().
views_megarow_views_pre_render Implements hook_views_pre_render(). Adds the CSS and JS needed for the functioning of the megarow.