You are here

flagging_dialog.module in Flagging Form 7

Same filename and directory in other branches
  1. 7.3 flagging_dialog.module

Provides menu callbacks for displaying the flagging forms in a dialog box.

<h3>Overview</h3>

There are two different methods for displaying dialogs. One is to fetch the content via AJAX and put it inside a DIV. The other is to to load the document into an IFRAME.

This module uses the first method, of using AJAX. It uses jQuery UI's dialog widget. jQuery UI is shipped with D7. This module also necessitates the <em>Dialog API</em> module, which provides a thin layer of supporting code.

<h3>Alternatives</h3>

The AJAX method is not as robust as the IFRAME one: the additional "magic" it involves may not be fully compatible with the JavaScript of the widgets shown in the dialog (search flagging_dialog.js for "@todo" comments to learn about known problems).

We should eventually provide another module (or replace this one?) implementing the "other method", of using an IFRAME. At the time of this writing it seems it could utilize either D7's <em>Overlay</em> module or the <em>Modal Frame API</em> module. However, both modules have bugs that must be fixed first.

File

flagging_dialog.module
View source
<?php

/**
 * @file
 * Provides menu callbacks for displaying the flagging forms in a dialog box.
 *
 * <h3>Overview</h3>
 *
 * There are two different methods for displaying dialogs. One is to fetch the
 * content via AJAX and put it inside a DIV. The other is to to load the
 * document into an IFRAME.
 *
 * This module uses the first method, of using AJAX. It uses jQuery UI's dialog
 * widget. jQuery UI is shipped with D7. This module also necessitates the
 * <em>Dialog API</em> module, which provides a thin layer of supporting code.
 *
 * <h3>Alternatives</h3>
 *
 * The AJAX method is not as robust as the IFRAME one: the additional "magic" it
 * involves may not be fully compatible with the JavaScript of the widgets shown
 * in the dialog (search flagging_dialog.js for "@todo" comments to learn about
 * known problems).
 *
 * We should eventually provide another module (or replace this one?)
 * implementing the "other method", of using an IFRAME. At the time of this
 * writing it seems it could utilize either D7's <em>Overlay</em> module or the
 * <em>Modal Frame API</em> module. However, both modules have bugs that must be
 * fixed first.
 */

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

  // We wrap the CRUD forms of flagging_form.module with ajax versions:
  //
  // 1. We duplicate the paths of flagging_form.module, but append '/ajax' to them.
  //
  // 2. To the outgoing flagging_form.module links we append '/nojs', which Drupal
  // replaces with '/ajax' if JavaScript is used.
  // Editing a flagging.
  $items['flag/flagging/%flag/%content_id/edit/ajax'] = array(
    'page callback' => 'flagging_dialog_edit_flagging',
    'page arguments' => array(
      2,
      3,
    ),
    'title callback' => '_flag_menu_title',
    'title arguments' => array(
      2,
    ),
    'access callback' => '_flagging_form_flagging_access',
    'access arguments' => array(
      'update',
      2,
      3,
    ),
    'delivery callback' => 'ajax_deliver',
  );

  // Creating a flagging.
  $items['flag/flagging/%flag/%content_id/create/ajax'] = array(
    'page callback' => 'flagging_dialog_edit_flagging',
    'page arguments' => array(
      2,
      3,
    ),
    'title callback' => '_flag_menu_title',
    'title arguments' => array(
      2,
    ),
    'access callback' => '_flagging_form_flagging_access',
    'access arguments' => array(
      'create',
      2,
      3,
    ),
    'delivery callback' => 'ajax_deliver',
  );

  // Deleting a flagging.
  $items['flag/flagging/%flag/%content_id/delete/ajax'] = array(
    'page callback' => 'flagging_dialog_delete_flagging',
    'page arguments' => array(
      2,
      3,
    ),
    'title callback' => '_flag_menu_title',
    'title arguments' => array(
      2,
    ),
    'access callback' => '_flagging_form_flagging_access',
    'access arguments' => array(
      'delete',
      2,
      3,
    ),
    'delivery callback' => 'ajax_deliver',
  );
  return $items;
}

/**
 * Implements hook_flagging_form_interactions().
 */
function flagging_dialog_flagging_form_interactions() {
  return array(
    'flagging_dialog' => array(
      'title' => t('Dialog box'),
      'description' => t('Forms are displayed in a dialog box, if JavaScript is available.'),
      'weight' => -1,
    ),
  );
}

/**
 * Implements hook_flagging_form_link_alter().
 *
 * Alters the links flagging_form.module generates.
 */
function flagging_dialog_flagging_form_link_alter(&$link, $flag) {
  if ($flag->form_interaction == 'flagging_dialog') {

    // Bug in Drupal's ajax.js: we must have a trailing '/'.
    $link['href'] .= '/nojs/';
    $link['attributes']['class'] = 'use-dialog use-ajax';

    // @todo: See Amitai's http://drupal.org/node/858764, which tries to
    // standardize js/css inclusion.
    // We need the CSS for .flag-message:
    drupal_add_css(drupal_get_path('module', 'flag') . '/theme/flag.css');

    // We need the anonyous-user handling (but nothing else):
    // @todo: split flag.js into flag-common.js and flag-toggle.js?
    drupal_add_js(drupal_get_path('module', 'flag') . '/theme/flag.js');

    // Finally, our own code:
    drupal_add_library('flagging_dialog', 'flagging_dialog');
  }
}

/**
 * Implements hook_library().
 */
function flagging_dialog_library() {
  $libraries['flagging_dialog'] = array(
    'title' => 'Flag Dialog',
    'version' => '1.0',
    'js' => array(
      // Omitting the 'group' bellow will use a default group of JS_LIBRARY,
      // which, since lighter than ajax.js's, won't enable us to add our
      // command(s) to the non-yet-existing Drupal.ajax.prototype.commands.
      drupal_get_path('module', 'flagging_dialog') . '/flagging_dialog.js' => array(
        'group' => JS_DEFAULT,
      ),
    ),
    'css' => array(
      drupal_get_path('module', 'flagging_dialog') . '/flagging_dialog.css' => array(),
    ),
    'dependencies' => array(
      array(
        'dialog',
        'dialog',
      ),
    ),
  );
  return $libraries;
}

/**
 * Implements hook_form_alter().
 */
function flagging_dialog_form_alter(&$form, &$form_state, $form_id) {
  if (!empty($form_state['flag_is_using_ajax'])) {

    // Make the buttons submit via ajax. (This could also be done by adding
    // class="use-ajax-submit" to buttons, but bugs in Drupal's misc/ajax.js
    // prevent this from working.)
    if (isset($form['actions'])) {
      foreach (element_children($form['actions']) as $button) {

        // For performance, we handle cancel buttons in our JavaScript.
        if ($button != 'cancel') {
          $form['actions'][$button]['#ajax']['path'] = $_GET['q'];
        }
      }
    }

    // Make sure #ids don't clash with existing ones.
    $form = dialog_process_ajax_form($form);
  }
}

/**
 * Menu callback.
 */
function flagging_dialog_edit_flagging($flag, $content_id) {
  $flagging = $flag
    ->get_flagging($content_id);
  if (!$flagging) {

    // New flagging.
    $flagging = $flag
      ->new_flagging($content_id);
  }
  $form_state = array(
    'no_redirect' => TRUE,
    // Tell our hook_form_alter() to kick in.
    'flag_is_using_ajax' => TRUE,
    'flag_suppress_messages' => TRUE,
    'build_info' => array(
      'args' => array(
        $flagging,
      ),
    ),
  );
  $form = drupal_build_form('flagging_form_flagging_form', $form_state);
  return flagging_dialog_process_form_result($form, $form_state, $flag, $content_id);
}

/**
 * Menu callback.
 */
function flagging_dialog_delete_flagging($flag, $content_id) {
  $flagging = $flag
    ->get_flagging($content_id);
  if (!$flagging) {

    // The item isn't flagged. The form function will deal with this error.
    $flagging = $flag
      ->new_flagging($content_id);
  }
  $form_state = array(
    'no_redirect' => TRUE,
    // Tell our hook_form_alter() to kick in.
    'flag_is_using_ajax' => TRUE,
    'flag_suppress_messages' => TRUE,
    'build_info' => array(
      'args' => array(
        $flagging,
      ),
    ),
  );
  $form = drupal_build_form('flagging_form_flagging_delete_form', $form_state);
  return flagging_dialog_process_form_result($form, $form_state, $flag, $content_id);
}

/**
 * Handles submission of the form.
 */
function flagging_dialog_process_form_result($form, $form_state, $flag, $content_id) {
  $commands = array();

  // The following line reloads the 'dialog' and 'flagging_dialog' libraries.
  // Why? Because there's a bug in Drupal/DialogAPI:
  //
  // When the JavaScript aggregator is turned on, DialogAPI (in
  // dialog_ajax_render_alter()) tells the browser to load drupal.js and
  // ajax.js as well, and this blows away the additions dialog.js did to some
  // data structures there (Drupal.{theme,ajax}.prototype). So we re-introduce
  // these additions.
  //
  // See http://drupal.org/node/XXXX
  drupal_add_library('flagging_dialog', 'flagging_dialog');
  if (!empty($form_state['executed'])) {

    // The form has been submitted. Either redirect to a new url or close the
    // dialog.
    if (is_array($form_state['redirect'])) {
      $target = $form_state['redirect'][0];
    }
    else {
      $target = $form_state['redirect'];
    }

    // If there's an ajax version for the target path, use it.
    $try = menu_get_item($target . '/ajax');
    if ($try && strpos($try['path'], '/ajax') !== FALSE) {

      // Yes, there is. Do an "internal" redirect.
      $target = $target . '/ajax';
      _flagging_dialog_set_drupal_path($target);
      menu_execute_active_handler($target);
      drupal_exit();

      // An alternative is to do a roundtrip using
      // dialog_command_boxed_redirect()
      // See http://drupal.org/node/XXXX
    }
    else {

      // No, there's no ajax page to go to. Close the dialog, and
      // update the flag link.
      $commands[] = dialog_command_dismiss();
      $commands[] = flagging_dialog_command_update_link($flag, $content_id, !empty($form_state['flag_status_has_changed']));
    }
  }
  else {

    // The form hasn't been submitted. So we just need to display it.
    dialog_display(TRUE);

    // Make dialog_ajax_render_alter() run.
    $commands[] = dialog_command_display($form, array(
      'title' => drupal_get_title(),
    ));
  }
  $output = array(
    '#type' => 'ajax',
    '#commands' => $commands,
  );
  return $output;
}

/**
 * Fool the system to think the current URL is $path.
 *
 * Used for "internal" redirections. This doesn't need to be foolproof: just
 * enough to handle our own code.
 */
function _flagging_dialog_set_drupal_path($path) {

  // Used by our form_alter():
  $_GET['q'] = $path;

  // Used as the action='...' for <form> tags:
  $_SERVER['REQUEST_URI'] = url($path);
  drupal_static_reset('element_info');
}

/**
 * Creates a Drupal AJAX command to update a flag link.
 */
function flagging_dialog_command_update_link($flag, $content_id, $status_has_changed) {

  // @todo: We're mimicing here the JavsScript structure flag_page() builds.
  // Let's factor out the flag_page() code and use it instead.
  return array(
    'command' => 'flagging_dialog_update_link',
    'flagName' => $flag->name,
    'contentId' => $content_id,
    // @todo: it was pointed out already, in a comment in flagging_form.module,
    // that we don't have a special message to print when a flagging is updated
    // (as opposed to created or deleted). When we factor flag_page() we should
    // make things future-proof to also work when we do have such a message.
    'newLink' => $flag
      ->theme($flag
      ->is_flagged($content_id) ? 'unflag' : 'flag', $content_id, $status_has_changed),
  );
}

Functions

Namesort descending Description
flagging_dialog_command_update_link Creates a Drupal AJAX command to update a flag link.
flagging_dialog_delete_flagging Menu callback.
flagging_dialog_edit_flagging Menu callback.
flagging_dialog_flagging_form_interactions Implements hook_flagging_form_interactions().
flagging_dialog_flagging_form_link_alter Implements hook_flagging_form_link_alter().
flagging_dialog_form_alter Implements hook_form_alter().
flagging_dialog_library Implements hook_library().
flagging_dialog_menu Implements hook_menu().
flagging_dialog_process_form_result Handles submission of the form.
_flagging_dialog_set_drupal_path Fool the system to think the current URL is $path.