Tuesday, July 19, 2011

Gittegylet meg a Yii - avagy Yiiki 2.0 (2. rész)

Az első részben elkészítettük a Yiiki 2.0 alkalmazásunk alapjául szolgáló keretünket a Yii segítségével és megismerkedtünk a Git forráskezelővel. Ebben részben először kicsit átírjuk a belépő index.php file-t, elkészítjük az adatbázisunkat a Yii-be épített migráció kezelővel, majd létrehozzuk a Modell osztályunkat Gii-vel, a Yii kódgeneráló varázságyújával (tényleg az).

Amikor az alkalmazásunkat létrehoztuk a yiic paranccsal, maga a Yii készített nekünk egy konfigurációs file-t a protected/config/ alatt main.php néven. Természetesen használhatnánk ezt a file-t úgy ahogy van, de igazából nem szerencsés a rendszer kofigurációs adatait a forráskezelő alá tenni, hiszen a beállítások mások lesznek különböző környezetek alatt (pl. fejlesztői, éles stb), sőt az adatbázis kapcsolathoz szükséges felhasználó és jelszó is ebben a file-ban van. Erre a problémára természetesen többféle megoldás is lehet, itt csak egyet mutatunk be.

Első lépésként másoljuk a protected/config/main.php -t development.config.php -ra:

$cp protected/config/main.php protected/config/development.config.php

Ha most git stat-tal megnéznénk, hogy hogyan is áll a forráskódunk, látnánk, hogy a git azt jelzi, hogy van egy új file-unk. Mi persze nem szeretnénk a konfigurációs file-t a forráskezelő alá helyezni, ezért tegyük is a .gitignore file-ba.

Belépő script módosítása

Most kedvenc kódszerkesztőnk segítségével (ami nyilván VIM), nyissuk meg az index.php file-t és módosítsuk a követlkező képpen.



Ezzel csupán azt értük el, hogy amikor ha a YII_DEBUG konstanst true-ra állítjuk, akkor programunk a development.config.php-t fogja betölteni, egyébként pedig a production.config.php-t amit majd később az éles, mindenki által elérhető szerveren fogunk használni.

Adatbázis Migrációk

Migrációkról más esett szó ezen a blogon, igaz akkor még egy különálló modulként kellett installálni, de szerencsére ez mára már a Yii, pontosabban a yiic része. Röviden arról van szó, hogy könnyebb hordozhatóság kedvéért az adatbázis táblákat migrációs osztályok és tömbök segítségével készítjük el.

Jelenleg, mivel az eredeti main.php konfigurációs file-t másoltuk át development.config.php-re, adatbázisunk még mindig a testdrive.db. Ezzel sajna nem sokra megyünk, úgyhogy állítsuk is át valami másra, pl:

'components' => array(
...

  'db'=>array(
    'connectionString' => 'sqlite:'.dirname(__FILE__).'/../data/yiiki2.db.sqlite',
  ),

...
),

Ugyanezt tegyük meg a protected/config/console.php file-ban is, hiszen a migrácókat készítő yiic parancs innen olvassa be az adatbázis hozzáférési adatait.


Már nincs más hátra mint előre, készítsük el a táblánkat az adatbázisban (a yiiki2/ mappában adjuk ki a következő parancsot):


$./protected/yiic migrate create pages_tabla_letrehozasa

Yii Migration Tool v1.0 (based on Yii v1.1.8)
Create new migration '/var/www/yiiki2/protected/migrations/m110710_033230_pages_tabla_hozzaadasa.php'? [yes|no]



Mondjuk neki, you 'y' (igen) és ha minden igaz, akkor egy a megerősítő "New migration created successfully." - üzenetet kapjuk vissza. HURRÁ! Ezzel egy új file került létrehozásra a protected/migrations alatt valami hasonló névvel: m110710_033230_pages_tabla_hozzaadasa.php. Itt az 'm' csak azt jelzi, hogy ez egy migráció, az utána következő számsorozat pedig a migráció sorszáma, majd a név amit megadtunk.


Nyissuk is meg ezt a file-t és módosítsuk a következők szerint:



Ezzel elkészítettük az új migrációs lépést, de még le is kell futtassuk, hogy a táblánk magában az adatbázisban is megjelenjen:


$ ./protected/yiic migrate


Yii Migration Tool v1.0 (based on Yii v1.1.8)


Creating migration history table "tbl_migration"...done.
Total 1 new migration to be applied:
    m110710_033230_pages_tabla_hozzaadasa


Apply the above migration? [yes|no]


Ez csak azt jelenti nekünk, hogy ez a Yii Migrációs Eszköz 1.0-ás változata, a Yii 1.1.8-ra épül és jelenleg 1 migrációs lépést lehet végrehajtani a pages_tabla_letrehozasa -t. Mondjuk neki, hogy 'y' vagy 'yes'. Ha miden jól sikerült valami hasonlót kell, hogy lássunk:


*** applying m110710_033230_pages_tabla_hozzaadasa
    > create table pages ... done (time: 0.201s)
*** applied m110710_033230_pages_tabla_hozzaadasa (time: 0.054s)
Migrated up successfully.


Ez kb. annyit jelent, hogy minden OK. Csak egy megjegyzés - ugyan ebben a cikkben nem fogunk többet a migrációkkal foglalkozni, érdemes őket tanulmányozni, hiszen nagyon sok mindenben segítséget nyújthatnak. Páldául ha az idegen kulcsokat (foreign-keys) a táblák között megfelelően beállítjuk, Yii automatikusan összeköti Modell osztályainkat a használandó BELONGS_TO, HAS_ONE stb. kapcsolatokkal. Persze ezt kézzel is be tudjuk állítani, de mennyivel egyszerűbb amikor nem kell gépelni ... elvégre is lusta programozók lennénk, vagy mi fene.

Mivel jelenleg egyedül programozunk ez a lépés elhanyagolható, de a példa gyakorlás kedvéért érdemes magát az új migrációs lépést egyből a forráskezelő alá tenni, hogy "csapattársaink" is frissíthessék az adatbázisaikat. Használjuk a git status-t és a git add-et és persze a git commit-ot :)

Gii Gii Gii Giiiiiiiii

Ha annak idején a yiic-et a Yii varázspálcájának hívtuk, a Gii-t joggal nevezhetjük varázságyúnak. Egy nagyon egyszerű de szexi, böngészőn keresztül futtatható kód generáló eszközről van szó, ami szépen felgyorsítja a programozást. Azoknak akik meg nyöszörögnek (mert akadnak páran), hogy túl sok a varázslás meg a mágia, vissza lehet menni saját keretrendszert barkácsolni. Sok szerencsét.

A Gii ugyan együtt érkezik a rendszerrel de biztonsági okok miatt alapból ki van kapcsolva. Nyissuk meg a konfigurációs file-unkat és keressünk rá a gii szóra a modules tömb környékén. Ha megvan, nyilván szedjük ki a /* ... */-t, és állítsunk be egy jelszavat (password), mondjuk: giiros -ra (vicces mi? haha).

Most látogassuk meg a Gii eszközt egy böngészőben ezen a URL-en: http://localhost/yiiki2/index.php?r=gii. Ha mindent jól csináltunk akkor a Yii Code Generator-nak kell megjelennie (ez a Gii) és egy jelszót bekérő form mezőnek. Büszkén írjuk is be a jelszavunkat (tudod, a giiros). Itt már láthatjuk is a köszöntő oldalt ami röviden annyit közöl, hogy a Kód Generáló segítségével gyorsan felépíthetjük a Yii alkalmazásunkat. És tényleg ... Elsőként tanácsos a Modell-t elkészítenünk, hiszen ez köti össze az adatbázisunkat a programmal. Klikkoljunk is rája:



A tablePrefix-szel most ne foglalkozzunk, mert nem állítottuk be, viszont a tábla nevét (Table Name) töltsük ki azaz írjuk be, hogy: pages -  (hiszen ez volt a táblánk neve). Gii elvileg okosan megpróbálja kitalálni, hogy mi lesz (legyen) a Modell osztály elnevezése ... Majdnem jó is, én személy szerinte egyes számba szoktam tenni a Modell-jeimet (tehát pl: Page a Pages helyett). A többi értéket hagyhatjuk úgy ahogy van, és kattinthatunk az Előnézet (Preview) gombra.

Remélhetőleg egy új file név jelenik meg alul: models/Page.php. Ha erre a link-re kattintunk, akkor meggyőződhetünk róla, hogy igen, a mezők mind itt vannak és nyomhatunk egyet a Generate gombra. Ha jó Atyánk és a jogosultságok is úgy akarják, akkor a Page Modell osztályunk elészült a models/ mappa alatt.

Hát ennyi fért így a második részbe, elég sok mindent átvettünk, persze tudom, lehetne még ezt azt jobban részletezni, de a cikk célja inkább csak amolyan ismertetés féle. A következő részben használni fogjuk a Gii eszköz CRUD generáló finomságát, ami elkészíti majd nekünk a Kontroller és Nézet file-okat, az alap Create, Update, Read, Delete funkciókkal. Ja, és talán majd gépelni is fogunk egy kicsit ;)

Tuesday, July 12, 2011

Gittegylet meg a Yii - avagy Yiiki 2.0 (1. rész)

Ugyan ez egy új cikk, de nem teljesen. Talán a tavaly szeptemberben megjelent Yiiki avagy WIKI a'la Yii cikkben bemutatott rendszer 2.0-ás változatának lehetne tekinteni. 

Elég sok idő eltelt azóta és maga az alap keretrendszer is sokat változott, szerencsénkre. Még annyival szeretnénk kicsit megfűszerezni a fejlesztést, hogy bevonjuk a mostanában nagy népszerűségnek örvendő Git forráskezelőt. Vágjunk is bele.

Bevezetés

Magába az alapok installálásába nem fogunk belemenni, itt csak felsoroljuk mi mindent kell ismerni:

Objektum Orientált Programozás (PHP 5): A jelenleg stabil Yii 1.1.8-es verzió a PHP 5.1-en alapszik, tehát annak ismerete mindenképp szükséges. - http://hu.wikipedia.org/wiki/Objektumorientált_programozás

Adatbázis (SQL): A Yii, alapból sokféle adatbázis motort támogat. Az itteni példában én a SQLite-ot fogjuk használni, mert nem igényel különösebben bonyolult szerver oldali beállítást -  http://hu.wikipedia.org/wiki/SQL

MVC (Model-View-Controller) avagy Modell-Nézet-Vezérlő: A Yii az egy MVC-t szorgalmazó keretrendszer. Dióhéjban annyit jelent, hogy az alkalmazás jól elkülöníthető 3 részre. Az M vagy modell, ami az adatbázis jellegű lekérdezéseket végzi, a C vagy vezérlő ami kapcsolatot teremt és feldolgoz felhasználók által bevitt információt és végül a V vagy nézet, ami pedig magát a megjelenítést végzi. Ilyen többek között a Ruby On Rails rendszer is. - http://hu.wikipedia.org/wiki/MVC

Active Record - ami tulajdonképpen "megszűnteti" a függőséget a különböző adatbázisok között és az adatbázis táblákat objektumokként kezeli. Ez a gyakorlatban azt jelenti, hogy mondjuk fejlesztői környezetben használhatunk SQLite-ot élesben pedig MySQL-t vagy MsSQL-t stb.  - http://en.wikipedia.org/wiki/Active_record_pattern

- Git, forráskezelés - vagy verziókezelés alatt több verzióval rendelkező adatok kezelését értjük. Leggyakrabban a mérnöki tudományokban és a szoftverfejlesztésben használnak verziókezelő rendszereket fejlesztés alatt álló dokumentumok, tervek, forráskódok és egyéb olyan adatok verzióinak kezelésére, amelyeken több ember dolgozik egyidejűleg.

Apache, Nginx és egyéb web szerverek beállításába nem szeretnénk itt belemenni. A programot a következő konfigurációval készítettük:
- Ubuntu Linux (10.10)
- Apache 2.x
- SQLite 3
- PHP 5.3.x
- Yii 1.1.8

- Csomagoljuk ki a letöltött Yii-t a web szerverünk fő könyvtárába (pl. /var/www/yii ), a forráskód majd szintén a /var/www alá kerül (pl: /var/www/yiiki2).

Az is fontos, hogy a Yii rendszer a parancssorból is futtatható legyen! (PHP CLI)


A WEB alkalmazás létrehozása

Az alap alkalmazás létrehozása rendkívül egyszerű, csak adjuk ki a kovetkező utasítást (a web szerver root könyvtárából, ahol reméljük a maga a Yii rendszer is elhelyezkedit (pl /var/www).

./yii/framework/yiic webapp yiiki2





Ha minden jól sikerült, akkor a "Your application has been created successfully under /var/www/yiiki2" - szerű üzenet jelenik meg, ami röviden annyit jelent, hogy az alap program sikeresen létrehozva a megadott mappa alatt.

Azt már itt érdemes megjegyezni, hogy a programunk már ebben a fázisban működőképes. Ha megtekintjük a URL-t egy böngészőben (pl http://localhost vagy http://yiiki.peldaprogram.local) akkor egy (remélhetőleg) üdvözlő képernyő fogad bennünket.


Na itt a Git

Már rögtön az elején érdemes a munkánkat a forráskezelő alá tenni. Lépjünk be a yiiki2/ mappa alá és adjuk ki a következő parancsot.

$git init
Initialized empty Git repository in /var/www/yiiki2/.git/


Ez csak annyit jelent, hogy a Git sikeresen létrehozott egy üres (empty) forrástárat a megadott mappa alatt. Ha most megnézzük a könyvtárszerkezetet, akkor valami ilyesmit kell, hogy kapjunk. (rejtett file-okat is mutatva!)

$ll

drwxr-xr-x  8 imehesz imehesz 4096 2011-07-08 22:34 ./
drwxr-xr-x 42 imehesz imehesz 4096 2011-07-08 22:19 ../
drwxrwxrwx  2 imehesz imehesz 4096 2011-07-08 22:19 assets/
drwxr-xr-x  2 imehesz imehesz 4096 2011-07-08 22:19 css/
drwxr-xr-x  7 imehesz imehesz 4096 2011-07-08 22:34 .git/
drwxr-xr-x  2 imehesz imehesz 4096 2011-07-08 22:19 images/
-rw-r--r--  1 imehesz imehesz  478 2011-07-08 22:19 index.php
-rw-r--r--  1 imehesz imehesz  481 2011-07-08 22:19 index-test.php
drwxr-xr-x 14 imehesz imehesz 4096 2011-07-08 22:19 protected/
drwxr-xr-x  3 imehesz imehesz 4096 2011-07-08 22:19 themes/

Láthatjuk is, hogy a .git mappa ott figyel, amibe a Git program fogja tárolni a változtatásainkat.

Rutinos Yii-s mókusként tudjuk azt, hogy az assets/ a protected/runtime/ és a protected/data/ mappa alatt a Yii olyan dolgokat fog tárolni ami 99%-ban minden rendszeren más és más (pl. fejlesztői és éles környezet!), tehát érdemes lenne ezeket nem forráskezelő alá tenni, mert egyszerűen semmi értelme.

A Git erre egy nagyon elegáns megoldást kínál (illetve többet is), egyszerűen hozzunk létre egy file-t .gitignore néven (a yiiki2/ mappa alatt), és adjuk meg neki ezeket az útvonalakat. Így amikor a git ezekhez a mappákhoz ér, egyszerűen figyelmen kívűl hagyja őket. A gyakorlat azt diktálja, hogy még pár dolgot ne vegyünk forráskezelés alá, ilyenek például az adatbázis file-ok, vagy tömörített file-ok stb. Tehát a .gitignore file-unk valahogy így nézzen ki.

$less .gitignore

assets/*
protected/data/*
protected/runtime/*
*.zip
*.sqlite


Nos, eddig megvolnánk, de ha jól emlékszünk akkor a Git az elöbb azt mondta, hogy egy üres rendszert hozott létre. Ez igy is van. Az egyik legfontosabb Git parancs amit gyakran használni fogunk az a git status. Adjuk is ki:

$git status
# On branch master

#
# Initial commit
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
# .gitignore
# css/
# index-test.php
# index.php
# protected/
# themes/
nothing added to commit but untracked files present (use "git add" to track)


Amint láthatjuk, annyi történt, hogy a Git kilistázta, hogy mely file-ok változtak. Jelen esetben azonban még semmi nem változott, hiszen még semmit nem rendeltünk a forráskezelő alá. Ezt egyébként alul a Git szépen jelzi is, hiszen azt mondja, hogy nincs semmit a commit-hez hozzáadnunk, azonban vannak olyan file-ok amiket nem követ. Ja, és azt tanácsolja, hogy ezeket a git add utasítással adhatjuk a forráskezelő alá. Remek, pont ez kell nekünk. Adjuk is ki:


$git add .


A pont "." az azt jelenti, hogy MINDEN használható file-t vagy változást adjon hozzá a forráshoz. Miután kiadtuk a parancsot, szemmel láthatóan semmi nem történt, azonban a Git a háttérben szorgosan dolgozik. Ha most kiadjuk a git status -t, egyből észrevesszük, hogy igenis valami történt:



# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
# new file:   .gitignore
# new file:   css/bg.gif
# new file:   css/form.css
# new file:   css/ie.css
# new file:   css/main.css
# new file:   css/print.css
# new file:   css/screen.css
# new file:   index-test.php
# new file:   index.php
...


A lista elég hosszú, ide csak egy kis részletet illesztettünk be! A lényeg az, hogy ezek a file-ok készen állnak az un. commit-álásra, ami annyit tesz, hogy a módosításaink (jelen esetben új file-jaink) véglegesen a forrás részei lesznek. Magát a commit-álást pedig a -drrrrr ... dobpergés- git commit paranccsal hatjuk végre:


$git commit -m "itt a Yiiki2.0, elso commit-unk"


A -m (message) azaz üzenet, azt jelenti, hogy üzenetet fűzhetünk a commit-hez, aminek hiányát néhány programozó törzsek gyakran kéz- és/vagy fejvesztéssel büntetnek. Persze a képernyőn minden gyorsan leszalad, azért nézzük meg mi is történt:



[master (root-commit) 9e22a96] itt a Yiiki2.0, elso commit-unk
 Committer: Imre Mehesz <imehesz@imehesz-laptop.(none)>
Your name and email address were configured automatically based
on your username and hostname. Please check that they are accurate.
You can suppress this message by setting them explicitly:


    git config --global user.name "Your Name"
    git config --global user.email you@example.com


If the identity used for this commit is wrong, you can fix it with:


    git commit --amend --author='Your Name <you@example.com>'


 34 files changed, 1512 insertions(+), 0 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 css/bg.gif
 create mode 100644 css/form.css
 create mode 100644 css/ie.css
 create mode 100644 css/main.css
...

Itt azt irta ki, hogy létrejött a commit, és a rendszer beállította a nevünket és az email címünket, de azért ellenőrizzük, hogy jó e. Még talán egy git parancsot érdemes itt megemlítenünk, a log -ot, ami az utolsó commit-okat listázza ki:

$git log
commit 9e22a96c0d94b58cb780eaa0d53fedd24df99242
Author: Imre Mehesz <imehesz@imehesz-laptop.(none)>
Date:   Fri Jul 8 23:24:51 2011 -0400

    itt a Yiiki2.0, elso commit-unk

Ebből a commit azonosítón kívül az is látszik, hogy pénteken este fél 12-kor nincs más dolgom, minthogy ilyeneket irogassak :)

A következő részben reméljük már talán valami kódot is sikerül majd írnunk!

Thursday, June 9, 2011

Csinibaba Szeress Belém avagy Sminkelés/Themeing a Yii rendszerrel

Alapozás

A "Theming vagy Sminkelés" nem más, mint egy (webes) alkalmazás kimenetét a felhasználó elé táró kinézet-lapok rendszere. Egy új Sminket használva az egész program általános kinézete azonnal megváltoztatható.

Yii-ben minden Sminket egy mappa képvisel, ami különböző Nézet és Layout (pl: header vagy footer stb...) fájlokat tartalmaz(hat), ugyancsak itt találhatók meg a Sminkhez tartozó képek, CSS- és JS file-ok is. A Smink neve minden esetben megegyezik a mappa nevével (fontos!). Az összes Smink a WebRoot/themes mappa alatt található, és egyszerre csak egy Smink lehet aktív.


Smink felkenése:

Egy Sminket úgy aktiválhatunk, hogy beállítjuk a Web programunk 'theme' paraméterét. Ezt vagy magában a fő konfigurációs file-ben tehetjuk meg (protected/config/main.php), vagy futás közben a Kontrollerben. (persze futás közben sminket feltenni veszélyes is lehet - minden körülmények között!)

Megjegyzés: maga a Smink neve kis- és nagybetű érzékeny. Ha egy olyan Smink-et probálunk aktiválni, ami nem létezik a 'Yii::app()->theme' null-t fog vissza adni.

Paletta készítése:

A Smink-nek mindenképpen követnie kell a Yii által használt és szorgalmazott könyvtárfelépítést. Például minden Nézet file-nak a '/views' mappában kell elhelyezkednie, a layout file-oknak pedig a '/views/layouts'- és a rendszer nézet file-oknak pedig a '/views/system' alatt. Tehát ha pl. le szeretnénk cserélni a 'PostController' kontroller 'create' nézet file-ját a 'classic' smink-ben, csak mentsük el az új nézet file-unkat valahogy így 'Webroot/themes/classic/views/post/create.php'.

Azokat a Nézet file-okat, amelyek olyan Kontrollerekhez tartoznak amik egy meghatározott Modul részei, ugyanúgy a 'views' mappa alá kell elhelyezni. Például, ha az előbb említett PostController egy 'forum' nevű modul alatt található, akkor az új "sminkelt" nézet file-t a WebRoot/themes/classic/views/forum/post/create.php-ként mentjük el. Ha pedig maga a forum modul egy másik modul, pl: 'support' része (mert iiyet lehet a Yii-vel ;) ) akkor egyszerűen ide mentjük: WebRoot/themes/classic/views/support/forum/post/create.php.

Megjegyzés: Mivel a 'views' mappa tartalmazhat kényes file-okat, érdemes úgy konfigurálni, hogy halandó internetfelhasználó ne férhessen hozzá.

Amikor a render()-t vagy renderPartial()-t meghívjuk, hogy  a Nézet-ünket megjelenítse, Yii először megpróbálja megtalálni magát a nézet file-t és a hozzá tartózó layout file-t az aktív smink könyvtár alatt. Ha sikerül, ezek a file-ok kerülnek megjelenítésre. Ha nem, akkor pedig az alap nézet és layout file-ok, amiket a viewPath/layoutPath-ek állítanak be.


Tipp: Gyakran előfordul olyan, hogy a nézet file-unkból szeretnénk elérni egyéb, csak az aktív sminkre jellemző, file-okat. Ilyenek lehetnek például a képek a smink 'images' mappában. A baseUrl változó segítségével ezt könnyedén megtehetjük, valahogy így: Yii::app()->theme->baseUrl.'/images/FileName.gif'.
Alább látható egy Yii alkalmazás teljes mappa rendszere, két sminkkel basic és fancy néven.

WebRoot/
  assets
  protected/
    .htaccess
    components/
    controllers/
    models/
    views/
      layouts/
        main.php
      site/
       index.php
themes/
  basic/
    views/
      .htaccess
      layouts/
        main.php
      site/
        index.php
  fancy/
    views/
      .htaccess
      layouts/
        main.php
      site/
        index.php
És ha az alkalmazásunk konfigurációs file-jába ezt beállítjuk (/protected/config/main.php):

return array(
  ...
  'theme'=>'basic',
  ...
);
akkor a basic smink lesz aktív, ami azt jelenti, hogy a themes/basic/views alatti /layouts/main.php és a site/index.php kerül megjelenítésre. Ha pedig ezek nem lennének meg, akkor Yii automatikusan a "gyári" /protected/views-t fogja használni.

then the basic theme will be in effect, which means the application's layout will use the one under the directory themes/basic/views/layouts, and the site's index view will use the one under themes/basic/views/site. In case a view file is not found in the theme, it will fall back to the one under the protected/views directory.

Hát ennyi :)

A Yii Themes oldalon lehet ingyé előre elkészített Sminkeket letölteni a Yii-hez.

Eredeti cikk (még több infóval): http://www.yiiframework.com/doc/guide/1.1/en/topics.theming



Saturday, May 28, 2011

Függyvény hívása Nézetből

Néha előfordul, hogy valamilyen bonyolultabb kimenetet kell produkálnunk Nézetünkben - például egy táblát. A legtöbben azt tanácsolják, hogy az MVC szabályainak megfelelően a Nézet az legyen egyszerű, ne legyen benne túl sok ciklus stb. Tehát valahogy a kódunkat máshová kell helyeznünk. Erre fogunk néhány példát bemutatni.


  1. Függvényünket betehetjük a Modellünkbe, így a Nézetben elérhetővé válik a modellen keresztül  mint osztály metódus. A hátulütője ennek az, hogy a modellbe nem igazán szép nézettel/kimenettel kapcsolatos függvényeket tenni. Tehát ez a módszer egyáltalán nem tanácsolt.


  2. Használjunk Widget-et. Készíthetünk egy osztályt, ami a CWidget-et bővíti ki, ebben az esetben a Nézetből csak meghívjuk a Widget-ünket és kész is. Ez akkor is nagyon hasznos, ha például több nézetben szeretnénk megjeleníteni ugyanazt a tartalmat.


  3. Tegyük a függvényünket a Kontrollerbe. Ha csak egyetlen Nézet használja a függvényünket (és nem tartalmaz nagyon összetett logikát) elég lehet, hogy a Kontrollerbe helyezzük el.
  4. Használjunk Nézet file-t. Mert a CController render() és renderPartial() metódusai visszaadhatnak értéket is megjelenítés helyett. Ez azt jelenti, hogy a függvényünket betehetjuük egy speciális Nézet file-ba, lehetőleg ugyanabba a mappába ahol maga a hívó file helyezkedik el. Ezt úgy érhetjük el, hogy a render() - vagy renderPartial()) - harmadik paraméterét true-ra állítjuk be.



Eredeti cikk: http://www.yiiframework.com/wiki/125/putting-function-to-be-called-in-view/

UI: Yii-s widget-ekrol meg itt olvashattok bovebben (magyarul!): Nosza irjunk widget-et tyuklepesrol tyuklepesre

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