M06UF4Pr2: JSONP: Receptes de cuina

De wikijoan
Salta a la navegació Salta a la cerca

RecipeXML i JSONP (versió 1)

Al començament de la UF, quan volíem accedir als fitxers XML que estan en un repositori comú i públic, vam tenir problemes degut a la política de domini únic. Ara que ja coneixem JSONP, estem en condicions de poder subsanar aquest problema.

Això és el que farem en aquesta pràctica.

La idea que s'ha comentat a classe és tenir un repositori amb tots els fitxers XML de receptes que han generat els alumnes. Aquest repositori és a:

En el servidor s'executa un script PHP que llegeix el fitxer xml. Per exemple:

script read_xml_recipe.php:

<?php
//header('content-type: application/json; charset=utf-8');
header('content-type: text/plain; charset=utf-8'); //millor
header("access-control-allow-origin: *");
$url = $_GET['url'];

$xmlrecipe = file_get_contents('recipes22/'.$url);

echo $_GET['callback'] . '('.json_encode($xmlrecipe).')';
//echo $_GET['callback'] . '('.$xmlrecipe.')'; //així no!
?>

I des el domini local (localhost o qualsevol altre domini diferent de joanqc.no-ip.biz), podem accedir a aquests fitxers XML amb la tècnica JSONP, i parsejar-los.

script recpetes_jsonp_v1.html:

<html>
<head>
<meta http-equiv="content-type" content="text/html charset=utf-8" />
<title>Receptes AJAX (JSONP)</title>
<script>

let global_url = "";

function loadXMLDoc_jsonp(url)
{
	global_url = url;
	tempscript = document.createElement("script");
	tempscript.type = "text/javascript";
	tempscript.id = "tempscript";
	tempscript.src = "http://joanqc.no-ip.biz/iesbalmes/wec/receptes/read_xml_recipe.php?callback=JSONPHandler&url="+url;
	document.body.appendChild(tempscript);
}

function JSONPHandler(data) {
	//console.log(data);

	//hem de parsejar el document XML que tenim a data
	//http://www.w3schools.com/xml/xml_parser.asp
	parser = new DOMParser();
	xmlDoc = parser.parseFromString(data,"text/xml");

	let titol = (xmlDoc.getElementsByTagName("title")[0]) ? xmlDoc.getElementsByTagName("title")[0].firstChild.nodeValue : "";
	let blurb = (xmlDoc.getElementsByTagName("blurb")[0]) ? xmlDoc.getElementsByTagName("blurb")[0].firstChild.nodeValue : "";
	let genre = (xmlDoc.getElementsByTagName("genre")[0]) ? xmlDoc.getElementsByTagName("genre")[0].firstChild.nodeValue : "";
	let author = (xmlDoc.getElementsByTagName("author")[0]) ? xmlDoc.getElementsByTagName("author")[0].firstChild.nodeValue : "";
	let yield = (xmlDoc.getElementsByTagName("yield")[0]) ? xmlDoc.getElementsByTagName("yield")[0].firstChild.nodeValue : "";
	let preptime = (xmlDoc.getElementsByTagName("preptime")[0]) ? xmlDoc.getElementsByTagName("preptime")[0].firstChild.nodeValue : "";
	let preparation = (xmlDoc.getElementsByTagName("preparation")[0]) ? xmlDoc.getElementsByTagName("preparation")[0].firstChild.nodeValue.replace(/\\n/g,'<br />') : "";
	let serving = (xmlDoc.getElementsByTagName("serving")[0]) ? xmlDoc.getElementsByTagName("serving")[0].firstChild.nodeValue : "";

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

	txt += `<img src='recipes/img/${global_url.replace('xml','png')}'/>`;

	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=xmlDoc.getElementsByTagName("ingredient");
		for (let i=0;i<x.length;i++) {
			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;
}

</script>
</head>
<body>
<h1>Receptes (JSONP)</h1>
Les receptes estan en un repositori remot:
<ul><li><a href="http://joanqc.no-ip.biz/iesbalmes/wec/receptes/recipes22/">http://joanqc.no-ip.biz/iesbalmes/wec/receptes/recipes22/</a></li></ul>

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

RecipeXML i JSONP (versió 2)

Què podem millorar de la versió 1? Si ens fixem, totes les receptes estan hardcoded dins de la select. Això vol dir que si pugem una nova recepta al servidor (via FTP, per ex), haurem d'actualitzar tota aquesta llista d'opcions.

L'objectiu de la versió 2 és que tinguem totes les opcions de les receptes generades de forma dinàmica. I això ho haurem de fer a l'inici de l'aplicació fent una consulta AJAX per saber què hi ha a dins de la carpeta de receptes.

El següent script s'executa en el servidor (tu no hi tens accés), i llista tots els fitxers del directori recipes22 i retorna totes les option de la select box (per veure-ho més clar fes Ctrl-U, ver código fuente).

script list_recipes.php

<?php
header('Content-Type: text/html; charset=utf-8');

if ($handle = opendir('recipes22/')) {

    /* This is the correct way to loop over the directory. */
    while (false !== ($entry = readdir($handle))) {
        if (strstr($entry, '.xml')) {
            //SimpleXML functions are part of the PHP core
            $xml = simplexml_load_file('recipes22/'.$entry)
                or die("Error: Cannot create object");
            
            foreach($xml->children() as $child)
            {
                if (!strstr($child->getName(),'recipe')) {
                //echo $child->getName() . ": " . $child . "<br />";
                if ($child->getName() == "title") {
                    $title = $child;
                    break;
                }
                }
            }
            echo "<option value=\"".$entry."\">".$title."</option>\n";
        }
    }

    closedir($handle);
}
?>

Per tant, si podem fer la crida a aquest script en l'inici de la nostra aplicació, podem construir la nostra llista de receptes actualitzada.

Ara necessitem encapsular-ho dins d'una funció de callback amb la tècnica del jsonp:

script list_recipes_callback.php:

<?php
header('content-type: text/plain; charset=utf-8');
header("access-control-allow-origin: *");

if ($handle = opendir('recipes22/')) {

    while (false !== ($entry = readdir($handle))) {
        if (strstr($entry, '.xml')) {
            //SimpleXML functions are part of the PHP core
            $xml = simplexml_load_file('recipes22/'.$entry)
                or die("Error: Cannot create object");
            
            foreach($xml->children() as $child)
            {
                if (!strstr($child->getName(),'recipe')) {
                //echo $child->getName() . ": " . $child . "<br />";
                if ($child->getName() == "title") {
                    $title = $child;
                    break;
                }
                }
            }
            $data .= "<option value=\"".$entry."\">".$title."</option>";
        }
    }

    closedir($handle);
}

echo $_GET['callback'] . '('.json_encode($data).')';
?>

Per provar-ho:

Finalment, la nostra aplicació html queda de la forma,

script receptes_jsonp_v2.html:

<html>
<head>
<meta http-equiv="content-type" content="text/html charset=utf-8" />
<title>Receptes AJAX (JSONP)</title>
<script>

let global_url = "";

function init() {
	let tempscript = document.createElement("script");
	tempscript.type = "text/javascript";
	tempscript.id = "tempscript";
	tempscript.src = "http://joanqc.no-ip.biz/iesbalmes/wec/receptes/list_recipes_callback.php?callback=loadLlistaReceptesJSONP";
	document.body.appendChild(tempscript);
}

function loadLlistaReceptesJSONP(data) {
	//alert(data);
	document.getElementById("receptes").innerHTML = data;
}

function loadXMLDoc_jsonp(url)
{
	global_url = url;
	let tempscript = document.createElement("script");
	tempscript.type = "text/javascript";
	tempscript.id = "tempscript";
	tempscript.src = "http://joanqc.no-ip.biz/iesbalmes/wec/receptes/read_xml_recipe.php?callback=JSONPHandler&url="+url;
	document.body.appendChild(tempscript);
}

function JSONPHandler(data) {
	//console.log(data);

	//hem de parsejar el document XML que tenim a data
	//http://www.w3schools.com/xml/xml_parser.asp
	parser = new DOMParser();
	xmlDoc = parser.parseFromString(data,"text/xml");

	let titol = (xmlDoc.getElementsByTagName("title")[0]) ? xmlDoc.getElementsByTagName("title")[0].firstChild.nodeValue : "";
	let blurb = (xmlDoc.getElementsByTagName("blurb")[0]) ? xmlDoc.getElementsByTagName("blurb")[0].firstChild.nodeValue : "";
	let genre = (xmlDoc.getElementsByTagName("genre")[0]) ? xmlDoc.getElementsByTagName("genre")[0].firstChild.nodeValue : "";
	let author = (xmlDoc.getElementsByTagName("author")[0]) ? xmlDoc.getElementsByTagName("author")[0].firstChild.nodeValue : "";
	let yield = (xmlDoc.getElementsByTagName("yield")[0]) ? xmlDoc.getElementsByTagName("yield")[0].firstChild.nodeValue : "";
	let preptime = (xmlDoc.getElementsByTagName("preptime")[0]) ? xmlDoc.getElementsByTagName("preptime")[0].firstChild.nodeValue : "";
	let preparation = (xmlDoc.getElementsByTagName("preparation")[0]) ? xmlDoc.getElementsByTagName("preparation")[0].firstChild.nodeValue.replace(/\\n/g,'<br />') : "";
	let serving = (xmlDoc.getElementsByTagName("serving")[0]) ? xmlDoc.getElementsByTagName("serving")[0].firstChild.nodeValue : "";

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

	txt += `<img src='recipes/img/${global_url.replace('xml','png')}'/>`;

	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=xmlDoc.getElementsByTagName("ingredient");
		for (let i=0;i<x.length;i++) {
			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;
}

</script>
</head>
<body onload="init()">
<h1>Receptes (JSONP)</h1>
Les receptes estan en un repositori remot:
<ul><li><a href="http://joanqc.no-ip.biz/iesbalmes/wec/receptes/recipes22/">http://joanqc.no-ip.biz/iesbalmes/wec/receptes/recipes22/</a></li></ul>

<div id="formulari">
<form name="frm_receptes" id="formu" action="#">
<select id="receptes" name="recipes" onchange="loadXMLDoc_jsonp(this.value)">
</select>
</form>
</div>
<div id="info">
</div>
</body>
</html>

RecipeXML i JSONP (versió 3)

En l'anterior versió hem vist com el script PHP ens enviava totes les opcions construïdes en format html. Això no és el més correcte. Sempre voldrem que la informació que rebem del servidor (o enviem cap al servidor) sigui agnòstica respecte la tecnologia. Per fer-ho, s'utiltizen formats com ara el JSON.

script list_recipes_callback_json.php:

<?php
header('content-type: text/plain; charset=utf-8');
header("access-control-allow-origin: *");

if ($handle = opendir('recipes22/')) {

$data = array();
    while (false !== ($entry = readdir($handle))) {
        if (strstr($entry, '.xml')) {
            //SimpleXML functions are part of the PHP core
            $xml = simplexml_load_file('recipes22/'.$entry)
                or die("Error: Cannot create object");

            foreach($xml->children() as $child)
            {
                if (!strstr($child->getName(),'recipe')) {
                //echo $child->getName() . ": " . $child . "<br />";
                if ($child->getName() == "title") {
                    $title = $child;
                    break;
                }
                }
            }
            $data[] = array('file' => $entry,'title'=> (string)$title);
        }
    }

    closedir($handle);
}

echo $_GET['callback'] . "('".json_encode($data)."')";
?>

script recpetes_jsonp_v3.html:

<html>
<head>
<meta http-equiv="content-type" content="text/html charset=utf-8" />
<title>Receptes AJAX (JSONP)</title>
<script>

let global_url = "";

function init() {
	let tempscript = document.createElement("script");
	tempscript.type = "text/javascript";
	tempscript.id = "tempscript";
	tempscript.src = "http://joanqc.no-ip.biz/iesbalmes/wec/receptes/list_recipes_callback_json.php?callback=loadLlistaReceptesJSONP";
	document.body.appendChild(tempscript);
}

function loadLlistaReceptesJSONP(data) {
	//alert(data);
	let arrReceptes = JSON.parse(data)
	let sel = document.getElementById("receptes")
	for (let i=0;i<arrReceptes.length;i++) {
		let option = document.createElement("option")
		option.text = arrReceptes[i].title;
		option.value = arrReceptes[i].file;
		sel.appendChild(option);
	}
}

function loadXMLDoc_jsonp(url)
{
	global_url = url;
	let tempscript = document.createElement("script");
	tempscript.type = "text/javascript";
	tempscript.id = "tempscript";
	tempscript.src = "http://joanqc.no-ip.biz/iesbalmes/wec/receptes/read_xml_recipe.php?callback=JSONPHandler&url="+url;
	document.body.appendChild(tempscript);
}

function JSONPHandler(data) {
	//console.log(data);

	//hem de parsejar el document XML que tenim a data
	//http://www.w3schools.com/xml/xml_parser.asp
	parser = new DOMParser();
	xmlDoc = parser.parseFromString(data,"text/xml");

	let titol = (xmlDoc.getElementsByTagName("title")[0]) ? xmlDoc.getElementsByTagName("title")[0].firstChild.nodeValue : "";
	let blurb = (xmlDoc.getElementsByTagName("blurb")[0]) ? xmlDoc.getElementsByTagName("blurb")[0].firstChild.nodeValue : "";
	let genre = (xmlDoc.getElementsByTagName("genre")[0]) ? xmlDoc.getElementsByTagName("genre")[0].firstChild.nodeValue : "";
	let author = (xmlDoc.getElementsByTagName("author")[0]) ? xmlDoc.getElementsByTagName("author")[0].firstChild.nodeValue : "";
	let yield = (xmlDoc.getElementsByTagName("yield")[0]) ? xmlDoc.getElementsByTagName("yield")[0].firstChild.nodeValue : "";
	let preptime = (xmlDoc.getElementsByTagName("preptime")[0]) ? xmlDoc.getElementsByTagName("preptime")[0].firstChild.nodeValue : "";
	let preparation = (xmlDoc.getElementsByTagName("preparation")[0]) ? xmlDoc.getElementsByTagName("preparation")[0].firstChild.nodeValue.replace(/\\n/g,'<br />') : "";
	let serving = (xmlDoc.getElementsByTagName("serving")[0]) ? xmlDoc.getElementsByTagName("serving")[0].firstChild.nodeValue : "";

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

	txt += `<img src='recipes/img/${global_url.replace('xml','png')}'/>`;

	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=xmlDoc.getElementsByTagName("ingredient");
		for (let i=0;i<x.length;i++) {
			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;
}

</script>
</head>
<body onload="init()">
<h1>Receptes (JSONP)</h1>
Les receptes estan en un repositori remot:
<ul><li><a href="http://joanqc.no-ip.biz/iesbalmes/wec/receptes/recipes22/">http://joanqc.no-ip.biz/iesbalmes/wec/receptes/recipes22/</a></li></ul>

<div id="formulari">
<form name="frm_receptes" id="formu" action="#">
<select id="receptes" name="recipes" onchange="loadXMLDoc_jsonp(this.value)">
</select>
</form>
</div>
<div id="info">
</div>
</body>
</html>

Tasques a realitzar

Ja vas fer l'aplicació de les receptes, ben maquetada i dissenyada. Agafaves les receptes del teu directori local.

Ara es tracta d'accedir a les receptes directament al recurs remot. Per fer-ho, has d'adaptar el teu codi per tal d'anar a cercar les receptes al directori en el núvol, i amb JSONP podràs tenir tota la informació en la teva aplicació loca. Pots fer les tres versions que se't proposen.

Entrega

Entrega dins del termini. Pots entregar el codi i captures de pantalla que demostrin que la pràctica l'has feta tu. Quan tinguis l'aplicació funcionant, avisa al professor.


creat per Joan Quintana Compte, febrer 2022