Jak na to: Drupal, Wysiwyg a tvorba tlačítka pro editor TinyMCE

Není tomu dávno, co jsem byl postaven před úkol doplnit do editoru TinyMCE v Drupalu další tlačítko, které by sloužilo k nějaké manipulaci s obsahem. Rád bych v tomto článku shrnul své zkušenosti s tímto úkolem. Zkusíme si praktickou ukázku, a sice vytvoření tlačítka zobrazujícího dialog pro vložení iframe s výřezem mapy z Google do těla editoru.

Nebudu zde probírat zadání svého úkolu, který jsem musel nedávno řešit. Každopádně jsem jeho zadavateli vděčný, protože jinak bych se asi nedostal k tomu, abych se naučil vytvářet nové funkce pro TinyMCE. Pro účely tohoto článku zadání trochu zjednoduším.

Zadání tedy bude následující - doplnit do vizuálního editoru v Drupalu tlačítko, které zobrazí dialog s políčky pro zadání lokality a rozměrů mapy. Po potvrzení zadaných údajů dojde k vložení značky iframe do těla editoru, přičemž tato značka bude obsahovat kód pro zobrazení Mapy Google s příslušnou lokalitou.

Co budeme potřebovat: Řešení vám ukáži pro Drupal 7 s instalovaným modulem Wysiwyg, do kterého si doplníte editor TinyMCE. Při tvorbě tlačítek, pluginů a funkcí pro TinyMCE vám přijde vhod popis TinyMCE API a samozřejmě základní povědomí o tom, jak stavět nové moduly pro Drupal, viz třeba moje kniha o Drupalu 7. Znalost jQuery taky nebude na škodu. To jsou základní předpoklad k tomu, abychom mohli postavit modul, který danou funkcionalitu doplní o tlačítko pro vkládání mapy. Postup při doplňování tlačítka do editoru CKeditor nebo jiného by byl podobný, jen byste museli pracovat se syntaxí jeho API.

Základ nového modulu

Protože je nesmysl editovat například modul Wysiwyg, jelikož bychom si jej při nejbližší aktualizaci přepsali, vytvoříme si nový modul. Začněme tím, že si vytvoříme v sites/all/modules novou složku nazvanou mapa. Nový modul se bude jmenovat stejně. Jako první si tedy připravte soubor mapa.info, ve kterém budou základní specifikace nového modulu:

name = Mapa
core = 7.x
description = Ukázkový modul pro vkládání mapy do editoru TinyMCE, vytvořil Jan Polzer
version = VERSION
package = Maxiorel
dependencies[] = wysiwyg

Jak vidíte, jedná se jen o specifikaci názvu a popisku modulu a informaci o jeho závislosti na modulu Wysiwyg. Pro jistotu připomínám, že všechny soubory je třeba ukládat s kódováním UTF-8.

Nyní si připravte soubor mapa.module s následujícím obsahem:

{
 
<?php

function mapa_wysiwyg_plugin($editor, $version) {
  switch($editor) {
    case 'tinymce':
      return array(
        'iframe' => array(
          'extensions' => array('iframe' => t('Iframe Fix')),
          'extended_valid_elements' =>
            array('iframe[src|width|height|frameborder|scrolling]'),
          'load' => FALSE,
          'internal' => TRUE,
          ),
      );
  }
}

function mapa_wysiwyg_include_directory($type) {
  switch ($type) {
    case 'plugins':
      return $type;
  }
}

function mapa_menu() { 
  $items['admin/content/mapa'] = array(
    'title' => 'Vložení mapy do textu',
    'access arguments' => array('administer nodes'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('mapa_insert_form'),    
    'type' => MENU_CALLBACK,    
  );   
  return $items;
}

function mapa_theme() {
  return array(
    'mapa' => array(
      'template' => 'mapa',
      'variables' => array('content' => NULL),
      'path' => drupal_get_path('module', 'mapa'),
    ),    
  );
}

function mapa_preprocess_page(&$vars){
  if (arg(1) == 'content' && arg(2) == 'mapa'){
    $vars['theme_hook_suggestions'][] = 'mapa';
    $vars['directory'] = drupal_get_path('module', 'mapa');
  }
}

function mapa_preprocess_html(&$variables) {
  if (arg(1) == 'content' && arg(2) == 'mapa'){
    unset($variables['page']['page_bottom']['admin_menu']);
    unset($variables['page']['page_top']['toolbar']);
    drupal_add_css(drupal_get_path('module', 'mapa') . '/mapa.css', array('group' => CSS_DEFAULT, 'every_page' => FALSE));
    drupal_add_js('/sites/all/libraries/tinymce/jscripts/tiny_mce/tiny_mce_popup.js', array('cache' => FALSE, 'preprocess' => FALSE));
    drupal_add_js(base_path().drupal_get_path('module', 'mapa') .'/plugins/mapa/mapa_get_data.js', array('cache' => FALSE, 'preprocess' => FALSE));
  }
}

function mapa_insert_form($form, &$form_state){
  $form = array();
  $form['mapa_lokalita'] = array(
    '#type' => 'textfield',
    '#title' => 'Zadejte místo na mapě',
    '#size' => 40,
    '#required' => TRUE,
  );
  $form['mapa_sirka'] = array(
    '#type' => 'textfield',
    '#title' => 'Šířka vložené mapy',
    '#size' => 10,
    '#required' => TRUE,
  );
  $form['mapa_vyska'] = array(
    '#type' => 'textfield',
    '#title' => 'Výška vložené mapy',
    '#size' => 10,
    '#required' => TRUE,
  ); 
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => 'Vložit mapu',
  );
  $form['cancel'] = array(
	'#type' => 'button',
	'#attributes' => array('onClick' => 'tinyMCEPopup.close();'),
	'#value' => 'Storno',
  );   
  $form['#theme'] = 'mapa';
  return $form;
}

Proberme si, co kód modulu obsahuje. Názvy funkcí jsou vždy tvořeny ve tvaru názevmodulu_funkce. Funkce mapa_wysiwyg_plugin() zde není povinná. Doplnil jsem ji jen proto, že budeme vkládat značku iframe, kterou editor TinyMCE v Drupalu standardně po vypnutí a zapnutí odstraní. Díky této úpravě značka iframe v obsahu zůstane.

Následuje funkce mapa_wysiwyg_include_directory(), která specifikuje informace o pluginech pro vizuální editor, které daný modul poskytuje. V tomto případě je zápis zcela redukován a konstrukce počítá s tím, že ve složce modulu vytvoříte složku plugins, která bude obsahovat příslušné kódy pro editor.

Jelikož tlačítko v editoru bude zobrazovat formulář, připravíme si jej tou nejsnazší cestou, a to jako klasický formulář v Drupalu. Pomocí funkce mapa_menu() tedy vytvoříme adresu, na které bude daný formulář dostupný. Z praktických důvodů nebudeme chtít, aby se ve formuláři zobrazovaly okolní prvky administrace nebo obsahu webu. Pomocí mapa_theme() vytvoříme konstrukci zajišťující načtení šablony mapa.tpl.php nacházející se ve složce modulu. Ta se aplikuje na stránku s formulářem, která se bude zobrazovat v dialogu editoru, což zajistíme funkcí mapa_preprocess_page(). V podmínce if zjišťuji, zda se jedná skutečně o stránku zobrazující daný formulář - kontroluji fragmenty v její URL.

Funkce mapa_preprocess_html() opět podobnou podmínkou zjistí, že je zobrazena právě stránka s formulářem a v takovém případě odstraní zobrazení administračního menu a případně i lišty modulu Toolbar. Dále do formuláře nalinkuje soubor mapa.css obsahující instrukce k nastavení vzhledu formuláře. Dvojice funkcí drupal_add_js() přidá do formuláře nejprve soubor s funkcemi pro vyskakovací okna editoru (omlouvám se za natvrdo napsanou cestu) a následně náš vlastní soubor, který bude obsluhovat zpracování formuláře.

Nakonec následuje funkce mapa_insert_form() volaná z mapa_menu() a zobrazující potřebný formulář na adrese specifikované v mapa_menu(). Je to klasická tvorba formuláře pomocí modulu v Drupalu, netřeba dále vysvětlovat. Snad jen u tlačítka pro zrušení formuláře - je zde doplněna událost kliknutí na tlačítko Storno, ke které je navázána funkce pro zavření vyskakovacího okna editoru. Bude to fungovat za předpokladu, že jste výše nezapomněli připojit soubor tiny_mce_popup.js. Do formuláře rovněž nezapomeňte přidat položku #theme s odkazem na příslušnou šablonu specifikovanou výše.

Šablona a CSS modulu

V hlavním kódu modulu se odkazujeme mimo jiné na dva další soubory nacházející se přímo ve složce s modulem. V prvé řadě je to soubor mapa.tpl.php. Ten nahrazuje klasickou šablonu page.tpl.php a obsahuje jen minimum kódu. V podstatě po něm chceme zobrazit jen obsahovou část, ve které bude formulář a nic dalšího. Případně i nějaké chybové hlášky, které jsem ovšem zakomentoval.

<?php
  //print $messages;
  print render($page['content']);
?>

Soubor mapa.css, který upravuje vzhled formuláře, je v našem případě také vcelku jednoduchý. Přidává jen nějaké vnitřní odsazení, aby formulář ve vyskakovacím okně dobře vypadal.

#admin-menu{
  display: none;
}

html body.admin-menu{
  margin-top: 0px !important;
}

html body form{
  padding: 5px;
  margin: 10px 0px;
}

Kód pluginu pro editor TinyMCE

Ve složce s modulem mapa vytvořte podsložku plugins. Do ní umístěte soubor mapa.inc s následujícím obsahem:

<?php

function mapa_mapa_plugin() {
  $plugins['mapa'] = array(
    'title' => 'Vložit mapu',
    'vendor url' => 'http://www.polzer.cz/',
    'icon title' => 'Vložit mapu',
    // The path to the button's icon; defaults to
    // '/[path-to-module]/[plugins-directory]/[plugin-name]/images'.
    //'icon path' => 'path to icon',
    
    // The button image filename; defaults to '[plugin-name].png'.
    //'icon file' => 'name of the icon file with extension',
    
    // An alternative path to the integration JavaScript; defaults to
    // '[path-to-module]/[plugins-directory]/[plugin-name]'.
    //'js path' => drupal_get_path('module', 'mymodule') . '/awesomeness',
    
    // An alternative filename of the integration JavaScript; defaults to
    // '[plugin-name].js'.
    //'js file' => 'awesome.js',
    
    // An alternative path to the integration stylesheet; defaults to
    // '[path-to-module]/[plugins-directory]/[plugin-name]'.
    'css path' => '',
    // An alternative filename of the integration stylesheet; defaults to
    // '[plugin-name].css'.
    'css file' => '',
    // An array of settings for this button. Required, but API is still in flux.
    'settings' => array(
    ),
    // TinyMCE-specific: Additional HTML elements to allow in the markup.
//    'extended_valid_elements' => array(
//      'tag1[attribute1|attribute2]',
//      'tag2[attribute3|attribute4]',
//    ),
  );
  return $plugins;
}

Pojmenování tohoto souboru vychází z funkce mapa_wysiwyg_include_directory() v souboru mapa.module. Tam jsme nespecifikovali žádné speciální pojmenování, takže tento includovací soubor má stejný název jako modul samotný. Jeho obsahem je funkce tvořená stylem názevmodulu_název_plugin, v našem případě tedy mapa_mapa_plugin(). Výstupem této funkce je pole obsahující informace o tlačítkách, která chceme do editoru přidat. V našem případě je to jen jedno tlačítko uvedené v $plugins['mapa']. Pokud byste chtěli přidat další, doplnili byste do pole $plugins['nazev-dalsiho-tlacitka'].

Struktura informací u tlačítku či pluginu, chcete-li, je následující: nezbyně musíte specifikovat titulek pluginu zobrazovaný v administraci Wysiwygu, URL adresu tvůrce pluginu a text tlačítka zobrazeného v editoru (text se zobrazí v plovoucí nápovědě nad tlačítkem). Další možné nastavení je uvedeno v zakomentované části. Pokud chcete JavaScript s pluginem pojmenovat jinak, než jak je definováno v indexu pole pro tento plugin, museli byste specifikovat cesty k souborům s kódem a s obrázkem na tlačítko. My se tomu vyhneme. Vyresetoval jsem informaci o CSS - žádné nepřidáváme, ale pokud necháte na výchozí hodnotě, editor by do javascriptové konzole hlásil chybu a nenalezeném souboru se styly.

Obrázek tlačítka v editoru a obsluha tlačítka

Nyní si ve složce plugins vyvořte složku mapa, resp. složku s takovým názvem, který vychází z výše zmíněného indexu pole $plugins. V této složce mapa vytvořte ještě složku images, do které umístěte soubor mapa.png - obrázek o rozměrech 25x25 px, který bude použit pro zobrazení na tlačítku v editoru. Nyní přejděte o úroveň výše, další složky už vytvářet nebudeme. Měli byste tedy být v mapa/plugins/mapa.

Vytvořte zde nový soubor mapa.js - bude obsahovat obsluhu tlačítka v editoru a jeho pojmenování opět vychází z indexu výše zmíněného pole $plugins:

Drupal.wysiwyg.plugins['mapa'] = {

  invoke: function(data, settings, instanceId) {
	  tinyMCE.activeEditor.windowManager.open({
		   url : '/admin/content/mapa',
		   width : 400,
		   height : 265,
		   inline : true
		});
  }
};

V naznačené konstrukci opět pracujeme s názvem odpovídající indexu pole $plugins - v našem případě stále se slůvkem mapa. Přidáme zde funkci pro zobrazení dialogu/vyskakovacího okna editoru TinyMCE. Specifikujeme zde cestu ke stránce zobrazené v okně (naše URL definovaná přímo Drupalem a zobrazující formulář), rozměry tohoto okna a informaci o tom, že okno bude modální, resp. přímo ve stránce.

Zpracování vstupu z formuláře

Když kouknete na to, co jsme doposud vytvořili, zbývá nám k dokončení jediný soubor. V souboru mapa.module jsme ve funkci mapa_preprocess_html() do stránky s formulářem linkovali soubor mapa_get_data.js. Ten nyní doplníme do složky mapa/plugins/mapa. Jeho obsah bude následující:

(function($) {

tinyMCEPopup.requireLangPack();

$(document).ready(function(){
  $(".form-submit").click(function(event){
    event.preventDefault();
    lokalita = $('#edit-mapa-lokalita').val();
    width = $('#edit-mapa-sirka').val();
    height =  $('#edit-mapa-vyska').val();
    if (Number(width) > 0 && Number(height) > 0 && lokalita.length > 0){
      mapa = '';
      tinyMCEPopup.editor.execCommand('mceInsertContent',false,mapa);
      tinyMCEPopup.close();	
    }
    else{
      alert('Musíte vyplni lokalitu a zadat rozměry mapy v celých číslech');
    }
  });	
});

})(jQuery);

Využijeme toho, že v Drupalu 7 je k dispozici jQuery už přímo v jádře. Celou funkcinalitu v tomto souboru obalíme do jQuery, aby se nám lépe přistupovalo k formuláři. Jako první načteme jazykové prostředí vyskakovacího okna editoru.

Nyní přistoupíme k tomu, že po načtení stránky doplníme k tlačítku pro odeslání formuláře naši vlastní obsluhu. V prvné řadě zde zakážeme výchozí zpracování tlačítka - odeslání dat PHP skriptu pro validaci a zpracování (to jsme v našem modulu ani nedefinovali). Nyní přiřadíme obsah políček lokalita, šířka a výška do nových proměnných.

V podmínce zkontrolujeme, zda byly vyplněny číslené rozměry výřezu mapy a nějaká lokalita. Pokud ano, poskládáme údaje získané z formuláře do řetězce tvořícího iframe pro vložení Mapy Google. Voláním tinyMCEPopup.editor.execCommand() s argumentem mceInsertContent předáme takto poskládaný řetězec k vložení do editoru a následně vyskakovací okno zavřeme. Pokud uživatel něco nevyplnil, zobrazíme jen upozornění a okno necháme dále otevřeno.

Použití nového pluginu pro TinyMCE

Modul máme tímto dokončen a zbývá jej pouze aktivovat. Přejděte tedy klasicky do administrační části Konfigurace > Vytváření obsahu > Wysiwyg profiles a v profilu pro formát Full HTML (nebo jiný) zapněte nové tlačítko Vložit mapu. Vyprázdněte si cache v prohlížeči i v Drupalu, jděte do editace nějakého obsahu a zobrazte si editor. Nové tlačítko by se zde již mělo objevit a zobrazit následující dialog:

TinyMCE

Editor TinyMCE s mapou a znovu zobrazeným dialogem pro její vložení

Po vložení do textu se vám zde zobrazí výřez mapy s danou lokalitou.

Možná další vylepšení a zjednodušení

Určitě se vám výše popsaný postup zdá po prvním přečtení velice zdlouhavý a složitý. Ale když se nad tím zamyslíte, není tomu tak. Jedná se o vytvoření jen několika málo souborů navíc oproti základnímu modulu pro Drupal. Nehledě na to, že si je pak můžete snadno kopírovat z projektu do projektu a upravovat.

U větších věcí můžete nechat zobrazovat více stránek s formuláři generovanými Drupalem a vytvořit tak komplexní průvodce pro vložení nějakého obsahu do editoru. Stejně tak můžete část s formulářem zcela vypustit a tlačítka použít se znalostí TinyMCE API k manipulaci s obsahem označeným v editoru. V takovém případě byste skončili v části se souborem mapa.js, kde byste neotevírali nové vyskakovací okno s nějakou adresou, ale rovnou zpracovali nějaký obsah a ten vrátili zpět do editoru.

Další variantou může být použití HTML souboru s formulářem, který by se nacházel ve složce pluginu. Vyhnuli byste se tak definici formuláře v kódu modulu Drupalu a definici šablon vzhledu. Na druhou stranu byste se museli zdržovat syntaxí HTML, což podle mého vyjde na stejno.

Pokud bude z vaší strany zájem, můžeme si někdy v budoucnu ukázat vylepšení tohoto modulu o výběr mapových podkladů, které budou do editoru vkládány a nebo variantu, kdy se lokalita nezadává ve formuláři, ale použije se na ni text označený v editoru.

Prozatím můžete trénovat a zkoumat modul a plugin ve stávající podobě. V příloze článku najdete kompletní popsaný příklad, stačí jej nakopírovat do Drupalu a zapnout.

Pro pořádek ješě uvedu příslušné odkazy na api.drupal.org k funkcím, které jsme v modulu použili:

Příloha Velikost
mapa.zip 7.35 KB
mapa-uprava.zip 7.35 KB
mapa-uprava-3.zip 7.35 KB

Buďme ve spojení, přihlaste se k newsletteru

Odesláním formuláře souhlasíte s podmínkami zpracováním osobních údajů. 
Více informací v Ochrana osobních údajů.

Autor článku: Jan Polzer

Tvůrce webů z Brna se specializací na Drupal, WordPress a Symfony. Acquia Certified Developer & Site Builder. Autor několika knih o Drupalu.
Web Development Director v Lesensky.cz. Ve volných chvílích podnikám výlety na souši i po vodě. Více se dozvíte na polzer.cz a mém LinkedIn profilu.

Komentáře k článku

návštěvník

Nefunguje to. Zobrazí se sice dialogové okénko pro vložení adresy a velikosti, ale po odeslání formuláře se nic nevloží a jen se reloaduje ono dialogové okénko s prázdnými políčky.

Profile picture for user Jan Polzer

Jakou máte konfiguraci? Zkuste pomocí admin menu resetovat cache Drupalu a pak cache prohlížeče.

návštěvník

Mam normalne Drupal 7.10, tinymce nejnovejsi. Nemam tam mit nainstalovany jeste nejaky google map modul s ověřeným klíčem?

Profile picture for user Jan Polzer

Nene, nepoužívá to Google Maps API, vidíte přeci, že to jen skládá iframe. Podle popisu, co Vám to dělá, si myslím, že je potřeba resetovat cache v Drupalu a případně i v prohlížeči.

Pokud to nepomůže, zkuste si zapnout Firebug a sledovat, jestli Vám modul zahlásí nějakou chybu v JavaScriptu. Zkoušel jsem jej dát na více webů, bylo to bez problémů.

Profile picture for user Jan Polzer

Dobrý den, udělal jsem malou úpravu. Zřejmě vám to zlobilo v případě, kdy máte zapnutou cache a agregaci JavaScriptu. Nová úprava si s tím již poradí, změna je zanesena i do kódu uvedeného v článku.

návštěvník

Našel jsem drobnou chybku - hodnota fieldu "inline" v metodě tinyMCE.aciveEditor.windowManager.open by podle dokumentace měla být typu Boolean (true místo "yes").
Díky za tutoriál, Honzo.

Přidat komentář

Odesláním komentáře souhlasíte s podmínkami Ochrany osobních údajů

reklama
Moje kniha o CMS Drupal

 

Kniha 333 tipů a triků pro Drupal 9


Více na KnihyPolzer.cz

Sledujte Maxiorla na Facebooku

Maxiorel na Facebooku

Hosting pro Drupal a WordPress

Hledáte český webhosting vhodný nejenom pro redakční systém Drupal? Tak vyzkoušejte Webhosting C4 za 1200 Kč na rok s doménou v ceně, 20 GB prostoru a automatické navyšováním o 2 GB každý rok. Podrobnosti zde.

@maxiorel na Twitteru

Maxiorel na Twitteru