Egy sima form betöltése Chaos tool suite (ctools) modal ablakba

Már régóta kacérkodok a gondolattal, hogy jó lenne valamit írni a Chaos tool suite (ctools) modulról is, mert egyrészt egy nagyon jó kis API modul - ha már csak azt megnézzük, hogy milyen kaliberű modulok építenek rá akkor gondolhatjuk, hogy nem rossz - másrészt pedig amilyen jó annyira nincs ledokumentálva a használhatósága, amolyan fekete lyuk a Drupal világában, meg amúgy sem tudtam aludni, akkor meg már forgolódás helyett inkább valami értelmes dolgot csinálok.

Igazából nagyon sokat lehetne róla írni, de szerintem kezdjük az alapokkal, én se őrülök meg mire megírom, Te sem alszol el mire elolvasod :)

Az egyszerű feladat

Az egyszerű feladat legyen csak annyi, hogy készíts egy olyan form-ot amin pár sörféle közül lehet checkbox-okkal választani, és a megjelenéshez (ha van JavaScript) akkor a ctools modal ablakát használd.

Lássuk akkor mi is kell ehhez:

  • kell a két menü elem, az egyik ahol maga a link fog megjelenni a másik pedig amelyik betöltődik a modal ablakba
  • kell egy form
  • kell a két menü elemhez az egy-egy callback fgv.

Ez bizony nem tűnik soknak, kezdjük is el.

A menu item-ek

/**
 * Implementation of hook_menu().
 *
 * @return An array of menu items.
 */
function modal_test_menu() {
  $items = array();
 
  $items['modal-test'] = array(
    'title' => 'Modal test page',
    'description' => 'Custom module, to check ctools modal page.',
    'page callback' => 'modal_test_page',
    'access arguments' => array('access content'),
    'file' => 'includes/modal_test.page.inc',
  );
 
  $items['modal-test/%ctools_js/beer'] = array(
    'title' => 'Modal test beer page',
    'description' => 'Custom module, to check ctools modal page.',
    'page callback' => 'modal_test_page_beer',
    'page arguments' => array(1),
    'access arguments' => array('access content'),
    'file' => 'includes/modal_test.page.inc',
    'type' => MENU_CALLBACK,
    'delivery callback' => 'ajax_deliver',
  );
 
  return $items;
}

Ebben itt semmi ördöngösség nincs, az első menü elem az az oldal lesz ahol megjelenik majd a link amire kattintva bejön a modal form, a második pedig ennek a modal ablaknak a legenerálásáért felel.
Két ismeretlen dolog lehet:

  • a %ctools_js, ami annyit csinál, hogy meghívja a ctools_js_load() fgv-t és ha a kapott paraméter az ajax string, akkor visszatér TRUE-val, ellenkező esetben pedig egy 0-val, vagyis azt vizsgálja van-e JavaScript a böngészőnkben
  • 'delivery callback' => 'ajax_deliver', amire pedig azért van szükség, mert a modal ablak tartalma egy ajax által feldogozott tartalom lesz. Persze bele lehetne gányolni, hogy print ajax_render($out); de azt inkább kerülném, minek ha van rá megoldás

A form

Ez biztosan nem fog meglepni senkit sem, egy sima mezei form:

/**
 * Build modal_test_beer_form form.
 *
 * @param array $form_state
 * @return array The created form.
 */
function modal_test_beer_form($form_state) {
  $form = array();
 
  $form['beers'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Choose your beers'),
    '#options' => array(
      'Arany ászok',
      'Soproni',
      'Nem nagyon jó egyik sem :(',
    ),
    '#required' => TRUE,
  );
 
  $form['actions'] = array(
    '#type' => 'actions',
  );
 
  $form['actions']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('OK'),
  );
 
 
  return $form;
}

A modal ablak linkje

Először nézzük meg a kódot:

function modal_test_page() {
  $out = '';
  $links = array();
 
  ctools_include('ajax');
  ctools_include('modal');
 
  ctools_modal_add_js();
 
  $links[] = ctools_modal_text_button(t('Test with default modal'), 'modal-test/nojs/beer', t('Pick a beer'));
 
  $out .= theme('item_list', array('items' => $links, 'title' => t('Actions')));
 
  return array(
    'markup' => array(
      '#markup' => $out,
    ),
  );
}

Az elején feltűnik a két include fgv. hívás, ezekre szükség van modal ablak készítésekor, csak úgy mint a JavaScript betöltésére, úgyhogy ezeket ne hagyjuk ki, különben a php el fog szállni hibaüzenettel.

Ezután generálunk egy linket. Látható, hogy a ctools modul ctools_modal_text_button() fgv-ével készítjük a linket és nem a Drupal l() fgv-ét használva. Az első paraméter a link szöveg, a második maga az url és a harmadik a link alt attribútuma. Létezik egy negyedik paraméter is, melyben a link class-okat lehet felsorolni, de amennyire tudom azt át fogják alakítani options tömbbé, hogy ne csak class-t lehessen neki átadni.
Látható, hogy a link paraméterben szerepel a nojs string. Ez fontos, mert a ctools ajax-responder-e az ilyen linkeken végigmegy és a nojs szöveget kicserélni ajax-ra, ezt figyeli a fent említett ctools_js_load() fgv.

A mágia itt véget is ért, visszatér a menu callback egy render tömbbel, amit a Drupal feldolgoz.

A modal ablak

Ha megnézzük itt sincs akkora varázslat:

function modal_test_page_beer($js = NULL) {
  if (!$js) {
    return drupal_get_form('modal_test_beer_form');
  }
 
  ctools_include('ajax');
  ctools_include('modal');
 
  $form_state = array(
    'title' => t('Choose beer'),
    'ajax' => TRUE,
  );
 
  $out = ctools_modal_form_wrapper('modal_test_beer_form', $form_state);
 
  if (!empty($form_state['executed'])) {
    // Overwrite the output if it was successful.
    $out = array();
    $out[] = ctools_modal_command_dismiss();
  }
 
  return array(
    '#type' => 'ajax',
    '#commands' => $out,
  );
}

Ha nincs JavaScript bejön a sima form, ellenkező esetben ismét behívjuk a megfelelő ctools lib-eket. Amikor létrehozzuk a $form_state tömböt, meg kell adni neki, hogy ajax-os legyen és szükséges hozzá egy cím is. Ezután a form legenerálást a ctools_modal_form_wrapper() ctools fgv-nyel végezzük el. Ekkor a $form_state felépül, és az executed kulcs csak csak akkor TRUE, ha a submit esemény sikeresen lefutott. Ekkor a kimeneti tömböt felülírjuk. Jelen esetben bezárjuk a modal ablakot a ctools_modal_command_dismiss() ctools fgv segítségével.
A végén visszatérünk a megfelelő tömbbel és az ajax_deliver() feldogozza.

Close, reload, redirect

Módosítások

Most nézzünk meg egy olyan esetet amikor nem bezárni akarjuk a modal-t, hanem kiíratunk rá egy szöveget. Ehhez szükséges még egy - az előzőhöz hasonló - menu item-et létrehozni, ez fogja kezelni, hogy mi is történjen a linkekre kattintáskor:

  $items['modal-test/%ctools_js/beer/%'] = array(
    'title' => 'Beer Action',
    'page callback' => 'modal_test_page_beer_action',
    'page arguments' => array(1, 3),
    'access arguments' => array('access content'),
    'file' => 'includes/modal_test.page.inc',
    'type' => MENU_CALLBACK,
    'delivery callback' => 'ajax_deliver',
  );

A modal készítő ablakot pedig módosítsuk:

function modal_test_page_beer($js = NULL) {
  if (!$js) {
    return drupal_get_form('modal_test_beer_form');
  }
 
  ctools_include('ajax');
  ctools_include('modal');
 
  $form_state = array(
    'title' => t('Choose beer'),
    'ajax' => TRUE,
  );
 
  $out = ctools_modal_form_wrapper('modal_test_beer_form', $form_state);
 
  if (!empty($form_state['executed'])) {
    // Overwrite the output if it was successful.
    $out = array();
 
    $close = ctools_ajax_text_button('close this modal', 'modal-test/nojs/beer/close', 'Close the dialog window.');
    $reloade = ctools_ajax_text_button('close this modal and reload page', 'modal-test/nojs/beer/reload', 'Close the dialog window and reload the page behind.');
    $account = ctools_ajax_text_button('go to account page', 'modal-test/nojs/beer/redirect', 'Redirect to accout page.');
    $out[] = ctools_modal_command_display('Beers checked', '<div class="modal-message">You checked a beer! Now you can ' . $close . ' or ' . $reloade . ' or ' . $account . ' Cheers!</div>');
  }
 
  return array(
    '#type' => 'ajax',
    '#commands' => $out,
  );
}

Látható, hogy lecseréltük a bezárást véghezvivő ctools_modal_command_dismiss() fgv-t a ctools_modal_command_display() ctools fgv-re. Ez a fgv valójában két paramétert vár, a title-t és a hozzá tartozó html-t, úgyhogy nem is ragoznám tovább, talán csak annyit említenék meg, hogy a html paraméternek nem feltétlen kell string-nek lennie, lehet a drupal_render() által feldolgozható tömb is. A linkek legenerálásához a ctools_ajax_text_button() ctools fgv-t használjuk, ennek a működése nagyon hasonlít a már említett ctools_modal_text_button() fgv-hez, lényegében ezt hívja meg az is.

Feldolgozás

És akkor nézzük meg a menu callback-et, ami a linkeket dolgozza fel:

function modal_test_page_beer_action($js, $action) {
  if (!$js) {
    // Only AJAX callbacks.
    return MENU_NOT_FOUND;
  }
 
  ctools_include('ajax');
  ctools_include('modal');
  ctools_add_js('ajax-responder');
 
  $out = array();
 
  switch ($action) {
    case 'close':
      // Close the modal.
      $out[] = ctools_modal_command_dismiss();
      break;
 
    case 'reload':
      // Close the modal and reload page.
      $out[] = ctools_ajax_command_reload();
      break;
 
    case 'redirect':
      // Redirect after 2 seconds.
      $out[] = ctools_ajax_command_redirect('user', 2000);
      break;
  }
 
  return array(
    '#type' => 'ajax',
    '#commands' => $out,
  );
}

Felépítése nagyon hasonlít az előzőre, de nézzük meg az akciókat:

Én első körben ennyit gondoltam elmondani erről a témáról, a teszt modul fájl forrása letölthető innen, ha valaki szeretné megnézni.