Sunday, March 20, 2011

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

1. rész

Írta: János Zahratka


Ok, de mi az a widget?

  1. A Yii egyik alapelve a DRY (Dont Repeat Yourself), vagyis ne ismételd önmagad! Ennek szellemében a widget egyik tulajdonsága, hogy kódrészleteket tárol, amelyeket aztán tetszés szerinti számossággal képes reprodukálni az oldalakon.

    Tegyük fel, hogy dobozokra szeretnénk felosztani weblapjainkat! Ekkor az átlag halandó nekiáll a HTML kódnak, és összeszedve minden kitartását bemásolgatja a dobozok leíró kódját a megfelelő helyekre. Ezzel önmagában nincs is semmi gond, egészen addig, amíg nem kell megváltoztatni a dobozok kódját. No, ekkor szokott előjönni az az effekt, hogy az ember fia/lánya rágyújt egy újabb koporsószögre, és nagyot sóhajtva nekiáll, hogy ismét végigmenjen az összes lap kódján, és egyenként megváltoztassa az egyes dobozokat. Kb. a harmadik, negyedik ilyen kimerítő változtatás után kezd testetölteni a gondolat, hogy ez az egész weblap készítés nagyon macerás dolog.
    Ha Yii-t használunk, akkor mázlisták vagyunk, hisz itt van nekünk a widget, s ennek segítségével szó szerint percek alatt képesek vagyunk arra, hogy a teljes weblap összes kódjában megváltoztassuk a dobozokat megjelenítő kódot. Nos, akkor nézzünk erre egy kis példát! Maradva a feltételezésnél, egy sima doboz konténer kódját kell előállítanunk.


    Yii kódHTML eredmény

    <?php $this->beginWidget('dobozWidget'); ?>
        ... A doboz tartalma ...
    <?php $this->endWidget(); ?>

    <div class="doboz">
        <div class="tartalom"
            ... A doboz tartalma ...
        </div>
    </div>

    Ó, igen, tudom, ez egy használhatatlan példa, viszont nagyon is jól mutatja a widget kód reprodukálási tulajdonságát.

  2. A widget újabb igen jó tulajdonsága, hogy paraméterezhető. Tehát nemcsak egy nevet tudunk neki megadni, hanem - a példánál maradva - mondjuk a doboz címét is.

    Yii kódHTML eredmény
    <?php $this->beginWidget('dobozWidget', array(
        'title' => 'A doboz címe'
    )); ?>
        ... A doboz tartalma ...
    <?php $this->endWidget(); ?>
    <div class="doboz">
        <div class="cim">A doboz címe</div>
        <div class="tartalom"
            ... A doboz tartalma ...
        </div>
    </div>

    Nos, igen, ezen a példán azért már látszik valami abból, hogy nemcsak kód gépelést takarít meg a widget a fejlesztőnek, hanem bizony a paraméterezési lehetőséggel igencsak összetett widgeteket lehet írni.

  3. És még mindíg vannak lehetőségek a widgetekben. A fejlesztők - nagyon okosan - gondoltak arra is, hogy ne csak a tartalmat lehessen hordozni a widget-ben, hanem a formát is. Ezért aztán lehetőség van arra, hogy a dobozunk formáját is belepakoljuk, sőt, ha netán arra vetemednénk, hogy javascript funkciókkal ruháznánk fel a dobozt (animált megjelenés, görgethető tartalom, stb), akkor az ehhez a kódrészlethez tartozó javascript állományokat is magában hordozhatja, és amikor szükség van rá, pár sor megírásával gondoskodik róla, hogy a szükséges CSS és egyéb file-ok elérhetővé váljanak a weblap megjelenítésénél.

  4. És még van egy nagyon-nagyon fontos tulajdonsága, amivel ez a cikk ugyan nem foglalkozik, de érdemes tudni, hogy arra is van lehetőség, hogy a kliens oldali megjelenítéshez hozzá lehet fűzni a szerver oldali kiszolgáló action-okat.

Talán első blikkre elég is ennyi a widget-ek tulajdonságairól. A most következő példa widget eléggé jól fogja szemléltetni, hogy milyen jó kis cuccokat lehet farigcsálni velük, ha egy kis időt rászánunk a dologra.


Lássuk a feladatot!

"A Gugli a barátod" című mondás nagyon is igaz voltát bizonyítja, hogy keresgélés közben ráakadtam a jQuery Tools oldalra. Ez egy jQuery alá írt kis gyűjtemény, ami a felhasználói felületet hivatott kiegészíteni, feltuningolni. Egy szó, mint száz, eldöntöttem, hogy lekapom a netről, és beledolgozom a kis Yii eszköztáramba. Jó, ha az embernek vannak ilyesmik a keze ügyében.
Az Overlay egy olyan kis plugin, amivel az oldal "fölé" lehet megjeleníteni különböző tartalmakat. Egyszerűbben fogalmazva egy panel szerűség, amit tetszőleges tartalommal lehet megtölteni, aztán ezt meg lehet jeleníteni - mondjuk egy linkre, vagy képre kattintva -, illetve el lehet rejteni. Az oldalon jó sok demo mutatja be az egyes plugin-ek működését, sőt, olyan demo-kat is feltettek, melyek az egyes elemek kombinációit demonstrálják.
Némi negatívumként meg kell említenem, hogy a letöltés nem ment simán. Megpróbáltam összerakni magamnak egy összeállítást, de a letöltés gombra kattintva nem találta a cuccot. Viszont egy szerkesztőt megnyitva, és egyenként ráklikkelgetve az egyes szkriptekre, majd kijelölve, és bemásolva a szerkesztőbe simán meg lehet csinálni a saját összeállítást.
Szóval a feladat az, hogy csináljunk egy olyan widget-et, ami:

  • lekezeli a megjelenítendő HTML kód paramétereit,
  • "legyártja" a plugin használatához szükséges HTML kódot,
  • elérhetővé teszi a megjelenő oldalon a plugin-hez tartozó CSS, és javascript állományokat,
  • beállítja a plugin lehetséges paramétereit,
  • elkészíti és befűzi az oldal végére azt a scriptet, amitől az Overlay működni fog,
  • és végül alkalmas arra, hogy ebből a widget-ből származtatással új widget-eket lehessen írni az Overlay kibővítéséhez.


A "kopasz" widget


A magam részéről egyfajta komponensnek tekintem a widget-eket, ezért a protected/components/widgets mappába szoktam tenni őket. Esetünkben a kiinduló widget a következőképpen néz ki:

<?php
class jQTBaseOverlay extends CWidget
{
    public function init() {
    }

    public function run() {
    }
}
A "kopasz", azaz üres widget-ünk összesen négy dolgot tartalmaz:
  1. A widget nevét, amire később hivatkozni fogunk.
  2. Azt az osztályt, amiből származtatjuk, s így örököljük a szülő paramétereit és metódusait (CWidget).
  3. A publikus init metódust, amiben majd beállítjuk a widget paramétereit, és elkészítjük a widget nyitó fejrészét.
  4. A szintén publikus run metódust, amiben megjelenítjük a widget tartalmát, és a záró lábrészét.


Paraméterek


A példa widget-ben szintaktikailag három, logikailag négy fajta osztály szintű paramétert fogunk használni:
  • publikus paraméterek azok, amelyek értékét közvetlenül megadhatja, illetve lekérheti a felhasználó.
  • privát, vagy protected (védett) paraméterek olyan változók, melyeknek értékét nem közvetlenül adjuk meg, vagy kapjuk meg, hanem valamilyen eljárás, számítás eredményét hordozzák. Jó példa erre a _clientScript, melynek értékét a Yii-től kapjuk meg. Csinálhatnánk azt is, hogy ezt az értéket minden alkalommal lekérjük a rendszertől és nem tároljuk, ám ez lassítaná a widget futását, és ezzel az egész lap megjeleítését.
  • Harmadik típusú változónk a privát, belső típus. Ezt arra használjuk, hogy két védett, vagyis az osztályon kívülről elérhetetlen metódusok között adatokat adjunk át.
  • A negyedik kategória, amit ebben a widget-ben használunk a privát, belső és statikus paraméter. Ez a fajta változó megkapja a kezdeti értékét, és a nem statikus változókkal szemben megőrzi azt akkor is, ha egy új Overlay widget-et szeretnénk létrehozni a lapon.


Publikus paraméterek
  1. CHtml helper metódusai használnak egy htmlOptions nevű tömböt, ami arra szolgál, hogy a megjelenítendő HTML kód paramétereit lehessen átadni vele a widget-nek, ezért mi is alkalmazni fogjuk, hogy csak azokat a paramétereket kelljen külön megtanulnunk, megjegyeznünk, amelyek kiegészítik a Yii kód formátumát.
  2. További ilyen, Yii szerű paraméter a tagName, ami azt a HTML tagot jelenti, amit majd a widget Overlay-ként megjelenít. Ez alapértelmezésben a divtag.
    Megjegyzés: Ez a paraméter nagyon hasznossá válhat abban az esetben, ha pl.: valaki netán arra vetemedne, hogy HTML5 alapú oldalt akar csinálni. Ekkor nem kell átírnia a widget-et, csak annyi a dolga, hogy a widget hívásakor beállítja az használni kívánt HTML tag nevét.
  3. title paraméterben lehet majd megadni a megjelenő Overlay panel címét. Ha nem adjuk meg, vagyis üresen hagyjuk, akkor nem jeleníti meg a címet HTML oldalon.
  4. trigger-ben kell megadni annak a másik HTML elemnek a nevét, amire klikkelve megjelenik az Overlay.
  5. useMask paraméter true/false (igaz/hamis) értékeket vehet fel, és arra szolgál, hogy amikor az Overlay megjelenik, takarja-e el egy valamilyen szinű felület az oldal többi részét.
  6. params, maskParams paraméterekben lehet megadni azokat a paramétereket, melyek az Overlay működését szabályozzák.
  7. Aki érzi annyira a szert, hogy tovább akarja bonyolítani a javascript kódot, annak itt vannak az Overlay adta események paraméterei: events, maskEvents.


Privát paraméterek
  1. Az _id paramétert elvileg nem kellene definiálni, mert azt már örököltük a CWidget osztálytól, viszont azt szeretnénk, hogy a widget-ünk ne a szokásos "yw0" id-t kapja, ha nem adunk meg neki mi magunk id-t a létrehozás során, hanem legyen saját, csak erre a widget-re jellemző egyedi neve.
  2. Az _assetsUrl a widget "tartozékainak" (CSS, javascript, képek, stb.) elérési útvonalát tárolja.
  3. Ahhoz, hogy a widget "tartozékait" hozzá tudjuk tenni a megjelenítendő HTML oldalakhoz, szükségünk van a _clientScript paraméterre, aminek érékét majd a Yii-től kell elkérnünk.
  4. Következő paraméterünk a _script, ami arra szolgál, hogy a widget működéséhez szükséges javascript kódot tárolja.
Privát, belső paraméterek
  1. Az _openTag-ot arra használjuk, hogy az init metódusban megkezdett widget megjelenítésre elkészített kódját átvigyük vele a run metódusba, hogy ott folytathassuk vele a megjelenítést.
    Megjegyzés:A megjelenítéshez a php Output Buffering Control (kimenet pufferelés) technikáját használjuk, hogy lehetőség legyen pl.: a header-ek módosítására még a megjelenítés előtt.
Privát, belső, statikus paraméterek
  1. Utolsó osztály szintű paraméterünk a statikus _counter, amit arra fogunk használni, hogy a widget egyedi nevének (_id) automatikus létrehozásakor legyen egy számlálónk, amit a név végéhez hozzáfűzve valóban egyedi értéket kapunk.


Ok, most lássuk mindezt kódban is!
    ...
    public $htmlOptions = array();
    public $tagName = 'div';
    public $title = null;
    public $trigger = null;
    public $useMask = false;
    public $params = array();
    public $events = array();
    public $maskParams = array();
    public $maskEvents = array();

    private $_id;
    private $_assetsUrl;
    private $_clientScript;
    private $_script;

    private $_openTag;
    private static $_counter = 0;
    ...

Metódusok

A widget-ben public (publikus) és protected (védett) metódusokat fogunk használni attól függően, hogy megengedjük-e a metódushoz való hozzáférést az osztályon kívülről.
A publikus metódusok egy része az un. getter/setter (olvasás/írás) kategóriába tartoznak, és arra szolgálnak, hogy a paraméterek bekérése vagy beállítása során kódrészleteket hajtsanak végre, mellyel befolyásolni tudják a paraméterek értékeit.
A "publikusok" másik része pedig vagy automatikusan kerül majd meghívásra, vagy pedig az ebből a widget-ből származtatott új widget-ekben lehet majd használni őket.
Nos, ha eddig nem volt eléggé hosszadalmas és unalmas a cikk, akkor most jön a java, mert elkezdem magyarázgatni, hogy mit is csinálnak az egyes metódusok.


init
A publikus init metódus nevéhez hűen inicializálja a widget paramétereit. Egy sima widget esetében nem kellene az inicializálást így szétdarabolni több almetódusra, ám ez egy speciális eset, mivel a jQTBaseOverlay widget alapnak készül, amiből majd az újabb widget-eket származtatni fogjuk. Ennek fényében már nem is olyan furcsa, hogy ennek a widget-nek egyáltalán nincs is megjelenése a view-ban.
Tehát ez a metódus meghívja a következő almetódusokat:
    ...
    public function init() {
        $this->initHtmlOptions();
        $this->initParams();
        $this->registerScriptFiles();
        $this->getScript();
    }
    ...


run
Ez a metódus ezúttal üresen marad. Pusztán annyi a szerepe, hogy majd a leszármaztatott widget-ben elővegyük. Egyébként a widget-ben ez a metódus futtatja le magát a widget-et.

...
    public function run() {

    }
    ...
getId
Ez a metódus az _id paraméter értékét állítja be, vagy adja vissza attól függően, hogy az autoGenerate bemeneti paraméter értéke false (hamis), vagy true (igaz).
Ha automatikusan kell előállítania az értéket, akkor fogja az osztály nevét, hozzáfűz egy kötőjelet, és a _counter változó eggyel megnövelt értékét, majd ezt a string-et visszaadja.
    ...
    public function getId($autoGenerate = true) {
        if(null !== $this->_id) {
            return $this->_id;
        } elseif($autoGenerate) {
            return $this->_id = get_class($this) . '-' . (self::$_counter++);
        }
    }
    ...


setId
A metódus cask annyit csinál, hogy a value bemeneti paramétert átadja a _counter változónak.
    ...
    public function setId($value = null) {
        if(null !== $value) {
            $this->_id = $value;
        }
    }
    ...

getAssetsUrl
Megvizsgálja, hogy az _assetsUrl paraméternek van-e már értéke. Ha nincs, akkor bekéri a Yii alkalmazástól az assetManager hivatkozását, aztán megállapítja, hogy hol van a widget állományunk, és hozzáfűzi a "/assets" stringet. Ha ezekkel végzett, publikálja a widget-hez tartozó CSS, javascript, és egyéb állományokat, majd beállítja az _assetsUrl értékét. Ezután már csak annyi van hátra, hogy felszabadítsa az assetManager változó által lefoglalt memóriát és visszaadja a beállított _assetsUrl-t.

publish metódus utolsó paramétere (YII_DEBUG) azért van megadva, mert ha a fejlesztői szakaszban vagyunk, akkor minden alkalommal publikálni kell a widget "tartozékait", hisz a rendszer nem tudja, hogy mit változtattunk a feljesztés közben. Ha az oldal már kint lesz a neten, és üzemel, akkor természetesen már nem kell majd minden alkalommal publikálni a cuccokat, és ezt a YII_DEBUG be is állítja helyettünk, tehát nem kell majd feltúrni az összes widget kódját, hogy átkapcsoljuk az erőltetett publikálást.



A "tartozékok" kezelésének megértéséhez tudom ajánlani a cikket.

    ...
    public function getAssetsUrl() {
        if(null === $this->_assetsUrl) {
            $assetManager = Yii::app()->getAssetManager();
            $path = dirname(__FILE__) . '/assets';

            $this->_assetsUrl = $assetManager->publish($path, false, -1, YII_DEBUG);

            unset($assetManeger);
        }

        return $this->_assetsUrl;
    }
    ...

getClientScript
Ó, igen. Kelleni fog a _clientScript is, amit ez a metódus állít be. Persze csak akkor, ha még nincs beállítva.

    ...
    public function getClientScript() {
        if(null === $this->_clientScript) {
            $this->_clientScript = Yii::app()->getClientScript();
        }

        return $this->_clientScript;
    }
    ...

getScript
_script paraméter értékét is elő kell állítani, hogy aztán majd hozzá lehessen fűzni az oldal végéhez.
Ha még nincs beállítva az értéke, akkor egy üres tömbbe beleteszi a params, majd az events értékeit. Mindezt úgy csinálja, hogy összefűzi a két paraméter értékeit, mert a jQery Tools Overlay plugin-je úgy lett megírva, hogy egy objektumban fogadja a beállításokat, és az eseményeket. Ha ezzel megvan akkor még azt is megvizsgálja, hogy akarunk-e mask-ot tenni a megjelenítendő panel mögé. Ezek után már csak annyi a dolga, hogy az összefűzött tömböt átalakítsa a javascript számára "emészthető" formára, aztán elkészítse a javascript-et, amihez még a trigger paramétert is felhasználja, s végül az így összerakott javascript string-et adja vissza.

    ...
    public function getScript() {
        if(null === $this->_script) {
            $params = array();
            $params = CMap::mergeArray($this->params, $this->events);
            if($this->useMask) {
                $params['mask'] = CMap::mergeArray($this->maskParams, $this->maskEvents);
            }
            $params = CJavaScript::encode($params);
            $this->_script = '$("#' . $this->trigger . '").overlay(' . $params . ');';
        }

        unset($params);

        return $this->_script;
    }
    ...
setScript
Persze arra is lehetőséget kell adni, hogy ne csak automatikusan lehessen beállítani a _script értékét. A metódus kap egy értéket, amit átad, és ezzel meg is van a dolog.

    ...
    public function setScript($value = null) {
        if(null !== $value) {
            $this->_script = $value;
        }
    }
    ...

initHtmlOptions
Ahol javascript van, ott bizony nem árt, ha a HTML elemeknek adunk nevet, azaz id-t, hogy könnyen és gyorsan azonosíthatóak legyenek. Ha a widget véletlenül nem kapna id-t, akkor majd ad saját magának. Ugyanezt megcsinálja a name HTML paraméterrel is. Végezetül pedig még arról is gondoskodik, hogy ha netán nincs CSS osztály hozzárendelve a widget-hez, akkor ad neki. Mindezt a htmlOptions paraméterben tárolja el a widget.
    ...
    protected function initHtmlOptions() {
        if(!isset($this->htmlOptions['id'])) {$this->htmlOptions['id'] = $this->getId();}
        if(!isset($this->htmlOptions['name'])) {$this->htmlOptions['name'] = $this->getId();}
        if(!isset($this->htmlOptions['class'])) {$this->htmlOptions['class'] = 'overlay';}
    }
    ...
initParams
Ez a metódus az egész widget legizmosabb metódusa. röviden összefoglalva arról gondoskodik, hogy a params, az events, a maskParams és a maskEventsparaméterek megkapják a kezdeti, vagy alapértelmezett értékeiket. Ha a "close", "target" elemek be vannak állítva, akkor az értékük elé odateszi a "#" jelet, mert ez a két paraméter HTML id (azonosító), amit a jQuery a CSS szelektorok szabályai szerint ismer fel. Ha a "top" értéke nincs megadva, akkor beállítja "center"-re. A jQuery Tools-ban az Overlay "top" paramétere alapértelmezésben "10%". Ezt az értéket saját hatáskörben felülírtam, hogy függőlegesen is középre igazítsa a megjelenítendő panelt. A következő lépés az, hogy megnézi, hogy az Overlay saját eseményei be vannak-e állítva. Ha igen, akkor leszedi előlük a "js:" szöveget, mert ez a Yii-ben szokásos jelölés, aminek a végeredményben nem szabad benne lennie. Ezutan néhány alapértelmezett érték beállítása következik a "mask" paraméterein, de csak abban az esetben, ha nincsenek megadva ("closeSpeed", "color", stb). A metódus utolsó blokkja ismét eseményekkel foglalkozik, csak ezúttal nem a panel eseményeivel, hanem a mögötte megjeletethető mask eseményeivel.
    ...
    protected function initParams() {
        if(isset($this->params['close'])) {$this->params['close'] = (strpos($this->params['close'], '#') !== 0 ? '#' : '') . $this->params['close'];}
        if(isset($this->params['target'])) {$this->params['target'] = (strpos($this->params['target'], '#') !== 0 ? '#' : '') . $this->params['target'];}
        if(!isset($this->params['top'])) {$this->params['top'] = 'center';}

        if(isset($this->events['onBeforeLoad'])) {$this->events['onBeforeLoad'] = (strpos($this->events['onBeforeLoad'], 'js:') !== 0 ? 'js:' : '') . $this->events['onBeforeLoad'];}
        if(isset($this->events['onLoad'])) {$this->events['onLoad'] = (strpos($this->events['onLoad'], 'js:') !== 0 ? 'js:' : '') . $this->events['onLoad'];}
        if(isset($this->events['onBeforeClose'])) {$this->events['onBeforeClose'] = (strpos($this->events['onBeforeClose'], 'js:') !== 0 ? 'js:' : '') . $this->events['onBeforeClose'];}
        if(isset($this->events['onClose'])) {$this->events['onClose'] = (strpos($this->events['onClose'], 'js:') !== 0 ? 'js:' : '') . $this->events['onClose'];}

        if(!isset($this->maskParams['closeSpeed'])) {$this->maskParams['closeSpeed'] = 'normal';}
        if(!isset($this->maskParams['color'])) {$this->maskParams['color'] = '#000000';}
        if(!isset($this->maskParams['loadSpeed'])) {$this->maskParams['loadSpeed'] = 'normal';}
        if(!isset($this->maskParams['maskId'])) {$this->maskParams['maskId'] = '#overlay-mask';}
        if(!isset($this->maskParams['opacity'])) {$this->maskParams['opacity'] = 0.5;}
        if(!isset($this->maskParams['zIndex'])) {$this->maskParams['zIndex'] = 99;}

        if(isset($this->maskEvents['onBeforeLoad'])) {$this->maskEvents['onBeforeLoad'] = (strpos($this->maskEvents['onBeforeLoad'], 'js:') !== 0 ? 'js:' : '') . $this->maskEvents['onBeforeLoad'];}
        if(isset($this->maskEvents['onLoad'])) {$this->maskEvents['onLoad'] = (strpos($this->maskEvents['onLoad'], 'js:') !== 0 ? 'js:' : '') . $this->maskEvents['onLoad'];}
        if(isset($this->maskEvents['onBeforeClose'])) {$this->maskEvents['onBeforeClose'] = (strpos($this->maskEvents['onBeforeClose'], 'js:') !== 0 ? 'js:' : '') . $this->maskEvents['onBeforeClose'];}
        if(isset($this->maskEvents['onClose'])) {$this->maskEvents['onClose'] = (strpos($this->maskEvents['onClose'], 'js:') !== 0 ? 'js:' : '') . $this->maskEvents['onClose'];}
    }
    ...
registerScriptFiles
Ahhoz, hogy a widget kinézetét befolyásolni tudjuk, érdemes hozzáfűzni egy CSS állományt, amiben kényelmesen meg tudjuk határozni a megjelenését. Ezen kívül a jQuery Tools-nak szüksége van magára a jQuery-re, illetve saját magára. Ezeket az állományokat "pakolja" ki ez a metódus a megjelenítendő HTML lapba. ...
    protected function registerScriptFiles() {
        $url = $this->getAssetsUrl();

        $cs = $this->getClientScript();
        $cs->registerCssFile($url . '/css/' . 'overlay.css');

        $cs->registerCoreScript('jquery');
        $cs->registerScriptFile($url . '/js/' . 'jquery-tools.min.js', CClientScript::POS_HEAD);

        unset($cs);
    }
    ...

registerClientScript
E metódus egyetlen faladata az, hogy a "legyárott" javascript-et befűzze az oldal végére.

    ...
    protected function registerClientScript() {
        if(!isset($this->_script)) {
            return false;
        }

        $cs = $this->getClientScript();

        $cs->registerScript(__CLASS__ . '#' . $this->_id, $this->getScript(), CClientScript::POS_READY);

        unset($cs);
    }
    ...
renderBegin
És elérkeztünk végre ahhoz a ponthoz, amikor a beállítások, alapértelmezett értékek és egyéb előkészületek után végre megjeleníthetjük magát a kész widget-et. Első lépésben kiírjuk a megjelenítő pufferbe a widget kezdő részét.
    ...
    protected function renderBegin() {
        ob_start();
        ob_implicit_flush(false);
        echo "\n" . CHtml::openTag($this->tagName, $this->htmlOptions) . "\n";
        echo CHtml::tag('div', array('class' => 'close'), 'X', true) . "\n";
        if(null !== $this->title || '' !== $this->title) {
            echo CHtml::tag('h3', array('class' => 'title'), CHtml::encode($this->title), true);
        }
        $this->_openTag = ob_get_contents();
        ob_clean();
    }
    ...
renderEnd
A megjelenítés második része pedig a widget tartalmának és végének kiírása és a puffer egészének kiküldése a böngészőbe.
    ...
    protected function renderEnd() {
        $content = ob_get_clean();
        echo $this->_openTag;
        echo trim($content);
        echo "\n" . CHtml::closeTag($this->tagName) . '' . "\n";
    }
    ...


Hú, ez jó hosszú volt eddig, és még nincs vége a dolognak. Odáig jutottunk, hogy elkészült az alap widget, amiből majd származtatni fogjuk az Overlay egyes változatait. Ahogy azt illik, nézzük meg a teljes kódot egyben!








protected/components/widgets/jQTBaseOverlay.php
protected/components/widgets/assets/css/overlay.css
protected/components/widgets/assets/js/jquery-tools.min.js


"Mára ennyit a tudomány, és a technika érdekességeiből" - szokták volt mondogatni anno a Delta c. műsorban.

Tudom, kicsit hosszúra sikeredett a dolog, de talán akad majd egy-két hasznos információ benne, amiért érdemes végigolvasni!

A következő részben már olyan widget-et fogunk készíteni, ami ebből származik, és ígérem, sokkal rövidebb lesz a kód is, és a leírás is, ami hozzá tartozik.

Tuesday, March 8, 2011

MVC Model-View-Controller avagy Modell-Nézet-Vezérlő

Model-View-Controller (MVC)

A Yii a model-view-controller (MVC / modell-nézet-kontroller/vezérlő) tervezési mintát valósítja meg, ami webprogramozás terén széleskörűen alkalmazott. Az MVC célja elválasztani az üzleti logikát a felhasználói felülettel kapcsolatos elgondolásoktól oly módon, hogy a fejlesztők minél könnyebben változtathassák meg az egyes részeket anélkül, hogy a többire hatással lennének. Az MVC mintában
a modell képviseli az információt (adatot) és az üzleti logikát; a nézet tartalmazza a felhasználói felület elemeit, úgy mint szövegek, űrlapok; a kontroller/vezérlő pedig kezeli a modell és a nézet közti párbeszédet.

Az MVC mellett a Yii a 'front-controller'-t, más néven alkalmazást, is bevezeti, ami a felhasználói kérések feldolgozásához biztosít végrehajtási környezetet. Az alkalmazás feloldja a felhasználói kérést, majd továbbítja a megfelelő kontrollernek további feldolgozásra.

A következő ábra egy Yii alkalmazás statikus felépítését mutatja:

Egy Yii alkalmazás statikus felépítése



Egy tipikus munkafolymat


A következő ábra egy tipikus munkafolyamatot mutat, aminek során a Yii alklamzás egy felhasználói kérést kezel:

Egy Yii alklamazás tipikus munkafolyamata


   1. A felhasználó kérést intéz a következő URL-lel `http://www.example.com/index.php?r=post/show&id=1` a webszerver pedig a kérést kezelendő, végrehajtja a rendszertöltő `index.php` szkriptet.

   2. A rendszertöltő szkript létrehoz egy alkalmazás példányt, és futtatja.

   3. Az alkalmazás részletes információt kap a felhasználói kérésről a `request` nevű alkalmazás komponens-től.

   4. Az alklamazás meghatározza a kért kontroller-t és tevékenységet egy `urlManager` nevű alkalmazás komponens segítségével. A jelenlegi példában ez a `post` kontrollert jelenti, ami a `PostController` osztályra hivatkozik; a tevékenység pedig `show`, aminek a tényleges jelentését a kontroller határozza meg.

   5. Az alkalmazás példányosítja a kért kontrollert, hogy a továbbiakban kezelje a felhasználói kérést. A kontroller meghatározza, hogy a `show` tevékenység a kontroller osztályának `actionShow` metódusára vonatkozik. Ezek után a tevékenységhez kapcsolódó szűrőket hoz létre (pl.: hozzáférés ellenőrzés, benchmarking). A tevékenység végrehajtódik, ha a szűrők ezt engedélyezik.

   6. A tevékenység beolvassa az adatbázisból az `1`-es ID-jű `Postmodell-t.

   7. A tevékenység lefordítja a `show` nevű nézet-et a megadott `Post` modellel.

   8. A nézet kiolvassa és megjeleníti a `Post` modell tulajdonságait.

   9. A nézet végrehajt néhány kütyü-t.

   10. A nézetfordítási eredmény beillesztésre kerül az elrendezés-be.

   11. A tevékenység befejezi a nézet fordítást és az eredményt megjeleníti a felhasználónak.

Eredeti: http://www.yiiframework.com/doc/guide/1.1/en/basics.mvc
Fordította: Endyl