M06UF4Pr1: Receptes de cuina

De wikijoan
Salta a la navegació Salta a la cerca

Introducció

Anem a fer receptes de cuina, amb la informació de les receptes codificada amb llenguatge XML. El format i els tags que farem servir és molt simple:

<recipe>
  <title>Bean And Ham Soup</title>
  <recipeinfo>
    <blurb>A great way to use leftover holiday ham.</blurb>
    <genre>Soup</genre>
    <author>David Horton</author>
    <yield>10 servings</yield>
    <preptime>Five or six hours</preptime>
  </recipeinfo>
  <ingredientlist>
    <ingredient>1 lb. dry navy beans</ingredient>
    <ingredient>medium onion, diced</ingredient>
    <ingredient>2 cloves of garlic, minced</ingredient>
    <ingredient>2 ribs of celery, chopped</ingredient>
    <ingredient>3 carrots sliced</ingredient>
    <ingredient>3 tomatoes, diced</ingredient>
    <ingredient>1 lb cooked ham scraps, chopped</ingredient>
    <ingredient>6 cups of water</ingredient>
    <ingredient>1 ham bone</ingredient>
    <ingredient>Salt and pepper to taste</ingredient>
  </ingredientlist>

  <preparation>Rinse the beans. Combine dry beans and 10 C of water in a 5-quart stockpot and bring to a boil.\n\nRemove from heat and let soak for two hours before continuing with the recipe. Use a strainer or colander to drain the water from the beans and set aside.\n\nIn the stockpot combine ham scraps, garlic, onion, celery and carrot. Sweat vegetables and ham over medium-low heat. There should be enough fat in the ham scraps to keep the veggies from sticking to the pan, but if not add a little vegetable oil.\n\nOnce the onion becomes translucent add the tomatoes and cook down for a few minutes. When tomatoes are soft add 6 C. of water and stir to create a vegetable broth. Drain and rinse the beans. Add beans and ham bone to the vegetable broth. Simmer over low heat until beans are tender, about 2 to 3 hours.\n\nStir occasionally. After beans are tender add salt and pepper to taste. Do not add salt before this as it can toughen the beans.\n\nDiscard ham bone and skim off any oil that may be on the surface before serving.</preparation>
  <serving>Serve with rye rolls and apple slices on the side.</serving>
</recipe>

Podem anomenar aquest format RecipeXML. Un cop s'escriuen les receptes amb aquest format es pot exportar la informació a d'altres formats (HTML, PDF, Rich Text,...), com ja vas veure l'any passat a l'assignatura de XML.

Guardem la informació xml en un fitxer amb extensió xml, i generem també una imatge de 200x200 px que sigui representativa de la recepta.

Fixem-nos que els paràgrafs i retorns de carro els fem amb el caràcter \n, doncs no hem de pressuposar que la renderització del fitxer serà en una pàgina web.

Desenvolupament

Parsejar XML en el cantó del client

L'objectiu és carregar el contingut de la recepta i parsejar el contingut XML.

El fitxer index1.html carrega en una select box 2 fitxers XML (fitxers d'exemple que pots descarregar de l'enllaç). Mitjançant la tècnica asíncrona d'AJAX mostra els ingredients per pantalla, en la capa corresponent. Aquest codi és bàsicament similar al que s'ha vist a classe en l'exemple de tutoria (catàleg de CDs). fitxer index1.html:

<html>
<head>
<title>Receptes AJAX</title>
<script>
function loadXMLDoc(url)
{

var xmlhttp;
var txt,x,xx,i;

xmlhttp=new XMLHttpRequest();

xmlhttp.onreadystatechange=function()
  {
  if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
    txt="<h2>Ingredients</h2>";
    txt = txt + "<ul>";
		x=xmlhttp.responseXML.documentElement.getElementsByTagName("ingredient");
		for (i=0;i<x.length;i++) {
			txt = txt + '<li>' + x[i].firstChild.nodeValue + '</li>';
		}
    txt = txt + "</ul>";
    document.getElementById('info').innerHTML=txt;
    }
  }
xmlhttp.open("GET",url,true);
xmlhttp.send();

}
</script>
</head>
<body>
<h1>Receptes</h1>
<div id="formulari">
<form name="frm_receptes" id="formu" action="#">
	<select name="recipes" onchange="loadXMLDoc(this.value)">
	<option value="recipes/bean-and-ham.xml">bean and ham</option>
	<option value="recipes/corn-chowder.xml">corn chowder</option>
	</select><br />
</form>
</div>
<div id="info">
</div>
</body>
</html>

Obtenim:

2 Tbsp. butter
medium onion, diced
2 cloves garlic, minced
2 ribs celery, chopped
1 red pepper, diced
4 ears of corn kernels cut from the cob
2 Tbsp. flour
2 C. chicken broth
2 C. whole milk
3 medium red potatoes, cubed
Salt and pepper to taste

Fixa't bé que estem parsejant el fitxer XML en el cantó del client (Javascript). L'objecte xmlhttp.responseXML conté tot els fitxer complet, i amb Javascript filtrem la informació que ens interessa, en aquest cas els ingredients. Això seria ineficient si el fitxer XML fos molt gros (posem per cas 500K) i només ens interessa una petita part de la informació.

L'alternativa és que sigui PHP, en el cantó del servidor, qui parsegi el fitxer XML i només envïi al client la informació que interessa, en aquest cas els ingredients

Parsejar XML en el cantó del servidor

Ara és PHP qui s'encarrega de parsejar el XML. La informació que rebem no és tot el fitxer XML, sinó només allò que ens interessa, com ara la llista d'ingredients. La funcionalitat PHP de parsejar XML forma part del core, no cal instal·lar res.

script index2.html:

<html>
<head>
<title>Receptes AJAX</title>
<script>
function loadXMLDoc(recepta) {

  let xmlhttp=new XMLHttpRequest();

  xmlhttp.onreadystatechange=function() {
    if (xmlhttp.readyState==4 && xmlhttp.status==200) {
      //alert(xmlhttp.responseText);
      //console.log(xmlhttp.responseText);
      document.getElementById('info').innerHTML=xmlhttp.responseText;
    } else {
      document.getElementById('info').innerHTML="<img src=\"img/ajax_wait.gif\" />";
    }
  }
  
  xmlhttp.open("GET","processar_ingredients.php?recepta="+recepta,true);
  xmlhttp.send();
}
</script>
</head>
<body>
<h1>Receptes</h1>
<div id="formulari">
<form name="frm_receptes" id="formu" action="#">
  <select name="recipes" onchange="loadXMLDoc(this.value)">
  <option value="recipes/bean-and-ham.xml">bean and ham</option>
  <option value="recipes/corn-chowder.xml">corn chowder</option>
  </select><br />
</form>
</div>
<div id="info">
</div>
</body>
</html>

script processar_ingredients.php:

<?php
$fitxerxml = $_GET["recepta"];
echo "<h1>Ingredients $fitxerxml</h1>";
$xml = simplexml_load_file($fitxerxml);
//echo $xml->getName() . "<br>";

//per bolcar tota la informació:
//print_r($xml);

echo "<ul>";
foreach($xml->ingredientlist->ingredient as $ingredient){
	echo "<li>";
	echo $ingredient;
	echo "</li>";
}
echo "</ul>";
sleep(1);
?> 

Per tal de simular una comunicació molt lenta o una transacció de molta informació (icona ajax_wait.gif), hem introduït sleep(1) en el codi PHP, i en el codi javascript introduïm:

if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
   document.getElementById('info').innerHTML=xmlhttp.responseText;
} else {
   document.getElementById('info').innerHTML="<img src=\"img/ajax_wait.gif\" />";
}

La idea és que abans d'arribar a l'estat 4 (xmlhttp.readyState==4) passem pels estats 1, 2 i 3, i aquests són els moments on podem mostrar la icona del wait típica del AJAX.

En aquest cas el que retorna el PHP és un echo o print. Però en d'altres ocasions el que ens interessarà és que retorni un objecte JSON.

Recuperar tota la informació

Ara que ja sabem parsejar una part del document XML, anem a recuperar tota la informació.

script index3.html:

<html>
<head>
<title>Receptes AJAX</title>
<meta charset="utf-8">
<script>
function loadXMLDoc(url)
{

var xmlhttp;
var txt,x;

xmlhttp=new XMLHttpRequest();

xmlhttp.onreadystatechange=function()
  {
  if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
    let xmlinfo = xmlhttp.responseXML.documentElement;
    let titol = (xmlinfo.getElementsByTagName("titol")[0]) ? xmlinfo.getElementsByTagName("title")[0].firstChild.nodeValue : "";
    let blurb = (xmlinfo.getElementsByTagName("blurb")[0]) ? xmlinfo.getElementsByTagName("blurb")[0].firstChild.nodeValue : "";
    let genre = (xmlinfo.getElementsByTagName("genre")[0]) ? xmlinfo.getElementsByTagName("genre")[0].firstChild.nodeValue : "";
    let author = (xmlinfo.getElementsByTagName("author")[0]) ? xmlinfo.getElementsByTagName("author")[0].firstChild.nodeValue : "";
    let yield = (xmlinfo.getElementsByTagName("yield")[0]) ? xmlinfo.getElementsByTagName("yield")[0].firstChild.nodeValue : "";
    let preptime = (xmlinfo.getElementsByTagName("preptime")[0]) ? xmlinfo.getElementsByTagName("preptime")[0].firstChild.nodeValue : "";
    let preparation = (xmlinfo.getElementsByTagName("preparation")[0]) ? xmlinfo.getElementsByTagName("preparation")[0].firstChild.nodeValue.replace(/\\n/g,'<br />') : "";
    let serving = (xmlinfo.getElementsByTagName("serving")[0]) ? xmlinfo.getElementsByTagName("serving")[0].firstChild.nodeValue : "";

    txt = `<h2>${titol}</h2>`;

    txt += `<h2>Capçalera</h2>`;
    txt += `<ul>`;
    txt += `<li>${blurb}</li>`;
    txt += `<li>${genre}</li>`;
    txt += `<li>${author}</li>`;
    txt += `<li>${yield}</li>`;
    txt += `<li>${preptime}</li>`;
    txt += `</ul>`;

    txt += `<h2>Ingredients</h2>`;
    txt += `<ul>`;
		x=xmlinfo.getElementsByTagName("ingredient");
		for (let i=0;i<x.length;i++) {
      //=${b}
			txt +=`<li>${x[i].firstChild.nodeValue}</li>`;
		}
    txt += `</ul>`;

    txt += `<h2>Preparation</h2>`;
    txt += preparation;

    txt += `<h2>Serving</h2>`;
    txt += serving;

    document.getElementById('info').innerHTML=txt;
    }
  }
xmlhttp.open("GET",url,true);
xmlhttp.send();

}
</script>
</head>
<body>
<h1>Receptes</h1>
<div id="formulari">
<form name="frm_receptes" id="formu" action="#">
	<select name="recipes" onchange="loadXMLDoc(this.value)">
	<option value="recipes/bean-and-ham.xml">bean and ham</option>
	<option value="recipes/corn-chowder.xml">corn chowder</option>
	</select><br />
</form>
</div>
<div id="info">
</div>
</body>
</html>

Apuntem al repositori públic

Tenim les nostres receptes en aquest recurs:

En l'anterior script index3.html canviem la línia:

xmlhttp.open("GET", url,true);

per

xmlhttp.open("GET","http://joanqc.no-ip.biz/iesbalmes/wec/receptes/recipes22/" + url,true);

és a dir, anem a cercar les receptes a un recurs públic. Ara, quan volem accedir, ens dóna l'error:

Access to XMLHttpRequest at 'http://joanqc.no-ip.biz/iesbalmes/wec/receptes/recipes22/recipes/macarrons_de_la_parenta.xml' from origin 'http://localhost' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
index3_public.html:60 GET http://joanqc.no-ip.biz/iesbalmes/wec/receptes/recipes22/recipes/macarrons_de_la_parenta.xml net::ERR_FAILED

És a dir, l'accés al domini http://joanqc.no-ip.biz està blocat per la política de seguretat habitual: no podem accedir al recurs d'un altre domini com si res. Per solucionar-ho, es pot utilitzar la tècnica JSONP, o bé configurar el servidor Apache per permetre aquests accessos, o bé que en el servidor remot hi hagi una API que faci d'interfície entre la nostra aplicació local i la informació remota.

Feina per l'alumne

Captura recepta.png

A partir d'aquí hem de fer una aplicació més interessant. S'ha de mostrar tota la informació que hi ha en el fitxer XML: la capçalera de la recepta (títol, autor), ingredients, mode de preparació, com es serveix... i la foto amb una bona maquetació


Recordatori:

  • les fotos han de tenir el nom igual que el fitxer XML, però amb extensió .png
  • format 200x200px
  • les fotos resideixen a recipes/img.


Per exemple, si tenim el fitxer patates_fregides.xml, la imatge corresponent serà patates_fregides.png.

  • Físicament la carpeta img/ penja de dins de la carpeta recipes/, on estan els fitxers XML.

La nostra aplicació completa inclou aspectes de disseny i aspectes de usabilitat i funcionalitat, tal com s'ha discutit a classe. Es fa la proposta següent (però l'alumne podrà fer altres proves i millores).

Disseny i funcionalitat

Primer has de fer una aplicació web que funcioni amb les teves dues receptes.

Hem de definir una estructura, una plantilla, on mostrar tota la informació desitjada. Evidentment, ho farem amb capes div. Una proposta és:

--------------  -----------------
| select box |  |   Títol       |
--------------  -----------------
-------------   ----------------
|            |  |    Gènere     |
|            |  |---------------|
|ingredients |  | foto          |
|            |  |               |
-------------   -----------------
---------------------------------
|                               |
|    preparació                 |
|                               |
---------------------------------
---------------------------------
|    servir                     |
---------------------------------

però l'alumne la pot canviar segons el seu criteri.

Tan important com el disseny és la manera com se'ns mostra la informació (cerquem que sigui una pàgina dinàmica i ben dissenyada i fàcil, amb la informació que es mostri de forma clara)

Repositori de les receptes dels alumnes

En aquest espai anirem carregant les receptes dels alumnes. Per tant, ara la teva aplicació ja no tindrà només dues receptes, sinó també la dels teus companys.

Descàrrega: Fitxer:Recipes 22.zip

Accés remot:

Tenim les següents receptes (01/02/2022):

<select name="recipes" onchange="loadXMLDoc(this.value)">
    <option value="recipes/bean-and-ham.xml">bean and ham</option>
    <option value="recipes/corn-chowder.xml">corn chowder</option>
    <option value="recipes/macarrons_de_la_parenta.xml">macarrons_de_la_parenta</option>
    <option value="recipes/conill_farcit_catalana.xml">conill_farcit_catalana</option>
    <option value="recipes/croquetes_levon_gukasyan.xml">croquetes_levon_gukasyan</option>
    <option value="recipes/khash_levon_gukasyan.xml">khash_levon_gukasyan</option>
    <option value="recipes/espaguetisBolonyesa.xml">espaguetisBolonyesa</option>
    <option value="recipes/truitaPatates.xml">truitaPatates</option>
    <option value="recipes/bolitas_rellenas_de_queso_Laura.xml">bolitas_rellenas_de_queso_Laura</option>
    <option value="recipes/espaguetis_carbonara_Laura.xml">espaguetis_carbonara_Laura</option>
    <option value="recipes/bunuelos.xml">bunuelos</option>
    <option value="recipes/pollo.xml">pollo</option>
    <option value="recipes/ous_de_guatlla_amb_bacon.xml">Ous de guatlla amb bacon</option>
    <option value="recipes/carpaccio_de_peu_de_porc.xml">Carpaccio de peu de porc</option>
    <option value="recipes/mandonguilles_de_sipia_i_marisc.xml">Mandonguilles de sípia i marisc</option>
    <option value="recipes/pollastre_amb_salsifis.xml">Pollastre amb salsifís</option>
    <option value="recipes/silpancho.xml">Silpancho</option>
    <option value="recipes/charque.xml">Charque</option>
</select>

Ara bé, això serà una feina manual: has de descarregar el xml, el png, col·locar-los dins la carpeta recipes/, i actualitzar la llista desplegable. Per poques receptes no passa res. Però... i si fossin centenars? Hem de pensar com automatitzar aquesta tasca, i com poder agafar la informació d'un repositori remot.

Entrega

Entregaràs en el classroom, dins el temps previst, els següents fitxers tot empaquetat en un zip:

  • recepta_nom_cognom.html
  • css/recepta_nom_cognom.css
  • js/recepta_nom_cognom.js
  • carpeta img/ amb una captura de pantalla de l'aplicació.
  • carpeta recipe/ i recipe/img/ amb les teves receptes, i si les tens, les dels companys (es dóna informació d'on es poden descarregar)

NOTA: totes les solucions han de ser diferents


creat per Joan Quintana Compte, gener 2021