Rendszeresen megkeresnek azzal a kéréssel, hogy hogyan kell egyedi Drupal sminket készíteni, mert nem akrarnyak egy már elkészített contrib fejlesztést használni.
Én is általában azt szoktam javasolni, hogy inkább készítsük el saját magunk a meglévő psd alapján a sminkünket, mert igaz az esetek legtöbbségében meg lehetne oldani valamely már meglévő fejlesztés css-ének módosításával, de az mégsem az igazi. Egyrészt azért, mert a contrib sminkek több dologra vannak felkészítve mint amire nekünk szükségünk van, másrészt a végeredményben úgyis egy 3000 soros css-t kapunk amiben egy rakás osztály, vagy id több helyen van definiálva. Másrészt ahogy a contrib modulokba se szokás beleirkálni, szerintem a sminkeket se kell piszkálni.

Ha saját sminket készítünk, saját tpl és css fájlokkal akkor ezek a problémák kiküszöbölhetőek, és nem egy ördöngös feladat.

Leszögezném, hogy ez a bejegyzés csak az alapokról szól, nagyon sok egyéb dologra felkészíthetjük a sminkünket, pl: $logo, $site_slogan, $mission, $search_box stb. Az elérhető változókat itt lehet megnézni.

Az első lépés az, hogy a psd alapján végig kell gondolnunk, hogy milyen régiókra lesz szükségünk. Persze könnyen lehet, hogy az alapértelmezett régiók bőven elegendőek, de általában nekem újakat kell létrehoznom. Itt egy fontos megjegyzés az, hogy az alapértelmezett régiók elvesznek ha akár csak egy sajátot is létrehozunk, ha az info fájlba definiálunk régiót, akkor az összeset meg kell adnunk!

Miután ez megvan elkezdhetjük a sminkünk elkészítését, vagyis a fizikai fájlok létrehozását.

A saját sminkeket különítsük el a core sminkektől, ezért hozzuk létre a sites/all/themes könyvtárat és ide helyezzük a contrib, illetve a custom sminkeket.

Hozzuk létre a sminkemneve könyvtárat és abba készítsük a sminkemneve.info fájlt. Fontos megjegyzés, hogy az info fájlokat a rendszer cache-eli, így ha abba módosítunk valamit, akkor be kell menni az admin/build/themes könyvtárba és nyomni egy mentést, ekkor újra menti az info fájl tartalmát.

De hogy is néz ki egy info fájl? Erről egy teljesen részletes leírás található itt. Az alapértelmezett értékek pedig itt szerepelnek (itt láthatjuk pl a default régiókat).

Íme egy info fájl:

  1. name = My theme's name
  2. description = My theme's description.
  3. version = VERSION
  4. core = 6.x
  5. engine = phptemplate
  6. stylesheets[all][] = style.css
  7. stylesheets[all][] = layout.css
  8. scripts[] = scripts.js
  9. regions[header] = Header
  10. regions[left_slidebar] = Left sidebar
  11. regions[left_banner] = Left banner
  12. regions[content_top] = Content top
  13. regions[in_top_of_content] = In top of content
  14. regions[content] = Content
  15. regions[right_slidebar] = Right sidebar
  16. regions[right_banner] = Right Banner
  17. regions[footer_top] = Footer top
  18. regions[footer_bottom] = Footer bottom

Talán csak annyit fűznék még hozzá, hogy a regions tömb kulcsa lesz a page.tpl.php-ban elérhető változó ami az adott régió tartalmát adja vissza html string formában.

Ezután jöhet a page.tpl.php.
Ebben állítjuk össze magát a layout-ot, tehát létrehozzuk a szükséges div-eket a psd implementálásához.

Én így szoktam kezdeni:

  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  2.   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  3. <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php print $language->language ?>" lang="<?php print $language->language ?>" dir="<?php print $language->dir ?>">
  4.   <head>
  5.     <?php print $head ?>
  6.     <title><?php print $head_title ?></title>
  7.     <?php print $styles ?>
  8.     <?php print $scripts ?>
  9.   </head>
  10.   <body class="<?php print $body_classes ?>">

Szerintem elég egyértelmű hogy mit is csinál, csak annyit fűznék hozzá, hogy a $body_classes változó egy csomó információt tartalmaz, pl hogy nyitólapon vagyunk-e vagy, be vagyunk-e jelentkezve vagy hogy mely régiókra lesz szükség. Ez szerintem hasznos lehet ha azt akarjuk hogy a sminkünk máshogy nézzen ki pl ha csak a jobb oldali slidebar létezik, vagy ha mind a kettő, esetleg egyik sem stb.

Ezután kezdjük el kialakítani a szükséges div-eket, ez nyilván a psd-től függ.

Jöhetnek a régiók! Ha nem akarjuk teleszemetelni üres div-ekkel a template-et akkor feltételek közé rakjuk hogy egy adott régiót körülvevő div megjelenjen, pl így:

  1. <?php if ($left_slidebar) : ?>
  2.   <div id="left_slidebar"><?php print $left_slidebar ?></div>
  3. <?php endif; ?>

Miután a div-ekkel megvagyunk, készítsük el a tabokat (view, edit stb), a rendszerüzeneteket (pl hibás felhasználónév) írassuk ki magát a content-et - esetleg az oldal címét is beletehetjük a page.tpl.php-ba:

  1. <?php print $title ?>
  2. <?php if ($tabs): print '<div id="tabs-wrapper" class="clear-block">'; endif; ?>
  3. <?php if ($tabs): print '<ul class="tabs primary">'. $tabs .'</ul></div>'; endif; ?>
  4. <?php if ($messages): print $messages; endif; ?>
  5. <?php print $content ?>

Végül egy fontos feladatunk van a page.tpl.php-vel, az pedig a $closure kiíratása:

  1. <?php echo $closure; ?>

Ez azért fontos, mert sok modul ebbe pakol dolgokat, pl a WYSIWYG modul esetében azt érzékeljük, hogy egyszerűen nem jelenik meg az editor ha ez kimaradt a sminkfájlból.

Amint írtam van még egy csomó dolog amiket beletehetünk a sminkünkbe (és ha a közössésgnek készítjük a sminket akkor bele is kell rakni), ezen változók listáját itt lehet megnézni.

A page.tpl.php-n kívül még létre kell hozunk a block.tpl.php-t, a comment.tpl.php-t és a node.tpl.php-t. Ha az alap sminkfájlokat használjuk, akkor tökéletesen működik a sminkünk.
Persze ezeket is egyedileg testre kell szabi, ezeket is felosztjuk a megfelelő div-ekkel hogy olyan kinézetet kapjunk mint a psd-ben.

Van még egy fájl ami általában kell, ez pedig a template.php. Fontos, hogy az ebben definiált fügvények nevét cache-eli a rendszer, tehát ha egy új fügvényt deklarálunk akkor be kell menni az admin/build/themes oldalra és menteni kell.

A WYSIWYG editorok lényege, hogy a felhasználó HTML tudás nélkül is tudjon webes felületen formázni. Ezt úgy érjük el, hogy a html szöveget tartalmazó textarea-ra egy JavaScript szerkesztőfelületet adunk, ami a begépelt és formázott szöveget HTML kódokká alakítja át.

Drupal alá is létezik sok közkedvelt WYSIWYG editor modul, pl: FCK Editor vagy a TinyMCE Editor, melyek mindig előkelő helyen vannak a leggyakrabban letöltött Drupal modulok között.

Nekem a kedvencem mégis a Wysiwyg API modul, mégpedig azért, mert egy alapvető problémát old meg: a beviteli formák összeütközése a WYSIWYG editorral a beviteli forma szűrői miatt. Ez röviden annyit takar, hogy a többi WYSIWYG editor jogosultsághoz van rendelve, tehát ha egy felhasználónak megvan a joga az editorhoz akkor használhatja, de ha kiválasztja Filetred HTML beviteli formát, amelyben a sortörések, url-ek stb. szűrve vannak, akkor az eléggé össze fog akadni a WYSIWYG editorral. Ilyenkor ugyebár a felhasználó boldogan formázza a szöveget, majd elmenti a Drupal a szűrt beviteli formában és egyáltalán nem úgy néz ki a végeredmény mint ahogy azt akarta a felhasználó. Persze Ő soha nem fog rájönni, hogy mimiatt történt, és úgy gondolja valami fejlesztési hiba van a dologban. Természetesen ez nem fejelsztési hiba, de akkor sem várható el egy editortól, hogy rájöjjön mi okozza a problémát.

A Wysiwyg API modul erre talált gyógyírt, mégpedig azzal, hogy az editor használhatóságát a beviteli formához lehet megadni, nem pedig egy user jogosultság. Minden definiált beviteli formához külön-külön más és más editoroka lehet beállítani. Ekkor két kérdés merül fel:

  1. mi az hogy más és más WYSIWYG editorokat lehet rendelni?
  2. bárki használhatja az editort hisz nincs jogosultsághoz kötve?

Az első kérdésre a válasz az, hogy a Wysiwyg API egyszerre több WYSIWYG editort képes kezelni. Ez annyit takar, hogy a beállítási oldalán fel van sorova egy csomó WYSIWYG editor (pl MarkItUp, FCK editor vagy a TinyMCE editor), és amelyik fel van telepítve a megadott könyvtárba azt ki lehet választani egy legördülőből a beviteli formák felsorolása mellett.

A másodikra a válasz: nem. A beviteli formák beállítása oldalán meg lehet adni, hogy egy adott beviteli formát milyen csoport hasznáhat, és tetszőleges számút létrehozhatunk. Tehát pl, ha a Full HTML-t meg akarjuk hagyni a HTML-t értő és WYSIWYG editort nem használó csoport számára, akkor létrehozunk egy új beviteli formát, amelyben szintén nincs semmilyen szürő és hozzárendeljük a kívánt csoporthoz.

Ennyit a Wysiwyg API modul szépségéről, térjünk rá a cikk lényegére, a teaser beállítására.

Miután feltelepítettük a modult észrevehetjük, hogy eltűnt a teaser lehetőségére szolgáló gomb a body rész felett. Ez annyit takar hogy innenstől kezdve magunk nem állíthatjuk be hogy a teaser rész meddig tartson és azt sem, hogy ez akár különbözzön a tartalom többi részétől.

Mivel én hasznosnak találom ezt a funkciót, és többször is szükség volt már rá, ezért elkezdtem kutakodni és utánajárni hogy is lehetne megoldani. A TinyMCE editorban van egy pagebreak plugin, tehát lehetséges hogy megoldódik a probléma, mert a Drupal body részében egy helyre beírjuk a

<!--break-->

kifejezést, akkor ezt a Drupal úgy értelmezi, hogy az előtte lévő rész a teaser rész. Ez eddig szép és jó, csak kéne egy ilyen gomb az editor szerkesztőfelületére.

Ehhez a modul editors/tinymce.inc fájl wysiwyg_tinymce_plugins() fgv-ét kell módosítanunk.
A végén találunk benne egy ilyen részt:

  1.   if ($editor['installed version'] > 3) {
  2.     $plugins['xhtmlxtras'] = array(
  3.       'path' => $editor['library path'] .'/plugins/xhtmlxtras',
  4.       'buttons' => array('cite' => t('Citation'), 'del' => t('Deleted'), 'abbr' => t('Abbreviation'), 'acronym' => t('Acronym'), 'ins' => t('Inserted')),
  5.       'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/xhtmlxtras',
  6.       'internal' => TRUE,
  7.       'load' => TRUE,
  8.     );
  9.     $plugins['safari'] = array(
  10.       'path' => $editor['library path'] .'/plugins/safari',
  11.       'extensions' => array('safari' => t('Safari compatibility')),
  12.       'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/safari',
  13.       'internal' => TRUE,
  14.       'load' => TRUE,
  15.     );
  16.   }

ezt kell kibővítenünk a következő képpen:

  1.   if ($editor['installed version'] > 3) {
  2.     $plugins['xhtmlxtras'] = array(
  3.       'path' => $editor['library path'] .'/plugins/xhtmlxtras',
  4.       'buttons' => array('cite' => t('Citation'), 'del' => t('Deleted'), 'abbr' => t('Abbreviation'), 'acronym' => t('Acronym'), 'ins' => t('Inserted')),
  5.       'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/xhtmlxtras',
  6.       'internal' => TRUE,
  7.       'load' => TRUE,
  8.     );
  9.     $plugins['safari'] = array(
  10.       'path' => $editor['library path'] .'/plugins/safari',
  11.       'extensions' => array('safari' => t('Safari compatibility')),
  12.       'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/safari',
  13.       'internal' => TRUE,
  14.       'load' => TRUE,
  15.     );
  16.     $plugins['pagebreak'] = array(
  17.       'path' => $editor['library path'] .'/plugins/pagebreak',
  18.       'buttons' => array('pagebreak' => t('Drupal Teaser')),
  19.       'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/pagebreak',
  20.       'internal' => TRUE,
  21.       'load' => TRUE,
  22.     );
  23.   }

Ekkor a TinyMCE editor beállítási oldalán a gombok között megjelenik a Drupal Teaser gomb. Ha kiválasztjuk, akkor a tartalom szerkesztésénél láthatjuk, hogy megjelenik és ha megnyomjuk megjelenik a

<--! pagebreak -->

felirat a szövegben. Várjuk a csodát, hogy majd biztos jó lesz, de sajnos nem működik. Ennek az az oka, hogy a TinyMCE editor pagebreak plugin-je a következőt helyettesíti be:

<--! pagebreak -->

holott a Drupal ezt tudja értelmezni:

<!--break-->

Ebből látszik, hogy a plugin JavaScript fájljába is bele kell kontárkodnunk egy kicsit, mégpedig a modul könyvtárán belül a tinymce/jscripts/tiny_mce/plugins/pagebreak/editor_plugin.js fájlba. Keressük meg a részt, ami beleteszi a kifejezést:

"pagebreak_separator","<!-- pagebreak -->"

és írjuk át a megfelelőre:

"pagebreak_separator","<!---break-->"

Ekkor ha újra megpróbáljuk beállítani, hogy meddig tartson a teaser rész, tökéletesen működni fog.

Mostmár csak egy dolgot akarunk megoldani, mégpedig hogy a teaser rész különbözhessen a tartalomtól.

Ennek megoldására a wysiwyg.module wysiwyg_form_alter() (hook_form_alter()) fgv-ét kell módosítanunk.
Ha megnézzük az egész fgv. csak ennyitből áll:

  1. function wysiwyg_form_alter(&$form, &$form_state) {
  2.   $form['#after_build'][] = 'wysiwyg_process_form';
  3.   // Disable 'teaser' textarea.
  4.   if (isset($form['body_field'])) {
  5.     unset($form['body_field']['teaser_js']);
  6.     $form['body_field']['teaser_include'] = array();
  7.   }
  8. }

Kommentbe benne is van, hogy a teaser részt kiszedi a modul. A teaser_js részt megszűnteti, ez jó is nekünk így, mert egyrészt összeakad a modullal a js (ez az, ha rányomunk a gombra akkor DHTML-el felkerül egy újabb textarea amelyben megadhatjuk a teaser rész tartalmát), másrészt pedig a WYSIWYG editorral már meg tudjuk oldali a teaser elkülönítését, és így legalább szerkeszthető is (úgy értem a WYSIWYG editorral). Nekünk gyakorlatilag csak az “Összefoglaló megjelenítése a teljes nézetben is” checkbox kellene, hogy meg tudjuk adni a teaser beépüljön a tartalomba, vagy különböző legyen.

Ezt úgy tudjuk elérni kiszedjük (vagy kikommentezzük) a következő részt:

  1. $form['body_field']['teaser_include'] = array();

Így a wysiwyg_form_alter() fgv. a követekző képpen módosul:

  1. function wysiwyg_form_alter(&$form, &$form_state) {
  2.   $form['#after_build'][] = 'wysiwyg_process_form';
  3.   // Disable 'teaser' textarea.
  4.   if (isset($form['body_field'])) {
  5.     unset($form['body_field']['teaser_js']);
  6.     //$form['body_field']['teaser_include'] = array();
  7.   }
  8. }

Ekkor szépen megjelenik a checkbox, és beállíthajuk a teaser-t is.

Nekem már többször előfordult olyan problémám, hogy a Drupal elsődleges menüjének (primary links) egyedi kinézete miatt, kizárólag képekből álló menüt kellett készítenem. Ezalatt azt értem, hogy nincs a háttérkép felett semmilyen szöveg, a menü címe is a képen szerepel.

A háttérképeket egyszerűen megadhatjuk, a menüpontok egyedi azonosítója miatt (pl. menu-130 stb.), viszont a rajta lévő szöveget el kell tüntetni valahogy.

CSS-ben ha levesszük a betűméretet 0px-re sajnos nem a megfelelő megoldás, mert IE-ben, Chrome-ban és Operában kicsiben, de látszik.

Egy megoldás, ha a hyperlinkek szövegét span elemek közé tesszük, és a span elemeket tüntetjük el CSS-ben a display: none; definícióval, tehát a cél az lenne, hogy így nézzen ki egy menüelem:

  1. <li class="menu-129"><a class="menu-129" href="/"><span class="primary-title">Nyitólap</span></a></li>

Ekkor minden további nélkül megadhatjuk CSS-ben a következőt:

  1. ul.primary-links span.primary-title {
  2. display: none;
  3. }

Igenám, de hogy érjük el, hogy a Drupal span elemek közé tegye a linkek címét?

A megoldás az, hogy a sminkünk template.php fájljába kifejtjük a theme_links() fgv-t.

Íme a kód:

  1. /**
  2.  * Csak kepekbol allo menu elkeszitesenek alapjai
  3.  *
  4.  * @param unknown_type $links
  5.  * @param unknown_type $attributes
  6.  * @return HTML
  7.  */
  8. function phptemplate_links($links, $attributes = array('class' => 'links')) {
  9.   global $language;
  10.   $output = '';
  11.  
  12.   if (count($links) > 0) {
  13.     $output = '<ul'. drupal_attributes($attributes) .'>';
  14.  
  15.     $num_links = count($links);
  16.     $i = 1;
  17.  
  18.     foreach ($links as $key => $link) {
  19.       $class = $key;
  20.  
  21.       // Add first, last and active classes to the list of links to help out themers.
  22.       if ($i == 1) {
  23.         $class .= ' first';
  24.       }
  25.       if ($i == $num_links) {
  26.         $class .= ' last';
  27.       }
  28.       if (isset($link['href']) && ($link['href'] == $_GET['q'] || ($link['href'] == '<front>' && drupal_is_front_page()))
  29.           && (empty($link['language']) || $link['language']->language == $language->language)) {
  30.         $class .= ' active';
  31.       }
  32.       $output .= '<li'. drupal_attributes(array('class' => $class)) .'>';
  33.      
  34.       $link['attributes'] = array('class' => $class);
  35.  
  36.       if (isset($link['href'])) {
  37.         // Pass in $link as $options, they share the same keys.
  38.         $link['html'] = true;
  39.         $output .= l('<span class="primary-title">' . $link['title'] . '</span>', $link['href'], $link);
  40.       }
  41.       else if (!empty($link['title'])) {
  42.         // Some links are actually not links, but we wrap these in <span> for adding title and class attributes
  43.         if (empty($link['html'])) {
  44.           $link['title'] = check_plain($link['title']);
  45.         }
  46.         $span_attributes = '';
  47.         if (isset($link['attributes'])) {
  48.           $span_attributes = drupal_attributes($link['attributes']);
  49.         }
  50.         $output .= '<span'. $span_attributes .'>'. $link['title'] .'</span>';
  51.       }
  52.  
  53.       $i++;
  54.       $output .= "</li>\n";
  55.     }
  56.  
  57.     $output .= '</ul>';
  58.   }
  59.  
  60.   return $output;
  61.  
  62. }

Ez a kód, majdnem megegyezik az API oldalon található fgv. kóddal. Nézzük az eltéréseket:

  1. $link['attributes'] = array('class' => $class);

Ezzel annyit érünk el, hogy ne csak a listaelemnek (li) legyen egyedi azonosítója, hanem a hyperlink-nek (a) is. Ez azért fontos, hogy a :hover eseményre is tudjunk képet rakni, ha esetleg különbözik.

  1. $link['html'] = true;
  2. $output .= l('<span class="primary-title">' . $link['title'] . '</span>', $link['href'], $link);

A link tömb lesz az l() fgv. harmadik paramétere, ebbe kell szerepelnie a beállításoknak, és mivel HTML-t adunk meg, ezért a $link tömb html kulcsát igaz értékre kell állítanunk. Az l() fgv. első paraméterének pedig beírjuk a span elemek közé a link címét.

Ha ezt elvégeztük, feltöltjük a template.php-t a smink könyvtárába, majd hogy a rendszer érzékelje az új fgv. létrejöttét, menjünk be a sminkek beállítási oldalába (admin/build/themes) és nyomjunk egy mentést.

Ezután minden menüelem egyedi azonosítoval fog rendelkezni, és a menü címe span elemek között lesz, amit könnyedén eltüntetetünk. Innenstől kezdve csak CSS kérédése az egész.

Észrevehetjük, hogy az Internet Explorer régebbi verziói nem tudják támogatni a PNG fájlokat. A jelenség az, hogy nem kezeli az átlátszóságukat, és ez elég nagy probléma mivel (sajnos) a mai napig is a felhasználók legnagyobb része IE6-ot használ.

Szerencsére erre is van megoldás, nem is bonyolult, nem is sok idő megoldani.

Mindösszesen három fájlra lesz szükségünk:

Az első css fájlt a Conditional Comment feltételes megoldással kell beilleszteni a weboldalunkba (Drupal CMS esetén a page.tpl.php fájlba):

  1. <!–[if lt IE 7]>
  2.   <link rel="stylesheet" href="http://www.kalman-hosszu.com/ie-fix/ie.css" type="text/css" media="screen" />
  3. <![endif]–>

Ezzel elérjük azt, hogy az IE7 előtt böngészőknél hozzáadja az oldalhoz az ie.css fájlt is.

Ha megnézzük ennek a css fájlnak a tartalmát akkor láthatjuk, hogy hova kell elhelyezni a többi fájlt:

  1. img {
  2.   behavior: url(/images/png.htc);
  3. }

Tehát az itt megadott könyvtárba (esetemben /images) elhelyezzük a png.htc és a transparent.gif fájlokat, és IE alatt is használhatunk PNG képeket.

Létezik egy jQuery fgv: show(). Ennek az a szerepe, hogy a nem látható html elemet láthatóvá teszi.

Tehát végülis annyi történik, hogy egy html elemet ami el van rejtve (display: none;) megmutat, mégpedig úgy, hogy inline css-sel ad neki egy display: block tulajdonságot.

Ez így rendben is lenne, ha nem akarnánk táblázatok soraira is alkalmazni (table tr). A probléma az, hogy pl. a Firefox szétcsúszik, ha a sor elemre vonatkozó css display: block. Erre a megoldás a display: table-row css definíció. Ezzel viszont az a gond, hogy az IE nem támogatja. Az IE viszont kezeli a táblázat soraira is a display: block tulajdonságot.

Nem értem miért nem építették bele a jQuery-be a böngésző ellenőrzését a show() fgv. meghívása esetében, de akkor vegyük figyelembe magunk, hiszen nem mindegy, hogy a táblázatunk így néz ki:

FF tr elem display: block

Vagy így:

FF tr elem display: table-row

Láthatjuk, hogy az első esetben a táblázat első oszlopa sokszorosára nőtt, ezért az oldal szétcsúszott.

JS-ből lekérdezni a böngésző típusát, nem nehéz, és a jQuery-be is beépített (core).

A következő kódot aztán símán beépíthetjük egy saját fgv-be.

  1. <script type="text/javascript">
  2. function show_tr_element(object) {
  3.   if ($.browser.msie) {
  4.     object.show();
  5.   } else {
  6.     object.css("display", "table-row");
  7.   }
  8. }
  9. </script>

Az egyik általam készített Drupal alapú webáruház termékeinek adminisztrálása CSV alapon működik. Ez annyit jelent, hogy van egy exportáló felület, ahol a site admin lementheti a termékeit egy csv fájlba, amit aztán valamilyen csv-t kezelő programban (általában Microsoft Excel) átszerkeszt, és egy importáló felületen feltölt. Ezután az oldal feldolgozza és érvénybe lépnek a változtatások.

Ez így mind szép és jó, csak egy gond van vele. A windows felhasználók nagy része, ISO-8859-2-es karakterkódolást használ, míg a Drupal UTF-8-at. Hogy a címek és egyéb karakterkódolástól függő érték problémáját kiküszöböljük, szükség van az mb_convert_encoding() fgv-re, amely segítségével az ISO-8859-2 karakterkódolású részeket, át tudjuk konvertálni a Drupal-nak is használható UTF-8-as karakterkódolásba.

Egyszercsak jelentkezett egy probléma. Azon kezdőbetűk amelyek ékezetesek egyszerűen elvesztek. Tehát például az “Öntapadó” szóból, “ntapadó” lett.
Elkezdtem debuggolni, és azt vettem észre, hogy a feldolgozásra használt beépített fgetcsv() PHP fgv. hibásan működik az ISO-8859-2-es karakterkódólásű fájlok feldolgozásában. A probléma az volt, hogy már a beolvasáskor elvesztek ez ékezetes kezdőbetűk, tehát még az átkonverálás előtt. Ha pedig már akkor elveszett, akkor utólag már nem lehet neki mit csinálni.

Úgyhogy az explode() fgv-t segítségül hívva írtam egy saját, egyszerűsített csv feldolgozó fgv-t, ami erre a célra pont alkalmas:

  1. <?php
  2. function fgetcsv_iso_8859_2($handle, $length, $separator) {
  3.  
  4.   if (!feof($handle)) {
  5.     return explode($separator, fgets($handle, $length));
  6.   } else {
  7.     return false;
  8.   }
  9.  
  10. }
  11. ?>

Persze ez így nem kezeli azon csv fájlokat, amikben mondjuk a mezők értékei ” (macskaköröm) között helyezkednek el, ám az én esetemben erre nem volt szükség. Ha mégis kellene, ki lehet egészíteni a fgv-t:

  1. <?php
  2. function fgetcsv_iso_8859_2($handle, $length, $separator) {
  3.   $enclosed = '"';
  4.   $out      = array();
  5.  
  6.   if (!feof($handle)) {
  7.     $fields = explode($separator, fgets($handle, $length));
  8.     foreach ($fields as $field_data) {
  9.       if (preg_match("/^\\$enclosed/i", $field_data)) {
  10.         $field_data = preg_replace("/(^\\$enclosed|\\$enclosed$)/i","", $field_data);
  11.       }
  12.       $out[] = $field_data;
  13.     }
  14.     return $out;
  15.   } else {
  16.     return false;
  17.   }
  18.  
  19. }
  20. ?>

Ügyfelünk kérése alapján el kellett készítenünk egy DropDown menüt a nyitólapjára. Most is - mint általában - a jQuery JavaScript könyvtárban kerestem a megoldást. Rövid keresgélés után megtaláltam a Superfish jQuery plugin-t, mely tökletesen megfelelőnek tűnt a célra…legalábbis első ránézésre.

A probléma a tesztelés során jelentkezett, mégpedig a szokásos IE6/IE7/FF kompatibilitási ellentétek keretében.
Mondanom sem kell, hogy a plugin FF alatt teljesen jól teljesített (ugyanúgy mint Safari-n vagy Opera-n), a Microsoft két böngészőjénél az oldal gyakorlatilag összeomlott.

A menü FF-ban:

DropDown menü FF

Az internet Explorerben elég érdeksen mutatott:

DropDown menü IE
DropDown menü IE
DropDown menü IE

Először úgy gondoltam megkeresem a megoldást, mert biztosan lokális gond van, hiszen az oldalon található bemutatókon nincs gond Internet Explorer alatt sem, de akárhogy csürtem csavartam nem akart működni. Nem csak a kinézettel volt összeakadás, hanem a funkcionalitás sem működött. IE alatt egyszerüen eltünt a DropDown menü amikor elhagytam az egérrel a legfelső meüelemet.

Néhány órányi küszködés után úgy felidegesítettem magam, hogy eldöntöttem inkább írok egy sajátot, hiszen nem egy atomfizika a DropDown menü, főleg a jQuery-t használva.

Nézzük meg, először a HTML osztályokat, az elrendezési szabályokat!

Íme egy főmeü, és a hozzá tartozó DropDown menü:

  1. <div class="menu-sor">
  2.   <div class="ddown-menu">
  3.       <div class="fomenu-ikon">
  4.         <a href="/beteg-vagyok-gyogyulni-akarok"><img src="/themes/nyitolap/images/segiteni_img.jpg" alt="" /></a>
  5.       </div>
  6.       <ul class="submenu">
  7.         <li><?php print l("Pozitív hozzáállás", "pozitiv-hozzaallas"); ?></li>
  8.         <li><?php print l("Gyógyító nevetés", "gyogyito-nevetes"); ?></li>
  9.         <li><?php print l("Függőségek", "fuggosegek"); ?></li>
  10.         <li><?php print l("Mozgásszervi betegségek", "mozgasszervi-betegsegek"); ?></li>
  11.         <li><?php print l("Orvosok, akik agykontrollt végeztek", "orvosok-akik-agykontrollt-vegeztek"); ?></li>
  12.         <li><?php print l("Kiadványok gyógyulni vágyóknak", "kiadvanyok-gyogyulni-vagyoknak"); ?></li>
  13.       </ul>
  14.   </div>
  15.   <div class="fomenu">
  16.     <p><a href="/beteg-vagyok-gyogyulni-akarok">Beteg vagyok és gyógyulni akarok</a></p>
  17.   </div>
  18.   <div class="clear"></div>
  19. </div>

Az l() fgv. használata ne tévesszen meg senkit sem, ez egy beépített Drupal fgv., síma link generálódik belőle.
Tehát a

  1. <li><?php print l("Pozitív hozzáállás", "pozitiv-hozzaallas"); ?></li>

Sorok nyugodtan lecserélhetőek síma HTML elemekre:

  1. <li><a href="/valamilyen-tartalom">Tartalom címe</a></li>

Nézzük meg a css-t, hogy ki is nézzen valahogy, nameg azért vannak benne fontos részek:

  1. <style type="text/css">
  2. ul.submenu {
  3.   position: absolute;
  4.   display: inline;
  5.   z-index: 99;
  6.   border: 5px solid #962ab2;
  7.   display: none;
  8.   margin-left: 65px;
  9.   margin-top: 0;
  10.   width: 250px;
  11.   margin-bottom: 0;
  12. }
  13. ul.submenu li {
  14.   margin: 0;
  15.   padding: 0;
  16.   width: 250px;
  17. }
  18. ul.submenu li a {
  19.   font-size: 10pt;
  20.   display: block;
  21.   color: #409066;
  22.   background: #fdf3fd;
  23.   padding: 5px 20px;
  24.   border-bottom: 1px solid #962ab2;
  25.  
  26.   margin: 0;
  27.   width: 210px;
  28.   line-height: 14pt;
  29. }
  30. ul.submenu li a:hover {
  31.   background: white;
  32.   text-decoration: none;
  33. }
  34. div.ddown-menu {
  35.   width: 70px;
  36.   float: left;
  37. }
  38. div.ddown-menu div.fomenu-ikon {
  39.   width: 70px;
  40. }
  41. div.fomenu {
  42.   width: 250px;
  43.   float: left;
  44. }
  45. div.fomenu p {
  46.   padding-left: 10px;
  47.   padding-top: 5px;
  48. }
  49. div.menu-sor {
  50.   padding-top: 10px;
  51. }
  52. div.clear {
  53.   clear: both;
  54. }
  55. </style>

Itt volt egy-két érdekesség, amit mindenképpen szeretnék megmelíteni!

Az ul.submenu css definícióban fontos elemek:

  1.   position: absolute;
  2.   display: inline;
  3.   z-index: 99;
  4.   width: 250px;

Az első három nélkül az oldal IE-ben szétcsúszik, a szélesség megadása nélkül pedig IE6-ban csak 70px lesz a lehullómenü szélessége.

Az “ul.submenu” és az “ul.submenu li a” css definíciónál is nagyon fontos a szélesség megadása, mert különben IE6-ban eltűnik a DropDown menü ha az első elemet elhagyjuk (tehát nem tudjuk kivűlasztani mondjuk a második, vagy harmadik elemet a mnüből).
Az a (link) elemnél nyiván a paddingot kivonjuk a szélességből, ezért lesz 40px-el kevesebb.

A harmadik dolog amire figyelni kell az, hogy mindenhol megadtam a margin-bottom értékét, mégpedig 0-nak. Ez is fontos, különben IE6 alatt nagy alsó margót rak minden menüelem alá.

Végül, mint IE6 mint IE7 alatt nem volt megfelelő az “ul.submenu” bal oldali margó értéke (65px), ezért teljesen kiment a DropDown menü a jobb oldalra. Erre a Conditional comments megoldással probálkoztam, vagyis:

  1. <!–[if lte IE 7]>
  2.   margin-left: -5px;
  3. <![endif]–>

De sajnos ez nem reagált semmit, úgyhogy megoldottam ezt is JavaScript-el:

  1. <script type="text/javascript">
  2. $(function() {
  3.   if ($.browser.msie) {
  4.     $('ul.submenu').css("margin-left", "-5px");
  5.   }
  6. });
  7. </script>

Nézzük meg végül a lényeget, a HTML-hez tarotó jQuery kódot, amiben kivitelezzük a DropDown menüt:

  1. <script type="text/javascript">
  2. $(function() {
  3.   var submenu_active = false;
  4.   $(".ddown-menu").hover(
  5.   function() {
  6.     $("ul.submenu", $(this)).show();
  7.     $("ul.submenu", $(this)).hover(
  8.     function() {
  9.       submenu_active = true;
  10.     },
  11.     function() {
  12.       $(this).hide();
  13.       submenu_active = false;
  14.     });
  15.   },
  16.   function() {
  17.     if (!submenu_active) {
  18.       $("ul.submenu", $(this)).hide();
  19.     }
  20.   }
  21.   );
  22. });
  23. </script>

Mit is csinál?

Az oldal betöltődése után létrehoz egy bool tipusú változót, melyben a DropDown menü aktivitását figyeljük, majd végigmegy az összes ddown-menu osztályon, és figyeli a hover esemény.
Amikor bekövetkezik, a hozzá tartozó submenu osztályt előhozza (show), majd ennek a megjelenített menünek figyeli a hover eseményét.
Ha bekövetkezik, akkor a submenu_active változó értéke igaz lesz, hiszen éppen a menüben vagyunk, ha pedig elhagyjuk, akkor elrejtjük, és a submenu_active értékét hamisra állítjuk.
Végül megnézzük, hogy a ddown-menu osztályba tartozó elemek hover eseményének végén a submenu_active igaz-e, és ha nem akkor a DropDown listát eltávolítjuk. Erre azért van szükség, mert olyan szituáció is elképzelhető, hogy valaki nem lép be a submenu-be, csak előhozza. Ha ez a sor kimaradna, akkor ilyen esetekben a sumenu nem tunne el.

Régebben írtam egy cikket a dinamikus form ellenőrzésről, és arra gondoltam leírom az eljárás modernebb változatát, azaz a hibaüzenetek nem alert ablakban jelennek meg, hanem DHTML segítségével.

Természetesen olyan kódot kell készíteni amely akárhány form-ra dinamikusan működik, tehát nem függ az input mezők számától, és az oldalon elhelyezett form-ok mennyiségétől.

A megoldást itt is a jQuery JavaScript könyvtár adja.

De mindenek előtt érdemes átnézni hogyan is kell felépíteni egy profi form-ot. Khauth György kollegám írt már erről néhány nagyon hasznos cikket:

Miután az alapelvek tanulmányozásával végeztünk készítsük el a form-ot:

  1. <style type="text/css">
  2. .form-row {
  3.   padding-bottom: 10px;
  4.   width: 290px;
  5. }
  6.  
  7. .form-row label {
  8.   float: left;
  9.   width: 100px;
  10.   padding-right: 10px;
  11. }
  12.  
  13. .form-row input {
  14.   float: left;
  15.   padding: 3px;
  16.   width: 160px;
  17. }
  18.  
  19. .clear {
  20.   clear: both;
  21. }
  22.  
  23. .form-error {
  24.   text-align: center;
  25.   color: #C52020;
  26.   border: 1px solid #DD7777;
  27.   background: #FFCCCC;
  28.   margin: 10px 0;
  29.   display: none;
  30. }
  31. </style>
  32.  
  33. <div style="margin: 0 auto; width: 300px;">
  34.   <form action="#" method="post">
  35.     <div class="form-row">
  36.       <label for="keresztnev">Keresztnév:</label><input type="text" name="keresztnev" class="kotelezo" id="keresztnev" />
  37.       <div class="clear"></div>
  38.       <div class="form-error hianyos">A keresztnév megadása kötelező!</div>
  39.     </div>
  40.    
  41.     <div class="form-row">
  42.       <label for="vezeteknev">Vezetéknév:</label><input type="text" name="vezeteknev" class="kotelezo" id="vezeteknev" />
  43.       <div class="clear"></div>
  44.       <div class="form-error hianyos">A vezetéknév megadása kötelező!</div>
  45.     </div>
  46.    
  47.     <div class="form-row">
  48.       <label for="email">Email:</label><input type="text" name="email" class="kotelezo email" id="email" />
  49.       <div class="clear"></div>
  50.       <div class="form-error hianyos">Az email cím megadása kötelező!</div>
  51.       <div class="form-error hibas">Nem valós az email cím!</div>
  52.     </div>
  53.    
  54.     <div class="form-row" style="text-align: center;">
  55.       <input type="submit" name="submit" value="Mehet" style="float: none;" />
  56.     </div>
  57.   </form>
  58. </div>

Ha megnézzük a kód a css stílus felépítésével kezdődik, ez gyakorlatilag csak az átlátható kinézetet kezeli. A lényeg természetesen a form, és a form-on belüli osztályok elhelyezkedése!

A form-ot körülöleli egy div, a JavaScript kóddal ezen belül fogunk keresni. Ennek az a lényege, hogy akárhány form-on el tudjuk sütni az eljárást, ne kelljen minden form-nál újra és újra megírni, hozzáigazítani a form-hoz.
Három adatok kérünk be: keresztnév, vezetéknév, és email cím.
Ha megfigyeljük mindegyik alatt megtalálható a hozzá tartozó hibaüzenet is. A hibaüzenetek mindegyike a form-error osztályba van sorolva, így a kinézetük egységesen definiálható, és láthatóságuk egységesen kezelhető.
A hibaüzenetek nem csak a form-error osztályba tartoznak, hanem a hiba típusába is. Ezesetben mind a három input mezőhöz tartozik egy hianyos osztály, ami akkor jelenik meg ha üresen hagytuk, az email címhez pedig egy hibas osztály, ami akkor jelenik meg ha az email cím nem valós.

Nézzük meg a hozzá tartozó JavaScript kódot, melyben dinamikusan elleőrizzük az input mezők kitöltöttségét, és az email cím helyességét, valósságát is:

  1. <script type="text/javascript">
  2.  
  3. $(function() {
  4.   $('form').submit(function() {
  5.     var mehet       = true;
  6.     var first_error = true;
  7.     var error_obj   = null;
  8.     $('div.form-error', $(this).parent()).each(function() {
  9.       $(this).hide();
  10.     });
  11.     $('input.kotelezo', $(this).parent()).each(function() {
  12.       if ($(this).val() == '') {
  13.         mehet = false;
  14.         if (first_error) {
  15.           first_error = false;
  16.           error_obj   = $(this);
  17.         }
  18.         $('div.hianyos', $(this).parent()).show();
  19.       }
  20.     });
  21.  
  22.     var email   = $('input.email', $(this).parent()).val();
  23.     var filter  = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
  24.     if (!filter.test(email)) {
  25.       mehet = false;
  26.       if (first_error) {
  27.         first_error = false;
  28.         error_obj   = $(this);
  29.       }
  30.       $('div.hibas', $(this).parent()).show();
  31.     }
  32.  
  33.     if (mehet) {
  34.       return true;
  35.     } else {
  36.       location = '#' + error_obj.attr('id');
  37.       return false;
  38.     }
  39.   });
  40. });
  41.  
  42. </script>

A dinamikus form ellenőrzésről írt cikkemhez képes van néhány eltérés, most ezekre térnék ki elsősorban.
Az első, hogy nem csak a mehet változót deklaráljuk, hanem a first_error bool típusút, és az error_obj html objektum típusút is. Ezek azt a célt szolgálják, hogy hibás kitöltés esetén le tudjuk tárolni melyik hiba volt az első a sorban, és automtaikusan oda ugorjunk az oldalon.
Utána láthatjuk a

  1. $('div.form-error', $(this).parent()).each(function() {
  2.       $(this).hide();
  3.     });

részt, ami arra való, ha már egyszer hibásan töltöttük ki a form-ot, akkor eltűntetjük az összes hibaüzenetet, különben ottmaradhat második hibás kitöltés esetén olyan üzenet is, ami már nem aktuális.

Ezután ellenőrizzük a kitöltöttséget, tehát végigmegyünk a kötelező input mezőkön. Ha valameny nincs kitöltve akkor a szülö objektumának hianyos osztályát megjelenítjük. Végül ellenőrizzük az email cím helyességét, tehát hogy illeszkedik-e a szabványra, és ha nem a szülő html objektumának hibas osztályát megjelenítjük.

Végül ha nem találtunk hibát tobábbengedjük a submit eseményt, ha találtunk akkor az első hibás mezőhöz ugrunk.

A Drupal 5-ben van egy hiba amiről a programozók sokszor elfelejtkeznek.

A $node objektumnak van egy path attribútuma:

  1. <?php
  2. $node->path;
  3. ?>

Ez a node-hoz tartozó url alias, tehát a megadott elérési címet tartalmazza. Sokszor használjuk különböző tartalmak dinamikus gyűjtőoldalán, ezért fontos tisztában lennünk ezzel a hibával.

A probléma az, hogy amikor admin-ként vagyunk bejelentkezve a path érték megfelelő, ám ha nem akkor üres, így minden link a kezdőoldalra mutat. Ez annyit takar, hogy nem lehet elérni a kívánt bejegyzéseket, és ha nem elég körültekintő a tesztelés, akkor fel sem tűnik, mivel a session-be bentmaradunk mint bejelentkezett admin-ok, így nem is érzékeljük a hibát.

A megoldás egy egyszerű beépített Drupal fgv használata

  1. <?php
  2. $node->path = drupal_get_path_alias("node/$node->nid");
  3. ?>

Ez egy nagyon egyszerű megoldás, mégis ha nem vagyunk vele tisztába komoly gondot okozhat a weboldalon, de hangsúlyozom: megfelelő tesztelés esetén ki kell hogy derüljön még az élesítés előtt.

A Drupal-nak van egy globális változója, melynek a funkicója az, hogy visszadja azt az URL címet ahol a telepített CMS-ünk aktuálisan el van helyezve.
Ennek a lényege az, hogy pl. ha a fejlesztés egy már meglévő domain egy alkönyvtárában zajlik, akkor a site élesítésekor ne történjen semmilyen probléma. Gyakorlatilag a telepített Drupal-t akárhova pakolhatjuk, a képek elérései és az oldal elérések nem változnak.

Ez a változo a

  1. <?php
  2. $GLOBALS['base_url'];
  3. ?>

Ha ezt használjuk akkor mondjuk egy kép, vagy egy oldal eléréshez nem kell beledrótóznunk a fejlesztéshez használt alkönyvtár nevét.
Ha a Drupal pl. a root/ujweboldal könyvtárban van, akkor ha egy oldal elérését így adjuk meg:

  1. <a href="/kapcsolat">Kapcsolat</a>

akkor nem a root/ujweboldal/kapcsolat oldal fog bejönni, hanem a root/kapcsolat. Ez pedig komoly problémékat okozhat.

Ezt elkerülendően általában a következő eljárást szoktuk alkalmazni:

  1. <a href="<?php print $GLOBALS['base_url'] ?>/kapcsolat">Kapcsolat</a>

Ekkor akárhova helyezzük az oldalt müködni fog az elérés.

Viszont felmerült egy probléma ha az oldalt át akarjuk irányítani a www nélküli címéről a www-s címére.
Ez azt jelenti, hogy beállítunk egy olyan redirect-et, hogy a http://example.com automatikusan menjen át a http://www.example.com címre.

A probléma a form-ok feldolgozásánál jelentkezik!

  1. <form action="<?php print $GLOBALS['base_url'] ?>/form-feldolgozas" method="post"></form>

A jelenség az, hogy a felhasználó megérkezik az oldalra és kitölti a űrlapot, majd a submit eseményt követően nem történik meg a mentés mert az adatok nincsenek benne a $_POST tömbbe. Elkedztem újra végignézni a folyamatot, hogy hol vesznek az értékek.

Észrevettem, hogy a form action paraméterében a $GLOBALS['base_url'] változó miatt az URL www nélküli címe szerepel, emiatt a $_POST adatok a domain www nélküli oldalára mennek át.
Ekkor a következő történik: az action meghívja a www nélküli oldal elérését, a redirect továbbdobja a www-s címre és közeben már nem szerepelnek az adatok a $_POST tömbben, mivel a síma, www nélküli elérésre lettek elküldve. Tehát a redirect miatt elvesztek.

A megoldás az, hogy a form-ok action paraméterében, nem a $_GOLBALS['base_url'] változót adjuk meg, hanem a Drupal beépített url() fgv-ét, mely nem a teljes URL címet adja vissza, hanem a telepített Drupal root könyvtárát.

Tehát a $_GLOBALS['base_url'] helyett a form-ban a következőt alkalmazzuk:

  1. <form action="<?php print url('form-feldolgozas') ?>" method="post"></form>

Hogy szemléltessem a kettő közötti különbséget hasonlítsuk össze a két kód HTML kimenetét:

  1. <form action="<?php print $GLOBALS['base_url'] ?>/form-feldolgozas" method="post"></form>
  2. <form action="<?php print url('form-feldolgozas') ?>" method="post"></form>
  1. <form action="http://example.com/ujweboldal/form-feldolgozas" method="post"></form>
  2. <form action="/ujweboldal/form-feldolgozas" method="post"></form>