Odesílání formulářů přes AJAX

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Panda
Člen | 569
+
0
-

Protože tuto vlastnost (zatím) formuláře nemají, rozhodl jsem se podělit se o mé řešení, třeba se to někomu bude hodit…

Řešil jsem to přes rozšíření třídy AppForm, ve které překryji metodu RenderBegin tak, aby se do onsubmit dostalo volání AJAXu (a současně fungovala validace formuláře), a přidání 2 metod do ‚objektu‘ nette v nette.js. Řešení nepodporuje inputy s obrázkem, nepovažoval jsem je za důležité.

Třída AjaxAppForm:

class AjaxAppForm extends AppForm
{
	/** @var bool	Use AJAX for the form submission? */
	public $useAjax = TRUE;

	/** @var InstantClientScript	Overrides original $js, which is private and cannot be used in derived classes */
	private $js;

	/**
	 * Renders form's start tag, enables AJAX
	 */
	public function renderBegin()
	{
		$this->js = new InstantClientScript($this);
		$this->js->enable();

		$element = $this->getElementPrototype();
		if ($this->useAjax) {
			if (empty($element->attrs['onsubmit']))
				$element->onsubmit = 'return !nette.formAction(this, event);';
			else
				$element->onsubmit .= ' && !nette.formAction(this, event);';
		}

		echo $element->startTag();
	}

	/**
	 * Renders the rest of the form.
	 * This method must be overriden, because original $js is private
	 *
	 * @return void
	 */
	public function renderEnd()
	{
		echo $this->getElementPrototype()->endTag();
		$this->js->renderClientScript();
	}

}

Zde by možná stálo za uvážení, zda vlastnosti třídy Form jako je $element a $js neudělat spíše protected než private, ale to je na Davidovi…

2 metody do nette.js:

	formAction: function(form, event)
	{
		if (this.processing > 0) return true;

		this.result = {};

		// create new AJAX request
		this.initAjax();
		if (!this.ajax) return false;

		var sender = document.activeElement || event.explicitOriginalTarget;
		var action = form.action;

		action += (action.indexOf('?') == -1) ? '?' : '&';
		if (typeof(this.state) === 'object') {
			action += this.buildQuery(this.state, '', '');
		}

		// create process indicator
		try {
			var img = document.getElementById(this.spinnerId);
			if (sender && img) {
				this.spinner = img.cloneNode(true);
				this.spinner.style.display = 'inline';
				sender.parentNode.insertBefore(this.spinner, sender.nextSibling);
			}
		} catch (e) {
		}

		try {
			var url = action + '-r=' + Math.random();
			var query = this.buildFormQuery(form, sender);

			this.ajax.open("POST", url, true);
			this.ajax.setRequestHeader("X-Requested-With", "XMLHttpRequest");
			this.ajax.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
			this.ajax.onreadystatechange = function() { nette.ajaxHandler(); }
			this.ajax.send(query);
			this.processing = 1;
			return true;

		} catch (e) {
			return false;
		}
	},

	buildFormQuery: function(form, sender)
	{
		var s = '';
		var amp = '';

		for (var i in form.elements) {
			switch (form.elements[i].tagName) {
				case 'INPUT':
					switch (form.elements[i].type) {
						case 'text':
						case 'password':
						case 'hidden':
							s += amp + encodeURIComponent(form.elements[i].name) +
								"=" + encodeURIComponent(form.elements[i].value);
							break;

						case 'checkbox':
						case 'radio':
							if (form.elements[i].checked)
								s += amp + encodeURIComponent(form.elements[i].name) +
									"=" + encodeURIComponent(form.elements[i].value);
							break;
					}
					break;

				case 'TEXTAREA':
					s += amp + encodeURIComponent(form.elements[i].name) +
						"=" + encodeURIComponent(form.elements[i].value);
					break;

				case 'SELECT':
					s += amp + encodeURIComponent(form.elements[i].name) +
						"=" + encodeURIComponent(form.elements[i].options[form.elements[i].selectedIndex].value);
					break;
			}
			amp = '&';
		}

		if (sender != null) {
			s += '&' + encodeURIComponent(sender.name) + '=' + encodeURIComponent(sender.value);
		}

		return s.replace(/%20/g, '+');
	}

V případě přidávání do nette.js pozor na čárky – v JavaScriptu jsou objetky v podstatě jen seznamy členů a metod oddělené čárkami, takže pokud to například budete přidávat na konec celého objektu, musíte ještě přidat čárku za tělo buildQuery.

Určitě by to šlo udělat nějak lépe, ale myslím, že jako řešení do doby, než se nové Forms skamarádí s AJAXem, to stačí.

David Grudl
Nette Core | 8218
+
0
-

Od revize 63 je potřeba řešení upravit tak, aby se rozšířila/vytvořila třída IFormRenderer a nastavit $form->setRenderer($myRenderer)

Ola
Člen | 385
+
0
-

Jde to nyní řešit nějak jinak? Udělal jsem patřičné úpravy, ALE jakmile odešlu formulář, NIC se neprovede .. :( Stránka se nemění ..

Jod
Člen | 701
+
0
-

Rozšírený renderer:

<?php
class AjaxRenderer extends ConventionalRenderer
{
    public $useAjax = true;

    public function renderBegin()
    {
	$this->counter = 0;

	foreach($this->form->getControls() as $control)
		$control->setOption('rendered', FALSE);


	$element = $this->form->getElementPrototype();
	if ($this->useAjax)
		if (empty($element->attrs['onsubmit']))
			$element->onsubmit = 'return !nette.formAction(this, event);';
	        else
	        	$element->onsubmit .= ' && !nette.formAction(this, event);';

	return $element->startTag();
     }
}
?>

Potom stačí na inštancii Form zavolať metódu setRenderer():

<?php
	$renderer = new AjaxRenderer();
	$form->setRenderer($renderer);
?>

Funguje na v.0.8 rev.106

Editoval Jod (12. 11. 2008 0:02)

Ola
Člen | 385
+
0
-

Nefunguje, mám stejnej problém jako když sem si to ubastlil sám :-/ Formulář se prostš neodešle potom, co tam přidám nette.js … bez něj se odešle, ale překreslí se celá stránka .. Nějakej větší sample by nebyl? Mam pocit, že prostě něco dělám blbě .. :/

EDIT: VŠE VYŘEŠEŠNO, MOC DÍKY!

Editoval Ola (12. 11. 2008 22:20)

Jod
Člen | 701
+
0
-

Tomu s tým heslom nejak nerozumiem :D

Ola
Člen | 385
+
0
-

Byla tam chyba, ze se neaktualizovala jeedna promenna v template ;-)

Editoval Ola (12. 11. 2008 22:55)

krajaac
Člen | 45
+
0
-

Zdravim, zkousel jsem kod od Pandy s upravou od Joda(Jody?). Funguje to bez problemu az na jednu vec: kdyz potrebuju form odeslat ne pres SubmitButton, ale pri zmene na SELECTU. Pak se form sice odesle, ale ne AJAXove (standartne se znovu nactenim cele stranky).

Udalost na SELECTU jsem nastavil podle prikadu ve vlakne onClick(change) na selectboxu takto:

<?php
// FORM
$form = new Form('selectForm');
$form->addSelect('name', 'label', $records)
	->controlPrototype->onChange('submit();');
$form->addSubmit('submit_name', 'submit_label')
	->setValidationScope(FALSE);

// SUBMIT
$form->onSubmit[] = array($this, 'handleSelectNode');

// CUSTOM RENDERING
$form->setRenderer(new AjaxRenderer());
?>

Muzete mi nekde poradit, jak nastavit select box, aby se pri zmene cely form odeslal AJAXove? v JS celkem plavu, pokud je potreba neco upravit tam, jsem rad za kazdou radu. :)

Editoval krajaac (17. 1. 2009 19:04)

krajaac
Člen | 45
+
0
-

VYŘEŠENO:

Po přečtení vlánka Odeslani formulare pomoci selectu? mě trochu osvítilo a zkusil jsem následující konstrukci:

<?php
// PREPARE FORM
$form = new Form('selectForm');
$form->addSelect('name', 'label', $records)
	//->controlPrototype->onChange('submit();');
	->controlPrototype->onChange('this.form.onsubmit();');

$form->addSubmit('submit_name', 'submit_label')
	->setValidationScope(FALSE);

// SUBMIT
$form->onSubmit[] = array($this, 'handleSelectNode');

// CUSTOM RENDERING
$form->setRenderer(new AjaxRenderer());
?>

Díky tomu, že se nad formulářem nezavolá klasický submit, ale kód, který je v atributu onsubmit:

<form onsubmit="return !nette.formAction(this, event);" method="post" action="">

tak se celý form odešle AJAXově při změně na SELECTu.

Editoval krajaac (19. 1. 2009 13:01)

David Grudl
Nette Core | 8218
+
0
-

Změnil jsem to i v odkazovaném threadu – místo onChange by mělo být spíš onchange

lopasovsky
Člen | 17
+
0
-

Ešte doplním: v Internet Exploreri nefunguje JavaScriptové iterovanie prvkami formulára:

for (var i in form.elements) {

takže daný riadok z kódu (v buildFormQuery) treba nahradiť nasledujúcim:

for (var i = 0; i < form.elements.length; i++) {
PetrP
Člen | 587
+
0
-

lopasovsky napsal(a):

Ešte doplním: v Internet Exploreri nefunguje JavaScriptové iterovanie prvkami formulára:

for (var i in form.elements) {

Nekoukal jsem přímo na zde zmiňovaný kód, ale toto funguje v IE6.
Dokonce i když se nejedná o array.

var form = {elements: ['a','b','c']};
for (var i in form.elements) {
	alert(i+' '+form.elements[i]);
}

var form = {elements: {a:'b'}};
for (var i in form.elements) {
	alert(i+' '+form.elements[i]);
}

Možná je ale myšleno něco jiného, popřípadě jina verze IE ;]

Ondřej Mirtes
Člen | 1536
+
0
-

Řeším teď podobný problém – mám select box, v něm seznam měsíců a v komponentě metodu handleMonth, která určuje, jaký měsíc se vykreslí a invaliduje oblast se články z minulého vybraného měsíce. Potřebuji tedy AJAXově odeslat ten formulář – do jeho události onsubmit nějak dostat {ajaxlink}, který odešle signál, jaký měsíc si uživatel vybral. A zároveň v případě nepřítomnosti Javascriptu, přidat k tomu selectboxu nějaké submitovací tlačítko, které zařídí, že při klasickém submitování formuláře se dojde k tomu samotnému výsledku jako přes AJAX (pro což bude hádám stačit dát do action formuláře správný link a správně pojmenovat <select> – aby se při odeslání methodou post vytvořil stejný link na výsledek jako v případě odeslání signálu přes AJAX).

Zatím mám toto:

<form class="monthinator" action="{link month ?}" onsubmit='{ajaxlink month '?vybraný option?'}' method="post"><fieldset>
<legend>Výběr měsíce</legend>
  <select name="monthinator" onchange="this.form.onsubmit();">
  {foreach $months as $month}
    <option value="{$iterator->getCounter()}">{$month}</option>
  {/foreach}
  </select>

</fieldset></form>
Honza Marek
Člen | 1664
+
0
-

Možná zkus toto. Jestli jsem to dobře pochopil, tak potřebuješ prostě odeslat formulář po změně selectu.

Takže by javacript (s jQuery) vypadal asi nějak takto:

<script>
// spustit po načtení celého htmlka stránky
$(function () {
	// navěsit funkci po změně
	$("css selektor k tvému selectu").change(function () {
		// ajaxové odeslání formuláře
		$(this.form).netteAjaxSubmit();
	});

	// a schovat tlačítko
	$("css selektor k tlačítu").hide();
});
</script>
Ondřej Mirtes
Člen | 1536
+
0
-

On se mi po změně odešle, v tom není problém, ale nevím, jak mám předat komponentě signál – value vybraného <option> z toho selectu…

Honza Marek
Člen | 1664
+
0
-

Co zavolat tu handle funkci komponenty přímo v nějaké onSubmit funkci formuláře?

Ondřej Mirtes
Člen | 1536
+
0
-

Problém je, že já netuším jak. Přece když dám <form onsubmit=„{ajaxlink month}“ …>, tak se nezavolá handleMonth se správným předaným parametrem…

Co se týče Javascriptu, potřebuju úplně dovést za ručičku k cíli :( Ajax v PHP a snippety jsem pochopil, vytvořil jsem si na tom už dvě funkční komponenty, ale v tomhle případě si nevím rady. Potřebuji zkrátka zavolat handleMonth($month) v komponentě, kde $month bude value z vybraného <option>.

Nepochopil jsem, jak mi s tím má pomoct „nette.js s jQuery“. Nebo je to „řešítko“ právě na tenhle případ? Ale stejně nevím, jak ho implementovat a s jakými ho volat parametry…

Ondřej Mirtes
Člen | 1536
+
0
-

Uff, tak jsem nejspíš pokročil.

Mám následující kód (render v Controlu):

$this->form = new Form('addCommentForm');
$this->form->addText("name", "Jméno", "10", "32");
$this->form->addTextArea("text", "text", "70", "5");
$this->form->addSubmit("ok", "&nbsp;")->getControlPrototype()->onclick[] = "this.form.onsubmit();";
$this->form->onSubmit[] = array($this, 'handleAddComment');
$this->form->setRenderer(new AjaxRenderer());

V template z toho vznikne (kouknul jsem, co mi vygeneruje samotný NForms a pak jsem to ručně okopíroval, vím, prasárna – nevěděl jsem, jak si poradit s napasováním formuláře do vlastního HTML kódu, NForms mi generovalo nějaké tabulky a já to potřeboval napasovat do těchto divů…):

      <form action="" method="post" onsubmit="return !nette.formAction(this, event);">
<fieldset><legend>Komentáře</legend>
        <div class="heading">
          <h3 class="left">Komentáře</h3>
          <div class="right">
            <label for="frmaddCommentForm-name">Jméno</label>
            <input type="text" maxlength="32" name="name" id="frmaddCommentForm-name" value="" />
            <input type="submit" onclick="this.form.onsubmit();" name="ok" id="frmaddCommentForm-ok" alt="OK" value="&nbsp;" />
          </div>
        </div>
        <div class="text">
          <textarea cols="70" rows="5" name="text" id="frmaddCommentForm-text"></textarea>
          <p>Komentáře podporují <a href="https://texy.info/cs/syntax">Texy!</a> syntaxi.</p>
          <p>Např.: "text odkazu":odkaz; <strong>**tučně**</strong>; <em>*kurzíva*</em></p>
        </div>
      </fieldset></form>

Po klepnutí na submit mi Firebug zaznamená nějaký POST požadavek.

Z příkladu jsem pochopil, že po tom odeslání už převezme kontrolu metoda handleAddComment. Jenže nevím, jestli má přijímat nějaký parametr ($form?) a nefunguje mi (zkouším v ní invalidovat Control a Firebug stejně nezaznamenává nějaké načítání, ten POST požadavek má 60 bajtů).

POST fungujícího AJAXu se mi odkazuje na tuto adresu:

test?poll-vote=2&do=poll-vote&-r=0.1446965222317057

Kdežto ten od toho formuláře vygeneruje adresu bez názvu signálu:

test?-r=0.20044537208007351&=&name=dsadas&ok=%C2%A0&text=dsad

Co dělám špatně? :)

Editoval LastHunter (15. 2. 2009 11:29)

Ondrej
Člen | 110
+
0
-

LastHunter napsal(a):
Co dělám špatně? :)

Form zadne signaly sam o sobe nezpracovana. Pokud mu chces vnutit signal do tveho controlu, tak mu jen nastav do action pres $this->link(‚signal‘)
V metode handleAddComment() mas pristup ke vsem FormControlum a jeho hodnotam. Nekde predtim ale musis zavolat $form->isSubmitted(), aby si formular nacetl hodnoty z requestu.

Ondřej Mirtes
Člen | 1536
+
0
-

Nemůžu to přece dávat do action, když to chci odesílat AJAXově (k čemu by pak byla celá ta třída ze začátku tohoto vlákna?). Toto mi tedy taky nefunguje (vyhodí to server error):

<form action="" method="post" onsubmit="{ajaxlink addComment}">
Ondrej
Člen | 110
+
0
-

LastHunter napsal(a):

Nemůžu to přece dávat do action, když to chci odesílat AJAXově.

Muzes ;)

nette.formAction(this, event) bere URL, kam se ma ajaxove poslat, prave z action. A obecne pokud funkce uvnitr onSubmit vraci false, tak k normalnimu odeslani formulare nedojde.

Ondřej Mirtes
Člen | 1536
+
0
-

Uff, začínám v tom mít bordel.

Když definuju v ArticleControl ten formulář až v renderArticle, musím pak tu jeho definici zkopírovat taky do toho handleComment? Mám ho sice jako objekt třídy, ale ten se nastavuje až v renderArticle a to se volá až po handle… Zkusil jsem to a padá mi tam Apache :D

Ukážu kód:

class Article extends BaseControl {

    var $form;
    var $article;

    public function __construct($presenter) {
        parent::__construct($presenter, "a");
    }

    public function handleComment() {

        $this->invalidateControl("komentare");

        if ($this->form->isSubmitted() && $this->form['ok']->isSubmittedBy() && $this->form->isValid()) {

            $model = new ArticleModel("news");
            $model->addComment($article->id, $this->form["name"], $this->form["text"]);

        }
    }

    public function renderArticle($article) {

        $template = parent::createMyTemplate(dirname(__FILE__) . '/' . 'Article' . '.phtml');

        $authorsModel = new StaffModel("staff");

        $template->article = $article;
        $this->article = $article;
        $template->authors = $authorsModel->getStaff();

        $model = new ArticleModel("news");
        $template->model = $model;

        $template->comments = $model->getComments($article->id);

        $this->form = new Form('addCommentForm');
        $this->form->addText("name", "Jméno", "10", "32");
        $this->form->addTextArea("text", "text", "70", "5");
        $this->form->addSubmit("ok", "&nbsp;")->getControlPrototype()->onclick[] = "this.form.onsubmit();";
        $this->form->onSubmit[] = array($this, 'handleComment');
        $this->form->setRenderer(new AjaxRenderer());

        $template->form = $this->form;
        $template->render();

    }

}

Když to mám takhle definované, tak mi to hlásí Call to a member function isSubmitted() on a non-object, protože v tu chvíli tam ten form ještě není :( A Control nemá žádné prepareView, do kterého bych to napral a zaregistrovalo by se to ještě před obsluhou signálu.
(Stejný problém tam je i s $article->id, také v tu chvíli ještě neexistuje).

Kam tedy nacpat definici toho formuláře, aby ho ten handleComment už znal + to samé s $article?

Editoval LastHunter (15. 2. 2009 13:34)

Ondrej
Člen | 110
+
0
-

LastHunter napsal(a):

Kam tedy nacpat definici toho formuláře, aby ho ten handleComment už znal + to samé s $article?

Ja definici formulare davam do konstruktoru. Ale kdyby David udelal nejaky zivotni cyklus pro komponenty, tak bych se nezlobil :) Viz. https://forum.nette.org/…iewtopic.php?…

Neni mi jasne kde definujes $this->article, pravdepodobne ho predavas z presenteru, takze k dispozici musi byt. (v handleComment mas preklep, ma tam byt $this->article)

Editoval Ondrej (15. 2. 2009 14:03)

Ondřej Mirtes
Člen | 1536
+
0
-

To je hroznej porod :)

Už mi to funguje, až na to, že se mi to nepřekreslí (invalidace nezafunguje) :/ Zkrátka to už nevidím.

Signal handler (když do něj přidám echo, tak se to echo vypíše taky dvakrát, tudíž chyba není v modelu):

public function handleComment() {

    if ($this->form->isSubmitted()) {

        $model = new ArticleModel("news");
        $values = $this->form->getValues();

        $model->addComment($this->article->id, $values["name"], $values["text"]);

        $this->invalidateControl();
    }
}

Formulář (i když odkomentuju/zakomentuju tu onsubmit část, i když vypnu/zapnu javascript, furt se to odesílá 2×, chyba bude tedy někde v PHP, nejspíš):

      <form action="{link comment}" method="post"<!-- onsubmit="return !nette.formAction(this, event); "-->>
<fieldset><legend>Komentáře</legend>
        <div id="komentare" class="heading">
          <h3 class="left">Komentáře</h3>
          <div class="right">
            <label for="frmaddCommentForm-name">Jméno</label>
            <input type="text" maxlength="32" name="name" id="frmaddCommentForm-name" value="" />
            <input type="submit" name="ok" id="frmaddCommentForm-ok" alt="OK" value="&nbsp;" />
          </div>
        </div>
        <div class="text">
          <textarea cols="70" rows="5" name="text" id="frmaddCommentForm-text"></textarea>
          <p>Komentáře podporují <a href="https://texy.info/cs/syntax">Texy!</a> syntaxi.</p>
          <p>Např.: "text odkazu":odkaz; <strong>**tučně**</strong>; <em>*kurzíva*</em></p>
        </div>
      </fieldset></form>

Kód v presenteru:

public function prepareArticle($seo)
{
 $model = new ArticleModel("news");

 $this->template->articleControl = new Article($this->getPresenter());
 $article = $model->getArticle($seo);
 $this->template->articleControl->article = $article;
}

Vykreslení komponenty v šabloně presenteru:

@{?$articleControl->renderArticle()}

Editoval LastHunter (15. 2. 2009 20:03)

Jod
Člen | 701
+
0
-

A máš v šablone onen magický zavináč? ,)

Ondřej Mirtes
Člen | 1536
+
0
-

Jojo, je tam :) Updatnul jsem ten předchozí příspěvek o zdrojáky a fakt se stydím, že potřebuju takhle vodit za ručičku. Ve výsledku se ale ze mě snad stane pohotový Nette uživatel :)

Jod
Člen | 701
+
0
-

A máš ho aj v layoute v @{include $content} ? ,)

Ondřej Mirtes
Člen | 1536
+
0
-

Super, bylo to tím, díky! Hotovo ;)

Jarda
Člen | 25
+
0
-

ahoj, potřeboval bych poradit s problémem při odesílání formuláře ajaxem. Upravil jsem všechny soubory a třídy dle návodů – AjaxAppForm, AjaxRenderer, nette.js

Problém mám ten, že se mi formulář postne 2x – když koukám na firebug konzoli, tak tam v nette.js vyskočí výjimka. Bohužel se hned potom odešle form klasicky, takže se konzola vyčistí pro novou stránku (nepřišel jsem na to, jak ve firebugu trasovat) nicméně se mi podařilo chybu vyfotit a hlásí to následující:

[Exception... "Component returned failure code: 0x80040111(NS_ERROR_NOT_AVAILABLE)
[nsIXMLHttpRequest.statusText]" nsresult: "0x80040111(NS_ERROR_NOT_AVAILABLE)"
location: "JS frame :: http://.../nette.js :: anonymous :: line 186" data: no]

- this.onError(this.ajax.status + " " ...Text + "\n\n" + this.ajax.responseText);

řádek funkce formAction: function(form, event) v nette.js, který vyvolal výjimku obsahuje:

<script>
try {
                var url = action + '-r=' + Math.random();
                var query = this.buildFormQuery(form, sender);

                this.ajax.open("POST", url, true);
                this.ajax.setRequestHeader("X-Requested-With", "XMLHttpRequest");
                this.ajax.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
                this.ajax.onreadystatechange = function() { nette.ajaxHandler(); }
tady to padne-> this.ajax.send(query);
                this.processing = 1;
                return true;

        }
</script>

hlásí to sice chybu, ale požadavek se odešle a událost handleAddItem() se provede a následně v důsledku chyby při ajaxu se provede klasicky, tudíž se mi položka přidá 2×.

Budu moc vděčný, když mi někdo dokáže poradit.

pro další informace přidávám i zdrojáky:

komponenta

<?php
class TDList extends Control
{

	/** @var bool */
	public $useAjax = true;

	/** @persistent */
	public $showAddForm = FALSE;

	protected $tddata;


	public function __construct()
	{
		parent::__construct();
		require_once dirname(__FILE__) . '/../models/TDData.php';
		$this->tddata = new TDData();
	}

	public function handleShowAddForm()
	{
		$this->showAddForm = $this->showAddForm ? false : true; //zobrazeni nebo skryti formuláře
		$this->invalidateControl('addForm');
	}

	public function handleMoveUp($item_id){
		$this->tddata->moveItemUp($item_id);
		$this->invalidateControl('itemsList');
	}

	public function handleAddItem(){
		$form = $this->getComponent("addItemForm");
		if ($form->isSubmitted()) {
			$formVals = $form->getValues();
			$this->invalidateControl();
			$this->handleShowAddForm();
			$this->tddata->createItem(array("subject"=>$formVals["subject"]));
		}
	}

	public function renderToDoList(){

		$td_list = $this->tddata->getItemsList();

		$template = $this->createTemplate();
		$template->useAjax = $this->useAjax;
		$template->setFile(dirname(__FILE__) . '/TDList.phtml');
		$template->showAddForm = $this->showAddForm;
		$template->td_list = $td_list;
		$template->addItemForm = $this->getComponent("addItemForm");

		$template->render();
	}

	protected function createComponentAddItemForm()
		{
				$form = new AjaxAppForm($this, $name);
				$renderer = new AjaxRenderer();
				$form->setRenderer($renderer);

				$form->addText("subject", "Subject: ")
						->addRule(Form::FILLED, 'Please enter a subject.');
				$form->addSubmit('submit1', 'Add Item');

				$form->onSubmit[] = array($this, 'handleAddItem');
	}
}
?>

šablona komponenty

<?php
{snippet addForm}
{if !$showAddForm}<a href="{link ShowAddForm}"{if $useAjax}onclick="return !nette.action(this)"{/if}>Přidat položku</a>{/if}
{if $showAddForm}
	{!$addItemForm}
{/if}
{/snippet}
{snippet itemsList}
	<ul>
	{? $pos = 0;}
	{foreach $td_list as $id=>$item}
	<li>
	{$id}&nbsp;{$item} {if $pos>0}<a href="{link MoveUp $id}"{if $useAjax}onclick="return !nette.action(this)"{/if}>&uarr; UP</a>{/if}
	</li>
	{? $pos++;}
	{/foreach}
	</ul>
{/snippet}
?>

@ v template view i layoutu mám.

Jen pro představu, co to má dělat. Snažím se udělat seznam položek, u kterých mohu měnit pořadí přesouváním položky o jednu výš a přidávat další položky pomocí jednoduchého formuláře, který se zobrazí až po kliknutí na odkaz pro zobrazení formu. Vše funguje hezky ajaxově, kromě přidání položky formulářem.

A ještě jedna a poslední záhada, kterou pozoruji. Při načtení formuláře je sice připojen js pro validaci formuláře, ale js ho nezvaliduje. Když odešlu prázdný formulář, validuje až server, ale když se pokusím o znovuodeslání špatně vyplněného formu, tak už vyskočí js alert. Je to zkrátka samá záhada :).


  • PHP 5.2.6
  • Nette Framework 0.8 (revision 220 released on 2009/02/25 19:45:34)
Jod
Člen | 701
+
0
-

Skús toto od Honzu, možno to pomôže: 1300-neco-jako-nette-js-s-jquery

A do formulára si pridaj $form->addProtection(‚Nepsrávne odoslaný formulár‘); možno to ošetrí to dvojité odoslanie.

Jarda
Člen | 25
+
0
-

díky za tip! sice to úplně nepomohlo, ale aspoň jsem se kousek pohnul a našel jsem zdroj problému.

  1. když jsem zaimplementoval jquery, tak se to stále ukládalo 2×, ale když jsem nejprve zkusil odeslat prázdný form, server provedl validaci, vypsal hlášku a znovu zobrazil formulář. Pak už to takhle jednou zvalidovaný formulář odesílalo pouze ajaxově, ale nevyskakoval alert o prázdném poli. Když jsem něco zadal, položka se ajaxově 1× přidala a formulář zmizel – tudíž očekávané chování, ale teprve u servervem zvalidovaného formuláře :(
  2. to mě přivedlo k tomu, abych z formuláře zkusil odebrat validační pravidlo a hle – formulář funguje ajaxově jak má. Jak přes jquery, tak i klasicky jen s nette.js.

Otázkou však zůstává, jak zaonačit, aby fungovala validace před odesláním formuláře? Poradí, prosím, někdo?

Jod
Člen | 701
+
0
-

Mne tam tá validácia funguje vporiadku,

Jarda
Člen | 25
+
0
-

jako ve tvých projektech s ajaxem nebo jsi zkoušel rozchodit ten můj příklad?

Editoval Jarda (10. 3. 2009 11:13)

Jod
Člen | 701
+
0
-

V mojich. Ale zase mi nefungujú iné veci, ale to je len môj prípad asi, že sa mi formuláre validujú 2× :D

Jarda
Člen | 25
+
0
-

hurrá, tak se mi to podařilo rozchodit… po celodenním seznamování se s jQuerry to lítá jak má vč. validace formuláře pomocí js.

Nakonec jsem skončil u následující sestavy:
Pro generování formuláře používám standardní AppForm a defaultní renderer, tak jak je v distribuci.
Pro vytváření ajaxových odkazů v šabloně používám pouze {if $useAjax}class="ajaxlink"{/if}
Pro zajaxovatění používám jQuery plugin od Honzy M. Díky za něj!

K tomu jsem si vytvořil (tedy spíše vykombinoval) tidyUp fci, která se volá při načtení dokumentu a po výměně snipetů, což je důležité, aby případně nově došlé klasické formuláře a odkazy zase zajaxovatěly.

<script>
var tidyUp   = function(){
	$(function () {
        // přiřaď všem současným i budoucím odkazům s třídou ajaxlink po kliknutí tuto funkci
        $("a.ajaxlink").live("click", function () {
                $.netteAjax(this.href);
                return false;
        });
	});

	$(function () {
        // odeslání na formulářích
        $("form").submit(function () {
                $(this).netteAjaxSubmit();
                return false;
    	});

        // odeslání pomocí tlačítek
        $("form :submit").click(function () {
                $(this).netteAjaxSubmit();
                return false;
        });

	});

	//tohle je taková moje vybastlenost pro zobrazení selectového
	//formuláře a jeho odeslání po změně hodnoty
	//nemá to obecné použití, nechávám pro inspiraci
        //takto mi funguje odesílání při změně selectboxu
	$("a.status").click(function(){
		$(this).hide();
		$($(this).attr("href")).show();
		$($(this).attr("href")+" select").change(function () {
			// ajaxové odeslání formuláře
              			 $(this.form).netteAjaxSubmit();
	 		});
	});

	//ostatní drobnosti, které potřebuji udělat při úklidu
	$('.hidden').hide();
	$("table tr:even").addClass("evenrow");
}
</script>

Omluvte, prosím, tento zápis. Mám takové tušení, že to není úplně jQuery košér,
že tam možná i něco přebývá, nebo je tam zbytečné. V jQuery jsem dneska dělal proprvé.

Nicméně tuto fci volám při načtení dokumentu a při dokončení ajaxového požadavku:

<script>
$(function () {
        $(document).ajaxStop(function () {
                tidyUp();
        });
});
</script>

Tak třeba to někomu pomůže, tak jako mně zde uvedené příklady, ze kterých se mi podařilo postupně vyzobat fungující řešení. Díky všem!

PS: nevím, v čem je chyba, že texy uvedlo oba kody v jednom bloku a zařadilo do toho i text, který do toho nepatří…

Jod
Člen | 701
+
0
-

jQuery má ponovom funkciu live (viď. aj v tvojom prípade), ktorá sa postará o to isté čo tidyUp. Zato je nápomocné pri tom .hide(), či $.texyla na ktorom live myslím nefunguje. Používam niečo podobné .

Jarda
Člen | 25
+
0
-

máš pravdu… na to tvé řešení jsem koukal… ale v jQuery zatím dost plavu a nepodařilo se mi to funkčně naimplementovat, i když je to určitě čistší řešení, než jsem použil…

Na fuknci live() jsem také koukal, že má umět obstarat i následně vytvořené elementy, ale právě bohužel neumí obsloužit vše, no časem to snad trochu učešu a případně postnu… ted jsem rád, že jsem rád :) když se mi to po celodenním úsilí podařilo zprovoznit, prostě euforie :D

Jod
Člen | 701
+
0
-

Jj, bolo by dobré mať aj nejakú univerzálnu live. Treba napísať jQuery request :D