Web programming

Client-server data exchange, testing, tester

Horváth Győző
Associate professor
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

Összegzés

Összegzés

Client

  • JavaScript
  • DOM
  • Events
  • Canvas
  • Code organization
  • AJAX

Server

  • Program → HTML
  • Output (HTML generation)
  • Input (link, form)
  • Data storage: files
  • Session management
  • Authentication
  • Code organization

Summary

  • Language
  • Technology
  • Tools
  • Documentation
    • official
    • Google

Client-server data exchange

Variations

Considerations

  • Data format
    • Urlencoded Request Text
    • JSON
  • Nature
    • Page reload
    • AJAX
  • Method
    • GET
    • POST

Client → server 1.

Form data, traditional

<pre><?php print_r($_POST) ?></pre>
<form action="" method="post">
  Data1: <input type="text" name="data1"> <br>
  Data2: <input type="text" name="data2"> <br>
  <button>Send</button>
</form>

Client → server 2.

Form data, AJAX

<form action="" method="post">
  Data1: <input type="text" name="data1"> <br>
  Data2: <input type="text" name="data2"> <br>
  <button>Send</button>
</form>
document.querySelector('form').addEventListener('submit', onSubmit)
function onSubmit(e) {
  e.preventDefault()
  
  const formData = new FormData(this)
  
  const xhr = new XMLHttpRequest()
  xhr.open('post', 'server-ajax.php')
  xhr.addEventListener('load', function () { console.log(this.responseText) })
  xhr.send(formData)
}
<?php print_r($_POST);

Client → server 3.

Form data + JSON, traditional

<pre><?php print_r($_POST) ?></pre>
<form action="" method="post">
  Data1: <input type="text" name="data1"> <br>
  Data2: <input type="text" name="data2"> <br>
  <input type="hidden" name="json">
  <button>Send</button>
</form>
const data = {
    prop1: 'value1',
    prop2: [1, 2, 3]
}
document.querySelector('form').addEventListener('submit', onSubmit)
function onSubmit(e) {
    this.querySelector('[name=json]').value = JSON.stringify(data)
}

Client → server 4.

Form data + JSON, AJAX

<form action="" method="post">
  Data1: <input type="text" name="data1"> <br>
  Data2: <input type="text" name="data2"> <br>
  <button>Send</button>
</form>
const data = { prop1: 'value1', prop2: [1, 2, 3] }
document.querySelector('form').addEventListener('submit', onSubmit)
function onSubmit(e) {
  e.preventDefault()
  const formData = new FormData(this)
  formData.append('json', JSON.stringify(data))

  const xhr = new XMLHttpRequest()
  xhr.open('post', 'server-ajax.php')
  xhr.addEventListener('load', function () { console.log(this.responseText) })
  xhr.send(formData)
}
<?php print_r($_POST);

Client → server 5.

JSON, traditional

Data1: <input type="text" name="data1"> <br>
Data2: <input type="text" name="data2"> <br>
<form action="" method="post">
  <input type="hidden" name="json">
  <button>Send</button>
</form>
document.querySelector('form').addEventListener('submit', onSubmit)
function onSubmit(e) {
  const data = {}
  data.data1 = document.querySelector('[name=data1]').value
  data.data2 = document.querySelector('[name=data2]').value
  this.querySelector('[name=json]').value = JSON.stringify(data)
}

Client → server 6.

JSON, AJAX

Data1: <input type="text" name="data1"> <br>
Data2: <input type="text" name="data2"> <br>
<button>Send</button>
document.querySelector('button').addEventListener('click', onSubmit)
function onSubmit(e) {
  e.preventDefault()
  const data = {
    data1: document.querySelector('[name=data1]').value,
    data2: document.querySelector('[name=data2]').value
  }
  const xhr = new XMLHttpRequest()
  xhr.open('post', 'server-ajax-body.php')
  xhr.addEventListener('load', function () { console.log(this.responseText) })
  xhr.send(JSON.stringify(data))
}
<?php
$json = file_get_contents('php://input');
$data = json_decode($json, true); print_r($data);

Server → client 1.

JSON, traditional

<?php
  $par = $_GET['par'] ?? 'default';
  $data = [
    'data1' => $par,
    'data2' => [1, 2, 3],
  ];
  $json = json_encode($data);
?>
<h3>Some page</h3>
<script>
  const data = JSON.parse('<?= $json ?>')
</script>
<script>
  console.log(data)
</script>

Server → client 2.

JSON, traditional

<?php $par = $_GET['par'] ?? 'default'; ?>
<h3>Some page</h3>
<script src="get-data.php?par=<?= $_GET['par'] ?>"></script>
<script>
  console.log(data)
</script>
<?php
  $par = $_GET['par'];
  $data = [
    'data1' => $par,
    'data2' => [1, 2, 3],
  ];
  $json = json_encode($data);
?>
const data = JSON.parse('<?= $json ?>')

Server → client 3.

JSON, AJAX

<h3>Some page</h3>
<script>
  var url = new URL(window.location.href)
  const par = url.searchParams.get('par')

  const xhr = new XMLHttpRequest()
  xhr.open('get', `get-data-ajax.php?par=${par}`)
  xhr.addEventListener('load', function () { console.log(this.response) })
  xhr.responseType = 'json'
  xhr.send(null);
</script>
<?php
$par = $_GET['par'];
$data = [
  'data1' => $par,
  'data2' => [1, 2, 3],
];
echo json_encode($data);

Server → client 4.

JSON, AJAX

<h3>Some page</h3>
<button>Get data</button>
document.querySelector('button').addEventListener('click', onClick)
function onClick(e) {
  var url = new URL(window.location.href)
  const par = url.searchParams.get('par')

  const xhr = new XMLHttpRequest()
  xhr.open('get', `get-data-ajax.php?par=${par}`)
  xhr.addEventListener('load', function () { console.log(this.response) })
  xhr.responseType = 'json'
  xhr.send(null);
}
<?php
$par = $_GET['par'];
$data = [ 'data1' => $par, 'data2' => [1, 2, 3] ];
echo json_encode($data);

Testing

Testing PHP functions

Unit tests, integration tests

Kahlan

include('./functions.php');
describe('Filtering positive numbers', function () {
    it('returns an empty array to an empty array', function () {
        expect(kivalogatas([]))->toBe([]);
    });
    it('returns positive to all positive arrays', function () {
        expect(kivalogatas([1, 3, 5, 7]))->toBe([1, 3, 5, 7]);
    });
});

DEMO

UI tests

  • JS: operating a loaded page
  • PHP: generates HTML pages
  • Functional testing
  • Puppeteer

Introducing Tester

Task + tester

(The order of execution is arbitrary)

Getting Started

  1. Create a folder
  2. Copy data (write access)
  3. Creating pages (static)
  4. Test
  5. Basic dependencies
    • jsonio.php
    • jsonstorage.php
    • avengersrepository.php
    • missionsrepository.php

1. task

<?php
include('avengersrepository.php');
include('missionsrepository.php');

function dash_name($name) {
  return str_replace(" ", "-", strtolower($name));
}

$avengersRepsoitory = new AvengersRepository();
$missionsRepsoitory = new MissionsRepository();
$avengers = $avengersRepsoitory->all();
?>
<?php foreach($avengers as $av) : ?>
  <li class="list-group-item" data-id="<?= $av['_id'] ?>">
    <div class="d-flex align-items-center p-1">
      <span class="avenger <?= dash_name($av['name']) ?>"></span>
      <h5 class="m-2 flex-fill">
        <a href="card.php?id=<?= $av['_id'] ?>">
          <?= $av['name'] ?>
        </a>
        <small class="text-muted">
          <?= $av['real_name'] ?: ''  ?>
          <i class="fas <?= $av['terrial'] ? 'fa-globe-africa' : 'fa-rocket'  ?>"></i>
        </small>
      </h5>
      <div class="d-flex flex-nowrap">
        <span class="mx-1 badge badge-primary"><?= $av['strength'] ?></span>
        <span class="mx-1 badge badge-success"><?= $av['speed'] ?></span>
        <span class="mx-1 badge badge-danger"><?= $av['durability'] ?></span>
      </div>
    </div>
    <img src="http://webprogramozas.inf.elte.hu/webfejl2/zh/avengers/noise.png">
  </li>
<?php endforeach ?>

2. task

<?php
include('avengersrepository.php');

function dash_name($name) {
  return str_replace(" ", "-", strtolower($name));
}

$id = $_GET['id'];
$avengersRepsoitory = new AvengersRepository();
$avengers = $avengersRepsoitory->all();
$avenger = $avengers[$id];
?>
<div class="container">
  <div class="row">
    <div class="col-lg">
      <h2>Avengers</h2>
      <div class="card">
        <div class="row no-gutters">
          <div class="col-md-3">
            <span class="card-img avenger <?= dash_name($avenger['name']) ?>" style="width: 180px; height: 240px;">
          </div>
          <div class="col-md-9">
            <div class="card-body">
              <h3 class="card-title"><?= $avenger["name"] ?></h3>
              <dl class="row">
                <dt class="col-sm-3">Real name</dt>
                <dd class="col-sm-9">
                  <?= $avenger["real_name"] ?>
                </dd>
                <dt class="col-sm-3">Terrial</dt>
                <dd class="col-sm-9">
                  <?= $avenger["terrial"] ? "Yes" : "No" ?>
                </dd>
                <dt class="col-sm-3">Strength</dt>
                <dd class="col-sm-9">
                  <span class="badge badge-primary"><?= $avenger["strength"] ?></span>
                </dd>
                <dt class="col-sm-3">Speed</dt>
                <dd class="col-sm-9">
                  <span class="badge badge-success"><?= $avenger["speed"] ?></span>
                </dd>
                <dt class="col-sm-3">Durability</dt>
                <dd class="col-sm-9">
                  <span class="badge badge-danger"><?= $avenger["durability"] ?></span>
                </dd>
              </dl>
            </div>
          </div>
        </div>
      </div>
      <a href="index.php" class="btn btn-primary">Back to the main page</a>
    </div>
  </div>
</div>

4. task

// 4/a
document.querySelector("form [type=range][name=strength] + *").innerHTML = document.querySelector("form [type=range][name=strength]").value
document.querySelector("form [type=range][name=speed] + *").innerHTML = document.querySelector("form [type=range][name=speed]").value
document.querySelector("form [type=range][name=durability] + *").innerHTML = document.querySelector("form [type=range][name=durability]").value
// 4/b
document.querySelector("form").addEventListener("input", onRangeInput);
function onRangeInput(e) {
  if (e.target.matches("[type=range]")) {
    const out = e.target.nextElementSibling;
    out.innerHTML = e.target.value;
  }
}

5/a. task

document.querySelector(".thanos").addEventListener("click", onStoneClick);
function onStoneClick(e) {
  if (e.target.matches(".stone")) {
    e.target.classList.add("collected")
  }
}

5/b. task

document.querySelector(".thanos").addEventListener("click", onStoneClick);
function onStoneClick(e) {
  if (e.target.matches(".stone")) {
    const place = e.target.parentNode;
    const {x, y} = place.getBoundingClientRect();
    e.target.style.left = x + "px";
    e.target.style.top = y + "px";
  }
}
document.querySelector(".thanos").addEventListener("transitionend", onTransitionEnd)
function onTransitionEnd(e) {
  if (e.target.matches(".stone")) {
    const place = e.target.parentNode;
    const {x, y} = place.getBoundingClientRect();
    if (Math.abs(x - parseInt(e.target.style.left)) < 10 &&
        Math.abs(y - parseInt(e.target.style.top)) < 10) {
      e.target.classList.add("collected")
    }
  }
}

5/c. task

function onTransitionEnd(e) {
  if (e.target.matches(".stone")) {
    const place = e.target.parentNode;
    const {x, y} = place.getBoundingClientRect();
    if (Math.abs(x - parseInt(e.target.style.left)) < 10 &&
        Math.abs(y - parseInt(e.target.style.top)) < 10) {
      e.target.classList.add("collected")
    }
    if (checkGauntlet()) {
      document.querySelector(".gauntlet").classList.add("activated")
    }
  }
}
function checkGauntlet(e) {
  return Array.from(document.querySelectorAll(".stone")).every(st => st.classList.contains("collected"))
}