TinyMCE popup készítése a WYSIWYG Drupal modulba

Ez a bejegyzés egy korábbi cikk folytatása, melyben egy TinyMCE plugin alapjait írtam le. A Soundcloud filter modulban társfejlesztő vagyok, így úgy gondoltam azon megyek végig, így egy gyakorlati példát mutatok.

A kiegészítő létrehoz egy menüpontot ami a popup forrása lesz, és JavaScript-tel kezel textfield és checkbox input-okat. Az input-ok alapján legenerálja a filter forrást, illetve felülírja ha már létezik.

A modul fájl

A hook_wysiwyg_plugin() implementálásáról már írtam a korábbi bejegyzésben, úgyhogy a hook_menu()-vel és a hook_perm()-mel kezdeném:

/**
 * Implementation of hook_menu().
 *
 * @return An array of menu items.
 */
function soundcloud_filter_menu() {
  $items = array();
 
  $items['soundcloud-filter/wysiwyg'] = array(
    'title' => 'Wysiwyg Popup',
    'page callback' => 'soundcloud_filter_wysiwyg_popup',
    'access arguments' => array('access soundcloud filter wysiwyg popup'),
    'type' => MENU_CALLBACK,
  );
 
  return $items;
}
 
/**
 * Implementation of hook_perm().
 *
 * @return array An array of valid permissions for the soundcloud_filter module
 */
function soundcloud_filter_perm() {
  return array('access soundcloud filter wysiwyg popup');
}

Látható, hogy a menü típusa MENU_CALLBACK, vagyis nem fog megjelenni és csak az 'access soundcloud filter wysiwyg popup' joggal rendelkező felhasználók érhetik majd el.

A soundcloud_filter_wysiwyg_popup() fgv-ben összeállítom a szükséges változókat és kiíratom a popup eredményét. A létrehozott változók:

  • $path A filter modul elhelyezkedése a fájlrendszerben
  • $tinymce_path A tinyMCE könyvtára a fájlrendszerben. Ezt egyenlőre nem használjuk a popup-ban, de inkább átadom neki, lehet a jövőben szükség lesz rá
  • $tinymce_js A tinyMCE popup elérésének útvonala
/**
 * Load the WYSIWYG popup window.
 *
 * @return NULL
 */
function soundcloud_filter_wysiwyg_popup() {
  $path = base_path() . drupal_get_path('module', 'soundcloud_filter');
  $tinymce_path = base_path() .'sites/all/libraries';
  $tinymce_js = $tinymce_path .'/tinymce/jscripts/tiny_mce/tiny_mce_popup.js';
 
  echo theme('soundcloud_filter_wysiwyg_popup', $path, $tinymce_path, $tinymce_js);
 
  return NULL;
}

Megfigyelhetjük, hogy a popup eredményével nem visszatérünk, hanem kiíratjuk és a visszatérés NULL. Ez azért van, mert ha az eredménnyel térnénk vissza, akkor a sminkbe ágyazva kapnánk meg a forrást, tehát a legenerált popup csak a smink $content értéke lenne, de nekünk nem ez kell.
Látható, hogy a theme() fgv-el generálom le a popup ablakot, ez azért van, hogy a sminkkészítők felül tudják írni, ezért a hook_theme()-t is implementálni kellett:

/**
 * Implementation of hook_theme().
 */
function soundcloud_filter_theme() {
  return array(
    'soundcloud_filter_embed' => array(
      'arguments' => array(
        'params' => NULL,
        'height' => NULL,
        'widht' => NULL,
        'host' => NULL,
      ),
    ),
    'soundcloud_filter_wysiwyg_popup' => array(
      'arguments' => array(
        'path' => NULL,
        'tinymce_path' => NULL,
        'tinymce_js' => NULL,
      ),
      'template' => 'soundcloud-filter-wysiwyg-popup',
      'path' => drupal_get_path('module', 'soundcloud_filter') . '/theme',
    ),
  );
}

Nekünk itt a soundcloud_filter_wysiwyg_popup az érdekes, látható hogy template fájlt készítek neki, és a theme alkönyvtárba helyezem el. Ez nem kötelező, én így szeretem mert átláthatóbb a fájlrendszer.

A template fájl

A modul fájlunk szerkesztése itt véget is ért, nézzük meg soundcloud-filter-wysiwyg-popup.tpl.tpl.php-t:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
  <head>
    <title>Soundcloud filter</title>
    <link type="text/css" rel="stylesheet" media="all" href="<?php echo $path ?>/wysiwyg/tinymce/popup.css" />
    <script type="text/javascript" src="<?php echo $tinymce_js ?>"></script>
    <script type="text/javascript" src="<?php echo $path ?>/wysiwyg/tinymce/popup.js"></script>
  </head>
  <body id="soundcloud_filter_popup">
    <form action="#" method="POST" onsubmit="insertSoundcloudFiltterCode();return false;" id="soundcloud_filter_popup_form">
      <div class="tabs">
        <ul>
          <li id="general_tab" class="current">
            <span><a onmousedown="return false;" href="javascript:mcTabs.displayTab('general_tab','general_panel');">Insert/edit soundcloud</a></span>
          </li>
        </ul>
      </div>
      <div class="panel_wrapper">
        <div id="general_panel" class="current">
          <table cellpadding="4" cellspacing="0" border="0">
            <tr>
              <td><label for="soundcloud_filter_url">Url:</label></td>
              <td><input type="text" id="soundcloud_filter_url" name="soundcloud_filter_url" value="" /></td>
            </tr>
            <tr>
              <td><label for="soundcloud_filter_width">Width:</label></td>
              <td><input type="text" id="soundcloud_filter_width" name="soundcloud_filter_width" value="" /></td>
            </tr>
            <tr>
              <td><label for="soundcloud_filter_height">Height:</label></td>
              <td><input type="text" id="soundcloud_filter_height" name="soundcloud_filter_height" value="" /></td>
            </tr>
            <tr>
              <td><label for="soundcloud_filter_color">Color:</label></td>
              <td><input type="text" id="soundcloud_filter_color" name="soundcloud_filter_color" value="" /></td>
            </tr>
            <tr>
              <td><label for="soundcloud_filter_showcomments">Show comments:</label></td>
              <td><input type="checkbox" id="soundcloud_filter_showcomments" name="soundcloud_filter_showcomments" /></td>
            </tr>
            <tr>
              <td><label for="soundcloud_filter_autoplay">Auto play:</label></td>
              <td><input type="checkbox" id="soundcloud_filter_autoplay" name="soundcloud_filter_autoplay" /></td>
            </tr>
          </table>
        </div>
      </div>
      <div class="mceActionPanel">
        <div style="float: left;"><input id="insert" type="submit" value="Insert" /></div>
        <div style="float: right;"><input type="button" onclick="tinyMCEPopup.close();" value="Cancel" name="cancel" id="cancel" /></div>
      </div>
    </form>
  </body>

Látható, hogy először betöltjük a szükséges JavaScript és CSS állományokat majd létrehozunk egy egyszerű form-ot aminek a submit eseményére egy általam készített JavaScript fgv. fog lefutni.
Itt nincs semmi extra, annyit jegyeznék meg, hogy egyszerűen használható a TinyMCE beépített lehetőségei is. Pl a tabs osztály egyből létrehozza a megfelelő kinézetet és a benne lévő lista elemekben több tabot is kezelhetnénk az mcTabs.displayTab() fgv. segítségével. A fgv első paramétere a tab ID a második pedig a panel_wrapper osztályon belül megfelelő tartalom ID-ja. Így a tab funkciót és kinézetet nekünk nem kell lekódolni.
Ugyanígy használható a mceActionPanel osztály, melynek segítségével a Submit és Cancel gombok kinézetét tudjuk kezelni.

A css fájl

Mivel a TinyMCE beépített osztályait használtam, így csak a wrapper magasságát kell definiálni:

#soundcloud_filter_popup .panel_wrapper {
  height: 150px;
}

A JavaScript fájl

a popup.js fájlban nézzük meg először a submit eseménykor lefutó fgv-t azaz az insertSoundcloudFiltterCode() forrását:

function insertSoundcloudFiltterCode() {
  var ed = tinyMCEPopup.editor, f = document.forms[0], nl = f.elements, v, args = {}, el;
  var elm = ed.selection.getNode().innerHTML;
  tinyMCEPopup.restoreSelection();
 
  // Delete original content
  if (elm != null && elm.match(/\[soundcloud:[^\]]*\]/)) {
    ed.selection.getNode().innerHTML = ed.selection.getNode().innerHTML.replace(/\[soundcloud:[^\]]*\]/, '');
  }
 
  if (nl.soundcloud_filter_url.value === '') {
    ed.execCommand('mceRepaint');
    tinyMCEPopup.close();
    return;
  }
  else {
    var filter_string = '[soundcloud:' + nl.soundcloud_filter_url.value;
 
    if (nl.soundcloud_filter_width.value !== '') {
      filter_string += ' width=' + nl.soundcloud_filter_width.value;
    }
 
    if (nl.soundcloud_filter_height.value !== '') {
      filter_string += ' height=' + nl.soundcloud_filter_height.value;
    }
 
    if (nl.soundcloud_filter_color.value !== '') {
      filter_string += ' color=' + nl.soundcloud_filter_color.value;
    }
 
    if (nl.soundcloud_filter_showcomments.checked) {
      filter_string += ' showcomments=true';
    }
 
    if (nl.soundcloud_filter_autoplay.checked) {
      filter_string += ' autoplay=true';
    }
 
    filter_string += ']';
 
    ed.execCommand('mceInsertContent', false, filter_string);
    ed.undoManager.add();
 
    tinyMCEPopup.close();
    return;
  }
}

Először ellenőrizni kell, hogy volt-e kijelölés, és ha igen akkor tartalmazott-e a filter szintaktikájának megfelelő részletet. Ha igen akkor ezt a részletet ki kell törölni, így lehet felülírni az eredetit, ezzel elkerülhető hogy már legenerált filter-be beleírjunk egy következőt.
Azt is ellenőrizni kell, hogy üres string lett-e megadva az url-ben, mert ha igen akkor az törlést jelent. Ellenkező esetben szépen fel kell építeni a filternek megfelelő struktúrát:

  • fel kell építeni az url mezőt, mert ez kötelező és ez az alap
  • ellenőrizni kell a textfield-ek értékét, és ha nem üres akkor hozzá kell fűzni
  • ellenőrizni kell a checkbox-ok állapotát
  • majd le kell zárni a filter string-et

Ha mindezekkel megvagyunk, akkor beillesztjük a kódot és bezárjuk a popup ablakot.

Ezzel igazából már egy elég jól használható popup plugin-t kapunk, csak egy problémát kell még kiküszöbölni, ez pedig a szerkesztés, pontosabban ha már egy meglévőt akarunk módosítani, akkor a form-ban az adatok annak megfelelően legyenek kitöltve. Ehhez én készítettem egy init() fgv-t:

function init() {
  // The selected text
	var elm = tinyMCEPopup.editor.selection.getNode().innerHTML;
  // Form elements
  var elements = document.forms[0].elements;
  // Regular result
  var reg_res;
 
  // If found soundcloud filter
  if (elm != null && elm.match(/\[soundcloud:[^\]]*\]/)) {
    // Set url value
    reg_res = elm.match(/\[soundcloud:([^ \]]*).*\]/);
    elements['soundcloud_filter_url'].value = reg_res[1];
 
    // If width found
    reg_res = elm.match(/ width=([^ \]]*).*\]/);
    if (reg_res !== null) {
      elements['soundcloud_filter_width'].value = reg_res[1];
    }
 
    // If height found
    reg_res = elm.match(/ height=([^ \]]*).*\]/);
    if (reg_res !== null) {
      elements['soundcloud_filter_height'].value = reg_res[1];
    }
 
    // If color found
    reg_res = elm.match(/ color=([^ \]]*).*\]/);
    if (reg_res !== null) {
      elements['soundcloud_filter_color'].value = reg_res[1];
    }
 
    // If show comments is true
    if (elm.match(/ showcomments=true.*\]/)) {
      elements['soundcloud_filter_showcomments'].checked = 1;
    }
 
    // If autoplay is true
    if (elm.match(/ autoplay=true.*\]/)) {
      elements['soundcloud_filter_autoplay'].checked = 1;
    }
  }
}

Látható, hogy reguláris kifejezéssel végigmegyek a kijelölt elemen, és ha a filter-nek megfelelő szintaktikát találok, akkor a form értékét módosítom, illetve a checkbox-ot kijelölöm.

Végül ezt az init fgv-t hozzá kell adni a TinyMCE init-hez, így betöltődéskor le fog futni.

// Add init function
tinyMCEPopup.onInit.add(init);

És íme a végeredmény: