Tuesday, April 19, 2011

Nosza, írjunk widget-et! 2. rész (tyúklépésről tyúklépésre)

2. rész

Írta: János Zahratka


Nos, ahogy ígértem, íme a második rész. Ebben a szakaszban származtatni fogjuk a már elkészített jQTBaseOverlay widget-ünket, aminek ezúttal már a HTML oldalon megjelenő eredménye is lesz.
Emlékeztetőül: az előző részben elkészítettünk egy alap widget-et, a jQuery Tools overlay plugin-jéhez. Erre azért volt szükség, mert a plugin-ból több, különböző eszközt lehet csinálni. Ebben a részben két felhasználási lehetőséget fogok megmutatni. Az egyik egy olyan overlay doboz, amibe bármilyen tartalmat el lehet helyezni. A másik pedig egy üzenő/kérdező dialógus, amivel üzeneteket, illetve kérdéseket lehet megjeleníteni a felhasználóknak, és utóbbi esetében a választól függően lehet "reagálni" a felhasználó választására.

jQTOverlay

Első lépésben létrehozunk egy új php állományt a protected/components/widgets mappában jQTOverlay.php névvel. Még mielőtt nekikezdenénk awidget osztályának kiterjesztéséhez, gondoskodnunk kell róla, hogy a widget megtalálja a szülő osztályát, ezt pedig a YiiBase osztály import metódusának segítségével fogjuk megoldani. Az üres osztályunk valahogy így fog kinézni:
<?php

Yii::import('application.components.widgets.jQTBaseOverlay');
class jQTOverlay extends jQTBaseOverlay
{

}
Ezután kicsit átírjuk a publikus init metódust, ami a widget alapértékeinek beállítását végzi el. Ebben az esetben mindőssze annyit fog csinálni ez a metódus, hogy lefuttatja a szülő osztály azonos nevű metódusát, majd meghívja a registerClientScript, és a renderBegin metódusokat. Egyik sem csinál mást, "csak" végrehajtja a szülő osztályban lévő azonos nevű eljárásokat. Ez azért lett így összerakva, mert az alap osztályban már minden szükséges metódus, és paraméter definiálva van, itt pedig csak a megfelelő sorrendben hívogatni kell őket.
...
    public function init() {
        parent::init();

        $this->registerClientScript();
        $this->renderBegin();
    }
    ...
Ha ezzel megvagyunk, előkapjuk a run metódust, és meghívjuk benne a renderContent és a renderEnd metódusokat. Ezek szintén definiálva vannak a szülő osztályban.
...
    public function run() {
        $this->renderContent();
        $this->renderEnd();
    }
    ...
Már csak négy metódus van hátra, melyek egy kivételével szintén csak annyit csinálnak, hogy meghívják a szülő osztály azonos nevű metódusait. Ezek aregisterClientScript, a renderBegin, a renderContent, és a renderEnd metódusok. Az üres metódus arra való, hogy ha valaki tovább szeretné származtatni ezt a widgetet, akkorű jobb, ha megvan ez a metódus is.
...
    protected function  registerClientScript() {
        parent::registerClientScript();
    }

    protected function  renderBegin() {
        parent::renderBegin();
    }

    protected function  renderContent() {

    }

    protected function  renderEnd() {
        parent::renderEnd();
    }
    ...
widget-et tovább lehetne rövidíteni azzal, hogy azokat a metódusokat, amelyek csak a szülő osztályban lévő önmagukat hívják meg nem kell deklarálni, csak meghívni a parent segítségével, azonban gondolni kell arra is, hogy esetleg olyan osztályt fogunk csinálni, ami ezt a már kibővített osztályt fogja tovább származtatni.
Ahogy ígértem, valóban sokkal rövidebb lesz a második rész, ugyanis az a helyzet, hogy a jQTOverlay widget ezzel készen is van. Lássuk a kódot!
<?php

Yii::import('application.components.widgets.jQTBaseOverlay');
class jQTOverlay extends jQTBaseOverlay
{
    public function init() {
        parent::init();

        $this->registerClientScript();
        $this->renderBegin();
    }

    public function run() {
        $this->renderEnd();
    }

    protected function  registerClientScript() {
        parent::registerClientScript();
    }

    protected function  renderBegin() {
        parent::renderBegin();
    }

    protected function renderContent() {

    }

    protected function  renderEnd() {
        parent::renderEnd();
    }
}
Már csak annyi van hátra, hogy bármely view (nézet) állományban meghívjuk, ami valahogy így néz ki:
...
<!-- Ha erre klikkel, akkor jöhet az overlay. -->
<?php echo CHtml::link(CHtml::encode('Overlay'),
    array('#'),
    array(
        'id' => 'showOverlay',
        'name' => 'showOverlay',
        'rel' => '#Overlay'
    )
); ?>
...

...
<!-- És ez a widget hívása. -->
<?php $this->beginWidget('application.components.widgets.jQTOverlay', array(
    'htmlOptions' => array(
        'id' => 'overlay',
        'name' => 'overlay'
    ),
    'trigger' => 'showOverlay',
    'useMask' => true,
    'title' => 'Overlay',
)); ?>
    Bármilyen HTML, vagy php tartalom, amit meg akarunk jeleníteni.
<?php //$this->endWidget(); ?>
...

jQTDialog

A második származtatott widget-ünk egy kicsit - de tényleg csak egy kicsit - bonyolultabb, de csak azért, mert arra vetemedtem, hogy egy type nevű paraméter segítségével három dialógus típust kezeljek egy widget megírásával. Mindhárom változat működése azonos. A különbség abban van, hogy milyen válaszlehetőségeket adok a felhasználónak.
Két paramétert vezettem be, amelyek a szülő osztályban nem léteznek. Az egyik a már előbb említett type, ami a dialógus típusát határozza meg. Értéke "alert" (figyelmeztetés), "question" (kérdés) vagy "decision" (döntés) lehet. A másik a message paraméter, amely a megjelenítendő üzenetet, vagy kérdést tárolja.
A szülő osztályból átvettem még az _openTag paramétert, ami pontosan ugyanazt tárolja, mint a szülő.
Az init metódus ezúttal összetettebb lesz, mint az előző esetben.
  • Megvizsgálja, hogy a type paraméter a megfelelő értéket tartalmaz-e, azután azt is meg kell vizsgálni, hogy van-e üzenet, amit meg kell jeleníteni (message). Ha ez a két paraméter nem megfelelő, akkor a widget egyszerűen leáll és nem jelenít meg semmit.
  • Megváltoztatja az overlay CSS osztályának nevét a dialógus típusának megfelelően. Ez nem kötelező, viszont szerintem jó megoldás arra, hogy az egyes dialógus típusok más-más megjelenési formát vehessenek fel.
  • Meghívja a szülő osztály azonos nevű metódusát, aztán az előbbi overlay widget-ben leírt módon a registerClientScript és renderBegin metódusok hívásai következnek.
...
    public function init() {
        if('alert' !== $this->type && 'question' !== $this->type && 'decision' !== $this->type) {
            return null;
        }
        if(null === $this->message) {
            return null;
        }

        $this->htmlOptions['class'] = $this->type;
        $this->params['closeOnClick'] = false;

        parent::init();

        $this->registerClientScript();
        $this->renderBegin();
    }
    ...
run metódus ugyanazt csinálja, mint az előző widget.
...
    public function run() {
        $this->renderEnd();
    }
    ...
registerClientScript metódus állítja elő azt a javascript kódot, ami az oldal végére kerül, és a dialógus kliens oldali működéséért felelős.
Ha kérdés típusú dialógust szeretnénk megjeleníteni, akkor több válaszlehetőséget kell biztosítanunk, és egyúttal gondoskodnunk kell róla, hogy a lap tudja, hogy mit válaszolt a felhasználó. Ennek első része az, hogy egy target nevű változóba betöltjük a dialógus azonosítóját, ami vagy meg van adva az overlay plugin paraméterei között, vagy ha nincs, akkor a dialógus azonosítója lesz az.
Ha ez megvan, akkor elkérjük a szülő osztálytól a script első részét, és hozzáfűzzük a válasz gombok kezelésének szkriptjét, aztán az így elkészült kódot visszatesszük a publikus script praméterbe, majd meghívjuk a szülő osztály azonos nevű metódusát.
A script kódja csak annyira van megírva, hogy az igényeknek megfelelően simán folytatni lehessen, tehát az igazi használathoz még dolgozni kell rajta.
...
    protected function  registerClientScript() {
        if('question' === $this->type || 'decision' === $this->type) {
            if(isset($this->params['target'])) {
                $target = $this->params['target'];
            } else {
                $target = $this->htmlOptions['id'];
            }

            $script = $this->script;

            $script = $script . "\n" .
                '$("#' . $target . ' .buttons button").click(function(event) {' .
                    'var result = $(this).attr("value");' .
                '});';

            $this->script = $script;
        }

        parent::registerClientScript();
    }
    ...
És már csak annyi kell, hogy megjelenítsük a dialógusunk HTML kódját. Ennek első felét a renderBegin metódus végzi, ami kiírja a dialógus kezdő részét, címét - ha van -, aztán az üzenetet, vagy kérdést.
...
    protected function  renderBegin() {
        ob_start();
        ob_implicit_flush(false);
        echo "\n" . CHtml::openTag($this->tagName, $this->htmlOptions) . "\n";
        if(null !== $this->title || '' !== $this->title) {
            echo CHtml::tag('h3', array('class' => 'title'), CHtml::encode($this->title), true) . "\n";
        }
        echo CHtml::tag('p', array('class' => 'message'), CHtml::encode($this->message), true);
        $this->_openTag = ob_get_contents();
        ob_clean();
    }
    ...
És jön az utolsó metódus, név szerint a renderEnd, ami mindössze annyit csinál, hogy a kód végére odateszi a dialógus típusának megfelelő gombokat, aztán lezárja a widget záró kódjával az egészet.
...
    protected function  renderEnd() {
        $content = ob_get_clean();
        echo $this->_openTag;
        echo trim($content);
        echo "\n" . CHtml::openTag('div', array('class' => 'buttons')) . "\n";
        if('alert' === $this->type) {
            echo CHtml::tag('button', array('id' => $this->htmlOptions['id'] . 'ButtonOk', 'value' => 'ok', 'class' => 'close'), 'OK', true) . "\n";
        } elseif('question' === $this->type) {
            echo CHtml::tag('button', array('id' => $this->htmlOptions['id'] . 'ButtonYes', 'value' => 'yes', 'class' => 'close'), 'Igen', true) . "\n";
            echo CHtml::tag('button', array('id' => $this->htmlOptions['id'] . 'ButtonNo', 'value' => 'no', 'class' => 'close'), 'Nem', true) . "\n";
        } elseif('decision' === $this->type) {
            echo CHtml::tag('button', array('id' => $this->htmlOptions['id'] . 'ButtonYes', 'value' => 'yes', 'class' => 'close'), 'Igen', true) . "\n";
            echo CHtml::tag('button', array('id' => $this->htmlOptions['id'] . 'ButtonNo', 'value' => 'no',  'class' => 'close'), 'Nem', true) . "\n";
            echo CHtml::tag('button', array('id' => $this->htmlOptions['id'] . 'ButtonCancel', 'value' => 'cancel',  'class' => 'close'), 'Mégsem', true) . "\n";
        }
        echo CHtml::closeTag('div');
        echo "\n" . CHtml::closeTag($this->tagName) . '' . "\n";
    }
    ...

Most pedig nézzük a teljes kódot!

<?php

Yii::import('application.components.widgets.jQTBaseOverlay');
class jQTDialog extends jQTBaseOverlay
{
    public $type = 'alert';
    public $message;

    private $_openTag;

    public function init() {
        if('alert' !== $this->type && 'question' !== $this->type && 'decision' !== $this->type) {
            return null;
        }
        if(null === $this->message) {
            return null;
        }

        $this->htmlOptions['class'] = $this->type;
        $this->params['closeOnClick'] = false;

        parent::init();

        $this->registerClientScript();
        $this->renderBegin();
    }

    public function run() {
        $this->renderEnd();
    }

    protected function  registerClientScript() {
        if('question' === $this->type || 'decision' === $this->type) {
            if(isset($this->params['target'])) {
                $target = $this->params['target'];
            } else {
                $target = $this->htmlOptions['id'];
            }

            $script = $this->script;

            $script = $script . "\n" .
                '$("#' . $target . ' .buttons button").click(function(event) {' .
                    'var result = $(this).attr("value");' .
                '});';

            $this->script = $script;
        }

        parent::registerClientScript();
    }

    protected function  renderBegin() {
        ob_start();
        ob_implicit_flush(false);
        echo "\n" . CHtml::openTag($this->tagName, $this->htmlOptions) . "\n";
        if(null !== $this->title || '' !== $this->title) {
            echo CHtml::tag('h3', array('class' => 'title'), CHtml::encode($this->title), true) . "\n";
        }
        echo CHtml::tag('p', array('class' => 'message'), CHtml::encode($this->message), true);
        $this->_openTag = ob_get_contents();
        ob_clean();
    }

    protected function  renderEnd() {
        $content = ob_get_clean();
        echo $this->_openTag;
        echo trim($content);
        echo "\n" . CHtml::openTag('div', array('class' => 'buttons')) . "\n";
        if('alert' === $this->type) {
            echo CHtml::tag('button', array('id' => $this->htmlOptions['id'] . 'ButtonOk', 'value' => 'ok', 'class' => 'close'), 'OK', true) . "\n";
        } elseif('question' === $this->type) {
            echo CHtml::tag('button', array('id' => $this->htmlOptions['id'] . 'ButtonYes', 'value' => 'yes', 'class' => 'close'), 'Igen', true) . "\n";
            echo CHtml::tag('button', array('id' => $this->htmlOptions['id'] . 'ButtonNo', 'value' => 'no', 'class' => 'close'), 'Nem', true) . "\n";
        } elseif('decision' === $this->type) {
            echo CHtml::tag('button', array('id' => $this->htmlOptions['id'] . 'ButtonYes', 'value' => 'yes', 'class' => 'close'), 'Igen', true) . "\n";
            echo CHtml::tag('button', array('id' => $this->htmlOptions['id'] . 'ButtonNo', 'value' => 'no',  'class' => 'close'), 'Nem', true) . "\n";
            echo CHtml::tag('button', array('id' => $this->htmlOptions['id'] . 'ButtonCancel', 'value' => 'cancel',  'class' => 'close'), 'Mégsem', true) . "\n";
        }
        echo CHtml::closeTag('div');
        echo "\n" . CHtml::closeTag($this->tagName) . '' . "\n";
    }
}
Végül pedig az elkészült widget használata a view (nézet) állományokban.
...
<!-- Ha erre klikkel, akkor jöhet a dialógus. -->
<?php echo CHtml::link(CHtml::encode('Dialog'),
    array('#'),
    array(
        'id' => 'showDialog',
        'name' => 'showDialog',
        'rel' => '#Dialog'
    )
); ?>
...

...
<!-- És ez a widget hívása. -->
<?php $this->widget('application.components.widgets.jQTDialog', array(
    'htmlOptions' => array(
        'id' => 'overlay',
        'name' => 'overlay'
    ),
    'trigger' => 'showDialog',
    'useMask' => true,
    'title' => 'Dialógus',
    'type' => 'question',
    'message' => 'Valamit kérdezni akartam, de elfelejtettem. Nem tudod, mi volt az?'
)); ?>
...
Remélem, lesz némi haszna a cikknek! Akinek netán kedve támadna, hogy megjegyzéssel illesse a két cikket, ne fogja vissza magát! Nem vagyok egy über okos programozó, csak szeretek ezzel foglalkozni, és szívesen tanulok én is abból, amit a nálam okosabbak mondanak/írnak.

Saturday, April 2, 2011

Hajrá MAGYAROK!

Hajrá MAGYAROK!

FRISSITES 2:

Na, csak hogy lathassatok kemeny munkank gyumolcset, bekuldtem a cuccost:

http://code.google.com/p/yiidoc/source/detail?r=2008

---

FRISSITES:

Hat holgyeim es uraim, ugy latom kesz vagyunk - 3.5 nap alatt ... nem rossz :) nagyon szepen koszonjuk mindenkinek az idejet es energiajat.

Ha valakinek lemaradt volna a neve, kerem jelezze!!! Remelem mar az 1.1.8-asban benne lesz a cucc. Koszi megegyszer!

--------------------------------------------------------------------------

Már nem lehet elbújni a felelősség alól, hehe.

Ha megy az angol, márpedig ha a Yii rendszert használod, akkor valamennyire biztos megy, légyszives légy oly kedves, segíts nekünk abban, hogy végre a Yii megszolaljon már magyarul is.

Az egész nem tart tovább mint tíz másodperc. Tényleg. Se regisztrálni nem kell, se semmi. Csak klikkolj a linkre, nézd meg melyik mondat hiányzik és ha tudod, fordítsd le:


 KÖSZI 


ui: ja, ha segítettél a fordításban, légyszi jelentkezz, hogy a nevedet feltüntethessük valahol!

Néhány kifejezés, amit jó lenne a fordítás során betartani, hogy nagyjából egységes legyen ( ahova ? jelet tettem, nem vagyok biztos ):

Controller - Vezérlő
Model - Modell
View - Nézet
MVC - MVC (szvsz, ne fordítsuk!)
Active Record - Aktív Rekord (más ötlet?)
Class - Osztály
Cache - Gyorsítótár
Alias - Álnév
Extension - Bővítmény
Widget - Widget, ne forditsuk!

Eddigi fordítóink:


- Balázs Simon
- Benyó Balázs
- Erős Bence
- Hajdu Mihály
- Kádár Péter
- Kelemen Zádor Dániel
- Méhész Imre
- Nagy Róbert
- Perjési Szabolcs
- Schumacher Zsolt
- Vajda János
- Zahratka János