Alkalmazások fejlesztése

7. előadás

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

Eddig

  • Szerveroldali dinamikus weboldal generálás

Ezen a héten

  • Kiegészítés a beadandóhoz
  • Kliensoldali webprogramozás
  • Kliensoldali alapfogalmak
  • DOM, jQuery
  • Weboldalak progresszív fejlesztése

Feladatok megoldási lépései

Útmutató

Lépések

  • Statikus oldalak készítése (HTML, CSS, Bootstrap)
  • Sablonok készítése (pl. HBS), kipróbálás beégetett adattal
  • Adatok "normális" előállítása
  • Felküldött adatok kezelése (POST, validálás, flash üzenet, redirect, mentés)

Kliensoldali webprogramozás

Kliens-szerver architektúra

Kliensoldali alkalmazások típusai

  • Vékonykliens
    • weboldalak
    • progresszív fejlesztés
    • JavaScript nélkül is működik
  • Vastagkliens
    • webalkalmazás
    • JavaScript nélkül nem működik

Kliensoldali programozás

  • Betöltődik az oldal (HTML, CSS, képek)
  • A böngésző megjeleníti azt
  • A betöltött tartalom és a böngésző manipulálása programozási interfészeken keresztül

Dokumentum Objektum Modell (DOM)

DOM

Kliensoldali programozás

DOM programozás

  • DOM Core (csomópontok fája, általános faműveletekkel)
  • HTML DOM (HTML specifikus elemek, ezekre jellemző adattagokkal és metódusokkal)
  • DOM események

DOM műveletek

  • a faszerkezet bejárása,
  • új elemek létrehozása,
  • meglévőek módosítása és
  • törlése.

Felhasználói interakció

Eseménykezelés

Eseményvezérelt alkalmazások

Események

jQuery

DOM absztrakció

Linkek

Alapkoncepciók

  • DOM absztrakció (böngészőfüggetlenség)
  • Kényelmes API
  • Plusz szolgáltatások

Működés

  • Elemek kiválasztása
  • Kiválasztott elemeken műveletek végrehajtása
$('p').hide();

Elemek kiválasztása

  • CSS 1,2,3 szelektorok
    • saját szelektorok
$("#profilkep");
$(".adat");
$("li");
$("div.adat");
$("#profilkep, #adatok");
$("img[src=profil.jpg]");
$("ul li.adat b");
$("ul li:first");
$("b:contains('Végzettség:')");

Műveletek

  • DOM bejárása (gyakorlatilag elemek kiválasztása relatív útvonalon)
  • Attribútum manipulálás
  • Elemmanipulálás
    • létrehozás
    • módosítás
    • törlés
  • Eseménykezelés
  • Stílusmanipuláció
  • AJAX absztrakció (ld. a félév során később)
  • Egyéb kiegészítő függvények

Bejárás

  • gyerekek (children()), leszármazottak (find())
  • szülő(k) (parent(), parents(), parentsUntil()), legközelebbi ős (closest())
  • testvérek (siblings(), next(), nextAll(), nextUntil(), prev(), prevAll(), prevUntil())
// szülő kiválasztása
$("#adatok").parent();    
// a következő elem kiválasztása
$("li").next();              
// az előző elem kiválasztása
$("li").prev();              
// az előző elem kiválasztása, de csak ha adat osztályú
$("li").prev(".adat");       
// az elem felmenői közül az első, amelyikre igaz, hogy div
$("b:first").closest('div'); 
// az elem leszármazottai, melyekre igaz, hogy adat osztályúak
$("#adatok").find(".adat");  
// a kiválasztott elemek közül az első
$("li").first();           
// a kiválasztott elemek közül az utolsó
$("li").last();              
// a kiválasztott elemek közül a harmadik
$("li").eq(2);               
// az elem azon testvérei, melyek h1 típusúak
$("#adatok").siblings("h1");

Kiválasztott elemek körének módosítása

  • szűrés (is(), has(), filter(), first(), last())
  • bővítés (add())
//Szűrés
// az elem jQuery objektumához hozzáadjuk az összes li-t is
$("#adatok").add("li");      
// a választásból kivesszük az összes li típusút
$("#adatok, li").not("li");  
// a kiválasztott elemekből csak a képek maradnak
$(".adat").filter("img");    

//Visszalépéses szelekció
$("#adatok")                                         // hatókör: div#adatok
    .find("li")                                      // hatókör: div#adatok
        .css({ padding: "5px" })                     // hatókör: div#adatok li
        .find("b")                                   // hatókör: div#adatok li
            .html('Csak a címeket hackoltam meg!!!') // hatókör: div#adatok li b
            .end()                                   // hatókör: div#adatok li b
        .end()                                       // hatókör: div#adatok li
    .animate({ paddingTop: '+=100' }, 200); 

Iterálás a szelekcióban

var cimkek = [];
$("b").each(function() {
    cimkek.push( $(this).text().replace(':','') );
});
cimkek.join(', ');

HTML struktúra megváltoztatása

  • elem(ek) létrehozása (jQuery(HTMLString))
  • elem lemásolása (clone())
  • elem hozzáadása, mozgatása (append(), prepend(), after(), before(), appendTo(), prependTo(), insertAfter(), insertBefore(), replace())
  • elem törlése (remove(), detach())
  • tartalom kezelése (html(), text())
  • attribútumok kezelése (attr())

HTML struktúra megváltoztatása

//Új jQuery objektumok létrehozása
var $a = $("<a class='12a' href='index.html'><b>Béla, Bulcsú</b></div>");

// Hasonló elemdefiníció attribútum-objektummal:
var $a = $("<a />", {
               className: '12a',
               href: 'index.html',
               html: '<b>Béla, Bulcsú</b>'
           });

//Attribútum és tartalom módosítása
$("<a />")
    .addClass('12a')
    .attr('href', 'index.html')
    .html('<b>Béla, Bulcsú</b>')
    .appendTo($("body"));

$("body").find('.12a').attr('href'); // => 'index.html'
$("body").find('.12a').html();       // => '<b>Béla, Bulcsú</b>', HTML tartalom
$("body").find('.12a').text();       // => 'Béla, Bulcsú', tartalom tag-ek nélkül

//jQuery objektumok beillesztése és törlése
$a.appendTo($("body"));        // $a beillesztése a body végére
$a.prependTo($("body"));       // $a beillesztése a body elejére
$a.insertAfter($("#adatok"));  // $a beillesztése az adatok id-jű div után
$a.insertBefore($("#adatok")); // $a beillesztése az adatok id-jű div elé
$a.remove();                   // $a elem törlése

//Ugyanez másik szemszögből
$("body").append($a);          // A body végére illeszti a $a-t
$("body").prepend($a);         // A body elejére illeszti a $a-t
$("#adatok").after($a);        // Az adatok id-jű div után illeszti a $a-t
$("#adatok").before($a);       // Az adatok id-jű div elé illeszti a $a-t

Stílusok kezelése

  • css()
  • addClass(), removeClass(), toggleClass(), hasClass()
  • animate()
  • show(), hide(), toggle()
  • fadeIn(), fadeOut(), fadeToggle()
  • slideDown(), slideUp(), slideToggle()
  • height(): számított magasság
  • innerHeight(): magasság + bélés
  • outerHeight(): magasság + bélés + keret (+margó true paraméter esetén)
  • width(), innerWidth(), outerWidth(): ld. a height függvényeit
  • position(): a szülőobjektumhoz viszonyított elhelyezkedés (top, left)
  • offset(): a dokumentumhoz viszonyított elrendezés (top, left)
  • scrollTop(), scrollLeft()

Eseménykezelés

  • $obj.on('type', fn): közvetlen hozzárendelés
  • $obj.on('type', 'selector', fn): delegálás
$('table').on('click', 'td', function (e) {
    console.log(this);
    console.log(e);
});

//Névterezés
$('table').on('click.myGame', 'td', function () {});
$('table').off('click.myGame', 'td', function () {});

//Még jobb
$('table').off('click.myGame').on('click.myGame', 'td', function () {});

Weboldalak progresszív fejlesztése

Rétegek

  1. HTML
    • A szerkezet
  2. CSS
    • A megjelenítés
  3. JavaScript
    • Viselkedés

Előnyök

  • JavaScript nélkül működik
  • Sokféle eszközön fut
  • Akadálymentesség jobb biztosítása

Lépések

  1. HTML szerkezet
  2. CSS
  3. JavaScript

Előkészületek

Oldalfüggő erőforrások

Nem globális

  • stíluslapok
  • szkriptek

Lehetőségek

  • res.render-ben átadni paraméterként
    • hátrány: többszöri megjelenítéskor mindig gondoskodni kell róla
  • sablont bővíteni
    • HBS helper definiálása

res.render

Layout

<head>
    <!-- ... -->
    {{#each stylesheets}}
    <link rel="stylesheet" href="{{this}}">
    {{/each}}
</head>
<body>
    <!-- ... -->
    {{#each scripts}}
    <script src="{{this}}"></script>
    {{/each}}
</body>

Konkrét oldal

res.render('errors/list', {
    /* ... */
    scripts: ['script1.js', 'script2.js'],
});

HBS helper

// server.js
var hbs = require('hbs');

var blocks = {};

hbs.registerHelper('extend', function(name, context) {
    var block = blocks[name];
    if (!block) {
        block = blocks[name] = [];
    }

    block.push(context.fn(this));
});

hbs.registerHelper('block', function(name) {
    var val = (blocks[name] || []).join('\n');

    // clear the block
    blocks[name] = [];
    return val;
});

HBS helper használata

Layout

<head>
    <!-- ... -->
    {{{block "stylesheets"}}}
</head>
<body>
    <!-- ... -->
    {{{block "scripts"}}}
</body>

Konkrét oldal

{{#extend "scripts"}}
<script src="script1.js"></script>
<script src="script2.js"></script>
{{/extend}}
<!-- ... -->

Feladat

Klinesoldali űrlapellenőrzés az új hiba oldalon

Űrlapellenőrzés

  • tipikus kliensoldali feladat
  • szerver erőforrásainak kímélése
  • felhasználói élmény növelése

Működés

  • trigger: form submit eseménye
  • logika
    • végigmenni az űrlapmezőkön
    • ellenőrizni a feltételeket
    • hibákat kigyűjteni
  • hiba esetén
    • űrlapküldés megakadályozása (e.preventDefault())
    • hiba kiírása
  • helyes kitöltés esetén
    • engedni továbbmenni (semmit sem kell csinálni)

Lehetőségek

Bootstrap Validator

bower install --save bootstrap-validator
  • Illeszkedik a BootStrap markup-vezérelt filozófiájához
  • HTML-t kell felparaméterezni
  • Példa használat

Lépések

  1. Installálás
  2. Szkript beemelése
  3. HBS sablon kiegészítése
  4. Nem kell JavaScriptet írni :)

Feladat

A listaoldal táblázatát válogassuk szét státusz szerint és jelenítsük meg klön táblázatokban. A táblázatok megjelenítését vezéreljük a Bootstrap Tab komponensével.

Előtte

Utána

Cél

<div>

  <!-- Nav tabs -->
  <ul class="nav nav-tabs" role="tablist">
    <li role="presentation" class="active"><a href="#new" aria-controls="new" role="tab" data-toggle="tab">New</a></li>
    <li role="presentation"><a href="#assigned" aria-controls="assigned" role="tab" data-toggle="tab">Assigned</a></li>
    <li role="presentation"><a href="#success" aria-controls="success" role="tab" data-toggle="tab">Success</a></li>
  </ul>

  <!-- Tab panes -->
  <div class="tab-content">
    <div role="tabpanel" class="tab-pane fade in active" id="new">
        <table class="table table-striped table-hover ">
            <thead>
                <tr>
                    <th>Időpont</th>
                    <th>Státusz</th>
                    <th>Helyszín</th>
                    <th>Leírás</th>
                    <th></th>
                </tr>   
            </thead>
            <tbody>
                <tr>
                    <td>qqq</td>
                    <td><span class="label label-danger">Új</span></td>
                    <td>pc6</td>
                    <td><span class="badge">12</span> rossz</td>
                    <td><a class="btn btn-default btn-sm" href="/errors/1" role="button">Megtekint</a></td>
                </tr>
            </tbody>
        </table>
    </div>
    <div role="tabpanel" class="tab-pane fade" id="assigned">
        <table class="table table-striped table-hover ">
            <thead>
                <tr>
                    <th>Időpont</th>
                    <th>Státusz</th>
                    <th>Helyszín</th>
                    <th>Leírás</th>
                    <th></th>
                </tr>   
            </thead>
            <tbody>
                <tr>
                    <td>qqq</td>
                    <td><span class="label label-info">Hozzárendelve</span></td>
                    <td>pc6</td>
                    <td><span class="badge">12</span> rossz</td>
                    <td><a class="btn btn-default btn-sm" href="/errors/1" role="button">Megtekint</a></td>
                </tr>
            </tbody>
        </table>
    </div>
    <div role="tabpanel" class="tab-pane fade" id="success">
        <table class="table table-striped table-hover ">
            <thead>
                <tr>
                    <th>Időpont</th>
                    <th>Státusz</th>
                    <th>Helyszín</th>
                    <th>Leírás</th>
                    <th></th>
                </tr>   
            </thead>
            <tbody>
                <tr>
                    <td>qqq</td>
                    <td><span class="label label-success">Kész</span></td>
                    <td>pc6</td>
                    <td><span class="badge">12</span> rossz</td>
                    <td><a class="btn btn-default btn-sm" href="/errors/1" role="button">Megtekint</a></td>
                </tr>
            </tbody>
        </table>
    </div>
  </div>

</div>

Lépések

  1. Sorok (tr) szétválogatása
  2. Cél ul előállítása
  3. Cél táblázatok előállítása
// Segítség
var statusClasses = {
    'new': 'danger',
    'assigned': 'info',
    'success': 'success',
    'rejected': 'default',
    'pending': 'warning',
};

// Cél táblázat
'<div role="tabpanel" class="tab-pane fade" id="">' +
    '<table class="table table-striped table-hover">' +
        '<thead>' +
            '<tr>' +
                '<th>Időpont</th>' +
                '<th>Státusz</th>' +
                '<th>Helyszín</th>' +
                '<th>Leírás</th>' +
                '<th></th>' +
            '</tr>'    +
        '</thead>' +
        '<tbody>' +
            
        '</tbody>' +
    '</table>' +
'</div>'

Továbblépési lehetőségek

  • HTML előállítása sablonok segítségével
    • Handlebars a kliensoldalon
    • bower install --save handlebars
    • kliensoldali sablon formája \{{ }}
  • Adat, logika, HTML szétválasztása
    • táblázat --> JS tömb (table-to-json)
    • Tömb szétválogatása
    • Megjelenítés sablonnal