Ajax w jQuery

Obsługa Ajaxa w jQuery jest jak zwykle bajecznie prosta. W poniższym artykule poznamy różne techniki i zastosowania tej wybitnej metody :).
Z rozdziału o ajaxie wiemy już czym to "coś" jest i jak to obsłużyć w czystym JS. Wiemy też, że nie jest to najłatwiejszą czy najwygodniejszą rzeczą na świecie. Nie w jquery.

Do najprostszych metody jQuery służących do obsługi ajaxa należą:

$.get(adres_skryptu, data_wysylana, funkcja_zwrotna)


$.get(
    "zapisz_do_bazy.php",
    {
        imie: "Johny",
        nazwisko: "Bravo"
    },
    function(dane){
        alert("Dane otrzymane: " + dane);
    }
);

Poza adresem do skryptu na serwerze reszta atrybutów jest opcjonalna. Nikt nie każe nam wysyłać ani danych ani wykonywać żadnych akcji po otrzymaniu odpowiedzi z serwera.

Bardzo podobne działanie ma metoda $.post, jednak jak sama nazwa wskazuje wysyła ona dane za pomocą post:


$.post(
    "saveToDatabase.php",
    {
        name: "Johny",
        surname: "Bravo"
    },
    function(data){
        alert("Dane otrzymane: " + data);
    }
);

Powyższe metody mimo, że bardzo proste w użyciu, nie dają nam pełnej kontroli nad połączeniem.
O wiele lepszą do tego celu jest metoda $.ajax:


$.ajax({
    url         : "saveToDatabase.php",
    type        : "post", //typ połączenia
    contentType : 'application/json', //gdy wysyłamy dane czasami chcemy ustawić ich typ
    dataType    : 'json', //typ danych jakich oczekujemy w odpowiedzi
    data        : { //dane do wysyłki
        name : 'Marcin',
        country : 'Polska'
    })
});

Tak samo jak w przypadku wcześniejszych metod, jedynym wymaganym atrybutem jest url.

Poza url możemy określić dodatkowe atrybuty, wśród których znajdują się między innymi:

type typ połączenia: post, get, delete, put, patch. Domyślnym jest get i wtedy nie musimy podawać tego parametru
data obiekt zawierający przekazywane dane
dataType typ danych jaki oczekujemy w odpowiedzi. Omówimy go za moment. Jeżeli nie zostanie ustawiony, jQuery spróbuje określić typ danych na podstawie tego co dostanie w odpowiedzi. Najczęściej będzie to 'json'
contentType typ danych jakie wysyłamy na serwer. Czasami musimy poinformować serwer jakie dane mu wysyłamy

W praktyce parametrów możliwych do ustawienia jest o wiele więcej, ale większości z nich na co dzień zwyczajnie się nie używa. Warto jednak przyjrzeć się stronie http://jqapi.com/#p=jQuery.ajax i zapoznać się z kilkoma z nich.

Podstawowe funkcje zwrotne

Funkcja $.ajax udostępnia nam funkcje zwrotne związane z obsługą połączenia. Te najczęściej używane to:

done() wywoływane po pomyślnym wysłaniu danych do skryptu (kiedy serwer nie zwrócił błędu typu 400, 404, 500 itp)
fail() wywoływane gdy nastąpi błąd w połączeniu
always() wywoływane po zakończeniu łączenia - bez względu czy zakończyło się błędem czy sukcesem

$.ajax({
    url : '...'
})
.done(function(response) {
    console.log(response);
})
.fail(function() {
    console.warn( "Wystąpił błąd w połączniu");
})
.always(function() {
    $('.loading').hide();
});

Przykładowe zapytanie ściągające dane ze strony https://jsonplaceholder.typicode.com/users może mieć postać:


$('.button').on('click', function() {
    const $btn = $(this);

    //wyłączamy loading
    //i wyłączamy przycisk by użytkownik nie był nadgorliwy
    $btn.addClass('loading');
    $btn.prop('disabled', 1);

    $.ajax({
        url : 'https://jsonplaceholder.typicode.com/users',
        dataType : 'json',
        type : 'get'
    })
    .done(function(res) {
        res.forEach(function(el) {
            console.log(el);
        })
    })
    .always(function() {
        //po zakończeniu wczytywania wyłączamy loading
        //i włączamy przycisk
        $btn.removeClass('loading');
        $btn.prop('disabled', 0);
    })
})

Dynamiczne selekty z wykorzystaniem JSON

Jako przykład wykorzystania powyższej wiedzy stworzymy mechanizm 2 selektów. Opcje w 2 selekcie będą zależne od tego, co wybierzemy w 1 selekcie.
Poniższy przykład pokazuje, jak taki problem rozwiązać z wykorzystaniem json:


    <select id="select1" name="gender">
        <option value="-1">Wybierz płeć</option>
        <option value="k">Kobiety</option>
        <option value="m">Mężczyźni</option>
    </select>

    <select id="select1" disabled name="personNames">
        <option value="-1">---</option>
    </select>

Mamy więc 2 selekty. Pierwszy z nich służy do wybrania płci, a drugi będzie służyć do wyboru imienia.
Po wybraniu płci w 1 selekcie, sprawdzamy czy ma ona jakąś sensowną wartość (czyli jest różna od -1) i wysyłamy ją do serwera. Następnie aktywujemy drugi selekt i za pomocą pętli dynamicznie ustawiamy jego optiony.


const $select1 = $('#select1');
const $select2 = $('#select2');

$select1.on('change', function() {
    const selectVal = $(this).find('option:selected').val();   //pobieramy wartość wybranego selekta

    if (selectVal !== -1) {  //jeżeli jest inna niż -1 (czyli jeżeli został wybrany model)
        $.ajax({
            type: "post",
            url: "skryptNaSerwerze.php",
            dataType : 'json',
            data: {
                gender : selectVal
            }
        })
        .done(function(json) {
            $select2.prop('disabled', 0); //aktywujemy 2 selekt
            $select2.empty(); //czyścimy go

            $.each(json, function(i, ob) {
            $select2.append('<option value="'+ob.value+'">'+ob.name+'</option>');
            });

            //ustawiamy jako wybrany pierwszy option w selekcie 2
            $select2.find('option').eq(0).prop('selected', 1);
        })
        .fail(function() {
            console.warn('wystąpił błąd');
        })
    } else {
        $select2.empty();
        $select2.prop('disabled', 1);
    }
});

$select1.val(-1);
$select2.prop('disabled', 1);

Aby powyższy skrypt miał sens, musimy stworzyć skrypt skryptNaSerwerze.php, który na podstawie przesłanego POST będzie wysyłał do przeglądarki json z odpowiednimi wartościami, na podstawie których tworzymy nowe optiony. Poniżej przedstawiam wersję dla php


<?php
$gender = $_POST['gender'];

$names = Array();

$names['m'] = Array();
$names['m'][0] = Array('name'=>'Arnold', 'value'=>'1');
$names['m'][1] = Array('name'=>'Bartek', 'value'=>'2');
$names['m'][2] = Array('name'=>'Darek', 'value'=>'3');
$names['m'][3] = Array('name'=>'Kamil', 'value'=>'4');
$names['m'][4] = Array('name'=>'Michał', 'value'=>'5');

$names['k'] = Array();
$names['k'][0] = Array('name'=>'Ania', 'value'=>'1');
$names['k'][1] = Array('name'=>'Beata', 'value'=>'2');
$names['k'][2] = Array('name'=>'Kamila', 'value'=>'3');
$names['k'][3] = Array('name'=>'Patrycja', 'value'=>'4');
$names['k'][4] = Array('name'=>'Zenona', 'value'=>'5');

echo json_encode($names[$gender]); ?>

Sprawdź w konsoli (zakładka Network) jak doczytywane są dane.

Dodatek: Poprzednie wersje jQuery

W poprzednich wersjach jQuery metody done, fail i always istniały pod nazwami success, error i complete.
Zmiana tych metod wiąże się z pojawieniem się promisów, dzięki którym możemy pisać o wiele przyjemniejszy kod.

W poprzednich wersjach gdy chcieliśmy wykonać jakąś funkcję po zakończeniu połączenia, musieliśmy ją odpalać z wnętrza deklaracji ajax:


$.ajax({
    url : '...',
    success : function(response) {
        calculateData(response);
    },
    error : function() {...},
    complete : function() {...}
})

Powodowało to, że nasz kod stawał się cięższy w utrzymaniu, ponieważ musieliśmy skakać z miejsca na miejsce:


const calculateData = function() {
    ...
}

//w poprzednich wersjach
const getData = function() {
    $.ajax({
        url : '...',
        success : function(response) {
            calculateData(response);
        },
        error : function() {...},
        complete : function() {...}
    })
}

getData();

Powyższy przykład jest i tak bardzo prosty. Wyobraź sobie, co by się działo, gdyby ściągnięcie naszych danych wymagało by kilku połączeń równocześnie. Oznaczało by to zagnieżdżanie jednej funkcji w drugiej i tak kilka razy. Wędrówka po kodzie zagwarantowana.

Dzięki nowemu podejściu możemy metody zwrotne podpinać "oddzielnie" od deklaracji ajaxa:


$.ajax({
    url : '...'
})
.done(function(response) {
    calculateData(response);
})
.fail(function() {
    console.warn( "Wystąpił błąd w połączniu :(");
})
.always(function() {
    $('.loading').hide();
});

Daje to o wiele większe możliwości niż w poprzednich wersjach, bo możemy czekać na to co zostanie zwrócone przez promise:


const calculateData = function() {
    ...
}

const getData = function() {
    return $.ajax({
        url : '...'
    });
}

//powyżej zadeklarowaliśmy klocki,
//poniżej tylko z nich korzystamy
getData().done(function(response) {
    calculateData(response);
})

const getData1 = function() {
    return $.ajax({
        url : '...'
    })
}

const getData2 = function() {
    return $.ajax({
        url : '...'
    })
}

//czekamy na skończenie działania naszych funkcji
$.when(getData1, getData2).then(function(res1, res2) {
    console.log(res1[0], res2[0]);
}, function() {
    console.log('Fail: ', error, error.status, error.statusText);
});

W powyższym przykładzie wykorzystaliśmy dwie nowe funkcje: when i then. Funkcje te wiążą się z obiektem deffered, który został wprowadzony w jQuery 1.8. Obiekt ten zwraca promise.