Vícestránkový formulář

Školení, která pořádám

Článek vyšel na serveru Interval.cz.

Pokud máme vytvořit dlouhý formulář, je možné ho rozdělit na několik stránek, abychom uživatele nevylekali a neodradili je od vyplňování.

Řešení v PHP

Pokud se vyplněné hodnoty jednotlivých stránek rozhodneme přenášet na server, musíme pro ně zvolit vhodné úložiště. Vhodným místem jsou skrytá formulářová pole, protože jsou přímo svázána s vyplňovaným formulářem, jejich nevýhodou ale je, že se data neustále přenášejí tam a zpátky. Vyplněné hodnoty je možné ukládat také rovnou do databáze (do záznamu označeného ve zvláštním sloupci jako neúplný). Nevhodným místem pro uložení jsou běžné session proměnné, protože znemožňují vyplňování více formulářů najednou. Při zkombinování session proměnných se skrytým formulářovým polem je ale použít můžeme – stačí, když vyplněná data budeme ukládat do prvku pole a identifikátor tohoto prvku si budeme přenášet ve skrytém formulářovém poli.

<?php
session_start();
$posledni_stranka = 1;

if (!$_POST) {
    $stranka = 0;
    $registrace = array();
} else {
    $stranka = intval($_POST["stranka"]);
    $form_id = (isset($_POST["form_id"]) ? intval($_POST["form_id"]) : count($_SESSION["registrace"]));
    $registrace = &$_SESSION["registrace"][$form_id];
    $chyba = "";
    
    // uložení vyplněných hodnot
    foreach ($_POST as $key => $val) {
        if ($key != "form_id" && $key != "zpet" && $key != "stranka") {
            $registrace[$key] = $val;
        }
    }
    
    if ($_POST["zpet"]) {
        $stranka--;
    } else {
        
        // kontrola vyplněných hodnot
        switch ($stranka) {
            case 0:
                if (!strlen($_POST["jmeno"])) {
                    $chyba .= "Jméno nesmí být prázdné.\n";
                }
            break;
            case 1:
                if (!check_email($_POST["email"])) {
                    $chyba .= "Vyplňte prosím e-mail.\n";
                }
                if (!$chyba && !mysql_query("INSERT INTO uzivatele (jmeno, email) VALUES ('" . mysql_real_escape_string($registrace["jmeno"]) . "', '" . mysql_real_escape_string($registrace["email"]) . "')")) {
                    $chyba = "Data se nepodařilo uložit.";
                }
                if (!$chyba) {
                    header("Location: ok.php");
                    exit;
                }
            break;
        }
        if (!$chyba) {
            $stranka++;
        }
    }
}

// vypsání formuláře
echo "<form action='' method='post'>\n";
if ($_POST) {
    echo "<input type='hidden' name='form_id' value='$form_id' />\n";
    echo "<input type='hidden' name='stranka' value='$stranka' />\n";
    if ($chyba) {
        echo "<p>$chyba</p>\n";
    }
}
switch ($stranka) {
    case 0:
        echo 'Jméno: <input name="jmeno" value="' . htmlspecialchars($registrace["jmeno"]) . '" />';
    break;
    case 1:
        echo 'E-mail: <input name="email" value="' . htmlspecialchars($registrace["email"]) . '" />';
    break;
}
echo "\n<input type='submit' value='" . ($stranka < $posledni_stranka ? "Další" : "Dokončit") . "' />\n";
if ($stranka) {
    echo "\n<input type='submit' name='zpet' value='Zpět' />\n";
}
echo "Strana: " . ($stranka + 1) . "/" . ($posledni_stranka + 1) . "\n";
echo "</form>\n";
?>

Viz check_email.

V části vypsání formuláře se vypíše aktuální stránka formuláře a tlačítka pro pohyb po stránkách formuláře. Tlačítko Další je před tlačítkem Zpět proto, aby bylo výchozí – jejich vizuální pořadí lze prohodit stylem. Pokud uživatel formulář odešle, tak se data uloží do prvku pole v session proměnné. Pokud se uživatel nechtěl vrátit na předchozí stránku, tak se vyplněné hodnoty zkontrolují a pokud jsou v pořádku, tak se přejde na další stránku. Na poslední stránce se provede přesměrování na skript informující o úspěšném vyplnění a uložení hodnot formuláře.

Řešení v JavaScriptu

Před tím, než vícestránkový formulář začneme vytvářet, bychom si měli uvědomit, jakou pro to vlastně máme motivaci. Je formulář moc dlouhý a my nechceme uživatele odradit od jeho vyplňování? Nebo se podoba dalších kroků liší podle toho, co uživatel vyplnil v dříve? Nebo chceme, aby se první stránka formuláře rychle stáhla? U první a s vyvinutím určitého úsilí i u druhé motivace můžeme stránkování vytvořit na straně klienta v JavaScriptu. Tím zároveň zrychlíme odezvy při přechodu mezi stránkami formuláře, což je dobrou protiváhou ke třetí motivaci. Zpracování na serveru bude stejné jako u obyčejného formuláře, uživatelům s vypnutým JavaScriptem jednoduše zobrazíme všechny stránky formuláře najednou.

<form action="" method="post">
<div id="stranka-0">
Jméno: <input name="jmeno" value="<?php echo htmlspecialchars($_POST["jmeno"]); ?>" />
</div>
<div id="stranka-1">
E-mail: <input name="email" value="<?php echo htmlspecialchars($_POST["email"]); ?>" />
</div>
<script type="text/javascript">
var stranka = 0;
var posledni_stranka = 1;

function stranka_prechod(f, posun) {
	var chyba = '';
	if (posun > 0) {
		switch (stranka) {
			case 0:
				if (!f['jmeno'].value) {
					chyba += 'Jméno nesmí být prázdné.\n';
				}
			break;
			case 1:
				if (!check_email(f['email'].value)) {
					chyba += 'Vyplňte prosím e-mail.\n';
				}
			break;
		}
		if (chyba) {
			alert(chyba);
		} else if (stranka == posledni_stranka) {
			return true;
		}
	}
	if (!chyba) {
		document.getElementById('stranka-' + stranka).style.display = 'none';
		stranka += posun;
		document.getElementById('stranka-' + stranka).style.display = '';
		document.getElementById('zpet').disabled = !stranka;
		document.getElementById('dalsi').value = (stranka == posledni_stranka ? 'Dokončit' : 'Další');
		document.getElementById('stranka-cislo').innerHTML = stranka + 1;
	}
	return false;
}

for (var i=1; i <= posledni_stranka; i++) {
	document.getElementById('stranka-' + i).style.display = 'none';
}
document.write('<input type="button" id="zpet" value="Zpět" onclick="stranka_prechod(this.form, -1);" disabled="disabled" />\n');
document.write('<input type="submit" id="dalsi" value="Další" onclick="return stranka_prechod(this.form, 1);" />\n');
document.write('Strana: <span id="stranka-cislo">1</span>/' + (posledni_stranka + 1) + '\n');
</script>
<noscript><p><input type="submit" value="Dokončit" /></p></noscript>
</form>

Tlačítka pro přechod mezi stránkami formuláře budeme zobrazovat vždy obě, u tlačítka Zpět pouze budeme nastavovat jeho zakázanost, u tlačítka Další zase jeho popis. Funkce stranka_prechod zkontroluje při přechodu vpřed vyplněné hodnoty a na poslední stránce dovolí formulář odeslat. V jiném případě skryje starou stránku formuláře a zobrazí novou. Kontrolu hodnot v JavaScriptu musíme brát samozřejmě jen jako službu pro uživatele a na serveru si musíme kvůli bezpečnosti a konzistenci data zkontrolovat znovu.

Závěr

Jako uživateli mi vícestránkové formuláře dvakrát příjemné nejsou, ale když už k nim provozovatel webu sáhne, měly by být vytvořeny s ohledem na pohodlí uživatele. Především by mělo fungovat vyplňování více formulářů najednou a pokud to je možné, přechody mezi stránkami by měly být co nejrychlejší. To oba popsané způsoby splňují.

Jakub Vrána, Dobře míněné rady, 13.8.2007, diskuse: 11 (nové: 0)

Diskuse

Gifi:

Dobrý den, mám nemalý problem . pouzivam vas vicestrankový formulář. na localhostu se odeslane data udržuji v session. na serveru již ne. taky jsem se u vás dočetl že nekteré hostingy uchovávají dvacet cookie a nějake 4 kb. ja těch session proměnnych používám celkem dost, tak jestli není zrovna problem tady....

ikona Jakub Vrána OpenID:

Limit cookies se nevztahuje k hostingu. Je to obecné chování prohlížečů.

Gifi:

Nechapu ze session na jinem hostingu me fungují bez problému a na dalším to dělá totální nesmysli.
kdyz ukladam to session hodnoty tak jak uvadite vy $registrace = &session['registrace'][$form_id] tak to nefunguje. kdyz v foreach pro prochazeni post pouziju jiny pripad, zase nic. zde priklad:

foreach ($_POST as $key => $val) {
        if ($key != "form_id" && $key != "zpet" && $key != "stranka") {
            $registrace[$key] = stripslashes($val);
            $_SESSION['registrace'][$form_id][$key] = stripslashes($val);
        }
    }

Nechtel bych nejak zatezovat svymi problemy, vzdy sem si poradil pres google a podobne. marne ted hledam a nic, tak mi nezbyva se obratit na odbornika a hlavne pána který mě sel přikladem od prveho počátku mého programování.

a tady print_r($_SESSION['registrace'])
localhost:
Array ( [0] =>
  Array ( [formid] => 6631223552183
          [id_ship] => 1
          [jmeno] => Tomáš Jirousek
          [adresa] => Hasičská 3030
          [mesto] => Ostrava
          [psc] => 77700
          [zeme] => cze
          [email] => djgifig@centrum.cz
          [telefon] => 733 107 200
          [doprava] => /ajax/order_part2_pay.php?lang=cs&id=1
          [uhrada] => 3 ) )

Hosting:

Array ( [0] => omáš Jirousek [jmeno] => Tomáš Jirousek [1] => Hasičská 3030 [adresa] => Hasičská 3030 [2] => Ostrava [mesto] => Ostrava [3] => 77700 [psc] => 77700 [4] => gbr [zeme] => gbr [5] => djgifig@centrum.cz [email]  djgifig@centrum.cz [6] => 733 107 200 [telefon] => 733 107 200 [formid] => 4111223551690 )

Někde sem se dočetl, že session na serveru neumí spracovávat vicenásobně vnořené pole. tak nevím co je na tom pravdy, ale vím že bud mi to na serveru informace zachova v takto zmatenné podobě nebo vubec.

Jirka:

Ad: "Nevhodným místem pro uložení jsou běžné session proměnné, protože znemožňují vyplňování více formulářů najednou."
Nechápu? Rozchodil jsem formulář přes session na jedné doméně a fungoval mi tam i druhý formulář?

ikona Jakub Vrána OpenID:

Zde uvedený příklad si u každého formuláře ukládá form_id, takže toho se problém netýká.

Kromě toho každý formulář může obsahovat různé položky, takže se vzájemně nemusí ovlivňovat.

Výhrada se tedy týká formulářů, které hodnoty ukládají přímo do session proměnné bez dalšího identifikátoru - takto navržené dva stejné formuláře nelze vyplnit najednou ve dvou tabech.

Honza:

Zdravim, chtel bych se zeptat jakym zpusobem vypisu vyplnene informace uplne na posledni strance "ok.php" . Diky za odpoved

ikona Jakub Vrána OpenID:

Stránce lze předat ID vloženého záznamu a nějaký kontrolní kód (aby si informace nemohl zobrazit nikdo jiný). Tedy např. ok.php?id=10&token=XXX, kde XXX je náhodný řetězec uložený k záznamu v databázi.

Alternativně lze stránce data předat v URL (pokud jich není moc).

lol:

<script>alert(10)</script>

lol:

%3c%73%63%72%69%70%74%3e%61%6c%65%72%74%28%31%30%29%3c%2f%73%63%72%69%70%74%3e   

lol:

xss

Michal:

A ono nic co :-)

Vložit komentář

Používejte diakritiku. Vstup se chápe jako čistý text, ale URL budou převedeny na odkazy a PHP kód uzavřený do <?php ?> bude zvýrazněn. Pokud máte dotaz, který nesouvisí s článkem, zkuste raději diskusi o PHP, zde se odpovědi pravděpodobně nedočkáte.

Jméno: URL:

avatar © 2005-2018 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.