Selenium teszt a böngészőből

Először is letöltjük a Selenium IDE-t a https://www.selenium.dev/ webcímről. Ez egy ipari standard a webalkalmazások automatizált teszteléséhez. Van robusztus megoldása is, a Selenium WebDriver, illetve elosztott teszteléshez a Selenium Grid. Nekünk bőven megfelel egy Chrome plugin a fejlesztő Debian 10.7 Linux gépemre – ahol Chrome-ot használok. Ez a plugin Selenium IDE része. Segítségével elég egyszerűen megnyomhatjuk a “like” gombot mondjuk ezerszer. 🙂

Miután beinstalláltuk a Chrome plugin-t (bővítményt), felvesszük a teszt esetet mintegy makróként, majd lejátszuk. Látható, hogy növekszik a like-ok száma. Lássuk a record/play képernyőrészleteket!.

Ennyi dióhéjban a webalkalmazások tesztelése. Tetszőleges teszt-utakat, teszt-eseteket vehetünk fel egy bonyolultabb weblap esetén, ahogy itt ezt egyszerűen “click”-nek neveztük a “mylike” nevű tesztünkben. Innentől ezt a scriptet bejátszhatjuk a robusztus script-futtató Selenium alrendszerekbe is akár. 🙂

A futtatást nézve egy másodperc alatt lefut a teszt oldal újratöltéssel együtt – persze ez nem mérvadó adat. Többet a skálázhatóságról, azaz a teljesítményről csak akkor tudhtunk meg, ha automatizáljuk pl. Selenium JavaScript motorral a végrehajtást.

A Selenium JavaScript kliens és WebDriver az alábbi oldalakról tölthető le:

https://www.selenium.dev/downloads/

https://www.npmjs.com/package/selenium-webdriver/v/4.0.0-alpha.8

Egyelőre többet erről nem árulok el, mert elvenném a tesztelők kenyerét. Annyit azonban elmondhatok programozóként, hogy egyszerű JavaScript-ben automatizálható a tesztelési folyamat, sőt: terheléses tesztelés is könnyen elvégezhető.

Teszt-világban kétféle filozófia létezik amúgy: ellenséges tesztelő (és coder) illetve a barátságos. Én ez utóbbit támogatom. Együttműködve jobb eredményt érhet el a fejlesztő és a teszt csapat. 🙂

Biztos ami biztos, ráeresztettem a https://wlammpp.wordpress.com/2020/08/14/meres-cgi-bash-perl-c-fastcgi-vagy-mod_perl-melyik-a-gyorsabb/ fejezetben írt parancssori script-et 1000x, hogy mennyi idő alatt fut le a mod_perl “like” alkalmazásunk tesztüzemben. Íme az eredmény.

gvamosi@gergo1:~/Documents/http_meres$ l
 total 4
 -rwxr-xr-x 1 gvamosi gvamosi 196 Aug 14 04:31 1000times_curl.sh
gvamosi@gergo1:~/Documents/http_meres$ ./1000times_curl.sh http://localhost/perl/mylike-dbi.cgi
 1610127040.356192334
 1610127051.815938599
 11.459746265

11,5 másodperc – ez adatbázis eléréssel 1000 lekérésre nem rossz teljesítmény! Nos, Mr. Zuckerberg, érdeklődik az ultragyors like-megoldásom iránt? 🙂

A “like” gomb működése és a cookie-k

Nézzük a két részletet, amelyek az előző részből kimaradtak. Az első rész a számláló növeléséért felelős. Ezt egy sima adatbázis UPDATE végzi a DBI interfészen keresztül. Fontos, hogy a finish() metódussal felszabadítsuk az utasítást, különben “lógna” a levegőben azaz a memóriában. A cookie-zás (sütizés) az egy kliensből történő többszöri like-olás megakadályozását szolgálja és az üzenetfejlécben állítjuk illetve kérdezzük le, mégis teszt célokból jelen kódban ki van “ütve”.

 my $cookie = $r->headers_in->get('Cookie');
 my $uri = URI::Encode->new( { encode_reserved => 1 } );
 my $uri_encoded_referer = $uri->encode($http_referer);
 #print $uri_encoded_referer;
 my $wasliked = $cookie ne '' && $cookie =~ /$uri_encoded_referer/;
 if ($like && ! $wasliked ) {
  # increment counter
  my $sthupd = $dbh->prepare('UPDATE mylike SET counter = counter + 1 WHERE approved = 1 and referer = ?');
  $sthupd->execute($http_referer);
  $sthupd->finish();

  my $cookie = CGI::Cookie->new(-name  => 'mylike'.$http_referer,
                                -value => 'SET');
  $r->headers_out->set('Set-Cookie' => $cookie);
  $wasliked = 1;

  # !! TEST ONLY !! temporary turn off cookie-mechanism !! COMMMENT OUT FOLLOWING TWO LINES FOR USE !!
  $r->headers_out->set('Set-Cookie' => '');
  $wasliked = 0;
 }

A like változó értékadása az előző részben szerepel, a GET metódus révén átadott paraméterként kerül kiértékelésre. Az alábbi gomb onclick javascript eseményéből kerül meghívásra like=1 URI változóként.

my $req = CGI->new($r);
my $like = $req->param('like');

A második kimaradt részecske maga a “like” gomb megjelenítéséért felelős. Ha már like-oltuk az oldalt, akkor disabled a gomb, azaz nem lehet többször megnyomni. 🙂

 print "<input type=\"button\" onclick=\"\$('#mylike').load('http://localhost/perl/mylike-dbi.cgi?like=1');\" value=\"like\" ".($wasliked?'disabled ':'')."/>\n";

És akkor nézzük meg a kimenetet!

Ennyi! That’s all! 🙂

Megjegyzés: azért a felhasznált javascript miatt ügyelni kell arra, hogy a site-unk include-olja script forrásként a jquery lib-eket.

Az utolsó részben megnézzük, hogyan lehet letesztelni illetve a teherbírását megmérni a like modulunknak.

A like-számlálójának működése, a REFERER a HTTP fejlécből

Minden további leírás helyett ideollózom a mylike-dbi.cgi azon részeit, amelyek a like-ok számának megjelenítéséért felelősek, illetve amelyek az első bejegyzés létrehozására alkalmasak.

#!/usr/bin/perl

use strict;
use warnings;
use DBI;
use Apache2::RequestRec ();
use Apache2::RequestUtil ();
use CGI;
use CGI::Cookie;
use URI::Encode;


my $r = Apache2::RequestUtil->request;

my $req = CGI->new($r);
my $like = $req->param('like');
#print $like;
my $http_referer = $ENV{'HTTP_REFERER'};

my $dbh = DBI->connect('DBI:mysql:mylike', 'mylike', 'mylike1'
                   ) || die "Could not connect to database: $DBI::errstr";

if ($http_referer) {

 # LIKE BUTTON CODE WAS HERE ##################

 my $sth = $dbh->prepare('SELECT id FROM mylike WHERE approved = 1 and referer = ?');
 $sth->execute($http_referer);
 my @result = $sth->fetchrow_array();
 my $len = @result;
 #print "numof result $len\n";
 $sth->finish();

 if ($len == 0) {
  # insert data into the links table
  my $sql = "INSERT INTO mylike (referer) VALUES(?)";

  my $stmt = $dbh->prepare($sql);

  # execute the query
  if($stmt->execute($http_referer)) {
   # ok
   #print "Mylike $http_referer inserted successfully";
  }

  $stmt->finish();
 }

 $sth = $dbh->prepare('SELECT counter FROM mylike WHERE approved = 1 and referer = ?');
 $sth->execute($http_referer);
 @result = $sth->fetchrow_array();

 print "$result[0] like(s)<br>\n";

 # LIKE BUTTON CODE WAS HERE ##################

 $sth->finish();
 
} else {
 # test

 my $results = $dbh->selectall_hashref('SELECT * FROM mylike', 'id');
 foreach my $id (keys %$results) {
  print "!!TEST!! ID $id: referer is $results->{$id}->{'referer'}, counter is $results->{$id}->{'counter'}\n";
 }

}

$dbh->disconnect();

A kód magyarázata.

Kivesszük a fejlécből a HTTP_REFERER-t. Ha ez definiált, akkor “éles” működés, ha nem akkor tesztüzemmód. Tesztüzemben az alábbi kimenetet kapjuk.

Látható, hogy két REFERER szerepel az adatbázisunkban. Ezeket sorolja fel tesztüzemmódban a script.

Éles üzemnél megvizsgálja a programunk, hogy létezik-e adatbázis-bejegyzés adott REFERER-rel. Ha még nincs, létrehoz egyet 0-ás like-oltsággal. 🙂 Ezután egy sima print utasítással kijelzi a kedvelések számát.

A következő részben megnézhetjük magának a “like” gombnak is a működését és a mögötte álló programkódot is. Ebben a forrásban ezt a

# LIKE BUTTON CODE WAS HERE ##################

részek jelölik.

A SELECT-jeinkben az approved = 1 az adminisztrátori funkció további lehetőségét hordozza magában. Tehát le lehet “tiltani” egy-egy beágyazást.

Látható a kód elején a use kulcsszavaknál, hogy használunk egynéhány modult. 🙂 Szinte gyerekjáték a Perl programozás.

Megjegyzem, hogy ez a like megvalósítás különböző webcímű (URI) weboldalanként ad lehetőséget like-olásra. Egy webcímen belül több like nem lehet. Nem a tökély a cél, csupán a megvalósíthatóság bemutatása.

A Hello World! mod_perl DBI alkalmazás

A /etc/apache2/conf-enabled/mod_perl.conf file-t az alábbiak szerint módosítottuk.

PerlModule ModPerl::Registry

Alias /perl /var/www/perl
<Directory /var/www/perl>
   AddHandler perl-script .cgi .pl
   PerlResponseHandler ModPerl::Registry
   Options +ExecCGI
</Directory>

Lehet saját handler-t is írni, de most enm tértünk ki erre a lehetőségre. 🙂

Nézzük a hello-world-dbi.cgi programocskánkat majd a beágyazását az apartman “Képek” oldalába.

#!/usr/bin/perl

use strict;
use warnings;
use DBI;
use Apache2::RequestRec ();
use Apache2::RequestUtil ();

my $r = Apache2::RequestUtil->request;

print "The referer in your HTTP-Header is: ", $ENV{'HTTP_REFERER'}, "\n";

my $dbh = DBI->connect('DBI:mysql:mylike', 'mylike', 'mylike1'
               ) || die "Could not connect to database: $DBI::errstr";

print "<br>\n";

my $results = $dbh->selectall_hashref('SELECT * FROM mylike', 'id');
foreach my $id (keys %$results) {
 print "Value of ID $id is $results->{$id}->{'referer'}\n";
}

$dbh->disconnect();

Használjuk a DBI és az új Apache2-es Request modulokat, majd lekérdezzük a HTTP_REFERER-t illetve egy select-tel belefetchelünk az adatbázisba. 🙂

Figyelem! Nagyon fontos a helyes beállítás az apache config oldalon.

A kis appunkat beágyazzuk mint widget-et az alábbi módon.

<div id="mylike"></div>
<script>$("#mylike").load("http://localhost/perl/hello-world-dbi.cgi</script>

És akkor lássuk az eredményt.

A “like” alkalmazás előtt

Mit is kell tudjon egy tetszőleges oldalba beágyazható like alkalmazás? Tulajdonképpen esetünkben arról van szó, hogy egy tetszőleges oldalon a weblapról egy tetszésnyilvánítást rögzíthessünk illetve kijelezzük a tetszésnyilvánítások számát. Nyilvánvalóan adatbázisra lesz szükségünk a dinamikus működés biztosításához.

I. Először is két részre kell bontsuk a megvalósítást. Van egy számláló-kijelzés funkciónk, azaz hogy hány like volt eddig illetve egy “like” gomb, hogy a számláló értékét növelhessük. Továbbá beágyazhatóvá kell tennünk egy tetszőleges weboldalba a funkciót. Innentől like-widget-ről beszélhetünk (https://hu.wikipedia.org/wiki/Vez%C3%A9rl%C5%91elem).

II. Másodszor is hozzá kell kötnünk a like widget-ünket a megjelenés helyéhez. Ez a funkcionalitás a REFERER HTTP fejléc attribútum adatbázisban letárolásával és vizsgálatával oldható meg. Ez az attribútum a hivatkozott oldal címét tartalmazza (https://hu.wikipedia.org/wiki/Referer). Innen fogjuk tudni, hogy melyik a “hívó” webcím.

III. Harmadszor pedig meg kell vizsgálnunk, hogy a like funkciót felhasználóhoz vagy böngésző példányhoz kössük-e nyilvánvalóan azért, hogy ne lehessen egy személynek többször like-olnia ugyanazon a helyen. Ha felhasználóhoz kötjük, úgy a facebook stílusú megoldást kapjuk. Ez nekünk azonban túl bonyolult volna, így egyszerűen a böngészőben fogunk letárolni egy cookie-t, hogy az adott kliensen már történt egy like-olás azaz egy tetszésnyilvánítás adott webcímre.

A tervezési megfontolások nagyjából ennyit tesznek ki. Még foglalkozhatnánk a színes-szagos külcsínyről is, de azt most elhanyagoljuk. Egyszerűen egy félkövér szám lesz a like-ok száma, alatta pedig sortöréssel egy “like” feliratú gomb. Ha nem “szürke” azaz “disabled”, akkor adott kliens még nem like-olt, azaz lehet like-olni. Ennyi. 🙂

Admin funkció, foglalás jóváhagyása, AJAX JSON :)

Most már, hogy egy űrlapon keresztül foglalásokat lehet elküldeni, szükségünk lesz egy admin oldalra, ahol az apartman ügyintézője meg tudja tekinteni, illetve jóvá tudja hagyni a foglalásokat.

Hogy mit jelent itt az hogy AJAX JSON? Elég érdekes öszvér elnevezés. 🙂 AJAX-szal normál esetben egy XML adatstruktúrát vinnénk át az aszinkron javascript üzenetben, ami valahogy így nézne ki egy konkrét foglalás (reservation) adataira.

<xml>
<reservation>
<apartment_id>1</apartment_id>
<approved>1</approved>
..
</reservation>
</xml>

Mégis azonban az ún. JSON formátumot használjuk, ami egy konkrét esetben az alábbi szerverválaszt adja.

{
apartment_id: "1",
approved: "1",
checkin: "2020-07-29",
checkout: "2020-08-01",
created_at: "2020-08-03 03:34:11",
email: "aaa@aaa.hu",
id: "10",
locale: null,
message: "aaa",
name: "aaa",
phone: "",
updated_at: "2020-08-05 01:08:08"
}

Látható, hogy egy eléggé egyszerű adatszerkezet. A JSON leírása részletesen az alábbi címen érhető el: https://hu.wikipedia.org/wiki/JSON.

És akkor nézzük meg a konkrét implementációt. Először is bővítenünk kell a models/Reservations_model.php modellünket három metódussal.

        public function get_reservations() {
                $this->db->select()->from('reservations')->order_by('created_at', 'DESC');
                $query = $this->db->get();
                return $query->result();
        }

        public function get_reservation($id) {
                $query = $this->db->get_where('reservations', array('id' => $id));
                return $query->result();
        }

        public function approve_reservation($id) {
                $date = date('Y-m-d H:i:s');
                $this->db->set('updated_at', $date);
                $this->db->set('approved', 1);
                $this->db->where('id', $id);
                $this->db->update('reservations');
        }

Az első get_reservations() metódus a foglalások keletkezésének idejével csökkenő sorrendben egy listát ad vissza a foglalások adataival.

A második get_reservation($id) metódus egy konkrét foglalás adatait adja vissza.

A harmadik approve_reservation($id) metódus pedig UPDATE-eli, tehát frissíti a megadott ID-jű foglalás rekordunkat a frissítés dátumával és beállítja az jóváhagyott foglalás értékét 1-esre, ami esetünkben a jóváhagyás tényét jelzi adatbázis szinten.

Nézzük a views/admin.php view-nkat, hogy mi is kerül bele.

<?php
        $my_base_url = '/apartman/';
?>
                                <div class="row">
                                        <div class="col-sm-2">
                                                <div id="fh5co-logo"><a href="index.html">Apartman<span>.</span></a></div>
                                        </div>
                                        <div class="col-sm-10 text-right menu-1">
                                                <ul>
                                                        <li><a href="photos"><?php echo $this->lang->line('menu_photos'); ?></a></li>
                                                        <li><a href="booking_ajax"><?php echo $this->lang->line('menu_booking'); ?></a></li>
                                                        <li><a href="contact"><?php echo $this->lang->line('menu_contact'); ?></a></li>
                                                </ul>
                                        </div>
                                </div>
                                
                        </div>
                </div>
        </nav>
        <div class="container">
                <div id="fh5co-intro">
                        <div class="row animate-box">
                                <div class="col-md-8 col-md-offset-2 col-md-pull-2">
                                        <h2><?php echo $this->lang->line('menu_booking'); ?></h2>
<table border="1">
<tr>
<th>Name</th><th>Email</th><th>Message</th><th>Checkin</th><th>Checkout</th><th>Created at</th><th>Updated at</th><th>Locale</th><th>Approved</th><th></th>
</tr>
<?php foreach ($query as $item):?>
<tr>
<form>
<td><?php echo $item->name;?></td>
<td><?php echo $item->email;?></td>
<td><?php echo $item->message;?></td>
<td><?php echo $item->checkin;?></td>
<td><?php echo $item->checkout;?></td>
<td><?php echo $item->created_at;?></td>
<td id="updated<?php echo $item->id;?>"><?php echo $item->updated_at;?></td>
<td><?php echo $item->locale;?></td>
<td id="approved<?php echo $item->id;?>"><?php echo $item->approved==1?'true':'false';?></td>
<td><input id="approve_button<?php echo $item->id;?>" type="button" value="Approve" class="btn btn-primary btn-modify" <?php echo $item->approved==1?'disabled':'';?>></td>
</form>
<script>
    $(document).ready(function(){
        $( "#approve_button<?php echo $item->id;?>" ).click(function(event)
        {
            $.ajax(
                {
                    type:"post",
                    url: "<?php echo $my_base_url; ?>index.php/apartments/admin_approve_ajax/<?php echo $item->id;?>",
                    data:{ },
                    contentType: "application/json",
                    dataType:"json", 
                    success:function(response)
                    {
                        console.log(response);
                        var obj = $.parseJSON(JSON.stringify(response));
                        $("#updated<?php echo $item->id;?>").html(obj.updated_at);
                        $('#approved<?php echo $item->id;?>').html(obj.approved==1?'true':'false');
                        $('#approve_button<?php echo $item->id;?>').prop('disabled', obj.approved==1?true:false);
                    },
                    error: function(response)
                    {
                        console.log(response);
                        alert("Invalide id=<?php echo $item->id;?>!");
                    }
                }
            );
        });
    });
</script>
</tr>
<?php endforeach;?>
</table>
                                </div>
                        </div>
                </div>
        </div><!-- END container -->

Látható, hogy ebbe egy táblázat került a foglalások adataival, melynek minden sora egy-egy foglalást tartalmaz. Erről az iterációról a <?php foreach ($query as $item):?><?php endforeach;?> blokk gondoskodik. Minden sort a foglalás ID-je azonosít. Minden sorhoz kerül egy-egy JavaScript blokk is a megfelelő Approve (Jóváhagy) gomb megfelelő “clickeseményének lekezelésével.

Felhívnám a figyelmet a JSON üzenet dekódolására. Ez a var obj = $.parseJSON(JSON.stringify(response)); sorban történik. Először szükséges sztringgé alakítani a válaszüzenetet, mivel az objektum-ként érkezik. Ez a JSON.stringify metódussal történik. Utána csak egy sima parse-olásra lesz szükségünk. 🙂

Nézzük meg végül a controllers/Apartments.php controller-ünket. Két új metódussal bővül. Az admin() metódusban csak kirajzoljuk az oldalunkat a modell réteg lista SQL SELECT metódusának meghívásával. Az admin_approve_ajax($id) metódusban pedig SQL UPDATE hívás majd egy SQL SELECT hívás történik a modell rétegben, illetve a válasz üzenet JSON becsomagolása.

        public function admin() {
                
                $this->load->model('reservations_model');

                $data['query'] = $this->reservations_model->get_reservations();

                $this->template->set('title', 'Admin');
                $this->template->load('menu_layout', 'contents' , 'admin', $data);
        }

        public function admin_approve_ajax($id) {

                $this->load->model('reservations_model');

                $this->reservations_model->approve_reservation($id);
                $data = $this->reservations_model->get_reservation($id)[0];

                header('Content-Type: application/json');
                echo json_encode($data);
        }

Látható, hogy viszonylag egyszerűen megoldottuk az egész adminisztrátori funkcionalitást az AJAX form vezérlés / adatátvitel és a JSON adatformátum hsználatával. 🙂

És akkor lássuk az admin oldalt (http://localhost/apartman/index.php/apartments/admin), egy jóváhagyott foglalással!

Látható, hogy a legfelső, az Approved true, tehát jóváhagyott rendelés – mivel csak angol nyelven implementáltam mindent az egyszerűség kedvéért – melletti Approve gomb már kiszürkült, nem megnyomható újra. Az Updated at – frissítés dátuma – pedig a késői vagy kora hajnali órát jelzi, amikor elkészültem a pilot kódokkal. 🙂

jQuery UI datepicker – a checkin és checkout mezők ellátása dátumválasztóval

Szeretnénk elérni, hogy a dátum mezőket ki lehessen választani a népszerű módon úgy, hogy “feljön” egy kis naptár egy ún. popup ablakban, ahol rákattintunk a kívánt napra, és a kivlasztott dátum bemásolódik a beviteli mezőnkbe a megadott formátumban.

Ehhez le kell tölenünk a jQuery UI-t a https://jqueryui.com/download/all/ webcímről, majd az assets könyvtárunk megfelelő js, css és css/image könyvtáraiba kell bemásolnunk a megfelelő file-okat.

A jQuery UI ún. datepicker – dátumválasztó widget-ét fogjuk használni. Leírása itt https://jqueryui.com/datepicker/.

A views/layouts/menu_layout.php sablonunkba a fejlécben (<head>) fell kell vegyük a jquery-ui.css illetve jquery-ui.js file-okat.

        <!-- jquery ui -->
        <link rel="stylesheet" href="<?php echo $my_base_url?>assets/css/jquery-ui.css">

        <!-- jquery ui -->
        <script src="<?php echo $my_base_url?>assets/js/jquery-ui.js"></script>

Ezek után a views/booking_ajax.php view-nkban a <script> részhez az alábbi két új függvényt fűzzük hozzá a $(document).ready(function() rész alá új elemként.

        $( "#checkin" ).datepicker({ dateFormat: "yy-mm-dd" });
        $( "#checkout" ).datepicker({ dateFormat: "yy-mm-dd" });

Itt egy sima inicializálásról van szó ID Selector alapján, a megfelelő ISO 8601 formátumban. Már működik is! 🙂

Lehetne hozzá sőt kell is még írni egy belső validációt, ami mégegyszer ellenőrzi a dátumformátumot adatbázisba írás előtt, de most ettől eltekintunk.

Ugyanígy kimaradtak az form serialize aspektusai is az előző fejezet AJAX űrlapküldésénél, továbbá még két apróság. Ugyanis a kettővel ezelőtti fejezetben nem másoljuk be a mező értékeket abban az esetben a form-unkba, ha valami validációs hiba történik, illetve az előző fejezetben nem töröljük a form mezőket (reset), ha sikeres a foglalás.

Apróságnak tűnnek, de ezek különböztetnek meg egy professzionális weblapot egy kezdetlegesebbtől. 🙂

jQuery AJAX – form elküldése a “háttérben” – foglalás oldal újratöltve

Az AJAX, azaz Asynchronous JavaScript and XML arra szolgál, hogy ne kelljen az egész weblapot újratölteni a form elküldésekor, hanem csak az adatokat, mintegy csendben a “háttérben” elküldeni, majd valami <div> tag-ben megjeleníteni a kívánt felhasználói üzeneteket. Részletesebb leírás az alábbi címen található https://hu.wikipedia.org/wiki/Ajax_(programoz%C3%A1s).

Nem nagyobb feladatra vállalkozunk, minthogy az előbbi foglalás-űrlap elküldésünket aszinkron JavaScript hívásra cseréljük.

Először pár apróság. A views/layouts/menu_layout.php file-ban előre kell vennünk a fejlécbe a JavaScript forrás beillesztéseket, hogy tudjuk használni a jQuery funkcionalitásait.

Aztán át kell “drótoznunk” a menüt pl. a views/photos.php file-ban.

<li><a href="booking_ajax"><?php echo $this->lang->line('menu_booking'); ?></a></li>

Nézzük a views/booking_ajax.php oldalunk tartalmát.

<?php
        $my_base_url = '/apartman/';
?>
                                <div class="row">
                                        <div class="col-sm-2">
                                                <div id="fh5co-logo"><a href="index.html">Apartman<span>.</span></a></div>
                                        </div>
                                        <div class="col-sm-10 text-right menu-1">
                                                <ul>
                                                        <li><a href="photos"><?php echo $this->lang->line('menu_photos'); ?></a></li>
                                                        <li class="active"><a href="booking_ajax"><?php echo $this->lang->line('menu_booking'); ?></a></li>
                                                        <li><a href="contact"><?php echo $this->lang->line('menu_contact'); ?></a></li>
                                                </ul>
                                        </div>
                                </div>
                                
                        </div>
                </div>
        </nav>
        <div class="container">
                <div id="fh5co-intro">
                        <div class="row animate-box">
                                <div class="col-md-8 col-md-offset-2 col-md-pull-2">
                                        <h2><?php echo $this->lang->line('menu_booking'); ?></h2>
                                </div>
                        </div>
                </div>
                <div id="fh5co-contact">
                        <div class="row">
                                <div class="col-md-4 animate-box">
                                        <h3>Contact Information</h3>
                                        <ul class="contact-info">
                                                <li><i class="icon-location4"></i>1117 Budapest, Irinyi J. utca 42.</li>
                                                <li><i class="icon-phone3"></i>+ 1235 2355 98</li>
                                                <li><i class="icon-location3"></i><a href="#">info@yoursite.com</a></li>
                                                <li><i class="icon-globe2"></i><a href="#">www.yoursite.com</a></li>
                                        </ul>
                                </div>
                                <div class="col-md-8 animate-box">
                                        <div class="form-wrap">
                                                <div class="row">
                                                        <form id="ajax_form" action="book_ajax" method="POST">
                                                        <div class="col-md-12" id="response_message" style="display: none;">
                                                                <div class="form-group" id="response_message_text">
                                                                        <!--<h3><font color="red">MESSAGE</font></h3>-->
                                                                </div>
                                                        </div>
                                                        <div class="col-md-12">
                                                                <div class="form-group">
                                                                        <input id="name" type="text" name="name" class="form-control" placeholder="Name">
                                                                </div>
                                                        </div>
                                                        <div class="col-md-12">
                                                                <div class="form-group">
                                                                        <input id="email" type="text" name="email" class="form-control" placeholder="Email">
                                                                </div>
                                                        </div>
                                                        <div class="col-md-12">
                                                                <div class="form-group">
                                                                        <input id="phone" type="text" name="phone" class="form-control" placeholder="Phone">
                                                                </div>
                                                        </div>
                                                        <div class="col-md-12">
                                                                <div class="form-group">
                                                                        <textarea id="message" name="message" class="form-control" id="" cols="30" rows="15" placeholder="Message"></textarea>
                                                                </div>
                                                        </div>
                                                        <div class="col-md-12">
                                                                <div class="form-group">
                                                                        <input id="checkin" type="text" name="checkin" class="form-control" placeholder="Check-in">
                                                                </div>
                                                        </div>
                                                        <div class="col-md-12">
                                                                <div class="form-group">
                                                                        <input id="checkout" type="text" name="checkout" class="form-control" placeholder="Check-out">
                                                                </div>
                                                        </div>
                                                        <div class="col-md-12">
                                                                <div class="form-group">
                                                                        <input id="submit_button" type="button" value="Book" class="btn btn-primary btn-modify">
                                                                </div>
                                                        </div>
                                                        </form>
                                                </div>
                                        </div>
                                </div>
                        </div>
                </div>
        </div><!-- END container -->
<script>
    $(document).ready(function(){
        $( "#submit_button" ).click(function(event)
        {
            event.preventDefault();
            var name = $("#name").val();
            var email = $("#email").val();
            var phone = $("#phone").val();
            var message = $("#message").val();
            var checkin = $("#checkin").val();
            var checkout = $("#checkout").val();
            var data = $("#ajax_form").serialize();

            $.ajax(
                {
                    type:"post",
                    url: "<?php echo $my_base_url; ?>index.php/apartments/book_ajax",
                    data:{ name: name, email: email, phone: phone, message: message, checkin: checkin, checkout: checkout },
                    success:function(response)
                    {
                        console.log(response);
                        $("#response_message_text").html("<h3><font color=\"red\">" + response + "</font></h3>");
                        $('#response_message').show();
                    },
                    error: function()
                    {
                        alert("Invalide!");
                    }
                }
            );
        });
    });
</script>

Nézzük mi változott! Minden form mezőnek, az elküldő gombnak és a felhasználói üzenet résznek (#response_message_text és #response_message) egyedi azonosítót, id-attribútumot adtunk, hogy a JavaScript részben #id néven hivatkozhassunk rájuk. Ezt a hivatkozási módszert amúgy css szelektornak hívjuk, ezen belül is ún. ID Selector, részletek itt https://www.w3.org/wiki/CSS3/Selectors.

Látható a <script> tag-ben az ajax hívásunk. Kiolvassuk az űrlapmezők értékeit, majd egy hívást indítunk, mind success (siker), mind error (hiba) esetére megfelelő válaszokkal.

Modellünk nem változik viszont a controller-ünkbe elkél két új metódus. Nézzük a controllers/Apartments.php file-t.

        public function booking_ajax() {
                $data = array();

                $this->template->set('title', $this->lang->line('menu_booking'));
                $this->template->load('menu_layout', 'contents', 'booking_ajax', $data);

        }

        public function book_ajax() {
                $this->load->library('form_validation');
                $this->form_validation->set_rules('name', 'Name', 'required');
                $this->form_validation->set_rules('email','Email','trim|required|valid_email');
                $this->form_validation->set_rules('checkin', 'Check-in', 'required');
                $this->form_validation->set_rules('checkout', 'Check-out', 'required');

                if($this->form_validation->run() == FALSE) {
                        echo validation_errors();
                } else {
                        $this->load->model('reservations_model');
                        $this->reservations_model->insert_reservation();
                        echo "Thanks for booking!";
                }
        }

A booking_ajax metódus csak betölti a view-nkat.

A book_ajax metódus már összetettebb. Tartalmaz egy ellenőrzést, ami email címnél valid_email-lel is bővül, tehát megvizsgálja a formátumot is és egy trim-mel, ami az esetleges felesleges ún. “trailing space” karaktereket vágja le (üres karakterek az elején és a végén).

Látható, hogy validációs hibák esetén visszatér hibával, ez ki is lesz írva a view-nkban a megfelelő div-nél, illetve ha sikerült, akkor a “Köszönet a foglalásért!” üzenettel válaszol. Ennyi az egész :).

És akkor vessünk egy pillantást egy sikeres foglalásra!

Nézzük a http://localhost/apartman/index.php/apartments/booking_ajax oldalunkat.

Láthatjuk, hogy az űrlap (form) helyes kitöltése és elküldése után az adatbázis konzolban látható az adatbázisba felvett sor.

mysql> select * from reservations where id = 10;
+----+------+------------+-------+------------+------------+---------+----------+--------------+--------+---------------------+---------------------+
| id | name | email      | phone | checkin    | checkout   | message | approved | apartment_id | locale | created_at          | updated_at          |
+----+------+------------+-------+------------+------------+---------+----------+--------------+--------+---------------------+---------------------+
| 10 | aaa  | aaa@aaa.hu |       | 2020-07-29 | 2020-08-01 | aaa     |     NULL |            1 | NULL   | 2020-08-03 03:34:11 | 2020-08-03 03:34:11 |
+----+------+------------+-------+------------+------------+---------+----------+--------------+--------+---------------------+---------------------+
1 row in set (0.00 sec)

II. rész – Airbnb apartman foglalás – esettenulmány – PHP CodeIgniter keretrendszerben

Amint megígértem, ebben az évben folytatom, és remélhetőleg be is fejezem a webprogramozásról szóló online könyvemet. 🙂

Hogy jobban megértsük a webprogramozást, egy kis architektúrális ismertetőt kell tartsak. Általánosságban már szóltam a kliens-szerver felépítésről (https://wlammpp.wordpress.com/2019/10/03/mit-ertunk-webprogramozas-webfejlesztes-alatt). Egy webes alkalmazás a felhasználóval a böngészőn (browser) keresztül “lép interaktív kapcsolatba”. Ez azt jelenti, hogy a felhsználó egy böngésző ablakban illetve tabfülön keresztül látja illetve használja a grafikus felhasználói felületet (GUI – graphical user interface, ide tartozik még a népszerű Ui / UX Design fogalma, vagyis a felhasználói felület és élmény tervezés). A felhasználó az alkalmazás funkcióit a böngészőben megjelenő felületen keresztül éri el: elolvas információkat, regisztrál egy webes űrlapon (formon) keresztül, keres, navigál, “kattint” (click-el). Ezek közül komolyabb programozást az űrlapkitöltés és a keresés igényel, illetve a különböző grafikai látványelemek is.

Az egész webprogramozás illetve -fejlesztés azért tűnhet bonyolultnak, mert egyrészt kellenek a “szerver oldali” dolgok: egy adatbázis – esetünkben relációs -, amit az ún. SQL nyelven lehet manipuláni, lekérdezni, feltölteni, frissíteni illetve törölni (https://hu.wikipedia.org/wiki/SQL), továbbá egy beágyazott, illetve szerver oldalon értelmezett (ún. script) nyelv, ez a PHP 7-es verzió konkrét esetünkben (https://www.php.net/), amely a funkcionalitást, az “üzleti folyamatot” valósítja meg (implementálja). A PHP Hyprtext Preprocessor-nak hirdeti magát, ami azt jelenti, hogy a HTML nyelven (ez a weblapok nyelve) írt hypertext-be van beágyazva, amit a webszerverünk megfelelő modulja értelmez, amikor valaki az adott weboldalt a böngészőjében megnyitja. Tehát nincs előre lefordítva, mint például egy C nyelven írt kernel modul (Linux), hanem “on the fly” fordul (compile), és “fut” (run). Ezt szemléltetném is egy pár soros kódban.

<!DOCTYPE html>
<html>
<head>
<title>Lapcím</title>
</head>
<body>

<h1>Ez egy címsor.</h1>
<p>Ez egy bekezdés.</p

<!-- Ez egy megjegyzés. Ide jön a PHP kódrészlet. -->

<?php
phpinfo();
?>

</body>
</html>

Ebben a rövid kódrészletben (snippet) ún. HTML tag-eket láthatunk. Az eleje egy dokumentum típus megjelölés, majd html, head – fejléc, body – törzs olvashatóak. Látható, hogy <? ?> jelek között van a PHP kódrészlet, ami konkrétan egy hosszú és részletes kiírást eszközöl a szerverünk beállításáról, ha megnézzük böngészőben (írtam már róla itt: https://wlammpp.wordpress.com/2019/10/04/i-resz-wordpress-webfejlesztes-a-wordpress-telepitese). Ezek a tag-ek mind “kacsacsőrök” között vannak. Ez a leíró nyelvek sajátossága. Az XML is ilyen formátumú. 🙂

A HTML oldal önmagában statikus, ha pl. PHP kódrészletet nem tartalmaz. Lehet benne hivatkozás képre, hangra, videóra, tehát Hypertext, ugyanakkor nem igényel szerveroldali processzálást (feldolgozást). Ezért a szerveren statikus oldalként kerül kiszolgálásra. Erre általában az oldalt tartalmazó állomány nevének kiterjesztése utal, ez többnyire .html – PHP oldalak esetében pedig .php. A nagy különbség egy “honlap” és egy “webalkalmazás” között abban rejlik, hogy milyen és mennyi “kód” van mögötte és benne a szerveren, illetve a kliens oldalon JavaScript formájában, értsd: hogy mennyire interaktív. Alábbiakban egy kis összefoglaló a HTML struktúráról, a tag-ekről.

<!-- Nyitó és záró tag, közte a tartalom. -->
<tagnev>Töltelék.</tagnev>

<!-- Önmagában záródó tag (XML helyes), paraméterekkel. -->
<tagnev parameter1="ertek1" parameter2="ertek2" />

<!-- A legegyszerűbb "beágyazás". -->
<tag1>
<tag2 />
</tag1>

Ha ez már bonyolult volna, szólnom kell még a kliens oldalon fontossá váló komponensekről. Ezek az előbbiekben taglalt az oldalak alapvető struktúrájáért és megjelenéséért felelős HTML (Hypertext Markup Language – https://www.w3schools.com/html/), a látványért felelős CSS (https://hu.wikipedia.org/wiki/Cascading_Style_Sheets, https://www.w3schools.com/css/) illetve a dinamikus működésért felelős JavaScript (https://www.w3schools.com/js/) nyelveken implementált böngészőben futó, “héj” réteg. A könyv legelején taglalt MVC architektúra (Model – View – Controller) View – nézet rétegéről szól ez a kliens oldali rész. A dinamikus működést biztosító JavaScript, ha AJAX-ról (https://hu.wikipedia.org/wiki/Ajax_(programoz%C3%A1s)) beszélünk, akkor lehet “okos” Controller – vezérló működésű is, sőt bizonyos szempontból még Model – adatszerkezet érintettségről is beszélhetünk, amennyiben XML-formátumban történik az adatátvitel pl. egy kliens oldali űrlap (form) és a szerver oldali Controller között.

A szerveroldali részek azonban a Model és a Controller rétegbe tartoznak. Technikailag ugyan a View is a szerver oldalon van, ámde csupán a böngészőben válik látvánnyá. Ha jól implementáltuk (programoztuk) az alkalmazást, az architektúrális elveknek megfelelően, akkor elválnak egymástól a rétegek. Ettől áttekinthető és karbantartható marad a kódunk. Sokkal kisebb a hibalehetőség, illetve egyszerűbb továbbfejleszteni, módosítani és dokumentálni. Hogy ezt az architektúrális szétválasztást tényleg meg is tehessük, segítségül hívtuk a CodeIgniter (https://codeigniter.com/) keretrendszert (framework-öt).

A CodeIgniter-t (3-as verzió) egész egyszerűen letöltjük (https://codeigniter.com/download), és kicsomagoljuk a webszerverünk gyökér könyvtára alatt létrehozott “apartman” könyvtárba.

gvamosi@gergo1:/var/www/html$ sudo mkdir apartman
[sudo] password for gvamosi:
gvamosi@gergo1:/var/www/html$ sudo chown gvamosi:gvamosi apartman/
gvamosi@gergo1:/var/www/html$ cd apartman/
gvamosi@gergo1:/var/www/html/apartman$ cp ~/Downloads/bcit-ci-CodeIgniter-3.1.11-0-gb73eb19.zip .
gvamosi@gergo1:/var/www/html/apartman$ unzip bcit-ci-CodeIgniter-3.1.11-0-gb73eb19.zip
gvamosi@gergo1:/var/www/html/apartman$ mv bcit-ci-CodeIgniter-b73eb19/* .
gvamosi@gergo1:/var/www/html/apartman$ l
total 3836
drwxr-xr-x 14 gvamosi gvamosi 4096 Sep 19 2019 application
-rw-r--r-- 1 gvamosi gvamosi 2758337 Apr 6 11:30 bcit-ci-CodeIgniter-3.1.11-0-gb73eb19.zip
drwxr-xr-x 2 gvamosi gvamosi 4096 Apr 6 11:32 bcit-ci-CodeIgniter-b73eb19
-rw-r--r-- 1 gvamosi gvamosi 594 Sep 19 2019 composer.json
-rw-r--r-- 1 gvamosi gvamosi 6841 Sep 19 2019 contributing.md
-rw-r--r-- 1 gvamosi gvamosi 1117463 Apr 6 11:23 framework-4.0.2.zip
-rwxr-xr-x 1 gvamosi gvamosi 10255 Sep 19 2019 index.php
-rw-r--r-- 1 gvamosi gvamosi 1114 Sep 19 2019 license.txt
-rw-r--r-- 1 gvamosi gvamosi 2343 Sep 19 2019 readme.rst
drwxr-xr-x 8 gvamosi gvamosi 4096 Sep 19 2019 system
drwxr-xr-x 14 gvamosi gvamosi 4096 Sep 19 2019 user_guide

Ezek után megnézhetjük a “helyi” webszerverünket, hogy sikerült-e a telepítés. Írjuk be a kedvenc böngészőnkbe (én a Google Chrome-ot használom) a következő címet: http://localhost/apartman/. 🙂

Siker! 🙂 Jól látható, hogy vagy 5 nyelven kell tudnunk kódolni egy átlagos webalkalmazáshoz. 😀 Megjegyzem, hogy ez az esettanulmány ugyan konkrét példát mutat be, azonban általános érvényű az architektúrában és főbb irányelveiben.

Oldalsáv, eseménynaptár widget (My Calendar bővítmény)

Az oldalsávba kerülhet pl. naptár widget. Ezt az alábbi webhelyről tölthetjük le és telepíthetjük: https://hu.wordpress.org/plugins/my-calendar/.

Amennyiben egy tárhelyszolgáltatónál vagyunk vagy vesszük a fáradságot beállítani a dolgokat, úgy pár kattintással is telepíthetjük a widget-et. A kettes adminisztrátori képernyőn a Bővítmények / Új hozzáadása menüpont alól indul a telepítés.

Keresősztringnek a calendar-t adom meg.

Innen a “Telepítés most” gombra kattintva telepíthető.

Nem bajlódom a telepítéssel, helyette hoznék egy élő oldalról működő naptárat. A http://szombathelyigorogkatolikus.hu oldalon található felprogramozott és karbantartott naptár widge, illetve bővítmény működés közben, a különböző egyházi liturgikus eseményekkel feltöltve. Lássuk! 🙂

Ahogy telepítettük, beépül a bővítmény a megfelelő helyekre az adminisztrátori képernyőn. Innen felvehetünk naptár-eseményt, illetve mindenféle beállítást elvégezhetünk a naptárunkon. 🙂