Webprogramozás

Tervezési minták, AJAX, JSON kommunikáció

Horváth Győző
Egyetemi docens
1117 Budapest, Pázmány Péter sétány 1/c., 2.408-as szoba
Tel: (1) 372-2500/8469
horvath.gyozo@inf.elte.hu

Ismétlés

Ismétlés

  • Dinamikus szerveroldali webprogramozás
  • Output (HTML generálás)
  • Input (link, űrlap)
  • Adattárolás: fájlok
  • Kódszervezés
  • Munkamenet-kezelés
  • Hitelesítés
  • Fájlfeltöltés

Kódszervezés, tervezési minták

Kódszervezés eszközei

  • Logikai
    • megjegyzések
    • függvények
    • osztályok
    • (névterek)
  • Fizikai
    • fájlok
    • könyvtárak
  • Nyelvi elemek
    • include
    • osztályok automatikus betöltése
  • Tervezési minták

Fogalmak

  • Vezérlő
    • Egy adott végponthoz rendelt logika
    • pl. kerulet.php
    • 1 végpont = 1 fájl = 1 funkcionalitás = 1 vezérlő
  • Irányítás (routing)
    • végpont vezérlőhöz rendelése
    • Eddig: webszerver (alacsony szintű)

Példa: regisztráció

<?php
// debug
var_dump($_POST);

// dependencies
include('jsonio.php');
include('jsonstorage.php');
include('userrepository.php');

// function declarations
function validate($input, &$data, &$errors, $userRepository) {
  if (!isset($input['username']) || trim($input['username']) === '') {
    $errors['username'] = "Username is required";
  } else {
      $data['username'] = trim($input['username']);
  }

  if (!isset($input['password']) || trim($input['password']) === '') {
    $errors['password'] = "Password is required";
  } else {
      $data['password'] = trim($input['password']);
  }

  if (count($errors) === 0) {
    if (user_exists($userRepository, $input['username'])) {
      $errors['global'] = "User already exists";
    } 
  }

  return count($errors) === 0;
}
function user_exists($userRepository, $username) {
  $users = $userRepository->filter(function ($user) use ($username) {
    return $user['username'] === $username;
  });
  return count($users) >= 1;
}
function add_user($userRepository, $user) {
  $user['password'] = password_hash($user['password'], PASSWORD_DEFAULT);
  return $userRepository->insert($user);
}

// main
$userRepository = new UserRepository();
$data = [];
$errors = [];
if ($_POST) {
  if (validate($_POST, $data, $errors, $userRepository)) {
    add_user($userRepository, $data);
    header('Location: login.php');
    exit();
  }
}
?>
<!-- template -->
<style>
.error {
    color: red;
}
</style>
<?php if (isset($errors['global'])) : ?>
    <p><span class="error"><?= $errors['global'] ?></span></p>
<?php endif; ?>
<form action="" method="post">
  Username:
  <input type="text" name="username">
  <?php if (isset($errors['username'])) : ?>
    <span class="error"><?= $errors['username'] ?></span>
  <?php endif; ?>
  <br>
  Password:
  <input type="password" name="password">
  <?php if (isset($errors['password'])) : ?>
    <span class="error"><?= $errors['password'] ?></span>
  <?php endif; ?>
  <br>
  <button>Register</button>
</form>

Kivonat

<?php
// debug
var_dump($_POST);

// dependencies
include('jsonio.php');
include('jsonstorage.php');
include('userrepository.php');

// function declarations
function validate($input, &$data, &$errors, $userRepository) { /* ... */}
function user_exists($userRepository, $username) { /* ... */}
function add_user($userRepository, $user) { /* ... */}

// main
$userRepository = new UserRepository();
$data = [];
$errors = [];
if ($_POST) {
  if (validate($_POST, $data, $errors, $userRepository)) {
    add_user($userRepository, $data);
    header('Location: login.php');
    exit();
  }
}
?>
<!-- template -->
<?php if (isset($errors['global'])) : ?>
  <p><span class="error"><?= $errors['global'] ?></span></p>
<?php endif; ?>
<form action="" method="post">
  <!-- ... -->
</form>

Osztályok betöltése

<?php
// ...

// dependencies
include('jsonio.php');
include('jsonstorage.php');
include('userrepository.php');

// ...

// ...

// auto-loading classes
spl_autoload_register(function ($class) {
  include './classes/' . strtolower($class) . '.php';
});

// ...

Függvény → osztály

// function declarations
function validate($input, &$data, &$errors, $userRepository) { /* ... */}
function user_exists($userRepository, $username) { /* ... */}
function add_user($userRepository, $user) { /* ... */}

// main
$userRepository = new UserRepository();
if ($_POST) {
  if (validate($_POST, $data, $errors, $userRepository)) {
    add_user($userRepository, $data);
    /* ... */
  }
}

// function declarations
function validate($input, &$data, &$errors, $auth) { 
  // ...
  if ($auth->user_exists($input['username'])) { /* ... */ }
  // ...
}

// main
$auth = new Auth();
if ($_POST) {
  if (validate($_POST, $data, $errors, $auth)) {
    $auth->register($data);
    /* ... */
  }
}
class Auth {
    private $userRepository;
    public function __construct() {
        $this->userRepository = new UserRepository();
    }
    public function register($user) { /* ... */ }
    public function user_exists($username) { /* ... */ }
}

Paraméter → osztály

// function declarations
function validate($input, &$data, &$errors, $auth) { 
  // ...
  if ($auth->user_exists($input['username'])) { /* ... */ }
  // ...
}

// main
$auth = new Auth();
$data = [];
$errors = [];
if ($_POST) {
  if (validate($_POST, $data, $errors, $auth)) {
    $auth->register($data);
    /* ... */
  }
}

class Registration_Controller {
  private $auth;
  public $errors = [];
  public $data = [];
  public function __construct() {
      $this->auth = new Auth();
  }
  public function validate($input) { /* ... */ }
  public function run() {
    if ($_POST) {
      if ($this->validate($_POST)) {
        $this->auth->register($this->data);
        header('Location: login.php');
        exit();
      }
    }
  }
}
<?php
// ...
$controller = new Registration_Controller();
$controller->run();
$errors = $controller->errors;
?>
<!-- template -->

Sablonkezelés

<?php
// ...
$controller = new Registration_Controller();
$controller->run();

$errors = $controller->errors;
?>
<!-- template -->
<?php if (isset($errors['global'])) : ?>
    <p><span class="error"><?= $errors['global'] ?></span></p>
<?php endif; ?>
<form action="" method="post">
  Username:
  <input type="text" name="username">
  <?php if (isset($errors['username'])) : ?>
    <span class="error"><?= $errors['username'] ?></span>
  <?php endif; ?>
  <br>
  Password:
  <input type="password" name="password">
  <?php if (isset($errors['password'])) : ?>
    <span class="error"><?= $errors['password'] ?></span>
  <?php endif; ?>
  <br>
  <button>Register</button>
</form>

class Registration_Controller {
  // ...
  public function run() {
    if ($_POST) { /* ... */ }
    $this->display('reg.php', [
      'errors' => $this->errors,
    ]);
  }
  public function display(string $template, array $data) {
    extract($data);
    include(__DIR__ . '/../templates/' . $template);
  }
}
// ...
$controller = new Registration_Controller();
$controller->run();

Ősvezérlő

abstract class Base_Controller {
  protected $errors = [];
  protected $data = [];

  abstract public function run();

  public function __construct() {}
  public function display(string $template, array $data) {
    extract($data);
    include(__DIR__ . '/../templates/' . $template);
  }
}
class Registration_Controller extends Base_Controller {
  private $auth;
  // ...
  public function run() {
    if ($_POST) { /* ... */ }
    $this->display('reg.php', [
      'errors' => $this->errors,
    ]);
  }
}

Több vezérlőmetódus

// ...
$controller = new Registration_Controller();
$controller->process_form(); // instead of: run
class Registration_Controller extends Base_Controller {
  private $auth;
  // ...
  public function process_form() { // instead of: run
    if ($_POST) { /* ... */ }
    $this->display('reg.php', [
      'errors' => $this->errors,
    ]);
  }
}
abstract class Base_Controller {
  protected $errors = [];
  protected $data = [];

  // no run method in base class
  public function __construct() {}
  public function display(string $template, array $data) { /* ... */ }
}

GET-POST szétválasztása

// ...
$controller = new Registration_Controller();
$controller->process_form();

// ...
$controller = new Registration_Controller();
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
  $controller->show_form();
} else {
  $controller->process_form();
}

class Registration_Controller extends Base_Controller {
  // ...
  public function process_form() {
    if ($this->validate($_POST)) {
      $this->auth->register($this->data);
      header('Location: login.php');
      exit();
    }
    $this->show_form();
  }
  public function show_form() {
    $this->display('reg.php', [
      'errors' => $this->errors,
    ]);
  }
}

Végső vezérlő

// debug
var_dump($_POST);
// auto-loading classes
spl_autoload_register(function ($class) {
    include(__DIR__ . '/classes/' . strtolower($class) . '.php');
});

// main
$controller = new Registration_Controller();
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
  $controller->show_form();
} else {
  $controller->process_form();
}

Több vezérlő

// debug
var_dump($_POST);
// auto-loading classes
spl_autoload_register(function ($class) {
    include(__DIR__ . '/classes/' . strtolower($class) . '.php');
});

// main
$controller = new A_Controller();
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
  $controller->method1();
} else {
  $controller->method2();
}
// debug
var_dump($_POST);
// auto-loading classes
spl_autoload_register(function ($class) {
    include(__DIR__ . '/classes/' . strtolower($class) . '.php');
});

// main
$controller = new B_Controller();
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
  $controller->method3();
} else {
  $controller->method4();
}
// debug
var_dump($_POST);
// auto-loading classes
spl_autoload_register(function ($class) {
    include(__DIR__ . '/classes/' . strtolower($class) . '.php');
});

// main
$controller = new C_Controller();
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
  $controller->method5();
} else {
  $controller->method6();
}

Elemzés

  • Közös dolgok
    • config
    • security
    • session
    • etc
  • → Közös belépési pont
    • index.php
    • Front Controller
    • irányítás (routing) is a feladata!

Több vezérlő

// debug
var_dump($_POST);
// auto-loading classes
spl_autoload_register(function ($class) {
    include(__DIR__ . '/classes/' . strtolower($class) . '.php');
});
// routing
// ???
// |----> A_Controller::method1
// |----> A_Controller::method2
// |----> B_Controller::method3
// |----> B_Controller::method4
// ...
$controller = new A_Controller();
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
  $controller->method1();
} else {
  $controller->method2();
}
$controller = new B_Controller();
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
  $controller->method3();
} else {
  $controller->method4();
}
$controller = new C_Controller();
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
  $controller->method5();
} else {
  $controller->method6();
}

Routing

  • Lehetőségek
    • http://example.com/index.php?path=registration
    • http://example.com/index.php?registration
    • http://example.com/index.php/registration
    • http://example.com/registration
  • Útvonal → metódus
  • HTTP metódusok
    • GET, POST, DELETE, stb.
// routes.config.php
$router->get('registration', 'Registration_Controller', 'show_form');
$router->post('registration', 'Registration_Controller', 'process_form');

Elővezérlő

index.php

// index.php
// start session
session_start();

// auto-loading classes
spl_autoload_register(function ($class) {
    include(__DIR__ . '/classes/' . strtolower($class) . '.php');
});

// start routing
$router = new Router();
include(__DIR__ . '/routes.config.php');
$router->start();
// routes.config.php
$router->get('registration', 'Registration_Controller', 'show_form');
$router->post('registration', 'Registration_Controller', 'process_form');

Router

class Router
{
  private $routes = [
    'GET' => [],
    'POST' => [],
  ];
  
  public function get(string $path, $controller, $method) {
    $this->routes['GET'][$path] = [$controller, $method];
  }
  public function post(string $path, $controller, $method) {
    $this->routes['POST'][$path] = [$controller, $method];
  }
  
  public function start() {
    $request_method = $_SERVER['REQUEST_METHOD'];
    $path = $_GET['path'] ?? '';

    if ($path) {
      unset($_GET[$path]);
    }
    list($controller, $method) = $this->routes[$request_method][$path] ?? NULL;
    if ($controller && $method) {
      $obj = new $controller();
      if (method_exists($obj, $method)) {
        $obj->$method();
      } else {
        die('404: Path not found');
      }
    } else {
      die('404: Path not found');
    }
  }
}

Eredmény

  • index.php: elővezérlő, adott
  • routes.config.php: irányítás
  • classes/vezérlő osztályok
    • beolvasás ($_POST, $_GET)
    • feldolgozás (→ repository)
    • kiírás (template)
  • classes/repository osztályok
    • feldolgozó függvények
  • data/: tárolt adatok
  • templates/: sablonok
  • → Modell-Nézet-Vezérlő tervezési minta
  • → Elővezérlő tervezési minta

Eredmény

// routes.config.php
$router->get('registration', 'Registration_Controller', 'show_form');
$router->post('registration', 'Registration_Controller', 'process_form');
// classes/registration_controller.php
class Registration_Controller extends Base_Controller {
  private $auth;

  public function __construct() {
    parent::__construct();
    $this->auth = new Auth();
  }
  private function validate($input) {
    if (!isset($input['username']) || trim($input['username']) === '') {
      $this->errors['username'] = "Username is required";
    } else {
      $this->data['username'] = trim($input['username']);
    }

    if (!isset($input['password']) || trim($input['password']) === '') {
      $this->errors['password'] = "Password is required";
    } else {
      $this->data['password'] = trim($input['password']);
    }

    if (count($this->errors) === 0) {
      if ($this->auth->user_exists($input['username'])) {
        $this->errors['global'] = "User already exists";
      } 
    }

    return count($this->errors) === 0;
  }
  public function process_form() {
    if ($this->validate($_POST)) {
      $this->auth->register($this->data);
      header('Location: login.php');
      exit();
    }
    $this->show_form();
  }
  public function show_form() {
    $this->display('reg.php', [
      'errors' => $this->errors,
    ]);
  }
}

// classes/userrepository.php
class UserRepository extends JsonStorage {
  public function __construct() {
    parent::__construct('users.json');
  }
}
// classes/auth.php
class Auth {
  private $userRepository;
  public function __construct() {
    $this->userRepository = new UserRepository();
  }
  public function register($user) {
    $user['password'] = password_hash($user['password'], PASSWORD_DEFAULT);
    return $this->userRepository->insert($user);
  }
  public function user_exists($username) {
    $users = $this->userRepository->filter(function ($user) use ($username) {
        return $user['username'] === $username;
    });
    return count($users) >= 1;
  }
  public function login($user) {
    $_SESSION["user"] = $user;
  }
  public function check_credentials($username, $password) {
    $users = $this->userRepository->filter(function ($user) use ($username) {
      return $user['username'] === $username;
    });
    if (count($users) === 1) {
      $user = array_values($users)[0];
      return password_verify($password, $user["password"]) 
        ? $user 
        : false;
    }
    return false;
  }
  public function is_authenticated() {
    return isset($_SESSION["user"]);
  }
  public function logout() {
    unset($_SESSION["user"]);
  }
}

Fájlok közvetlen meghívásának védelme

  • Tokenes védelem
    • alkalmazás szintű
  • .htaccess védelem
    • webszerver szintű
  • webkönyvtáron kívüli elhelyezés
    • operációs rendszer szintű

Tokenes védelem

  • Token definiálása az elővezérlőben
// index.php
define('TOKEN', 'A secret token');
// ...
  • További szkriptek első sora ezt vizsgálja
// classes/registration_controller.php
<?php if ( ! defined('TOKEN')) die('Directly not accessible!');

.htaccess védelem

  • Könyvtár alapú konfigurációs fájl
  • Apache képes kezelni
  • Ehhez külön könyvtárba kell
  • (webprogramozas szerveren nem működik (nginx))
  • Könyvtáranként .htaccess állomány
    • classes
    • templates
    • data
# .htaccess
deny from all
|+ (D) classes
|  |--+ .htaccess
|+ (D) data
|  |--+ .htaccess
|+ (D) templates
|  |--+ .htaccess
|+ index.php

Elhelyezés webkönyvtáron kívül

Könyvtárszerkezet

- (D) www
  |--+ index.php
- (D) app
  |--+ (D) classes
  |--+ (D) data
  |--+ (D) templates

AJAX

Szerveroldali kiegészítés

AJAX

  • HTTP kérés JavaScriptben
  • Oldalújratöltés nélkül
  • A háttérben

Kliens – XMLHttpRequest

function getPoster() {
  const title = input.value
  const xhr = new XMLHttpRequest()
  xhr.addEventListener('load', responseHandler)
  xhr.open('GET', `http://www.omdbapi.com/?t=${title}&apikey=<key>`)
  xhr.responseType = 'json'
  xhr.send(null)
}
function responseHandler() {
  img.src = this.response.Poster
}

Kliens – fetch

async function getPoster() {
  const title = document.querySelector('input').value
  const response = await fetch(`http://www.omdbapi.com/?t=${title}&apikey=2dd0dbee`)
  const json = await response.json()
  document.querySelector('img').src = json.Poster
}

Szerveroldal

  • Mindegy, hogy normál vagy AJAX kérés (HTTP)
  • A válasz létrehozása programmal
  • Kimenet generálás
  • Statikus prototípus → dinamikus sablon
Mozart;Beethoven;Dvorak
$composers = [
  'Mozart',
  'Beethoven',
  'Dvorak',
];
echo implode(';', $composers);

Statikus

[
  "Mozart",
  "Beethoven",
  "Dvorak"
]

Dinamikus

$composers = [
  'Mozart',
  'Beethoven',
  'Dvorak',
];
echo json_encode($composers);
{
  "success": true
}
<?php $success = true; ?>
{
  "success": <?= (string)$success ?>
}
<ul>
  <li>Mozart</li>
  <li>Beethoven</li>
  <li>Dvorak</li>
</ul>
<?php $composers = ['Mozart', 'Beethoven', 'Dvorak',]; ?>
<ul>
  <?php foreach($composers as $name) : ?>
    <li>Beethoven</li>
  <?php endforeach ?>
</ul>

AJAX kommunikáció a szerverrel

Kommunikáció kellékei

  • Űrlapadatok
  • URL paraméterek (GET)
  • HTTP üzenettörzs (POST)
  • fejlécek
  • választípusok

Űrlapadatok – FormData

<form>
  <input type="text" name="event" value="Advent">
  <input type="text" name="year" value="2019">
</form>
const form = document.querySelector('form')
// creating FormData from a form
const formData = new FormData(form);
// append new item
formData.append('place', 'St Gerardus Church')
// output key-value pairs
for (const pair of formData.entries()) {
    console.log(pair[0], pair[1])
}
// event Advent
// year 2019
// place St Gerardus Church

// creating an empty FormData object
const formData2 = new FormData()
// adding items manually
formData2.append('event', 'Advent')

Kérésszöveg – URLSearchParams

<form>
  <input type="text" name="event" value="Advent">
  <input type="text" name="year" value="2019">
</form>
const form = document.querySelector('form')
const formData = new FormData(form);
formData.append('place', 'St Gerardus Church')

// Option 1.
const params = new URLSearchParams(formData);
const queryString = params.toString();
// "event=Advent&year=2019&place=St+Gerardus+Church"

// Option 2.
const  url = new URL("http://example.com/foo.php")
Array.from(formData).forEach(([key, value]) => url.searchParams.append(key, value))
const queryString = url.searchParams.toString()
url.toString()
// http://example.com/foo.php?event=Advent&year=2019&place=St+Gerardus+Church

GET adatok küldése

const form = document.querySelector('form')
const formData = new FormData(form)
const url = new URL("http://example.com/foo.php")
Array.from(formData).forEach(([key, value]) => url.searchParams.append(key, value))
const xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.send(null)
async function get(url) {
  const response = await fetch(url)
}

POST adatok küldése

const form = document.querySelector('form')
const formData = new FormData(form)
const url = new URL("http://example.com/foo.php")
const xhr = new XMLHttpRequest()
xhr.open('POST', url)
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(formData)
async function post(url) {
  const response = await fetch(url, {
    method: 'POST',
    body: formData
  })
}

HTTP fejlécek küldése

  • XMLHttpRequest
    • setRequestHeader()
  • fetch
    • Request
    • Headers
const headers = new Headers()
headers.append('Content-Type', 'text/plain')

const options = {
  method: 'POST',
  headers: headers,
}

const request = new Request(url, options)
const response = await fetch(request)

AJAX kérés jelzése – kliens

XMLHttpRequest

const xhr = new XMLHttpRequest()
xhr.open('GET', `foo.php`)
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.send(null)

fetch

async function get() {
  const response = await fetch(`foo.php`, {
    headers: {
      'X-Requested-With': 'XMLHttpRequest'
    }
  })
  // ...
}

AJAX kérés jelzése – szerver

if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && 
  strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest' ) {
  //request is ajax
}

De nem lehetünk benne biztosak!!!

JSON küldése

const data = {
  type: 'drama',
  favourites: [
    'Macbeth',
    'Hamlet',
  ]
}
const xhr = new XMLHttpRequest()
xhr.open('GET', `foo.php`)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send(JSON.stringify(data))
async function postJSON(url, data) {
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(data)
  })
}

Fájlfeltöltés

<form>
  <input type="file" name="photo">
</form>
const form = document.querySelector('form')
const formData = new FormData(form);
const xhr = new XMLHttpRequest()
xhr.open('POST', url)
xhr.send(formData)
async function post(url) {
  const response = await fetch(url, {
    method: 'POST',
    body: formData
  })
}

Válaszformátumok

Válaszformátumok

  • A válasz az előállítás módjától függően lehet
    • Statikus tartalom
    • Dinamikus tartalom
  • A kliens szempontjából mindegy, hogy ki állítja elő a tartalmat (nem is tud róla)
  • Formátumok
    • Egyszerű szöveg
    • JSON
    • HTML
    • XML
    • JavaScript
    • stb. (pl. CSS,…)

Technikai háttér

XMLHttpRequest

  • responseType
    • text
    • json
    • document
    • blob
    • arraybuffer

fetch

  • Response objektum
    • .text()
    • .json()
    • .formData()
    • .blob()
    • .arrayBuffer()

Szöveges válasz

alma,körte,szilva,barack,eper,málna,szeder
const xhr = new XMLHttpRequest()
xhr.addEventListener('load', function () {
  const fruits = this.response.split(',');
})
xhr.open('GET', `fruits.txt`)
xhr.responseType = 'text'
xhr.send(null)
async function text() {
  const response = await fetch(`fruits.txt`)
  const text = await response.text()
  const fruits = text.split(',');
}

Ritkán használatos, bármi lehet, egyedi feldolgozás

JSON válasz

[
  "alma",
  "körte",
  "szilva",
  "barack",
  "eper",
  "málna",
  "szeder"
]
const xhr = new XMLHttpRequest()
xhr.addEventListener('load', function () {
  const fruits = this.response;
})
xhr.open('GET', `fruits.json`)
xhr.responseType = 'json'
xhr.send(null)
async function json() {
  const response = await fetch(`fruits.json`)
  const fruits = await response.json()
}

Legelterjedtebb, egyszerű

HTML válasz

<ul>
  <li>alma</li>
  <li>körte</li>
  <li>szilva</li>
  <li>barack</li>
  <li>eper</li>
  <li>málna</li>
  <li>szeder</li>
</ul>
const xhr = new XMLHttpRequest()
xhr.addEventListener('load', function () {
  const fruitsHTML = this.response; // text or document
  // innerHTML
})
xhr.open('GET', `fruits.html`)
xhr.responseType = 'text' // or 'document'
xhr.send(null)
async function html() {
  const response = await fetch(`fruits.html`)
  const fruitsHTML = await response.text()
  // innerHTML
}

Mikroformátum (AHAH), egyszerű feldolgozás

XML válasz

<?xml version="1.0" encoding="UTF-8"?>
<fruits>
  <fruit>alma</fruit>
  <fruit>körte</fruit>
  <fruit>szilva</fruit>
  <fruit>barack</fruit>
  <fruit>eper</fruit>
  <fruit>málna</fruit>
  <fruit>szeder</fruit>
</fruits>
const xhr = new XMLHttpRequest()
xhr.addEventListener('load', function () {
  const fruitsDOM = this.response;
  const fruitsElems = fruitsDOM.getElementsByTagName('fruit');
  const fruits = [];
  for (let i = 0; i < fruitsElems.length; i++) {
    fruits.push(fruitsElems[i].firstChild.nodeValue);
  };
})
xhr.open('GET', `fruits.xml`)
xhr.responseType = 'document'
xhr.send(null)

Szabványos adatleírási formátum, ritka

JavaScript kód

function getFruits() {
  return [
    "alma",
    "körte",
    "szilva",
    "barack",
    "eper",
    "málna",
    "szeder"
  ];
}
const xhr = new XMLHttpRequest()
xhr.addEventListener('load', function () {
  eval(this.response);
  const fruits = getFruits();
})
xhr.open('GET', `fruits.js`)
xhr.responseType = 'text'
xhr.send(null)
const xhr = new XMLHttpRequest()
xhr.addEventListener('load', function () {
  const script = doc.createElement('script')
  const src = URL.createObjectURL(this.response)
  script.src = src;
  document.body.appendChild(script);
})
xhr.open('GET', `fruits.js`)
xhr.responseType = 'blob'
xhr.send(null)

Lazy loading

További tudnivalók

Korlátozások

  • Biztonsági okokból nem engedélyezett különböző domainek között ajax kommunikáció
    • Kivéve JSONP (JSON with Padding): script elem dinamikus beszúrásával idegen tartalom futtatása
  • CORS (Cross-Origin Resource Sharing)
  • Nincsen szerver push itt sem: ajax kéréseket mindig a kliens kezdeményezi

JSONP

http://www.omdbapi.com/?s=hobbit&callback=processResponse
processResponse({"Search":[{"Title":"The Hobbit: An Unexpected Journey","Year":"2012","imdbID":"tt0903624","Type":"movie"},{"Title":"The Hobbit: The Desolation of Smaug","Year":"2013","imdbID":"tt1170358","Type":"movie"},{"Title":"The Hobbit","Year":"1977","imdbID":"tt0077687","Type":"movie"}]});
function omdbCall(search) {
  const script = document.createElement('script');
  script.src = 'http://www.omdbapi.com/?s='+search+'&callback=processResponse';
  document.body.appendChild(script);
}
function processResponse(json) {
  console.log(json);   
}

JSON kommunikáció

Feltöltés

  • Tetszőleges adatszerkezet
    • objektum, tömb
  • Sorosítás → szöveg
    • sz = JSON.stringify(adat)
  • Felküldés
    • Űrlap
    • AJAX

Felküldés – űrlap

<form id="formMent" action="" method="post">
  <input type="hidden" name="adat" id="adat">
  <input type="submit" value="Mentés">
</form>
//Eseménykezelők hozzárendelése
function init () {
  $('formMent').addEventListener('submit', mentes, false);
}
//Elküldéskor
function mentes (e) {
  var adat = ...;
  $('adat').value = JSON.stringify(adat);
}

Felküldés – AJAX

//Eseménykezelők hozzárendelése
function init () {
  $('gombMent').addEventListener('click', mentes, false);
}
//Elküldéskor
function mentes (e) {
  var adat = ...;
  var s = JSON.stringify(adat);
  ajax({
    url: 'mentes.php',
    mod: 'post',
    postadat: 'adat=' + s,
    siker: function () {
      //...
    }
  });
}

Fogadás – űrlap, AJAX

$s = $_POST['adat'];
//s feldolgozása, elmentése, pl.
$tomb[] = $s;

// vagy

$adat = json_decode($s, true);

Leküldés – AJAX

Szerver

$s = json_encode($adat);
echo $s;

Kliens

function leker(e) {
  ajax({
    url: 'leker.php',
    mod: 'get',
    getadat: 'azon=12',
    siker:  function (xhr, text) {
      var json = JSON.parse(text);
      console.log(json);
      //JSON feldolgozása
    }
  });
}

Leküldés – szkript

Verzió 1.

<?php
  $s = json_encode($adat);
?>
<script>
  var adat = <?php echo $s; ?>
</script>

Verzió 2.

<?php
$id = $_GET['id'];
?>
<!doctype html>
<html>
<head>
  <!-- ... -->
  <script type="text/javascript" src="adat.php?id=<?php echo $id; ?>"></script>
  <!-- ... -->
</head>

Megjegyzendő

  • Elővezérlő használata
    • index.php
  • Modell-Nézet-Vezérlő minta
    • vezérlő: I/O
    • modell: adatok
    • nézet: sablon
  • AJAX
    • GET, POST adatok küldése