You are here

abstract class AjaxPageControllerBase in Forena Reports 8

Hierarchy

Expanded class hierarchy of AjaxPageControllerBase

3 files declare their use of AjaxPageControllerBase
AjaxFormBase.php in src/Form/AjaxFormBase.php
AjaxFormTrait.php in src/Form/AjaxFormTrait.php
ReportModalForm.php in src/Form/ReportModalForm.php

File

src/Controller/AjaxPageControllerBase.php, line 25

Namespace

Drupal\forena\Controller
View source
abstract class AjaxPageControllerBase extends ControllerBase implements AjaxControllerInterface {
  const LAYOUT = 'sample/dashboard';
  const DEFAULT_ACTION = 'view';
  const TOKEN_PARAMETER = 'c';
  const TOKEN_PREFIX = 'ajax';
  const MAX_STATE_AGE = 43260;

  /**
   * @var string
   *   Controller token for keeping track of state information between page
   *   loads of the controller.
   */
  protected $token;

  /**
   * @var string Name of forena report used for layout
   */
  public $layout;

  /**
   * @var array
   *   Current parameters for the report.
   */
  public $parms = [];

  /**
   * @var bool
   *   Indicates whether the current form being processed is a modal.
   */
  public $is_modal_form = FALSE;

  /**
   * @var bool
   *   Inidates whether a modal has already been added
   */
  public $modal_added = FALSE;

  /**
   * @var string
   */
  public $section = '';
  public $prior_section = '';

  /** @var FormStateInterface */
  public $form_state;

  /** @var object */
  protected $state;

  /**
   * @var AjaxPageControllerBase
   *   Singleton instance.
   */
  protected static $instance;

  /**
   * @var array
   *   Array of librarries to load with this controller.
   */
  public $libraries = [];

  /**
   * Application context to load.
   * @var static
   */
  protected $context;

  /**
   * @var array
   *   Drupal render array containing content.
   */
  protected $build = [];

  /**
   * Render array of modal content.
   * @var array
   */
  public $modal_content = [];

  /**
   * The JSMode passed to the the controller to determine how to deliver the
   * page. "ajax" implies an ajax page load "nojs" implies a normal page.
   * @var string
   */
  public $jsMode;

  /**
   * Ajax commands that are to be returned by the object.
   * @var AjaxCommandInterface[]
   */
  protected $commands = [];

  /**
   * The final command that updates the page.  Note this is generally either
   * a setURL ajax command or it is a CloseDialog command.
   * @var
   */
  public $endCommand = NULL;

  /**
   * @var bool Prevent the further processing of default action pages
   */
  public $prevent_action = FALSE;
  public $action;
  public $post_form_id = '';

  /**
   * Singleton factory method.
   * @return static
   */
  public static function service() {
    if (static::$instance === NULL) {
      static::$instance = new static();
    }
    return static::$instance;
  }

  /**
   * Indicates whether the current callback is an ajax call.
   * @return bool
   */
  public function isAjaxCall() {
    return $this->jsMode != 'nojs';
  }

  /**
   * AjaxPageControllerBase constructor.
   */
  public function __construct() {
    static::$instance = $this;
    if (isset($_GET)) {
      $this->parms = $_GET;
      if (isset($_REQUEST[static::TOKEN_PARAMETER])) {
        $this->token = $_REQUEST[static::TOKEN_PARAMETER];
        unset($this->parms[static::TOKEN_PARAMETER]);
      }
      unset($this->parms['_wrapper_format']);
      unset($this->parms['ajax_form']);
    }
    $this->context = AppContext::create();
    $this->layout = static::LAYOUT;
    $this->libraries[] = 'forena/forena';

    // Set the context for the forena applcation.
    $this
      ->setReportContext('app', $this->context);

    // Load the state if it hasn't been loaded.
    $this
      ->loadState();
  }

  /**
   * Sets the report context for a report.
   * @param $name
   * @param $value
   */
  public function setReportContext($name, &$value) {
    Forena::service()
      ->setDataContext($name, $value);
  }

  /**
   * Generate a token for form
   */
  protected function generateStateToken() {
    $this->token = static::TOKEN_PREFIX . '-' . bin2hex(openssl_random_pseudo_bytes(20));
  }

  /**
   * Return the state token.
   * @return string
   */
  public function getStateToken() {
    if (!$this->token) {
      $this
        ->generateStateToken();
    }
    return $this->token;
  }

  /**
   * @return Object|NULL
   */
  public function getState() {
    return $this->state;
  }

  /**
   * Loads the state from the token.
   */
  protected function loadState() {
    if (!$this->token) {
      $this
        ->generateStateToken();
    }
    else {
      $svc = \Drupal::keyValueExpirable(static::TOKEN_PREFIX);
      $data = $svc
        ->get($this->token);
      if ($data) {
        $this->state = unserialize($data);
      }
    }
  }

  /**
   * Save the state of the controller
   */
  public function saveState() {
    if ($this->state !== NULL) {
      $state = serialize($this->state);
      $svc = \Drupal::keyValueExpirable(static::TOKEN_PREFIX);
      $svc
        ->setWithExpire($this->token, $state, static::MAX_STATE_AGE);
    }
  }

  /**
   * Default page controller implementation.
   * This method is typically what you would reference in the routing.yml
   * file as the main menu callback for the controller.
   *
   * @param $action
   * @param string $js_mode
   * @return array|\Symfony\Component\HttpFoundation\RedirectResponse
   */
  public function page($action = '', $js_mode = 'nojs') {
    $this->jsMode = $js_mode;
    if (!empty($_GET['_wrapper_format'])) {
      $this->jsMode = $_GET['_wrapper_format'];
    }
    if (!$action && $this->jsMode == 'nojs') {
      $route_name = \Drupal::routeMatch()
        ->getRouteName();
      return $this
        ->redirect($route_name, [
        'action' => static::DEFAULT_ACTION,
      ]);
    }
    else {
      $build = $this
        ->response($action);
      return $build;
    }
  }

  /**
   * Return the response based on ajax pramters.
   * @param string $action
   *   The action used by the reout to generate a reponse.
   * @return array | null
   *   Drupal render array containing action.
   */
  public function response($action) {
    $this->action = $action;
    switch ($this->jsMode) {
      case 'nojs':
        $this
          ->initLayout();
        $this
          ->processFormRequest();
        if (!$this->prevent_action) {
          $this
            ->route($action);
        }
        foreach ($this->build as $section => $content) {
          if (is_array($content)) {
            $content = \Drupal::Service('renderer')
              ->render($content);
          }
          $this->context->{$section} = $content;
        }
        $content = Forena::service()
          ->report($this->layout);

        // Add the ajax librarries used by teh controller.
        if (!empty($content['#attached']['library'])) {
          $content['#attached']['library'] = array_merge($content['#attached']['library'], $this->libraries);
        }
        else {
          $content['#attached']['library'] = $this->libraries;
        }
        $response = $content;
        break;
      case 'drupal_modal':

        // This is the type sent when you use the "data-dialog-type" class.
        // Note that it is not the common way to display a modal.
        $this
          ->processFormRequest();
        if (!$this->prevent_action) {
          $this
            ->route($action);
        }
        $response = $this->build;
        break;
      default:

        // All other types are assumed to be some ajax variant.
        $this
          ->processFormRequest();
        if (!$this->prevent_action) {
          $this
            ->route($action);
        }
        $commands = $this
          ->getCommands();

        //print ajax_render($commands);
        $response = new AjaxResponse();
        foreach ($commands as $command) {
          $response
            ->addCommand($command);
        }
    }
    $this
      ->saveState();
    return $response;
  }

  /**
   * Process the post requests for an action.
   */
  public function processFormRequest() {
    if (!empty($_REQUEST['form_id'])) {
      $form_id = $_REQUEST['form_id'];
      $this
        ->processForm($form_id);
    }
  }

  /**
   * Processes the form based on a form_id
   * @param string $form_id
   */
  public function processForm($form_id) {
    switch ($form_id) {
      case ReportModalForm::FORM_ID:
        $this
          ->getModalForm('main', ReportModalForm::class, '');
    }
  }
  public function preventAction($prevent_action = TRUE) {
    $this->prevent_action = $prevent_action;
  }

  /**
   * @param string $section
   *   The location where the content should go.
   * @param string|array $content
   *   The content to replace.
   */
  public function render($section, $content) {
    if ($this->jsMode != 'nojs' && $this->jsMode != 'drupal_modal') {
      $this->commands[] = new HtmlCommand('#' . $section, $content);
    }
    else {
      $this->build[$section] = $content;
    }
  }

  /**
   * Get the AJAX commands to return to the blroser.
   * @return array
   *   Array of commands to return.
   */
  public function getCommands() {
    $commands = $this->commands;
    if (!empty($this->endCommand)) {
      $commands[] = $this->endCommand;
    }
    $this->commands = [];
    return $commands;
  }

  /**
   * \Add an ajax controller command to the stack.
   * @param $command
   */
  public function addCommand($command) {
    $this->commands[] = $command;
  }

  /**
   * Clear the commnds from the ajax buffer.
   */
  public function clearCommands() {
    $this->commands = [];
  }

  /**
   * Generate the ajax form replacement commands.
   * @param $section
   * @param $form
   * @return array
   */
  protected function generateAjaxReplace($section, $form) {
    $commands = [];

    // If the form build ID has changed, issue an Ajax command to update it.
    $build = $this->form_state
      ->getCompleteForm();
    if (isset($_POST['form_build_id']) && $_POST['form_build_id'] !== $build['#build_id']) {
      $commands[] = new UpdateBuildIdCommand($_POST['form_build_id'], $build['#build_id']);
    }

    // We need to return the part of the form (or some other content) that needs
    // to be re-rendered so the browser can update the page with changed
    // content. It is up to the #ajax['callback'] function of the element (may
    // or may not be a button) that triggered the Ajax request to determine what
    // needs to be rendered.
    $callback = NULL;
    $wrapper = NULL;
    $ajax_callback = NULL;

    // If we have an ajax callback assume we're doing a drupal style ajax replacment
    if (($triggering_element = $this->form_state
      ->getTriggeringElement()) && isset($triggering_element['#ajax']['callback'])) {
      $callback = $ajax_callback = $triggering_element['#ajax']['callback'];
      $wrapper = $triggering_element['#ajax']['wrapper'];
    }

    // Determine if there is a callback.
    $callback = $this->form_state
      ->prepareCallback($callback);
    if ($callback) {
      if (empty($callback) || !is_callable($callback)) {
        $commands[] = new HtmlCommand('#' . $section, $form);
      }
      $result = call_user_func_array($callback, [
        &$form,
        &$this->form_state,
      ]);

      // At this point we know callback returned a render element. If the
      // element is part of the group (#group is set on it) it won't be rendered
      // unless we remove #group from it. This is caused by
      // \Drupal\Core\Render\Element\RenderElement::preRenderGroup(), which
      // prevents all members of groups from being rendered directly.
      if (is_array($result)) {
        if (!empty($result['#group'])) {
          unset($result['#group']);
        }
        if ($wrapper) {
          $commands[] = new ReplaceCommand('#' . $wrapper, $result);
        }
        else {

          // we don't have a wrapper so assume form section replacement
          $commands[] = new HtmlCommand('#' . $section, $form);
        }
      }
    }
    else {

      // No ajax callback implies we are doing a normal full form replacment.
      $commands[] = new HtmlCommand('#' . $section, $form);
    }
    return $commands;
  }

  /**
   * Retrieve a drupal form for inclusion in the app.
   * Will load based on controlllerr's jsMode property as either
   * an ajax command or as an inline form.
   *
   * @param string $section
   *   The section of the template in which to render the form.
   * @param string $class
   *   The class name of the form to render.
   */
  protected function getForm($section, $class) {
    $modal = $this->is_modal_form;
    $this->prior_section = $this->section;
    $this->section = $section;
    $this->is_modal_form = FALSE;

    // We need to skip the second redering of this form in a route because
    // it may lead to an ajax error.  It should already have been rendered
    // in the form processing phase.
    if (empty($_POST['form_id']) || $class::FORM_ID != $this->post_form_id) {
      $content = \Drupal::formBuilder()
        ->getForm($class);
      if ($this->jsMode != 'nojs' && $this->jsMode != 'drupal_modal') {
        $this->commands = array_merge($this->commands, $this
          ->generateAjaxReplace($section, $content));
      }
      else {
        $this->build[$section] = $content;
      }
      $this->is_modal_form = $modal;
      $this->section = $this->prior_section;
    }
  }

  /**
   * @param string $section
   *   The class name of the form to render.
   * @param string $class
   *   The class name of the form to get.
   * @param string $title
   *   The title to be used on the modal.
   * @param array $options
   *   Array of Jquery UI Dialog options as described at
   *   http://api.jqueryui.com/dialog
   *
   */
  protected function getModalForm($section, $class, $title, $options = []) {
    $modal = $this->is_modal_form;
    $this->is_modal_form = TRUE;
    if (empty($_POST['form_id']) || $class::FORM_ID != $this->post_form_id) {
      $content = \Drupal::formBuilder()
        ->getForm($class);
      if ($this->jsMode != 'nojs') {
        if (!$this->modal_added) {
          $this->commands[] = new OpenModalDialogCommand($title, $content, $options);
          $this->modal_added = TRUE;

          // If autoResize is not manually disabled draggable will always be disabled
          // by drupal javascript.  So we add a command to enable it manually after
          // the form is initially sized.
          if (!isset($options['autoResize']) && !empty($options['draggable'])) {
            $this->commands[] = new InvokeCommand('#drupal-modal', 'eModalDraggable');
          }
        }
      }
      else {
        $this->build[$section] = $content;
      }
    }
    $this->is_modal_form = $modal;
  }

  /**
   * Render a forena report.
   * @param string $section
   *   The id of the section where the report goes.
   * @param string $report
   *   The name of the report to render.
   */
  protected function report($section, $report) {

    /** @var Forena $forena */
    $forena = \Drupal::service('forena.reports');
    $content = $forena
      ->report($report, $this->parms);
    if ($this->jsMode != 'nojs' && $this->jsMode != 'drupal_modal') {
      $this->commands[] = new HtmlCommand('#' . $section, $content);
    }
    else {
      $this->build[$section] = $content;
    }
  }
  public function runReport($report) {

    /** @var Forena $forena */
    $forena = \Drupal::service('forena.reports');
    return $forena
      ->runReport($report, $this->parms);
  }

  /**
   * Render a forena report in a modal
   * @param string $section
   *   The id of the section where the report goes.
   * @param string $title
   *   Title of modal window
   * @param string $report
   *   The name of the report to render.
   * @param array $options
   *   Modal dialog options.
   */
  protected function modalReport($section, $report, $title = '', $options = []) {

    /** @var Forena $forena */
    $forena = \Drupal::service('forena.reports');
    if ($this->jsMode == 'nojs') {
      $this
        ->report($section, $report);
    }
    else {
      $this->modal_content = $forena
        ->report($report, $this->parms);
      if (!isset($options['draggable'])) {
        $options['draggable'] = TRUE;
      }
      $this
        ->getModalForm($section, ReportModalForm::class, $title, $options);
    }
  }

  /**
   * Set the url for the controller, including current parameters.
   * @param string $action
   *   The relative action to set
   * @param string $title
   *   The browser title.
   * @param array|NULL $parms
   *   Parmaters.  If null speicificied then use the parms properties
   *   of the cotnroller.
   */
  public function setUrl($action, $title = NULL, $parms = NULL) {
    $query = "";
    if ($parms === NULL) {
      $parms = $this->parms;
    }
    if ($parms) {
      unset($parms['form_id']);
      $query = http_build_query($parms);
    }
    if ($query) {
      $action .= "?{$query}";
    }
    $this->endCommand = new InvokeCommand('html', 'forenaAjaxChangeUrl', [
      $action,
      $title,
    ]);
  }

  /**
   * Set the final command to be run as part of the ajax replacment.
   *
   * @param object $command
   *   AjaxCommand object.  There doesn't appear to be a base ajax command type
   *   for drupal.
   */
  public function setEndCommand($command) {
    $this->endCommand = $command;
  }

  /**
   * Initialize the layout report.
   */
  public function initLayout() {
    $this->build = [];
  }
  public function __sleep() {
    return [];
  }

}

Members

Namesort descending Modifiers Type Description Overrides
AjaxControllerInterface::route public function Route the requested action for the controller
AjaxPageControllerBase::$action public property
AjaxPageControllerBase::$build protected property Drupal render array containing content.
AjaxPageControllerBase::$commands protected property Ajax commands that are to be returned by the object.
AjaxPageControllerBase::$context protected property Application context to load.
AjaxPageControllerBase::$endCommand public property The final command that updates the page. Note this is generally either a setURL ajax command or it is a CloseDialog command. @var
AjaxPageControllerBase::$form_state public property @var FormStateInterface
AjaxPageControllerBase::$instance protected static property Singleton instance.
AjaxPageControllerBase::$is_modal_form public property Indicates whether the current form being processed is a modal.
AjaxPageControllerBase::$jsMode public property The JSMode passed to the the controller to determine how to deliver the page. "ajax" implies an ajax page load "nojs" implies a normal page.
AjaxPageControllerBase::$layout public property
AjaxPageControllerBase::$libraries public property Array of librarries to load with this controller.
AjaxPageControllerBase::$modal_added public property Inidates whether a modal has already been added
AjaxPageControllerBase::$modal_content public property Render array of modal content.
AjaxPageControllerBase::$parms public property Current parameters for the report.
AjaxPageControllerBase::$post_form_id public property
AjaxPageControllerBase::$prevent_action public property
AjaxPageControllerBase::$prior_section public property
AjaxPageControllerBase::$section public property
AjaxPageControllerBase::$state protected property @var object
AjaxPageControllerBase::$token protected property Controller token for keeping track of state information between page loads of the controller.
AjaxPageControllerBase::addCommand public function \Add an ajax controller command to the stack.
AjaxPageControllerBase::clearCommands public function Clear the commnds from the ajax buffer.
AjaxPageControllerBase::DEFAULT_ACTION constant
AjaxPageControllerBase::generateAjaxReplace protected function Generate the ajax form replacement commands.
AjaxPageControllerBase::generateStateToken protected function Generate a token for form
AjaxPageControllerBase::getCommands public function Get the AJAX commands to return to the blroser.
AjaxPageControllerBase::getForm protected function Retrieve a drupal form for inclusion in the app. Will load based on controlllerr's jsMode property as either an ajax command or as an inline form.
AjaxPageControllerBase::getModalForm protected function
AjaxPageControllerBase::getState public function
AjaxPageControllerBase::getStateToken public function Return the state token.
AjaxPageControllerBase::initLayout public function Initialize the layout report. Overrides AjaxControllerInterface::initLayout
AjaxPageControllerBase::isAjaxCall public function Indicates whether the current callback is an ajax call.
AjaxPageControllerBase::LAYOUT constant
AjaxPageControllerBase::loadState protected function Loads the state from the token.
AjaxPageControllerBase::MAX_STATE_AGE constant
AjaxPageControllerBase::modalReport protected function Render a forena report in a modal
AjaxPageControllerBase::page public function Default page controller implementation. This method is typically what you would reference in the routing.yml file as the main menu callback for the controller. Overrides AjaxControllerInterface::page
AjaxPageControllerBase::preventAction public function
AjaxPageControllerBase::processForm public function Processes the form based on a form_id
AjaxPageControllerBase::processFormRequest public function Process the post requests for an action.
AjaxPageControllerBase::render public function
AjaxPageControllerBase::report protected function Render a forena report.
AjaxPageControllerBase::response public function Return the response based on ajax pramters. Overrides AjaxControllerInterface::response
AjaxPageControllerBase::runReport public function
AjaxPageControllerBase::saveState public function Save the state of the controller
AjaxPageControllerBase::service public static function Singleton factory method.
AjaxPageControllerBase::setEndCommand public function Set the final command to be run as part of the ajax replacment.
AjaxPageControllerBase::setReportContext public function Sets the report context for a report.
AjaxPageControllerBase::setUrl public function Set the url for the controller, including current parameters.
AjaxPageControllerBase::TOKEN_PARAMETER constant
AjaxPageControllerBase::TOKEN_PREFIX constant
AjaxPageControllerBase::__construct public function AjaxPageControllerBase constructor.
AjaxPageControllerBase::__sleep public function
ControllerBase::$configFactory protected property The configuration factory.
ControllerBase::$currentUser protected property The current user service. 1
ControllerBase::$entityFormBuilder protected property The entity form builder.
ControllerBase::$entityManager protected property The entity manager.
ControllerBase::$entityTypeManager protected property The entity type manager.
ControllerBase::$formBuilder protected property The form builder. 2
ControllerBase::$keyValue protected property The key-value storage. 1
ControllerBase::$languageManager protected property The language manager. 1
ControllerBase::$moduleHandler protected property The module handler. 2
ControllerBase::$stateService protected property The state service.
ControllerBase::cache protected function Returns the requested cache bin.
ControllerBase::config protected function Retrieves a configuration object.
ControllerBase::container private function Returns the service container.
ControllerBase::create public static function Instantiates a new instance of this class. Overrides ContainerInjectionInterface::create 40
ControllerBase::currentUser protected function Returns the current user. 1
ControllerBase::entityFormBuilder protected function Retrieves the entity form builder.
ControllerBase::entityManager Deprecated protected function Retrieves the entity manager service.
ControllerBase::entityTypeManager protected function Retrieves the entity type manager.
ControllerBase::formBuilder protected function Returns the form builder service. 2
ControllerBase::keyValue protected function Returns a key/value storage collection. 1
ControllerBase::languageManager protected function Returns the language manager service. 1
ControllerBase::moduleHandler protected function Returns the module handler. 2
ControllerBase::redirect protected function Returns a redirect response object for the specified route. Overrides UrlGeneratorTrait::redirect
ControllerBase::state protected function Returns the state storage service.
LinkGeneratorTrait::$linkGenerator protected property The link generator. 1
LinkGeneratorTrait::getLinkGenerator Deprecated protected function Returns the link generator.
LinkGeneratorTrait::l Deprecated protected function Renders a link to a route given a route name and its parameters.
LinkGeneratorTrait::setLinkGenerator Deprecated public function Sets the link generator service.
LoggerChannelTrait::$loggerFactory protected property The logger channel factory service.
LoggerChannelTrait::getLogger protected function Gets the logger for a specific channel.
LoggerChannelTrait::setLoggerFactory public function Injects the logger channel factory.
MessengerTrait::$messenger protected property The messenger. 29
MessengerTrait::messenger public function Gets the messenger. 29
MessengerTrait::setMessenger public function Sets the messenger.
RedirectDestinationTrait::$redirectDestination protected property The redirect destination service. 1
RedirectDestinationTrait::getDestinationArray protected function Prepares a 'destination' URL query parameter for use with \Drupal\Core\Url.
RedirectDestinationTrait::getRedirectDestination protected function Returns the redirect destination service.
RedirectDestinationTrait::setRedirectDestination public function Sets the redirect destination service.
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.
UrlGeneratorTrait::$urlGenerator protected property The url generator.
UrlGeneratorTrait::getUrlGenerator Deprecated protected function Returns the URL generator service.
UrlGeneratorTrait::setUrlGenerator Deprecated public function Sets the URL generator service.
UrlGeneratorTrait::url Deprecated protected function Generates a URL or path for a specific route based on the given parameters.