Jak na modální okna v nette?
- mildhouse
- Člen | 27
Zdravim,
na stránkách používám modální okna a to tak, že v BasePresenteru mám definované metody setDialog($blockName) a closeDialog(). setDialog nastaví jméno bloku v templatě, který se má zobrazit v modálním okně (podobně jako setView() nastaví view). setDialog() volám v handle funkci (přijde mi to logické, když se nemění view). Problem ale nastane, když v mod. okně zobrazím formulář. Při odeslání formuláře se volá funkce pro zpracování formu (do=form-submit) místo handle pro zobrazení modalu (do=zobrazitForm), takže v případě, že nastane chyba při odeslání formu, tak se formulář už nezobrazí s výpisem chyb, prostě se nic nestane. Napadla mě 2 řešení :
- V případě chyby formuláře znovu volat setDialog()
- v metodě setDialog() nenastavovat property $blockName, ale uložit název bloku do session, tzn. že se form bude zobrazovat dokud nezavolám closeDialog()
Teď mě napadlo, jestli by se nedalo využít perzistentních parametrů, to
jsem ještě nezkoušel.
Jak řešíte modální okna vy?
- khero2t
- Člen | 6
Ahoj,
Já modální okna řeším takto.
Vyhovuje mi to snad na všechny možné případy.
Určitě by to šlo napsat mnohem pěkněji, ale aspoň pro inspiraci :)
Když chci zobrazit v dialogu např nějakou komponentu tak ji volám
následovně.
<?php
public function handleShowDialog(){
$this->dialog()->show($this['myComponent'])->width("600px");
// nebo
$this->dialog()->show("Zpráva")->width("600px");
// nebo
$this->dialog()->show(Array("<div class='form'>",$this['myComponent'],"</div>"))->width("600px");
}
?>
Dialog pak mohu skrýt např následovně
<?php
public function handleCloseDialog(){
$this->dialog()->close();
}
?>
v BasePresenteru mám vytvořenou továrničku na dialog
<?php
public function createComponentDialog($name) {
return new \Any\Components\Dialog($this, $name);
}
/** @var \Any\Components\Dialog */
public function dialog(){
return $this['dialog'];
}
?>
a v šabloně @layout.latte
{control dialog}
Samotný kód komponenty
<?php
namespace Any\Components;
class Dialog extends \Any\AnyControl {
protected $config;
public function show($config, $title = null, $width = "450px", $height=null){
$this->config = $config;
$this->template->config = $config;
$this->template->title = $title;
$this->template->width = $width;
$this->template->height = $height;
$this->invalidateControl();
return $this;
}
public function title($title){
$this->template->title = $title;
return $this;
}
public function width($width){
$this->template->width = $width;
return $this;
}
public function height($height){
$this->template->height = $height;
$heightContent = (int)trim(str_replace("px", "", $height))-30;
$this->template->heightContent = $heightContent."px";
return $this;
}
public function close(){
$this->template->close = true;
$this->invalidateControl();
}
public function flashMessage($message, $type = 'info') {
parent::flashMessage($message, $type);
$this->invalidateControl("flashes");
}
public function render() {
$this->template->componentName = $this->getName();
$this->template->setFile(dirname(__FILE__) . '/dialog.latte');
$this->template->render();
}
}
?>
Šablona komponenty
{snippet}
{if isset($config) && !isset($close)}
<div id="customDialog{$componentName}">
<div id="customDialogBtnClose{$componentName}"></div>
{if $title!=null}<div id="customDialogTitle{$componentName}">{$title}</div>{/if}
{snippet flashes}
{foreach $flashes as $flash}<div class="flash {$flash->type}">{$flash->message}<div class="closeFlashes"></div></div>{/foreach}
{/snippet}
<div class="clear"></div>
<div class="customDialogContent{$componentName}">
{if is_array($config)}
{foreach $config as $c}
{if $c instanceof \Nette\Application\UI\Control}
{$c->render()}
{else}
{!$c}
{/if}
{/foreach}
{else}
{if $config instanceof \Nette\Application\UI\Control}
{$config->render()}
{else}
{!$config}
{/if}
{/if}
</div>
</div>
<script type="text/javascript" n:syntax="double">
/*
* jQuery resize event - v1.1 - 3/14/2010
* http://benalman.com/projects/jquery-resize-plugin/
*/
(function($,h,c){var a=$([]),e=$.resize=$.extend($.resize,{}),i,k="setTimeout",j="resize",d=j+"-special-event",b="delay",f="throttleWindow";e[b]=250;e[f]=true;$.event.special[j]={setup:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.add(l);$.data(this,d,{w:l.width(),h:l.height()});if(a.length===1){g()}},teardown:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.not(l);l.removeData(d);if(!a.length){clearTimeout(i)}},add:function(l){if(!e[f]&&this[k]){return false}var n;function m(s,o,p){var q=$(this),r=$.data(this,d);r.w=o!==c?o:q.width();r.h=p!==c?p:q.height();n.apply(this,arguments)}if($.isFunction(l)){n=l;return m}else{n=l.handler;l.handler=m}}};function g(){i=h[k](function(){a.each(function(){var n=$(this),m=n.width(),l=n.height(),o=$.data(this,d);if(m!==o.w||l!==o.h){n.trigger(j,[o.w=m,o.h=l])}});g()},e[b])}})(jQuery,this);
</script>
<script type="text/javascript">
jQuery.fn.center = function () {
this.css("position","fixed");
this.css("top", Math.max(0, (($(window).height() - this.outerHeight()) / 2) ) + "px");
this.css("left", Math.max(0, (($(window).width() - this.outerWidth()) / 2) + $(window).scrollLeft()) + "px");
if(this.height() > $(window).height()){
newHeight = ($(window).height()-50)+"px";
newHeightContent = ($(window).height()-70)+"px";
this.css("height", newHeight);
$('.customDialogContent{!$componentName}').css("height", newHeightContent);
$('.customDialogContent{!$componentName}').css("overflow-y", "scroll");
}
return this;
}
$(function(){
$('#customDialogBtnClose{!$componentName}').click(function(){
$(this).parent("#customDialog{!$componentName}").remove();
return false;
});
$('#customDialog{!$componentName}').center();
$('.customDialogContent{!$componentName}').resize(function(){
$('#customDialog{!$componentName}').center();
});
});
</script>
<style>
#customDialog{$componentName}{
display: block;
position: fixed;
top: 200px;
left: 400px;
width: {$width};
{if $height!=null}
height: {$height};
{/if}
background-color: #fff;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
padding: 20px;
box-shadow: 10px 10px 30px #888;
z-index: 9999;
}
.customDialogContent{$componentName} {
{if $height!=null}
height: {$heightContent};
overflow-y: scroll;
{/if}
}
#customDialogTitle{$componentName} {
font-size: 16px;
float: left;
margin-bottom: 15px
}
#customDialogBtnClose{$componentName} {
cursor: pointer;
background: url('{$basePath}/anylibs/libs/Any/components/Dialog/close.png') no-repeat;
float: right;
margin: -12px -12px 0 0;
width: 20px;
height: 20px;
}
#customDialog{$componentName} .flash {
text-align: left;
margin: 20px 0;
}
</style>
{/if}
{/snippet}
- mildhouse
- Člen | 27
Ahoj
tohle ale neřeší ten problém když v dialogu zobrazím formulář. Při
odeslání formuláře se volá funkce pro zpracování formu (do=form-submit)
místo handleShowDialog() (do=showDialog), takže v případě, že nastane
chyba při odeslání formu, tak se formulář už nezobrazí s výpisem chyb.
Stejně tak, pokud je v handleShowDialog() nějaké ověřování – pak tam
bude bezpečnostní díra. Jako nejlepší řešení mě napadá uložit si to
do session, třeba :
<?php
public function actionDefault(){
$session = $this->getSession('dialog');
if(isset($session->starter)){
$funcName = $session->starter;
$this->$funcName();
}
}
public function handleShowDialog(){
$this->getSession('dialog')->starter = 'showDialog';
$this->showDialog();
}
public function showDialog(){
$this->dialog()->show($this['myComponent'])->width("600px");
// nebo
$this->dialog()->show("Zpráva")->width("600px");
// nebo
$this->dialog()->show(Array("<div class='form'>",$this['myComponent'],"</div>"))->width("600px");
}
?>
Takhle by se nastavil dialog při volání handleShowDialog() a i při odeslání formuláře v actionDefault().
Další možností je neukládat to do session, ale jako perzistentní parametr.
Ale líbí se mi, jak to máš zabalené v komponentě, to se mi nedařilo. Takže jako inspirace super. Zkusím něco spáchat a pak to sem taky dám.
- Filip111
- Člen | 244
Ahoj, vyzkoušel jsem si vytvořit komponentu pro práci s modálními okny – je to kopie od khero2t, a trochu jsem si to upravil k obrazu svému. Především to pracuje s bootstrap modal okny, ale část v Nette je skoro stejná.
Narazil jsem na problém, při zpracování formuláře z komponenty, která
je zobrazena uvnitř modálního okna, tedy uvnitř jiné komponenty.
Háže mi
to The signal receiver component 'EditAttModal-attDetailForm' is not found.
Protože komponenta modálního okna je zobrazena pomocí signálu, asi se to nějak hádá se signálem vnořené komponenty, ale nenapadá mě jak to vyřešit.
Vytvoření komponenty pro práci s modálními okny (v presenteru):
public function createComponentModalDialog($name) {
return new \web132\Components\ModalDialog($this, $name);
}
public function dialog() {
return $this['modalDialog'];
}
Zobrazní modálního okna s vykreslením jiné komponenty (v presenteru):
public function handleEditAttModal($contentId, $attId) {
$this->dialog()
->setTitle('Editace přílohy')
->show(new \web123\Components\EditAttModal(
$this,
'EditAttModal',
$attModel,
$attId)
);
}
Komponenta EditAttModal umí zobrazit formulář pro
editaci, ale taky volá jinou komponentu pro oříznutí obrázku podle potřeby
(jen zdůvodnění, proč nevolám samotný formulář v modálním okně, ale
komponentu).
Formulář v komponentě EditAttModal a jeho zpracování:
public function createComponentAttDetailForm() {
$form = new BaseForm;
$form->addText('title', 'Název', 50, 100);
$form->addTextArea('description', 'Popis', 40, 2);
$form->addSubmit('send', 'Uložit');
$form->onSuccess[] = callback($this, 'editFormSubmitted');
return $form;
}
public function editFormSubmitted(BaseForm $form) {
// NEZAVOLA SE
$this->model->edit($this->attId, $form->values);
//$this->presenter->redirect('this');
}
A právě odeslání tohoto formuláře vyhodí chybu
BadSignalException,
The signal receiver component 'EditAttModal-attDetailForm' is not found.
Tedy funkce editFormSubmitted pro zpracování formuláře se nenajde.
Můj odhad je, že metoda show a inicializace EditAttModal komponenty nesmí být provedena v metodě handleEditAttModal. Ale tam to přesně potřebují mít, abych mohl ajaxově aktivovat modální okno a zobrazit v něm relevantní informace předané v tomto signálu.
Poradíte? Díky.
ps.: nevíte o komponentě pro práci s modálními okny – nemusel bych pak řešit výše uvedený problém. Taky si nejsem jist nakolik je tento koncept správný.
Editoval Filip111 (25. 4. 2013 15:04)
- mildhouse
- Člen | 27
Zdar, no to je totiž ten problém, o kterém mluvím od začátku. Možná kecám, ale myslim, že ty zadáš adresu např.
www.nejakastranka.cz/?do=editAttModal&contentId=123&attId=987
a tím se spustí handleEditAttModal($contentId, $attId) která do modálního okna přidá title a komponentu formuláře. Ty ten form vyplníš a odešleš na
www.nejakastranka.cz/?do=EditAttModal-attDetailForm-submitted
a handleEditAttModal() neproběhne, to znamená, že formulář neexistuje a tudíž není nikdo, kdo by ten signál odchytil.
- Filip111
- Člen | 244
Nojo, sorry – četl jsem ještě nějaký jiný témata ohledně modálních oknen a nějak jsem si zafixoval, že v tomhle vlákně se řeší problém s tím, že nejde modální okno zavřít. Když teď čtu ale tvůj první dotaz, tak je to stejnej problém na jaký jsem narazil já.
Tak co teď s tím?
- mildhouse
- Člen | 27
Já mam na to komponentu, která to řeší, prostě si tu handle (její název) uložim do session a následně jí volám v action metodě. Asi nějak takhle :
public function createComponentModalDialog($name) {
return new \web132\Components\ModalDialog($this, $name);
}
public function dialog() {
return $this['modalDialog'];
}
//Zobrazní modálního okna s vykreslením jiné komponenty :
public function editAttModal($contentId, $attId) {
$this->dialog()
->setTitle('Editace přílohy')
->show(new \web123\Components\EditAttModal(
$this,
'EditAttModal',
$attModel,
$attId)
);
}
public function actionDefault(){
$session = $this->getSession('modalDialog');
//pri odeslani formulare se handleEditAttModal() nezavola, ale mame ji ulozenou v sessions
if(isset($session->methodName)){
$name = $session->pseudoHandle;
$params = $session->params;
//a zavolame ji
$this->$name($params['contentId'], $params['attId']);
}
}
public function handleEditAttModal($contentId, $attId) {
//ulozit metodu do sessions
$session = $this->getSession('modalDialog');
$session->methodName = 'editAttModal';
$session->params = array('contentId' => $contentId, 'attId' => $attId);
//a zavolat ji
$this->editAttModal($contentId, $attId);
}
Píšu to z hlavy, ale tak nějak to v podstatě funguje. Napřed je volaná handle, která uloží nazev metody, ktera inicializuje dialog. okno a parametry, pak tu metodu zavolá. Při odeslání formuláře se v action zjistí, zda je co volat a pokud ano, zavolá se. Tím se vytvoří formulář a provede do=form-submitted.
Já sem když tak o víkendu dám tu komponentu.
- Filip111
- Člen | 244
Ahoj, to co popisuješ mi funguje, ale nelíbí se mi to. Má to daleko k samostatné, jednoduše a masivně použitelné komponentě.
Zaprvé se mi nelíbí obcházení problému s handle – ideální by bylo to vyřešit tak, aby vůbec nenastal a nemusel jsem to řešit pomocí nějakých session (nenapadá mě jak).
Zadruhé se mi nelíbí, že aby tohle řešení fungovalo, je potřeba vyjmou kód z handle funkce a umět ho volat samostatně, na jednom místě nastavit session, na jiném ji zpracovat a vyvolat handle (resp. jen tu vyjmutou funkci). Je to dost balastu co znepřehledňuje kód.
Koncepce modálních oken se mi líbí – spoustu kódu lze vyhodit do komponent a odlehčit jak presenter tak samotnou stránku. Zkusím s tím ještě něco vymyslet – pořád by mě ale zajímalo, jestli to někdo nepoužívá a koncepčně by nás nenakopnul lepším směrem.
Editoval Filip111 (26. 4. 2013 11:19)
- Glottis
- Člen | 129
hodim sem jen ukazku jak jsem to ted resil ja. nevim jestli vam to k necemu bude ale treba si z toho neco vyzobnete. je to jen modalni confirm form. ale ikdybych delal nejaky edit form tak to bude prakticky stejne
<a class="confirm-modal" data-confirm-header="{$invoice->invoiceNumber}" data-confirm-text="Opravdu chcete smazat fakturu <b>{$invoice->invoiceNumber}</b>" href="{link delete! $invoice->id}">Smazat</a>
{#confirmFrom}
<div id="modal-confirm-dialog" class="modal hide fade">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3>Smazat</h3>
</div>
<div class="modal-body">
<p>Opravdu chcete položku smazat?</p>
</div>
<div class="modal-footer">
<a id="modal-confirmed-url" href="" class="btn danger ajax">Ano</a>
<a href="javascript:$('#modal-confirm-dialog').modal('hide')" class="btn secondary">Ne</a>
</div>
</div>
<script>
$.nette.ext('confirm', {
load: function () {
$('.confirm-modal').click(function(e) {
e.preventDefault();
$("#modal-confirm-dialog .modal-header>h3").html($(this).data("confirm-header"));
$("#modal-confirm-dialog .modal-body>p").html($(this).data("confirm-text"));
$("#modal-confirmed-url").attr("href", $(this).attr('href'));
$('#modal-confirm-dialog').data('modal-confirm-dialog', "xxx", 'yy').modal('show');
});
}
});
</script>
{/#}
jestli to chapu dobre co delate, tak to co davate do session si castecne davam do data atributu
okna pak zaviram pres toto
$.nette.ext('closeModal', {
success: function (payload) {
if (payload.closeModal) {
$('#'+payload.closeModal).modal('hide');
}
}
});
a v presenteru v handle nebo action pak si poslu payload stim, co zavrit
$this->payload->closeModal = "modal-confirm-dialog";
jo a je to nad jquery a twitter bootstrap
Editoval Glottis (26. 4. 2013 11:35)
- mildhouse
- Člen | 27
@Filip111:
Ale payload budeš muset taky pořešit v handle ne? Zase se ti neprovede po
odeslání formuláře. Máš pravdu, že ukládat to do sessions není
ideální, to jsem zjistil, když se modální okno snažilo ukládat/načítat
sessions při vykreslování, což vyhodilo chybu. Zkus to místo sessions
ukládat do persistentních parametrů. Nechat parametr ‚do‘ volný pro
odeslání formuláře a vytvořit si svůj parametr např. ‚dialog-do‘. To
pak přece není obcházení, ale vyřešení problému, protože ke kolizi
nedojde. Uděláš vlastně to, co dělá nette : v parametru předáš název
metody a její parametry. Akorát si jí budeš muset volat sám někde
v action.
- Glottis
- Člen | 129
mozn ami neco unika a cele to nechapu ale proc si ty pripadne formulare nedas do snipetu? ja to tak kdysi delal a to mi i fungovalo
<div id="{$control->name}" class="window-container" style="width: {$width}; max-width: {$maxWidth}">
<div id="win-window-handle" class="window-head-bar">
<span style="">
{snippet win-caption}
{$caption}
{/snippet}
</span>
<a href="{link close!}" class="ajax close"></a>
</div>
<div class="window-body" id="{$control->name}-windows-body">
<div class="window-body-container" id="{$control->name}-windows-body-container" style="height: {$height}; max-height: {$maxHeight}">
{snippet win-body}
{snippet flashMessages}
<div n:foreach="$flashes as $flash" class="flash {$flash->type}">{$flash->message}</div>
{/snippet}
{foreach $control->components as $comp}
{snippet $comp->name}
{control $comp}
{/snippet}
<div style="clear: both;"></div>
{/foreach}
{/snippet}
</div>
</div>
</div>
//v tovarne
$form->onError[] = callback($this->presenter, 'errorForm');
//obsluha chyby
public function errorForm(Nette\Application\UI\Form $form) {
/** @var Glo\Windows */
$win = $this->getComponent('win');
$win->setCaption('testicek error');
$this->flashMessage('testaa');
$win->invalidateControl('win-body');
$this->invalidateControl();
}
a pripadna chyba se mi v okne prekreslila i s formularem a okno zustalo