PHP: jak na jednoduchý objednávkový formulář

V prakticky laděném článku určeném spíše pro začátečníky si ukážeme vytvoření jednoduchého objednávkového formuláře načítajícího produkty z XML a odesílajícího objednávku na e-mailovou adresu.

Přestože poslední dobou stavím weby prakticky výlučně s použitím některého z open source redakčních systémů, jako jsou Drupal, WordPress nebo GetSimple CMS, občas narazím na požadavek jednoduché HTML stránky bez administrace, jen s nějakým formulářem. Pokud to máte stejně, možná využijete návod na vytvoření jednoduchého objednávkového formuláře v PHP.

Formulář, který budeme v tomto článku stavět, bude načítat seznam produktů z XML. To vám umožní jednoduše přidávat nové produkty a měnit ceny bez toho, že byste potřebovali nějaké administrační rozhraní na webu. Prostě jen aktualizujete příslušný soubor a přes FTP jej na web nahrajete.

Pro každý z produktů, které v XML najde, zobrazí formulář políčko pro zadání počtu objednávaných kusů. Vespod formuláře pak budou políčka pro zadání údajů o kupujícím a informace o objednávce - tedy přehled objednávaných produktů a výpočet celkové ceny. Po odeslání formulář data zpracuje a odešle objednávku na e-mail.

Příprava XML se seznamem produktů

Soubor XML se seznamem produktů pro objednávkový formulář si nazvěte třeba zbozi.xml. Struktura tohoto souboru bude následující. Hlavní uzel <obchod> bude obsahovat uzly <zbozi>. Každý uzel se zbožím pak bude mít uvedenu identifikační značku, název a cenu. Vzorový souboru tedy bude vypadat následovně:

<?xml version="1.0" encoding="UTF-8"?>
<obchod>
  <zbozi>
    <id>1</id>
    <nazev>Plnící pero</nazev>
    <cena>300</cena>
  </zbozi>  
  <zbozi>
    <id>2</id>
    <nazev>Poznámkový blok</nazev>
    <cena>50</cena>
  </zbozi> 
  <zbozi>
    <id>3</id>
    <nazev>Guma</nazev>
    <cena>10</cena>
  </zbozi>
  <zbozi>
    <id>4</id>
    <nazev>Tužka vícebarevná</nazev>
    <cena>25</cena>
  </zbozi>    
  <zbozi>  
    <id>5</id>
    <nazev>Dárkový balíček</nazev>
    <cena>400</cena>
  </zbozi>
</obchod>

Načtení produktů z XML do formuláře

Pro sestavení formuláře si nyní připravte HTML stránku v PHP souboru. Nazvěte jej třeba index.php. Vložte HTML hlavičku, nadpis a formulář čekající na zpracování metodou post. Do atributu action mu nastavte javascriptovou funkci prepocitat(), kterou si ukážeme za chvíli.

Do formuláře vložte tabulku se čtyřmi sloupečky, přičemž její první řádek bude obsahovat nadpisy sloupečků Kód zboží, Název zboží, Cena a Počet kusů.

Nyní je třeba, aby se nám do dalších řádků tabulky dynamicky vykreslily další řádky, podle toho, kolik zboží máme připraveno v souboru XML. Vložte tedy PHP kód, který toto zařídí. Do proměnné $obchod si pomocí funkce simplexml_load_file() načtěte obsah souboru XML.

Připravte si smyčku foreach, ve které projdete všechny položky zbozi v objektu $obchod. Ten nám vznikl načtením pomocí simple_xml_load_file() a funguje jako objekt obsahující jednotlivé uzly se zbožím, které máme definovány v XML.

Ve smyčce foreach nyní jednotlivé zboží předáme do proměnné $zbozi, což bude objekt obsahující vlastnosti jednotlivých produktových uzlů ze XML. Můžeme tedy jednoduše přistupovat k id produktu, jeho názvu a ceně. Ty pak vypíšeme do jednotlivých sloupečků tabulky.

Do čtvrtého sloupce pak vložíme formulářový prvek <input type="text">, který bude sloužit pro vložení počtu objednávaných produktů. Všimněte si, že každému z nich přiřadíme dynamicky atributy name a id tak, že jejich hodnota bude tvořena řetězcem pocet_ a identifikátorem zboží. Znak \n v příkazech pro vypsání řetězce do stránky slouží pro ukončení řádku ve zdrojovém kódu.

Jako poslední řádek tabulky vložte tři prázdné sloupce a do čtvrtého pak tlačítko pro odeslání formuláře. Do HTML přidejte ještě značku <div>id rekapitulace, do které se po přepočítání objednávky zobrazí rekapitulace objednávaného zboží.

Přepočet objednávky

U definice objednávkového formuláře jsem zmínil javascriptovou funkci prepocitat(), která se použije pro přepočet objednávky, výpočet celkové ceny a zobrazení údajů o kupujícím. Do těla stránky tedy doplňte do značek <script type="text/javascript"></script> kód této funkce.

Formulář bude fungovat AJAXově, tedy bez opakovaného načítání stránky. Ve funkci prepocitat() tedy definujeme základní JavaScriptovou kostru pro AJAXové volání. Podrobněji o tomto viz článek AJAX pro začátečníky: formulář zpracovaný bez opětovného načtení stránky.

Dále připravte proměnnou pocetpolozek, která bude obsahovat informaci o počtu uzlů <zbozi> v našem XML. Buď ji můžete psát ručně s přidáním každého nového zboží nezapomenout změnit, nebo použijte jednoduchou konstrukci v PHP, která do JavaScriptu tento počet doplní.

Pokud je funkce prepocitat() volána, projde ve smyčce jednotlivá políčka formuláře nazvaná pocet_ČÍSLO, kde číslo je identifikátor zboží, jak vím z předchozího textu. Celý tento příklad tak spoléhá na to, že identifikátory zboží jsou postupně narůstající čísla. Hodnotu z každého z těchto políček doplníme do proměnné request obsahující již informaci o tom, že budeme volat nějakou adresu s parametrem akce=prepocet.

Následuje ještě zbytek konstrukce AJAXu, kdy definujeme funkci zpracovávající požadavek, adresu skriptu, který nám vrátí nějaká data (nazvaný je v tomto případě prepocitej.php, a zavoláme zpracování.

Z toho nám vyplývá, že je ještě třeba definovat funkci prepocitej(), která do HTML prvku s id rekapitulace doplní výstup ze skriptu prepocitej.php. V případě problému zobrazí oznámení o chybě.

Abychom soubor index.php měli již kompletní, doplníme ještě obsluhu odeslání objednávky. Půjde o dvojici funkcí objednat() a objednej(), které podobně jako prepocitat() a prepocitej() zpracují data z formuláře a předají je skriptu prepocitej.php na zpracování. Tyto další dvě funkce se od předchozích dvou liší jen v tom, že přepočítávají skript v PHP volají s parametrem akce=objednavka a přidávají do odesílaných dat ještě informace z formuláře o kupujícím.

Celý soubor index.php by tedy měl vypadat následovně:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html dir="ltr" xmlns="http://www.w3.org/1999/xhtml">

<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <link href="style.css" rel="stylesheet" type="text/css" />
  <title>Objednávkový formulář</title>
</head>

<body>
  <script type="text/javascript">
    function prepocitat() {      
      var http_request = false;
      var request = "akce=prepocet&";
      if (window.XMLHttpRequest) {
         http_request = new XMLHttpRequest();
      }
      else if (window.ActiveXObject) {
        try {
          http_request = new ActiveXObject("Msxml2.XMLHTTP");
        }
        catch (error) {
          http_request = new ActiveXObject("Microsoft.XMLHTTP");
        }
      }          
      var pocetpolozek = <?php $obchod = simplexml_load_file('zbozi.xml'); print $obchod->count(); ?>;  
      for(var i=1; i <= pocetpolozek; i++)
      {
        if (document.getElementById('pocet_' + i) != null)
        {
          request = request + 'pocet_' + i + '=' + document.getElementById('pocet_' + i).value + '&';			   			   
        }
      }      
      http_request.onreadystatechange = function() { prepocitej(http_request); };
      http_request.open('POST', 'prepocitej.php', true);            
      http_request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');                        
      http_request.send(request);
    }

    function prepocitej(http_request) {
      if (http_request.readyState == 4) {
         if (http_request.status == 200) {
            document.getElementById('rekapitulace').innerHTML = http_request.responseText;            
         }
         else {
           alert('Chyba');
         }
      }
    }
    
    function objednat() {  
        var http_request = false;
        var request = "akce=objednavka&";
        if (window.XMLHttpRequest) {
          http_request = new XMLHttpRequest();
        }
        else if (window.ActiveXObject) {
          try {
            http_request = new ActiveXObject("Msxml2.XMLHTTP");
          } 
          catch (error) {
            http_request = new ActiveXObject("Microsoft.XMLHTTP");
          }
        }                               
        request = request + 'jmeno=' + document.getElementById('jmeno').value + '&';
        request = request + 'ulice=' + document.getElementById('ulice').value + '&';
        request = request + 'mesto=' + document.getElementById('mesto').value + '&';
        request = request + 'psc=' + document.getElementById('psc').value + '&';
        request = request + 'stat=' + document.getElementById('stat').value + '&';
        request = request + 'telefon=' + document.getElementById('telefon').value + '&';
        request = request + 'e-mail=' + document.getElementById('e-mail').value + '&';
        request = request + 'fakturacni=' + document.getElementById('fakturacni').value + '&';
        request = request + 'dodaci=' + document.getElementById('dodaci').value + '&';
        request = request + 'celkem=' + document.getElementById('celkem').value + '&';
        request = request + 'zprava=' + document.getElementById('zprava').value + '&';
        http_request.onreadystatechange = function() { objednej(http_request); };
        http_request.open('POST', 'prepocitej.php', true);            
        http_request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');                        
        http_request.send(request);
   }
   
   function objednej(http_request) {
      if (http_request.readyState == 4) {
        if (http_request.status == 200) {
          document.getElementById('rekapitulace').innerHTML = http_request.responseText;            
        }
        else {
          alert('Chyba');
        }
      }
   }  
  </script>

  <h1>Objednávka produktů</h1>
  <form method="post" action="javascript:prepocitat()">		
    <table>
	  <tr>
		<td>Kód zboží</td>
		<td>Název zboží</td>
		<td>Cena</td>
		<td>Počet kusů</td>
	  </tr>		
  
  <?php
	$obchod = simplexml_load_file('zbozi.xml');     
	foreach ($obchod->zbozi as $zbozi)
  	{
	  	print "<tr>\n";
	  	print "<td>". strip_tags($zbozi->id->asXML())."</td>\n";
	  	print '<td>'. strip_tags($zbozi->nazev->asXML())."</td>\n";
	  	print "<td>". strip_tags($zbozi->cena->asXML())."</td>\n";
	  	print '<td><input name="pocet_'. strip_tags($zbozi->id->asXML()).'" id="pocet_'. strip_tags($zbozi->id->asXML()).'" type="text" /></td>'."\n";
	  	print "</tr>";
	}            
  ?>
     <tr>
       <td colspan="3">&nbsp;</td>
       <td><input type="submit" name="submit" value="Přepočítat" title="Přepočítat" /></td>
    </tr>
   </table>
  </form>
<div id="rekapitulace"></div>
</body>
</html>

Přepočtená objednávka a informace o kupujícím

Jelikož se z AJAXového volání ve formuláři odkazujeme na soubor prepocitej.php, musíme jej nyní vytvořit a doplnit do něj potřebnou funkcionalitu. Tento soubor bude mít dvě větve. V prvním případě, kdy mu bude předána proměnná akce=prepocet, přepočítá ceny objednaných produktů a zobrazí formulář s informacemi o kupujícím. V druhém případě, kdy jej budeme volat s parametrem akce=objednavka pak data z formuláře odešle na e-mailovou adresu.

Budeme pracovat se session pro uložení informace o počtu produktů. V případě, kdy dochází k přepočtu, tak projdeme jednotlivé produkty a uložíme počet objednávaných kusů do session.

Jakmile budeme v tomto skriptu cokoli posílat na výstup pomocí print, tak se nám to objeví v kontejneru, který jsme si výše definovali v AJAXovém volání. Tedy ve značce <div>id rekapitulace. To je i případ zobrazení mezinadpisu Rekapitulace objednávky a následného vypsání tabulky se seznamem objednaného zboží. Všimněte si, že v ní pracujeme se sessions, ve kterých máme informace o objednávaném počtu kusů, dáváme je do názvu produktu a násobíme jimi cenu. Text s popisem zboží, počtem kusů a ceně dáváme ještě do pomocné proměnné $zprava.

Částky za zboží průběžně sčítáme a po dokončení smyčky procházející jednotlivé produkty ji zobrazíme coby výslednou částku za celou objednávku.

Následuje zobrazení formuláře s informacemi o kupujícím. Všimněte si, že jako jeho akce je definována javascriptová funkce objednat(), kterou jsme si popsali výše.

Druhá větev zpracování objednávky je podstatně jednodušší. V případě, že je již voláno odeslání objednávky, zpracujeme jednotlivé položky z formuláře o kupujícím a odešleme je na emailovou adresu.

Výsledná podoba souboru prepocitej.php tedy bude následující:

<?php

$obchod = simplexml_load_file('zbozi.xml'); 
$pocetpolozek = $obchod->count();
  
if ($_POST['akce'])
{
  //ini_set('session.save_path','tmp');
  //ini_set("session.cookie_domain",".domena.cz");
  session_start();
  if ($_POST['akce'] == 'prepocet')
  { 
      for ($i = 1; $i <= $pocetpolozek+1; $i++) {
          if ($_POST['pocet_'.$i.'']){
              $_SESSION['pocet_'.$i.''] = $_POST['pocet_'.$i.''];
          }
          else {
            unset($_SESSION['pocet_'.$i.'']);
          }
      }
      print '<h2>Rekapitulace objednávky</h2>';
      print '<table>';
      print '<tr><th>Zboží</th><th>Cena celkem</th></tr>';
      $obchod = simplexml_load_file('zbozi.xml');
      $castka = 0; 
      $hmotnost = 0; 
      $zprava = '';      
      foreach ($obchod->zbozi as $zbozi)
      		{
      	  	if (isset($_SESSION['pocet_'.strip_tags($zbozi->id->asXML()).'']))
      	  	{
      	  	  $castka_ = 0;
              $polozka = "";	  	 	  	
              $polozka .= "<tr>\n";
        	  	$polozka .= "<td>".$_SESSION['pocet_'.strip_tags($zbozi->id->asXML()).''].'x '. strip_tags($zbozi->nazev->asXML())."</td>\n";
        	  	$castka_ = $_SESSION['pocet_'.strip_tags($zbozi->id->asXML()).''] * strip_tags($zbozi->cena->asXML());
        	  	$polozka .= '<td>'. $castka_ ." Kč</td>\n";
        	  	$polozka .= "</tr>";
        	  	$zprava .= $_SESSION['pocet_'.strip_tags($zbozi->id->asXML()).''].'x '. strip_tags($zbozi->nazev->asXML()) . ', ' . $castka_." Kč\n"; 
        	  	$castka = $castka + $castka_;
        	  	print $polozka; 
      	  	}	  	
      		}              	  
      print '<tr><td>Celkem</td><td>'.$castka .' Kč</td></tr>';
      print '</table>';
      ?>
      <h3>Objednací údaje</h3>
      <form method="post" action="javascript:objednat()">
      	<table style="width: 100%">
      		<tr>
      			<td>Jméno</td>
      			<td><input name="jmeno" id="jmeno" type="text" /></td>
      		</tr>
      		<tr>
      			<td>Ulice</td>
      			<td><input name="ulice" id="ulice" type="text" /></td>
      		</tr>
      		<tr>
      			<td>Město</td>
      			<td><input name="mesto" id="mesto" type="text" /></td>
      		</tr>
      		<tr>
      			<td>PSČ</td>
      			<td><input name="psc" id="psc" type="text" /></td>
      		</tr>
      		<tr>
      			<td>Stát</td>
      			<td><input name="stat" id="stat" type="text" /></td>
      		</tr>
      		<tr>
      			<td>Telefon</td>
      			<td><input name="telefon" id="telefon" type="text" /></td>
      		</tr>
      		<tr>
      			<td>E-mail</td>
      			<td><input name="e-mail" id="e-mail" type="text" /></td>
      		</tr>
      		<tr>
      			<td>Fakturační adresa</td>
      			<td><textarea name="fakturacni" id="fakturacni" type="text" cols="20" rows="5"></textarea></td>
      		</tr>
      		<tr>
      			<td>Dodací adresa</td>
      			<td><textarea name="dodaci" id="dodaci" type="text" cols="20" rows="5"></textarea></td>
      		</tr>
      		<tr>
      			<td>&nbsp;</td>
      			<td>
              <input type="hidden" value="<?php print $castka; ?>" name="celkem" id="celkem"/>
              <input type="hidden" value="<?php print $zprava; ?>" name="zprava" id="zprava"/>
            </td>
      		</tr>      		
      		<tr>
      			<td>&nbsp;</td>
      			<td><input name="objednat" type="submit" value="Objednat" /></td>
      		</tr>
      	</table>
      </form>

<?php      
  }
  else if ($_POST['akce'] == 'objednavka')
  {
    
    $email = '';
    $message = "Objednané položky\n".$_POST['zprava']."\n\n";
    $email = $_POST['e-mail'];

    $message .= 'Celkem: ' . $_POST['celkem'] . " Kč\n";
    
    $message .= "\n\n";
    $message .= "Kupující:\n";
    $message .= 'Jméno: ' . $_POST['jmeno']."\n";
    $message .= 'Ulice: ' . $_POST['ulice']."\n";
    $message .= 'Město: ' . $_POST['mesto']."\n";
    $message .= 'PSČ: ' . $_POST['psc']."\n";
    $message .= 'Stát: ' . $_POST['stat']."\n";
    $message .= 'Telefon: ' . $_POST['telefon']."\n";
    $message .= 'E-mail: ' . $_POST['e-mail']."\n\n\n";
    $message .= "Fakturační adresa:\n " . $_POST['fakturacni']."\n\n\n";
    $message .= "Dodací adresa:\n " . $_POST['dodaci']."\n\n\n";  
        
    $message .= "Jakmile bude vaše objednávka připravena, budeme vás kontaktovat.\n";           
         
    $headers = "Content-type: text/plain; charset=utf-8\n";
    $headers .= 'From:'.$email;
           
    $komu = 'nekdo@nekde.cz';
    
    mail( $komu, 'Objednavka z webu', $message, $headers );
    mail( $email, 'Objednavka z webu', $message, $headers );
    
    print $message;
    
    print '<p>Potvrzení objednávky bylo odesláno na vaši e-mailovou adresu. Jakmile bude vaše objednávka připravena, budeme vás kontaktovat.</p>';
        
  }
}
?>

Možná vylepšení formuláře

Na první pohled se může zdát, že to až příliš mnoho kódu na jednoduchý objednávkový formulář. Určitě by šlo zjednodušit AJAXové volání, například s využitím jQuery. Bude-li zájem, můžeme si to ukázat v nějakém dalším článku. Formulář má také nevýhodu v tom, že kvůli jednoduchosti předpokládá jen číselné označení produktů ve zdrojovém XML.

Objednávkový formulář

Doplnit můžete ověření vyplněných políček předtím, než dojde k odeslání formuláře. Ale to již jistě zvládnete sami. V příloze najdete kompletní příklad s doplněnými zápisy pro CSS.

Příloha Velikost
formular.zip 3.69 KB

Buďme ve spojení, přihlaste se k newsletteru

Odesláním formuláře souhlasíte s podmínkami zpracováním osobních údajů. 
Více informací v Ochrana osobních údajů.

Autor článku: Jan Polzer

Tvůrce webů z Brna se specializací na Drupal, WordPress a Symfony. Acquia Certified Developer & Site Builder. Autor několika knih o Drupalu.
Marketing Director v Lesensky.cz. Ve volných chvílích podnikám výlety na souši i po vodě. Více se dozvíte na polzer.cz a mém LinkedIn profilu.

Komentáře k článku

návštěvník

Ahoj, díky za zajímavý scriptík. Když jsem si jednou nechal něco podobného programovat, chtěl jsem ještě pro jistotu ukládání do souboru, protože nějak nevěřím že email vždy dorazí. A xml je dobré psát rovnou se strukturou na zbozi.cz. Jen co mě napadlo k tomu aby to bylo dokonalé a šlo to používat na 100% :)

návštěvník

Presne toto potrebujem !! Trápim sa už od včera. Mám čistú obrazovku bez hlášky. Skúšam to tu http://www.pilates.cz/formular/objednavka.php   Už som si vytvorila aj subor, ktorý vypíše info o verzii php - na serveri je "PHP Version 5.2.6-1+lenny9"

To by malo stačiť, lebo funkcia Simplexml_load_file() je v php4 nedostupna. Kde robím chybu, prosííím ?

Profile picture for user Jan Polzer

Upravte si konfiguraci serveru nebo vývojového prostředí tak, aby zobrazovala chybové hlášky a ne jen bílou obrazovku. Jinak nepoznáte, kde je chyba. Může to být třeba tím, že sice máte PHP 5.2.x, ale nemáte zapnutu podporu pro SimpleXML

návštěvník

hmmm, tak teda toto :
Fatal error: Call to undefined method SimpleXMLElement::count() in C:\Program Files\VertrigoServ\www\formular\prepocitej.php on line 4
To znamena, ze na serveri je PHP 5.2, ale metoda count je implementovana az vo verzii 5.3....Da sa s tym prosim nieco urobit ?

návštěvník

Ukázka je zajímavá až na to, že když dám položky přepočítat, tak se nezapočítá poslední položka objednávky.

návštěvník

Nějak mi ten formulář nejde spustit. Chtěl jsem si ho vyzkoušet, ale ukazuje se mi to, jako kdyby tam byla chybná závorka.

návštěvník

Dobrý den,
jak by bylo možné vyřešit, aby zakazník neposílal email sám sobě? Když vyplní pole email, tak z tohoto emailu se odešle mail na "obchod", ale z toho samého se odešlě "kopie emailu" i na jeho email. Takže ve vysledku to vypadá, že zákazník poslal sám sobě email s objednávkou.

návštěvník

Dobrý den,
kdybych chtěl k celé objednávce přičíst jednotnou taxu za dopravu, jak to udělám? Děkuji

Přidat komentář

Odesláním komentáře souhlasíte s podmínkami Ochrany osobních údajů

reklama
Moje kniha o CMS Drupal

 

Kniha 333 tipů a triků pro Drupal 9


Více na KnihyPolzer.cz

Sledujte Maxiorla na Facebooku

Maxiorel na Facebooku

Poslední komentáře
Hosting pro Drupal a WordPress

Hledáte český webhosting vhodný nejenom pro redakční systém Drupal? Tak vyzkoušejte Webhosting C4 za 1200 Kč na rok s doménou v ceně, 20 GB prostoru a automatické navyšováním o 2 GB každý rok. Podrobnosti zde.

@maxiorel na Twitteru

Maxiorel na Twitteru