« PHP::HTMLElements »

Nem titkoltan »azonnal beleszerettem« az ASP.net nyújtotta lehetőségekbe. Leginkább a code behind modell az, ami rögtön megfogott. Ezzel kapcsolatban jönne most néhány gondolat, a címből sejthető témával.

Az elején mondom, hogy akiket a kísérőszöveg nem érdekel, a kódot ide kattintva böngészhetik, nem bonyolult, nem nagy és UTF-8-as, a böngészőben színezve jelenik meg.

Klakkolás után tömény kódokkal és súlyos gondolatokkal folytatom.

PHP-ben amikor egy-két HTML elemet (leginkább legördülő listát) kicsit macerás kezelni. Tudom, hogy léteznek frameworkök, illetve kismillió megoldás, de én általában szeretek a dolgok mögé látni, megérteni azok működését, így valamelyik nap kíváncsiságból összeraktam néhány osztályt, ami akár jó alap is lehet. Vagy nem, majd elválik.

Na, de miről is van szó. Tegyük fel hogy szeretnénk egy listát, amiben a blog kategóriái jelennek meg, de úgy, hogy ha éppen egy bejegyzést módosítunk, akkor a listában az aktuális kategória legyen kiválasztva. Ehhez valószínűleg mindenki írna valami olyan kódot, amiben lenne egy adag SQL, egy adag PHP és egy adag HTML. Mondjuk így:

function getCategories($selectedID = null) {
    $q = query('SELECT id, name FROM cat;');
    if($isResult) {
		echo '<select name="select_cat_id">';
		while($r) {
			$selected = ($selectedID == $r[0]) ? ' selected="selected' : null;
			echo '<option value="' . $result[0] . '"' . $selected. '>' . $r[1] . '</option>';
		}
		echo '</select>';
    }
	else {
		return false;
    }
}

Nem a legszebb megoldás, és ismételten: biztos van erre kismillió jobb út, de ez az egyik legalapvetőbb.

Oké, ezt tegyük is félre. Gondolkodjunk egy kicsit teljesen máshogy. Egyrészt objektumorientált világban, másrészt ezzel elmondtam mindent. A HTML struktúrájára tökéletesen illeszthető egy PHP-kód, mely annak OOP nyújtotta lehetőségeit kihasználva próbálja voltaképpen szimulálni egy HTML elem létrehozását. Ráadásul ne felejtsük el, hogy általában van tíz-tizenöt elem, amivel dolgozunk, ezeknek is javarészt ugyanazon tulajdonságaival. Persze a megoldás, amit most szeretnék bemutatni, inkább igyekszik univerzális lenni. Fontos azért azt is megjegyezni, hogy ezek a kódok gyakorlatilag egy agymenés után néhány perc gépelés eredményeként születtek, lenne még mit átgondolni és megtervezni rajtuk, de úgy vélem jó alap lehet.

Lépcső a Mennyországba

Legeslegelőször a CSS az, amivel foglalkozni kell, ezt ugyanis később majdnem minden használni fogja. Első körben gondolkodjunk nagyon minimális dolgokban: minden elemnek van egy style tulajdonsága, amelybe tetszőleges CSS-t írhatunk a CSS-pedig tulajdonság-érték párokból áll. Ezek alapján akkor már csak annyi a dolgunk, hogy lefordítjuk ezt PHP-re. Íme:

class HTMLElementCSS{

    private $properties = array();
    private $values = array();

    public function addProperty($property, $value) {
		if(strlen($property) > 0 && strlen($value) > 0) {
			array_push($this->properties, $property);
			array_push($this->values, $value);
		}
    }

    public function removeProperty($property) {
		$key = array_search($property, $this->properties);
		if($key !== false) {
			$this->properties[$key] = null;
			$this->values[$key] = null;
		}
		return false;
    }

    public function getStyle($onlyStyle = true) {
		if(count($this->properties) > 0) {
			$style = null;
			$i = 0;
			foreach($this->properties as $p) {
				if($p !== null) {
					$v = $this->values[$i];
					$style .= $p . ':' . $v . ';';
				}
				$i++;
			}
			return ($onlyStyle) ? $style : ' style="' . $style . '"';
		}
		else {
			return false;
		}
    }

Mint mondtam, semmi komoly, csak a legszükségesebbek. A használata eléggé egyértelmű, ezért arra most külön nem térnék ki, talán csak annyiban, hogy ugyan az osztály példányosítható, de haszna csak valamelyik HTMLElement osztálynál lesz

És mondá az úr: hát, É emel!!

Itt is van a lényeg. Elsőre úgy gondoltam, hogy egy interfésszel kellene próbálkozni, de ez több szempontból sem bizonyult megfelelőnek, hiszen van egy rakatnyi tulajdonság (id, class, style, name stb.), amiket jó lenne valahol először leírni, aztán később csak hivatkozni rá. Így egy absztrakt HTMLElement osztály született, amelyet később minden elem örökölni fog.

abstract class HTMLElement {
	public $ID;
	public $style;
	public $class;

	private $name;
	private $value;
	private $innerHTML;

	private $selected;

	function setInnerHTML() {
	}
	
	function render($echo = false) {
	}
}

A dolog még mindig roppant egyszerű. Persze lenne kismilliárdtrillió tulajdonság még, de elsőre csak néhányat vettem fel.

Kezdjünk bele

A következő kód az egyik legegyszerűbb és legtöbbet használt elem lesz, a bekezdés. Egy bekezdésnek van valamilyen tartalma, lehet esetleg stílusa, osztálya és azonosítója. Ezeket akár első körben rögtön meg is adhatjuk, de persze az is lehet, hogy először csak megcsináljuk azt a bekezdést, és később adunk neki mondjuk színt. (Például egy bejelentkezés űrlapjánál a hibás adatokra vonatkozó felhívást először nem jelenítjük meg, csak ha már volt egy hibás próbálkozás.) A kód:

class HTMLParagraph extends HTMLElement {

    public function setInnerHTML($content) {
		$this->innerHTML = $content;
    }

    public function render($echo = false) {
		$ID = ($this->ID !== null) ? ' id="' . $this->ID . '"' : null;
		$class = ($this->class !== null) ? ' class="'. $this->class . '"' :null;
		$style = $this->style->getStyle(false);
		$element = sprintf('%s

', $ID, $class, $style, $this->innerHTML); if($echo) { echo $element; } else { return $element; } } public function __construct ($innerHTML = null, $ID = null, $class = null, $render = false, $echo = false) { $this->setInnerHTML($innerHTML); $this->ID = $ID; $this->class = $class; $this->style = new HTMLElementCSS(); if($render) { $this->render($echo); } } }

Első körben ha valaki ránéz, lehet hogy fejbe akar lőni. Minek ennyi sor egy árva bekezdés kiírásához? Valahol tejesen jogos, de ennek az igazi erejét akkor fogjuk látni, amikor megcsináljuk a korábban említett legördülő listát, ami pedig így néz ki:

class HTMLSelectOption extends HTMLElement {

    public function setInnerHTML($innerHTML) {
		$this->innerHTML = $innerHTML;
    }

    public function render() {
		$value = ($this->value !== null) ? ' value="' . $this->value . '"' : null;
		$selected = ($this->selected !== false) ? ' selected="selected"' : null;
		$element = sprintf('%s', $value, $selected, $this->innerHTML);

        return $element;
    }

    public function __construct ($value = null, $innerHTML = null, $selected = false) {
		$this->value = $value;
		$this->setInnerHTML($innerHTML);
		$this->selected = $selected;
    }
}


class HTMLSelect extends HTMLElement {
    
    public function addOption($value, $innerHTML, $selected = false) {
		if(strlen($value) > 0 && strlen($innerHTML) > 0) {
			$option = new HTMLSelectOption($value, $innerHTML, $selected);
			array_push($this->options, $option);
			return true;
		}
		else {
			return false;
		}
    }

    public function getOptions() {
		$optionValues = null;
		foreach($this->options as $option) {
			$optionValues .= $option->render();
		}
		return $optionValues;
    }

    public function render($echo = false) {
		$ID = ($this->ID !== null) ? ' id="' . $this->ID . '"' : null;
		$name = ($this->name !== null) ? ' name="' . $this->name . '"' : null;
		$class = ($this->class !== null) ? ' class="'. $this->class . '"' :null;
		$style = $this->style->getStyle(false);
		$options = $this->getOptions();
		$element = sprintf('%s', $ID, $name, $class, $style, $options);

		if($echo) {
			echo $element;
		}
		else {
			return $element;
		}
    }

    public function __construct ($ID = null, $name = null, $class = null) {
		$this->ID = $ID;
		$this->name = $name;
		$this->class = $class;

		$this->style = new HTMLElementCSS();

		$this->options = array();
    }
}

És hoppá, a fentebb említett kód rögtön gusztusosabban néz ki, sőt, egy lépéssel közelebb vagyunk a code behind modellhez, de akár még az MVC-hez is (persze attól ez nagyon messze van, mint maga a PHP):

function getCategories($selectedID = null) {
    $q = query('SELECT id, name FROM cat;');
    if($isResult) {
		$selectElement = new HTMLSelect();
		while($r) {
			$selected = ($selectedID == $r[0]) ? true : false;
			$selectElement->addOption($r[0], $r[1], $selected);
		}
		return $selectElement->render();
    }
    else {
		return false;
    }
}

És így tovább.


Még mindig szeretném hangsúlyozni, hogy ezeket leginkább agymenésnek szántam, semmi komoly szándék nincs mögötte. Talán annyi, hogy esetleg egy kis fórumot beindítana az ezzel kapcsolatos ötletekkel, gondolatokkal. Ilyen módon tehát nyugodtan jöhet bármilyen nemű hozzászólás, amíg nem megyünk el olyan irányba, hogy ez mennyire felesleges és mennyire szar, mert annak nem lenne sok értelme.

A fájl letölthető, ide kattintva is.

Kommentek RSS ikon
A bejegyzéshez érkezett kommentek, amiket RSS csatornán is követhetsz.
Strike of a genius, my old chump
Hát ha legyen fórum, akkor én megmondom.
Szerintem alapvetően aláássa az OOP szemléletet, hogy a HTMLElement oszálynak van id, style, class, name, satöbbi mezője. Én a következőt képzelném el:

HtmlElement{

public:
__construct($tag, $selfclose = false);
addParameter(HtmlElementParameter $parameter);
removeParameter(HtmlElementParameter $parameter);
render();

}

HtmlElementParameter{

public:
__construct($name, $value);
render();

}

(Kicsit csaltam a szintaxissal, nem akartam nagyon terjengős lenni.) Ezektől az osztályoktól pedig születhetnek HtmlElementOption, stb osztályok.
Bocsánat, két lényeges dolgot kihagytam:
Egyrészről a fenti példa HtmlElement osztálya rendelkezik a következő két public methoddal:

addChild(HtmlElement $parameter);
removeChild(HtmlElement $parameter);

Valamint magyarázatként a két osztály a render metóduson keresztül megvalósítják a Composition (Összetétel) mintát.
erenon: vissza lehetne ennyire bontani, jogosan, ez tipikusan tervezés kérdése. Meg pl. a meghívás is lehetne virtuális, és négy-öt sort lehetne spórolni renderelésenként.
Ncore-on, mint eredetiséget igazoló link jelentél meg: http://www.imgupload.org/images/65[…]
erenon: amúgy ilyenkor érezni kicsit a php gyengeségeit

Split: lol, kösz.
Mefi: Szerintem részben ez az OOP lényege, hogy az entitások közös elemeit egyre absztraktabb osztályokba gyűjtjük, amikből szükség szerint konkrétakat származtatunk. Bár te is sokszor említetted, én is megerősíteném, hogy HTML generálásra valószínűleg nem használnék ilyen aprólékos PHP kódot, a megvalósítás számos hátránya miatt (nagyon lassú, a legtöbb elemnél nincs szükség rá).
Ezt a virtuális meghívást nem értem. Elmagyaráznád?
Régebben elkezdtem tervezni valami hasonlót, aztán a lekódolásnál valahogy félbemaradt :S
erenon: arra gondolok hogy az ismétlődő kódokat, amik a renderelésénél biztosan előjönnek (ugye nem minden elemet kell ugyanúgy renderelni, erre jó példa lehet pont az option), az egyik ősosztályban ki lehetne fejteni, és itt lehetne kihasználni a virtualizációt. Máskülönben lassú és körülményes, de ezt megfelelően kiépítve végső soron szerintem gyorsabb, mert ha megnézed az ASP.net-et, ott az <asp:textbox /> is hasonlóképpen lesz <input type="text" />. Persze a PHP nyelvi adottságai nagyon sok mindent megnehezítenek vagy -akadályoznak, ahogy picit mélyebben beleástam magam a Java és a C#-.net világba, pontosan ezért kezdtem el megkedvelni, pedig a Javával nagyon nehéz kör volt.
Ehhez valószínűleg mindenki írna valami olyan kódot, amiben lenne egy adag SQL, egy adag PHP és egy adag HTML.
Talán nem szeretik bonyolítani a dolgokat. :]

Egyébként érdekes megoldás. Beindítja az ember fantáziáját.
Mefi: Nem igazán értem, miről beszélsz. A selectet és az option gyerekeit pont úgy kell generálni, mint az összes többi HTML elemet. Ha lesz néhány pillanatnyi időm, feldobok valahova néhány sort, és megvitatjuk ha gondolod, mert tényleg érdekel, hogy a PHP milyen hiányosságaira gondolsz.
(Kétségtelenül van neki, nem is kevés, de én ezt nem érzem itt olyan durván)
Szép dolog az `echo $object->render()` hívás, de mennyivel szebb az `echo $object` – csinálj __toString() függvényt, és meg is vagy. Elég annyi a törzsbe, hogy `return $this->render();`.
HunTeR: szerintem az ilyen nagyon összegyúrt dolgok sokszor inkább bonyolítják. Én jobban élvezném ha pl. az SQL-kód leírása kimerülne a tárolt eljárás meghívásában, a kiírás meg a sablonkezelőnek való átadásban. Persze ez nem lehetetlen. :)

erenon: nem feltétlenül. Egy p-t meghívhatsz úgy, hogy a konstruktor már rögtön ki is írja, míg egy optionnél ez fölösleges, mert ugye egyrészt a selectbe mehet csak, másrészt pedig mi értelme rögtön kiírni. A hátrányait itt még annyira nem, de azért ennél összetettebb feladatoknál erősen érezni; korábban nem értette mikor valami ilyet mondott, hogy miért lehet kevés a PHP, de mostanában egyre jobban kezdem. Gondolok például arra, hogy PHP-ben egy collection már nem vicces annyira.

Maerlyn: $this->render(true); :)
Meg fogod unni a -> render hivogatást, és rájössz, milyen jó a __toString() :)
Ezt dobtam össze 20 perc alatt:
http://pastie.org/834729

És ajánlanám még ezt:
http://hu.php.net/domelement
Maerlyn: annyira nem vészes szerintem, áttekinthetőbb, meg én az egyetlen echo híve vagyok, ha lehet. Viszont köszi a tippet. :)

erenon: zsír, köszi. Mondjuk megnézném hogy mennyire gyors ezzel, mennyire lassú, mennyire kényelmes és így tovább.
Felraktam gitHubra is, így talán könnyebben megy a belenyúlkálás, hibajavítás, ötletek hozzáadása.

http://gist.github.com/309999
Szerintem ez hülyeség, a forráskód meg hiába UTF-8, ha iso-8859-2-vel akarja megjeleníteni.

Kicsit bővebben: Szerintem ezt az egész html-t valamilyen template-engine-nek (ami nálam mostanában php) kell megjeleníteni. Az ilyen kódok bár szépek, egy idő után visszakövethetetlenek.
erenon megoldása azért közelebb áll az OOP szemlélethez. Engem leginkább a natív javascript, html elem generálására emlékeztet. OOP példának nem rossz. De
osztom sajt véleményét. A template generálására vannak kész, jól használható megoldások. Némelyik még selectet is képes generálni a selected optionnel együtt. :)

Eléggé nehézkes a html tartalom, ilyetént történő generálása. Mi van akkor ha változik az oldal megjelenése? Hány helyen kell új tageket generálni vagy a meglévő attribútumait módosítani? Átláthatatlan a megjelenítés.

A lényeg az, hogy ha csak a megjelenítés változik, ne kelljen a php kódot átírni.
Ja! És hacsak nincs valami html cachelés. Gyanítom, hogy a html generálása is elég lassú lesz.
sajt: ha normalisan generalsz hozza htmlt, UTF-8 lesz az, meg ha view -> character encoding -> UTF-8 akkor is jo.

thgab: a PHP-t (ugy ertem hogy nem a megjelenitesert felelos PHP-t) nem kell modositani, pontosan ez a lenyeg. Valami kod adja az adatot, egy masik meg abbol a HTML-t.
Mefi: Arra gondoltam, hogy a forrásfile-nál rosszak az ékezetek (aheaderben rossz a charset.) Egyébként honnan tudod, hogy milyen böngészőt, használok?
sajt: én mindent tudok :)
Mefi: Akkor már ketten vagyunk.
Mefi: szerintem tutira, mivel PHP-vel generálod a tageket.
Új komment

Itt az adott bejegyzésben elhangzottakhoz szólhatsz hozzá. Ha primitív, csúnya, vagy bunkó erkölcsről teszel tanúbizonyságot, tuti, hogy kimoderállak és rosszat mondok rólad. A hozzászólás nem kötelező, amit írsz vállald föl!

Ezeket az adatokat - ha a böngésződ kezeli a kukikat - csak egyszer kell megadnod, később módosíthatod.

Ha van gravatarod - és a gravataros e-mail-címeddel kommentálsz -, akkor az megjelenik. Ha nincs, vagy nem tudod miaz, akkor olvasd el az útmutatót és regisztrálj.

Neved: E-mail címed (nem jelenik meg): Webszájtod (ha van): Kommented: Mennyi öt és egy összege?
Ez védelmi célokat szolgál, szimplán írd be a fenti összeadás összegét!

A kommentedet írhatod nagyobb mezőbe vagy akár formázhatod is, de ha nem szalonképes, akkor moderálom!

Ajánló
Ebben a témában, esetleg ezen a napon voltak még ilyenek is:

Trendi vagyok (2007. március 18., 06:28:16)
Házi dolgozat (2006. május 28., 08:57:18)
Vista rajt (2007. január 15., 06:08:56)

Érdekességek
Száraz számok, pusztán csak tények:

Ez a bejegyzés 1182 napja született, 1593 szóból, és 10007 karakterből áll. Ajánlhatod bizonyos linkgyűjtő oldalaknak: