Nette – pohled začátečníka (splní má očekávání?)
- Ondřej Mirtes
- Člen | 1536
Dobře, takže jediný „problém“ je s Texy. Pokaždé, když odevzdám
novou zakázku s mým CMS, ve kterém bude Texy zabudované, odvedu ti
licenční poplatek. Kolem takovéto 16. zakázky (16 * 490 Kč + DPH = cca
9750 Kč) už dostanu multilicenci, kterou budu moci používat neomezeně.
A v případě, že budu CMS s Texy aplikovat na nějaký vlastní projekt
(který nikomu „neodevzdávám“), nemusím platit nic. Je to tak? :)
- romansklenar
- Člen | 655
Já chápu multilicenci tak, že si o ní musíš zažádat a že i na 20 jednotlivě zakoupených licencí se vztahuje jednotná cena 490,– + dph, pokud koupíš najednou 10 a více máš 40% slevu.
Takže dle mého koupí 16ti jednotlivých licencí multilicenci nezískáš, pokud tedy víš že se nad těch 16 zakázek dostaneš, vyplatíce se ti ji koupit už teď.
PS: Pokud se Davide pletu, oprav mě, samotného mě zajímá jak to je.
- Ondřej Mirtes
- Člen | 1536
Tak to se omlouvám, já myslel, že jsem četl o té automatické platnosti
multilicence i při postupném zakupování jednotlivých licencí. Asi to bylo
jinde :)
Až to bude aktuální, tak se s tebou dohodnu, díky :)
Mám opět pár dotazů ohledně práce s Nette:
- Má @layout.phtml nějaký svůj vlastní presenter? Potřebuji do něj nějak dostat data, např. title tag, klíčová slova pro jednotlivá views a spousta dat do obsahu, který je v každém view – obvykle nějaké menu či postranní sloupec, ve kterém generuji dynamický obsah.
- Jak pro nějaký presenter-view udělám výjimku, že se nemá zobrazovat v kontextu @layout.phtml? Např. RSS zdroj či sitemap pro Google. Celý web chci mít zkrátka tak, aby se zobrazoval jako content->render() v @layout.phtml, ale pro nějaké stránky potřebuji výjimku…
Díky.
- David Grudl
- Nette Core | 8218
Změnu layoutu zajistí $this->setLayout('jinysoubor')
nebo
úplné vypnutí $this->setLayout(FALSE)
, kde tím $this je
myšlen presenter. Proměnné, které se vloží do
$this->template->promenna = 'hodnota'
, jsou pak dostupné jak
v layoutu, tak v šabloně view.
- Ondřej Mirtes
- Člen | 1536
Geniální :) Je myšleno opravdu na vše.
Ještě jeden na dobrou noc (okruh toho, co jsem kdy v čistém PHP potřeboval řešit, se pomalu, ale jistě zužuje): Potřebuji na webu provést akci, ke které ale nebude přiřazené žádné view, po správném provedení se provede někam redirect (např. přidání komentáře – mám stránku s článkem, napíšu komentář, odešlu ho formulářem nějakému skriptu, ten ho přidá do DB a pošle prohlížeč zpátky na stránku s článkem). Jak toto provést v Nette? Určitě nějak AJAXově, ten jsem ale zatím ještě nezkoumal. Kdybych na něj nepomyslel, asi bych vytvořil AddCommentPresenter, v nějaké z metod jeho životního cyklu bych přidal komentář a „sprostě“ pomocí HttpResponse provedl přesměrování. Tuším, že to není asi ideální řešení, ale zároveň mě nenapadá lepší.
- na1k
- Člen | 288
LastHunter napsal(a):
Geniální :) Je myšleno opravdu na vše.
Potřebuji na webu provést akci, ke které ale nebude přiřazené žádné view, po správném provedení se provede někam redirect
Já jsem tohle řešil pomocí signálů (funkce handle{Signal}) – je to krásně jednoduché a dá se to i zAJAXit. Většinou funkci končím rozhodnutím, zda byl požadavek přes JS nebo GET a podle toho buď přesměruju nebo spouštím nějakou JS odezvu. (Jestli je to správný postup nevím, mně to tak funguje :-) )
- Ondřej Mirtes
- Člen | 1536
Díky. Další dotázky:
- Mám komponentě přiřazovat také její model? A může být u ní nebo ho mám přesouvat také do složky models? Je mi jasné, že může být kdekoli, ale jde mi o co „nejlepší“ best-practice. Jestli mám mít ArticleControl.php v /controls/ArticleControl/ a a ArticleControlModel.php v /models/ArticleControl/…
- Jak vyřešit odkazy na úrovni jednotlivých textů v databázi? Jde mi o to, když už mám zprovozněný web a píšu v něm články a chci ve svém článku odkázat někam dovnitř webu. V editoru/Texyle zadám pevný link, tím ale podkopu systém dynamicky se vytvářejících odkazů. Když pak přidám nějaké persistentně přenášený parametr, ve všech odkazech v aplikaci se sice automaticky přidá, ale ve všech článcích ho budu muset upravovat ručně…
- _Martin_
- Generous Backer | 679
LastHunter napsal(a):
- Jak vyřešit odkazy na úrovni jednotlivých textů v databázi? Jde mi o to, když už mám zprovozněný web a píšu v něm články a chci ve svém článku odkázat někam dovnitř webu. V editoru/Texyle zadám pevný link, tím ale podkopu systém dynamicky se vytvářejících odkazů. Když pak přidám nějaké persistentně přenášený parametr, ve všech odkazech v aplikaci se sice automaticky přidá, ale ve všech článcích ho budu muset upravovat ručně…
Podobná otázka se tu už kdysi řešila, moje odpověď zní následovně.
- Ondřej Mirtes
- Člen | 1536
Tak jsem se do toho už pustil :)
Vytvořil jsem si komponentu ShortNews, která má za úkol zobrazovat poslední aktuality v pravém sloupci na stránce a v případě nakliknutí archivu pak i výpis novinek po měsících.
Postřehy:
- Hodil by se mi nějaký BaseControl, abych v dalších komponentách opisovat vytvoření šablony (Control bohužel nedědí od Presenteru, takže nemá metody beforeRender aj. kam bych to mohl nacpat), registraci filtrů, helperů apod. A nejlépe, pokud by se registrace filterů a helperů shodovala s tím, co mám v BasePresenteru, abych opravdu nic nemusel psát/měnit dvakrát. Máte nějaký nápad, jak toho docílit?
- Filter date z továrny jsem si musel nahradit vlastním, který použije funkci date, protože strftime nepodporuje zobrazení měsíce bez počáteční nuly, takže se pro mě stal nepoužitelným (10. 2. vypadá lépe než 10. 02.). Nebo jsem něco přehlédl?
- V jakém formátu dibi doporučuje ukládat čas článků? Já jsem zvyklý na unix timestamp, takže to v dibi musím házate jako %i (integer), což mi nepřijde zrovna přirozené.
Váhám, jestli zde pastovat teď ze začátku některé zdrojáky, abyste mi řekli, jestli to dělám špatně/dobře, ale tak aspoň ukážu model:
/**
* ShortNews Control's model
*/
class ShortNewsModel {
public static function getFirstNews()
{
$news = dibi::query("SELECT time, text from [:pre:shortnews]
where del=0 and time<=%i",time(),"
order by time desc
limit 0,%i",Environment::getConfig("count")->news);
return $news;
}
}
Zde jsem chtěl předávat jako konstruktor název tabulky, ve které má
hledat, ale přišlo mi pak neobratné dibi posílat ten název tabulky jako
parametr (cca takhle: "[:pre:%s",$this->tableName,"]"
, takže
jsem to nechal takto.
Šel by ten model napsat nějak lépe/dělá se to jinak? :)
Teď jdu studovat Ajax a snippety :))
Díky.
Editoval LastHunter (10. 2. 2009 15:55)
- Etch
- Člen | 403
LastHunter napsal(a):
Zde jsem chtěl předávat jako konstruktor název tabulky, ve které má hledat, ale přišlo mi pak neobratné dibi posílat ten název tabulky jako parametr (cca takhle:
"[:pre:%s",$this->tableName,"]"
, takže jsem to nechal takto.
To by mělo jít zapsat myslím i jednodušeji například takto :
dibi::query("SELECT * FROM %n", ':pre:'.$this->tableName);
- David Grudl
- Nette Core | 8218
LastHunter napsal(a):
- Hodil by se mi nějaký BaseControl, abych v dalších komponentách opisovat vytvoření šablony (Control bohužel nedědí od Presenteru, takže nemá metody beforeRender aj. kam bych to mohl nacpat), registraci filtrů, helperů apod. A nejlépe, pokud by se registrace filterů a helperů shodovala s tím, co mám v BasePresenteru, abych opravdu nic nemusel psát/měnit dvakrát. Máte nějaký nápad, jak toho docílit?
O novou šablonu lze požádat přímo presenter
($this->presenter->createTemplate()
), ale vzniká tak
nadstandardní závislost mezi komponentou a presenterem a komponenta nebude
znovupoužitelná.
Pro registraci filtrů přímo v komponentě doporučuji přepsat createTemplate, jako to dělá BasePresenter v příkladu skeleton.
- Filter date z továrny jsem si musel nahradit vlastním, který použije funkci date, protože strftime nepodporuje zobrazení měsíce bez počáteční nuly, takže se pro mě stal nepoužitelným (10. 2. vypadá lépe než 10. 02.). Nebo jsem něco přehlédl?
Na strftime tu padlo už více stížností, asi to nahradím za date. Jen nevím, jak to udělat zpětně kompatibilně.
- V jakém formátu dibi doporučuje ukládat čas článků? Já jsem zvyklý na unix timestamp, takže to v dibi musím házate jako %i (integer), což mi nepřijde zrovna přirozené.
dibi je to fuk, ukládej jak chceš.
Váhám, jestli zde pastovat teď ze začátku některé zdrojáky, abyste mi řekli, jestli to dělám špatně/dobře, ale tak aspoň ukážu model:
Je to v pohodě, jen taková obecná rada: statické metody jsou zlo :-)
- Ondřej Mirtes
- Člen | 1536
David:
strftime – když se ve $format detekuje znak %, použít strftime, jinak
date? :) Jen takový nástřel, takhle bych to řešil já-prase :)
unix timestamp – tak jinak, jaký formát doporučuješ ty? :)
statické metody – na to už jsem před chvílí přišel, byla to taková
oprátka. Už jsem to nahradil za klasické OOP. Počet SQL dotazů byl totiž
nepříjemně vysoký, takhle je to vše jen jednou, jak má být :)
- Ondřej Mirtes
- Člen | 1536
Uff, tak mám první problém.
Definoval jsem následující routy:
$router[] = new Route('index.php', array(
'presenter' => 'Default',
'view' => 'default',
), Route::ONE_WAY);
$router[] = new Route('/', array(
'presenter' => 'Default',
'view' => 'default',
));
$router[] = new Route('/archiv-anket', array(
'presenter' => 'PollsArchive',
'view' => 'default',
));
.htaccess:
php_flag magic_quotes_gpc off
php_flag register_globals off
RewriteBase /
# front controller
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L]
Odkazy se mi podle toho v aplikaci vytvoří správně, tedy ten jeden odkaz
na PollsArchive:
. Ale když na něj klepnu, tak server mi
předhodí Error 404. Myslím, že se to k Nette ani nedostane. Dělá mi to
i na localhostu i na webhostingu.
Jiné (neNette) weby mi na localhostu fungují, ale tam mám v .htaccess vypsanou každou cestu (podobně jak to teď budu vypisovat v routách místo do .htaccess), takže jí to najde.
BTW: Mám presentery pojmenovávat podle toho, jakou chci cestu, která
k nim pak vede? Ušetřil bych v definicích rout, ale možná bych v tom
nadělal zmatky.
BTW2: Když chci, aby stránku Nette našlo i v případě, že je na konci
cesty lomítko (preferuji tvary bez lomítka), mám ke každé variantě
připsat ještě ONE_WAY verzi s lomítkem? Nebo to jde nějak úsporněji?
- Jod
- Člen | 701
No mám pocit že máš buď chybu v htaccess, alebo si vypni v bootstrap errorPresener a catchErrors.
BTW1: Pomenovávaj si ich ako ti ako programátorovi najlepšie vyhovuje a
všetko si nastavíš podľa požiadaviek v routach na konci. To PoolsArchive
sa mi zdá fajn.
BTW2: Ja keď dám link s lomítkom na konci tak mi presmeruje na ten istý bez
lomítka, tak tam nevidím problém.
- Ondřej Mirtes
- Člen | 1536
.htaccess jsem sem postnul, v něm chybu nevidím (dokonce má i správný název souboru :)). Mod_rewrite taky funguje. Ukážu ještě index.php a bootstrap.php:
<?php
// absolute filesystem path to the web root
define('WWW_DIR', dirname(__FILE__));
// absolute filesystem path to the application root
define('APP_DIR', WWW_DIR . '/../app');
// absolute filesystem path to the libraries
define('LIBS_DIR', WWW_DIR . '/../libs');
// absolute filesystem path to the libraries
define('MODELS_DIR', APP_DIR . '/models');
// relative filesystem path (for hypertext link) to the phpBB3 forum
define('FORUM_DIR', '/forum');
// load bootstrap file
require APP_DIR . '/bootstrap.php';
?>
<?php
require_once LIBS_DIR . '/Nette/loader.php';
if (substr_count($_SERVER["HTTP_HOST"],"alanwake.cz") > 0)
{
Environment::setMode('production');
}
Environment::loadConfig(APP_DIR."/config.ini");
Debug::enable();
$autoload = new RobotLoader();
$autoload->addDirectory(LIBS_DIR);
$autoload->addDirectory(APP_DIR);
$autoload->register();
$application = Environment::getApplication();
require_once(dirname(__FILE__) . '/routers.php');
$application->run();
?>
K názvům těch presenterů – musím to ještě rozmyslet. Správnými (českými) názvy, tak, jak je chci v URL, bych ušetřil hodně v definicích těch rout…
UPDATE: Já jsem vůl :D V tom .htaccess mi chybělo RewriteEngine On :D
Editoval LastHunter (12. 2. 2009 20:39)
- Ondřej Mirtes
- Člen | 1536
skocourek: To je přesně to, co jsem hledal :)
Musím říct, že jsem se při výběru Nette opravdu nezmýlil. Konečně mám v aplikaci lepší pořádek a hlavně mě dostala přeměna komponent na ajaxové pár řádky kódu :) A když se objeví nějaký problém, vždycky přispěchá Nette s nějakým elegantním řešením.
Sice mívám problém sem tam něco rozchodit, je to přeci jen práce v „cizejším“ prostředí, než když pracuji v čistém PHP, ale to se časem naučím :) A jestli píšu komponenty znovupoužitelné, to se dozvím až ve druhém projektu :o)
- Ondřej Mirtes
- Člen | 1536
kravco napsal(a):
LastHunter napsal(a):
…
$router[] = new Route('/archiv-anket', array( 'presenter' => 'PollsArchive', 'view' => 'default', ));
Nemôže byť problém v lomke na začiatku?
Nene, bylo to v tom, že se mi z .htaccess nějak vytratil RewriteEngine on ;)
- Ondřej Mirtes
- Člen | 1536
Nikde jsem nenašel, jestli se dá ovlivnit podoba URL, pokud na ni vede
signál nějaké komponenty. Např. budu mít výpis nějakého archivu
článků, co když ho zpracuji celý jako komponentu? (tzn. nebude to presenter
volaný s různými parametry view, ale komponenta, jejíž vykreslený obsah
se bude ovlivňovat signálem).
Řeším takhle archiv článků – mám ho, tedy budu mít
v Ajaxu, nahoře lišta s výběrem měsíců, po nakliknutí měsíce se bez
znovunačtení stránky zobrazí články z jiného měsíce. V šabloně
ArticleArchive:default je jen kód {?$articleArchive->renderArchive()}.
Nevím, jak v routách ovlivnit tvar té URL, na které se ten výsledek bude zobrazovat a také nevím, jak to podstrčit vyhledávači (možná skrytými odkazy na jednotlivé měsíce?), když na ty podstránky vede cesta přes nakliknutí měsíce v selectboxu (viz ten link v minulém odstavci).
Navrhnul jsem to celé blbě nebo se to dá nějak vyřešit? :)
EDIT: Když nad tím tak přemýšlím, měl bych se jako vývojář vždy rozhodovat, zda poslat data presenteru jako argument view (parametru funkce renderView) anebo jako signál presenteru či komponentě. Jak to rozlišit a kdy co použít? Signál je méně praktický – router nejspíš neumožňuje upravit tvar URL, který vede na presenter ovlivněný signálem a je tak horší pro SEO…
Doufám, že mě z toho někdo rozmotáte :)
Editoval LastHunter (14. 2. 2009 0:51)
- Ondřej Mirtes
- Člen | 1536
Achjo, Nette mě přestává mít rádo. Nikde jsem nenašel, jak by se měly ošetřovat neexistující stránky. Nakonec jsem to z několika threadů tady na fóru poslepoval:
public function actionArticle($seo) {
//handling non-existing article etc.
$model = new MiscArticleModel("misc");
if (!$model->articleExists($seo))
{
throw new BadRequestException("Page not found", 404);
}
}
class ErrorPresenter extends BasePresenter {
public function actionDefault($exception)
{
if ($exception instanceof /*Nette\Application\*/BadRequestException) {
Environment::getHttpResponse()->setCode(404);
$this->template->title = '404 Not Found';
$this->setView('404');
} else
{
Environment::getHttpResponse()->setCode(500);
$this->template->title = '500 Internal Server Error';
$this->setView('500');
/*Nette\*/Debug::processException($exception);
}
}
public function render404() {
}
}
Problém je, že v takovém případě se to na ErrorPresenter vůbec nedostane. Laděnka je vypnutá pomocí catchExceptions = TRUE, ale vše, co se mi dostává, je jen apacheovská stránka „404 Not Found – The requested URL was not found on this server.“
- Ondřej Mirtes
- Člen | 1536
Uff, díky, zafungovalo. Já měl zato, že ta hodnota Error v té proměnné je už nastavená jako defaultní…
- Ondřej Mirtes
- Člen | 1536
Už se mi to podařilo zprovoznit, ale mám problém, že se mi ty komponenty překreslují, i když nevolám invalidaci. (Jedna komponenta se mi vykresluje do více podob, renderArchive na daném view a renderFewNews na každé stránce vpravo). Pokud zavolám handleMonth (který ovlivňuje pouze archiv), který invaliduje pouze archiv, zinvaliduje se mi i ta část vpravo. Koukal jsem, co posílá JSON a jsou tam napsány fakt oboje. Když oboje (!) invalidace zakomentuju, stále to funguje :/
K nahlédnutí zde: http://beta.alanwake.cz/kratke-zpravy
public function handlePage($page)
{
$this->page = $page;
//$this->invalidateControl("news");
}
public function handleMonth()
{
$this->month = $this->months[$_POST["month"]];
//$this->invalidateControl("archivenews");
}
public function renderFewNews() {
$template = parent::createMyTemplate(dirname(__FILE__) . '/' . 'FewNews' . '.phtml');
//filling variables
$newsModel = new ShortNewsModel();
$template->news = $newsModel->getFewNews($this->page);
$template->page = $newsModel->getPaginator()->page;
$template->nextPage = $newsModel->getPaginator()->page+1;
$template->previousPage = $newsModel->getPaginator()->page-1;
$template->isFirst = $newsModel->getPaginator()->isFirst();
$template->isLast = $newsModel->getPaginator()->isLast();
$template->render();
}
public function renderArchive() {
$template = parent::createMyTemplate(dirname(__FILE__) . '/' . 'Archive' . '.phtml');
$newsModel = new ShortNewsModel();
$monthinator = new Monthinator($this->month);
$template->news = $newsModel->getNewsByMonth($monthinator->getStart(), $monthinator->getEnd());
$template->render();
}
- Ondřej Mirtes
- Člen | 1536
Jod napsal(a):
Pozri či sa ti control neinvaliduje sám. $this->invalidateControl(false);
ee, to nic neudělá :( I když tohle zavolám v konstruktoru, stále se to přepisuje.
BTW: Zavolání:
$this->validateControl();
$this->invalidateControl("news");
už je lepší, ale nevím, proč je Control defaultně celý zinvalidněný.
Editoval LastHunter (16. 2. 2009 15:06)
- Ondřej Mirtes
- Člen | 1536
Jod napsal(a):
A neinvaliduješ ten control ešte niekde inde? Alebo neinvaliduješ nadradený/parent Presenter/Control?
Vyzerá to akoby sa ti po nejakom kliku invalidovala celá stránka. Nemáš nič také v presenteru?
Inak ten design sa mi páči, tvoja práca? =)
Ee, nic takového nedělám. Ale je to vyřešené zavolám
validateControl(), jen by mě zajímalo, proč je jako defaultně nastavený
celý invalidateControl();
Design – „můj“ grafik, se kterým na tom webu spolupracuji :)
- Ondřej Mirtes
- Člen | 1536
LM: A k čemu je pak volání invalidateControl(„snippet“), když už je celý invalidovaný?
Mám dotaz: Umí Nette už nějakou simulaci jmenných prostorů? Rád bych odlišil webovou prezentaci a administrační rozhraní a zároveň to měl v jedné struktuře app/root/libs. Omlouvám se, že mám takto základní otázky, ale nepodařilo se mi o tom najít článek/zmínku…
- Honza Marek
- Člen | 1664
Mám dotaz: Umí Nette už nějakou simulaci jmenných prostorů? Rád bych odlišil webovou prezentaci a administrační rozhraní a zároveň to měl v jedné struktuře app/root/libs.
Jo, řiká se tomu moduly.
- Ondřej Mirtes
- Člen | 1536
Díky.
Zkopíruju sem příspěvek z minulé stránky, protože se na něj jaksi pozapomnělo a pro mě má dost zásadní význam. Takže díky za odpovědi :)
Nikde jsem nenašel, jestli se dá ovlivnit podoba URL, pokud na ni vede signál nějaké komponenty. Např. budu mít výpis nějakého archivu článků, co když ho zpracuji celý jako komponentu? (tzn. nebude to presenter volaný s různými parametry view, ale komponenta, jejíž vykreslený obsah se bude ovlivňovat signálem). Řeším takhle archiv článků – mám ho v Ajaxu, nahoře lišta s výběrem měsíců, po nakliknutí měsíce se bez znovunačtení stránky zobrazí články z jiného měsíce. V šabloně >ArticleArchive:default je jen kód {?$articleArchive->renderArchive()}.
Nevím, jak v routách ovlivnit tvar té URL, na které se ten výsledek bude zobrazovat a také nevím, jak to podstrčit vyhledávači (možná skrytými odkazy na jednotlivé měsíce?), když na ty podstránky vede cesta přes nakliknutí měsíce v selectboxu (viz ten link v minulém odstavci).
Navrhnul jsem to celé blbě nebo se to dá nějak vyřešit? :)
EDIT: Když nad tím tak přemýšlím, měl bych se jako vývojář vždy rozhodovat, zda poslat data presenteru jako argument view (parametru funkce renderView) anebo jako signál presenteru či komponentě. Jak to rozlišit a kdy co použít? Signál je méně praktický – router nejspíš neumožňuje upravit tvar URL, který vede na presenter ovlivněný signálem, neumožňuje mi zakázat přístup na něj pro roboty (např. hlasování v anketě) a je tak horší pro SEO…