Kliensoldali webprogramozás

jQuery. Függvények.

Horváth Győző
Egyetemi docens
1117 Budapest, Pázmány Péter sétány 1/c., 4.725-as szoba
horvath.gyozo@inf.elte.hu

Ismétlés

Weboldalak progresszív fejlesztése

  • Elv
    • JS nélkül is működjön
    • JS-sel kényelmesebb, szebb
  • Felépítés
    1. szerkezet (szemantikus HTML)
    2. megjelenés, stílus (CSS)
    3. viselkedés (JavaScript)

Progresszív fejlesztés

<form action="" method="get">
  <input type="password" name="password1" value="secret1" data-show-password>
  <input type="password" name="password2" value="secret2" data-show-password>
  <button>Submit</button>
</form>
<style></style>
class ShowPassword {
  // ...
}

document.querySelectorAll('[data-show-password]').forEach(
  el => new ShowPassword(el))

Web komponensek

  • Web komponensek
    • Custom elements
    • Templates
    • Shadow DOM
  • Progresszív fejlesztésre használható
<show-password>
  <input type="password" name="password1" value="secret">
</show-password>

<input type="password" name="password1" value="secret" is="show-password">

Tömbök és objektumok

Referenciatípusok
Destructuring és spread operátorok

Referenciatípus (tömb)

// const reference, content dynamic
const x = []
x.push(10)

// copy reference
const x1 = [1, 2, 3]
const x2 = x1
x2[1] = 20
console.log(x1) // --> [1, 20, 3]

// shallow copy
const x3 = [1, 2, 3]
const x4 = x3.slice()
// const x4 = x3.concat()
x4[1] = 20
console.log(x3) // --> [1, 2, 3]

const m1 =  [
  [1, 2, 3],
  [4, 5, 6],
]
const m2 = m1.concat()
m2[0][1] = 20
console.log(m1[0]) // --> [1, 20, 3]

// deep copy
const m3 =  [
  [1, 2, 3],
  [4, 5, 6],
]
const m4 = JSON.parse(JSON.stringify(m3))
m4[0][1] = 20
console.log(m3[0]) // --> [1, 2, 3]

Destructuring and spread (tömb)

const numbers = [1, 2, 3, 4, 5];
const a = numbers[0];
const b = numbers[1];

// instead destructuring
const [a, b] = numbers;

// default values
const [a = 10, b = 20] = [100] // a:100, b:20

// rest
const [a, b, ...rest] = numbers; // --> rest:[3, 4, 5]

// swapping variables
[a, b] = [b, a]

// ignoring
const [a,,b] = numbers; // a:1, b:3

// spread
const a = [1, 2, 3];
const b = [9, ...a, 10]; // b:[9, 1, 2, 3, 10]

Referenciatípus (objektum)

// const reference, content dynamic
const o = {}
o.field1 = 12

// copy reference
const o1 = { field1: 2 }
const o2 = o1
o2.field1 = 20
console.log(o1) // --> { field1: 20 }

// shallow copy
const o3 = { field1: 2 }
const o4 = {}
for (let key in o3) {
  o4[key] = o3[key]
}
// Object.assign(o4, o3)
o4.field1 = 20
console.log(o3) // --> { field1: 2 }

const n1 =  {
  field1: { subfield1_1: 1 },
  field2: { subfield2_1: 2 },
}
const n2 = Object.assign({}, n1)
n2.field2.subfield2_1 = 20
console.log(n1.field2) // --> { subfield2_1: 20 }

// deep copy
const n3 =  {
  field1: { subfield1_1: 1 },
  field2: { subfield2_1: 2 },
}
const n4 = JSON.parse(JSON.stringify(n3))
n4.field2.subfield2_1 = 20
console.log(n3.field2) // --> { subfield2_1: 2 }

Destructuring and spread (objektum)

const o = {
  a: 42,
  b: 28,
}
const a = o.a
const b = o.b

// instead destructuring
const {a, b} = o;

// renaming
const {a: c, b: d} = o;

// default values
const {a = 10, b = 20} = {a: 42};
const {a: c = 10, b: d = 20} = {a: 42};

// rest
const o = {
  a: 42,
  b: 28,
  c: 12
};
const {a, ...rest} = o; // rest={b:28, c:12}

Destructuring and spread (objektum)

// nested objects
const david = {
  kor: 4,
  cim: {
    iranyitoszam: '1241',
    varos: 'Budapest',
    utca: 'Egyszervolt utca',
    hazszam: 63
  }
};
const { cim: { utca }} = david

Függvények

Függvény létrehozása

// Függvénydeklaráció
function multiply(a, b) {
  return a * b
}

// Névtelen függvénykifejezés
const multiply = function(a, b) {
  return a * b
}

// Nevesített függvénykifejezés
const multiply = function multiply(a, b) {
  return a * b
}

// Arrow function szintaxis
const multiply = (a, b) => a * b

// Function konstruktor: dinamikus függvények, alapvetően kerülendő
const multiply = new Function('a, b', 'return a * b');

Függvényparaméterek

Alapértelmezett értékek

function multiply(a = 1, b = 1) {
  return a * b
}
multiply()      // 1
multiply(10)    // 10
multiply(10, 8) // 80

Függvényparaméterek

Paraméterek száma, rest paraméterek

function multiply(a, b, ...rest) {
  console.log(a, b, rest)
  return a * b
}
multiply()          // undefined undefined [], NaN
multiply(1)         // 1 undefined [],         NaN
multiply(1, 2)      // 1 2 [],                 2
multiply(1, 2, 3)   // 1 2 [3],                2

Függvényparaméterek

Paraméter destructuring, alapértelmezett értékekkel

function multiply([a = 1, b = 1, ...rest]) {
  console.log(a, b, rest)
  return a * b
}
multiply([])            // 1 1 [],     1
multiply([10])          // 10 1 [],    10
multiply([10, 20])      // 10 20 [],   200
multiply([10, 20, 30])  // 10 10 [30], 200

Függvényparaméterek

Paraméter destructuring, alapértelmezett értékekkel

function multiply({a = 1, b: _b = 1, c = 1, ...rest} = {}) {
  console.log(a, _b, c, rest)
  return a * _b
}
multiply()                  // 1 1 1 {}, 1
multiply({})                // 1 1 1 {}, 1
multiply({ a: 10 })         // 10 1 1 {}, 10
multiply({ a: 10, b: 20 })  // 10 20 1 {}, 200
multiply({ a: 10, d: 20 })  // 10 1 1 { d: 20 }, 10

Függvényparaméterek

Paraméter destructuring, extrém példa :)

function f([a, b] = [1, 2], {x: c} = {x: a + b}) {
  return a + b + c
}
f()   // 6

Függvényparaméterek

Spread szintaxis

function multiply(a, b) {
  return a * b
}
multiply(...[10, 20])

Hatókör

  • A függvény lokális hatókört ad
  • A függvényen belül definiált függvények elérik a külső függvény lokális változóit
  • Belső függvény ugyanolyan nevű változója elfedi a külső függvényét
function outer() {
  let a = 10, b = 20
  function inner() {
    console.log(a, b)
    b = 30
  }
  console.log(a, b) // 10 20
  inner()           // 10 20
  console.log(a, b) // 10 30
}
outer()
console.log(a, b)   // ReferenceError

Függvénykifejezés

Függvény literálformái (ez nem függvénydeklaráció)

// Névtelen függvénykifejezés
function (par1, par2) {

}

// Nevesített függvénykifejezés
function fn(par1, par2) {

}

// Arrow function expression
(par1, par2) => {
  statements
}

// Arrow function expression
(par1, par2) => return_value

Függvénykifejezés

  • Egy függvénykifejezés bárhol állhat
    • literál
    • értékadás
    • objektum adattagja
    • függvényparaméter
    • visszatérési érték
    • “használva”
    • kifejezésben

Függvénykifejezés

Értékadás → Névtelen függvénykifejezés

const multiply = 8
const multiply = function(a, b) {
  return a * b
}

Függvénykifejezés

Objektum adattagja → metódus

const obj = {
  multiply: 8
}
const obj = {
  multiply: function (a, b) {
    return a * b
  }
}

Függvénykifejezés

“Használva” → önkioldó függvény

(8)
(function multiply(a, b) {
  return a * b
})(2, 3)

Önkioldó függvény

  • A függvény lokális hatókört ad
  • → egységbe zárhatók programrészek
    • adatok
    • függvények
  • Nincs ütközés
  • Önkioldó névtelen függvény: olyan függvény, amit létrehozás után rögtön futtatunk is
  • Később nem tudunk rá hivatkozni

Önkioldó függvény

// Önkioldó függvény definiálása (megj: semmit nem csinál...)
(function() {
  const x = [1, 2, 3]
  function multiply(a, b) {
    return a * b
  }
}());

// Önkioldó függvény paraméterezése
(function(a, b) {
  console.log(a + b)
}(1, 2))    // 3

// Önkioldó függvény visszatérési értéke
var ize = (function(str) {
    return str;
}('bize'));
console.log(ize);    // 'bize' 

Önkioldó függvény

Felfedő modul minta → ES6 modul alapja

const stack = (function () {
  const elems = [];
  const push = function (e) {
    elems.push(e);
    return this;
  };
  const pop = function () {
    return elems.pop();
  };
  const top = function () {
    return elems[size()-1];
  };
  const size = function () {
    return elems.length;
  };

  return { push, pop, top, size };
})();

stack.push(10)

Függvénykifejezés

Függvény mint paraméter → callback minta

const f = function (p) {
  return p
}
f(8)
const f = function (fv, a, b) {
  return fv(a, b);
}
f(function (a, b) {
  return a * b
}, 2, 3)

Függvény mint paraméter

// Saját függvény
function execOperator(a, b, op) {
  if (op) {
    return op(a, b)
  }
}
execOperator(3, 4, (a, b) => a * b)

// Tömbfüggvény
[1, 2, 3].map(e => e * 2)

// Eseménykezelő
document.addEventListener('click', function (e) { /* ... */ })

// Időzítő
setTimeout(function () {
  console.log('Something')
}, 1000)

Függvénykifejezés

Függvény mint visszatérési érték → függvénygenerátor

const f = function () {
  return 8
}
const f = function () {
  return function (a, b) {
    return a * b
  }
}

Függvény mint visszatérési érték

const f = function () {
  return function (a, b) {
    return a * b
  }
}
// or
const f = () => (a, b) => a * b

// Usage
const g = f()
const m = g(3, 4)   // 12

Függvény mint visszatérési érték

Mi van abban az esetben, ha a külső függvény egy lokális változójára hivatkozom? A külső függvény ugyanis már lefutott.

var setup = function () {
  var lokalis = 1;
  return function () {
    console.log(lokalis);
  };
};

var fv = setup();
fv();     // ???

Closure

  • Zárlat, lexikális scope
  • Függvény a függvényben
  • Belső függvény hivatkozhat a külső függvény változóira (függvény scope) az után is, hogy a külső függvény már lefutott
  • Closure egy környezet, amiben a változók definiálva vannak
  • Egy változó addig marad életben, amíg esély van arra, hogy bárki használja őket
  • Szemétgyűjtő szabadítja fel, ha nincs rá referencia

Partial application

function buildUri(scheme, domain, path) {
  return `${scheme}://${domain}/${path}`
}
buildUri('https', 'twitter.com', 'favicon.ico') // https://twitter.com/favicon.ico

const buildHttpsTwitterUri = partial(buildUri, 'https', 'twitter.com')
buildHttpsTwitterUri('favicon.ico')             // https://twitter.com/favicon.ico
const partial = function (func, ...args) {
  return function (...args2) {
    return func(...[...args, ...args2])
  }
}
function partial(fn, ...args) {
  return fn.bind(null, ...args);
}

Closure és ciklus

(function() {
    var i;
    for (i = 0; i < 3; i += 1) {
        setTimeout(function () {
            console.log(i);
        }, 500);
    }
})();

//Output
// 3
// 3
// 3

Függvény mint objektum

var fgvObj = function () {
    return 'Meghívtál, visszatértem';
};

//Saját adattagok hozzáadása
fgvObj.adat = 'Adat vagyok';
fgvObj.metodus = function () {
    return this.adat;
};

console.log(fgvObj());          //'Meghívtál, visszatértem'
console.log(fgvObj.metodus());  //'Adat vagyok'

Memoization minta

var myFunc = function (...args) {
  const cachekey = JSON.stringify(args)
  let result
  if (!myFunc.cache[cachekey]) {
    result = {};
    // ... expensive operation ...
    myFunc.cache[cachekey] = result;
  }
  return myFunc.cache[cachekey];
};
// cache storage
myFunc.cache = {};

Alkalmazkodó függvényhívási minta

  • Függvénymetódusok: call, apply, bind
  • A call() és apply() segítségével megmondhatjuk, hogy a függvény this paramétere melyik objektumra mutasson (első paraméterük)
    • apply() 2. paramétere argumentumok tömbje
    • call() 2., 3., stb. paramétere a függvény 1., 2., stb. paramétere lesz
  • bind(): a függvény kontextusát véglegesen a paraméterül megadott objektumhoz köti

this kontextusa

//Setting the context of this with call and apply
let peter = {
    name: 'Peter',
    hello: function () {
        return this.name
    }
};
let julia = {
    name: 'Julia',
    hello: function () {
        return this.name
    }
};
peter.hello.call(julia) // "Julia"

this kontextusa

// Alapeset
class AppView {
  constructor(appState) {
    this.elem = document.querySelector('something')
    this.elem.addEventListener('click', this.onClick)
  }
  onClick(e) {
    // this === document.querySelector('something')
  }
}

// Bind
class AppView {
  constructor(appState) {
    this.elem = document.querySelector('something')
    this.elem.addEventListener('click', this.onClick.bind(this))
  }
  onClick(e) {
    // this === appView
  }
}

// Fat arrow
class AppView {
  constructor(appState) {
    this.elem = document.querySelector('something')
    this.elem.addEventListener('click', e => this.onClick(e))
  }
  onClick(e) {
    // this === appView
  }
}

// Class property initializer
class AppView {
  constructor(appState) {
    this.elem = document.querySelector('something')
    this.elem.addEventListener('click', this.onClick)
  }
  onClick = (e) => {
    // this === appView
  }
}

jQuery

DOM absztrakció

Kontextus

  • Időszak: 2006-2010
  • Ma már nincs rá szükség!
  • Miért nézzük mégis?
    • Legacy kódok megértése
    • Jquery pluginek használata
    • Progresszív fejlesztéseknél még gyakran alkalmazzák
    • Programozási minták megfigyelése
    • API tervezés megfigyelése
    • Nagy hatással volt a JS és a böngésző API-k fejlődésére

Linkek

Alapkoncepciók

  • DOM absztrakció (böngészőfüggetlenség)
  • Kényelmes API
  • Plusz szolgáltatások, pl. Ajax, animációk

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ó
  • 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())

Bejárás

// 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()

Méretek

  • 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 () {});

AJAX jQuery-vel

  • Alacsony szintű metódus
    • $.ajax()
  • Magas szintű metódus
    • $.get()
    • $.post()
    • $.getJSON()
    • $.getScript()
    • $elem.load()

load()

A letöltött tartalmat a kiválasztott elembe helyezi.

// Alap használat
$("#hirek").load("hirlista.html");

// Oldaltöredék betöltése
$("#hirek").load("hirlista.html #content");

// Kérés küldése paraméterekkel
$("#hirek").load("hirlista.php", { honnan: 0, mennyit: 10 });

// Callback függvény hívása a kommunikáció végén
$("#hirek3").load("hirlista.php", { honnan: 0, mennyit: 10 }, function() {
    alert('Betöltöttem a hírlistát!');
});

get, post

// GET kérés a szervernek
$.get("hirlista.php", { honnan: 0, mennyit: 10 });

// POST kérés a szervernek
$.post("hirlista.php", { honnan: 0, mennyit: 10 });

// JSON kommuniáció
$.getJSON("hirlista.php", { formatum: 'json' });

// Dinamikus szkriptbetöltés
$.getScript("jquery_ui.js");

$.ajax()

$.ajax({
    url: 'hirlista.php',
    data: { mettol: 0 },
    type: 'POST',
    dataType: 'html',
});

Válasz feldolgozása

jqXHR objektum metódusai

  • done()
  • fail()
  • always()
$.post("hirlista.php", { honnan: 0, mennyit: 10 })
    .done(function (data) {
        console.log(data);
    })
    .fail(function (error) {
        console.log(error);
    });

Űrlap küldése

serializeArray() metódus

$('#registration form').submit(function(e) {
    // A form elküldésének megakadályozása
    e.preventDefault();
    var $this = $(this);
    $.post('regisztracio.php', $this.serializeArray(), function(data) {
        $this.html(data);
    });
});

jQuery natív alternatívái

Elemek kiválasztása

// jQuery
$('ul li')

// Native
document.querySelectorAll('ul li')
Array.from(document.querySelectorAll('ul li'))
[...document.querySelectorAll('ul li')]
// Native $ function 
const $ = document.querySelectorAll.bind(document)
// or
function $(selector) {
    return Array.from(document.querySelectorAll(selector))
    // return [...document.querySelectorAll(selector)]
}

Iteráció

// jQuery
$('p').html('Apple')

// Native
Array.from(document.querySelectorAll('p'))
  .map(el => el.innerHTML = 'Apple')

Iteráció és láncolás

// jQuery
$('p')
  .html('Apple<span>1</span><span>2</span>')
  .css('color', 'red')
  .find('span')

// Native
Array.from(document.querySelectorAll('p'))
  .map(el => (el.innerHTML = 'Apple<span>1</span><span>2</span>', el))
  .map(el => (el.style.color = 'red', el))
  .flatMap(el => Array.from(el.querySelectorAll('span')))
  .map(el => (console.log(el), el))

// Native
const paragraphs = Array.from(document.querySelectorAll('p'))
paragraphs.forEach(el => {
  el.innerHTML = 'Apple<span>1</span><span>2</span>'
  el.style.color = 'red'
  const spans = Array.from(el.querySelectorAll('span'))
  spans.forEach(span => console.log(span))
})

Natív alternatívák

Láncolás (chaining)

  • A módosító metódusok adják vissza magát az objektumot
  • return this
const s = new Stack()
s
  .push(10)
  .push(20)
  .log()
  .clear()
  .log()
class Stack {
  constructor() {
    this.items = []
  }
  push(e) {
    this.items.push(e)
    ✒>return this<✒
  }
  log() {
    console.log(this.items)
    ✒>return this<✒
  }
  clear() {
    this.items = []
    ✒>return this<✒
  }
}

Végszó

  • Függvények
    • Callback minta
    • Függvénygenerátorok
    • Closure
    • Függvény mint objektum
  • jQuery
    • manapság új projekthez ne használjuk
    • legacy kódok, pluginok