Chapter 11: jQuery i Ajax

jQuery i Ajax

Ajax

Chociaż web2py służy głównie do obsługi kodu po stronie serwera, to aplikacja szkieletowa welcome dostarczana jest z podstawową biblioteką jQuery[jquery], kalendarzami jQuery (date picker, datetime picker i clock) oraz z kilkoma dodatkowymi funkcjami JavaScript opartymi na jQuery.

Nic nie stoi na przeszkodzie, aby w web2py używać inne biblioteki Ajax, takie jak Prototype, ExtJS czy YUI, ale zdecydowaliśmy się na dołączenie jQuery, ponieważ stwierdziliśmy, że jest ona łatwiejsza w stosowaniu oraz bardziej wydajna, niż inne równoważne biblioteki. Uważamy też, że oddaje ona ducha web2py, będąc funkcjonalna i zwięzła.

web2py_ajax.html

Szkieletowa aplikacja "welcome" web2py zawiera plik o nazwie

1
views/web2py_ajax.html

którego zawartość jest następująca:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{{
response.files.insert(0,URL('static','js/jquery.js'))
response.files.insert(1,URL('static','css/calenadar.css'))
response.files.insert(2,URL('static','js/calendar.js'))
response.include_meta()
response.include_files()
}}
<script type="text/javascript"><!--
    // These variables are used by the web2py_ajax_init
        // function in web2py.js (which is loaded below).
    var w2p_ajax_confirm_message =
        "{{=T('Are you sure you want to delete this object?')}}";
    var w2p_ajax_date_format = "{{=T('%Y-%m-%d')}}";
    var w2p_ajax_datetime_format = "{{=T('%Y-%m-%d %H:%M:%S')}}";
//--></script>
<script src="{{=URL('static','js/web2py.js')}}"
        type="text/javascript"></script>

Plik ten jest dołączany w sekcji HEAD domyślnego układu "layout.html" i dostarcza następujące usługi:

  • Dołacza "static/jquery.js".
  • Dołacza "static/calendar.js" i "static/calendar.css", które są używane dla okienek kalendarza.
  • Dołacza wszystkie nagłówki response.meta.
  • Dołacza wszystkie pliki response.files (wymaga CSS i JS, jak zadeklarowano w kodzie).
  • Ustawia zmienne formularza i dołacza "static/js/web2y.js".

Plik "web2py.js" działa następująco:

  • Definiuje funkcję ajax (opartą na jQuery $.ajax).
  • Wykonuje kod sprawiający, że każdy DIV z klasą "error" lub każdy obiekt znacznikowy z klasą "flash" będzie sie zsuwać w dół.
  • Zapobiega wpisaniu złych liczb w polacj INPUT z klasą "integer".
  • Zapobiega wpisaniu złych liczb w polach INPUT z klasą "double".
  • Łączy pola INPUT typu "date" z okienkiem wyboru dat.
  • Łączy pola INPUT typu "datetime" z okienkiem wyboru dat i czasu.
  • Łączy pola INPUT typu "time" z okienkiem wyboru czasu.
  • Definiuje web2py_ajax_component, bardzo ważny komponent opisany w rozdziale 12.
  • Definiuje web2py_websocket, funkcję która może zostać zasosowana dla WebSockets HTML5 (nie opisane w tej ksiązce, ale można zpoznać się z przykładami w kodzie źródłowym "gluon/contrib/websocket__messaging.py").
    websockets
  • Definiuje funkcje do obliczania entropii i walidacji danych wejściowych pola hasła.

Zawiera on również funkcje popup, collapse i fade dla zapewnienia kompatybiności wstecznej.

Oto przykład współdziałania innych efektów.

Rozważmy aplikację test z następującym modelem:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
db = DAL("sqlite://db.db")
db.define_table('child',
     Field('name'),
     Field('weight', 'double'),
     Field('birth_date', 'date'),
     Field('time_of_birth', 'time'))

db.child.name.requires=IS_NOT_EMPTY()
db.child.weight.requires=IS_FLOAT_IN_RANGE(0,100)
db.child.birth_date.requires=IS_DATE()
db.child.time_of_birth.requires=IS_TIME()

i z takim kontrolerem "default.py":

1
2
3
4
5
def index():
    form = SQLFORM(db.child)
    if form.process().accepted:
        response.flash = 'record inserted'
    return dict(form=form)

oraz następujacym widokiem "default/index.html":

1
2
{{extend 'layout.html}}
{{=form}}

Akcja "index" generuje następujący formularz:

image

Jeśli zostanie złożony zły formularz, serwer zwróci stronę ze zmienionym formularzem, zawierającym komunikaty o błedach. Komunikaty o błędach są elementami DIV klasy CSS "error" i pod działaniem powyższego kodu web2py.js code, komunikaty te pojawiać się będą z efektem slide-down:

image

Kolor tekstu komunikatów błędów jest podany w kodzie CSS w "layout.html".

Kod web2py.js zabezpiecza formularz przed wprowadzeniem niewłaściwych wartości w polach wejściowych. Jest to realizowane przed złożeniem formularza i jest funkcjonalnością dodatkową, nie będącą substytutem walidacji po stronie serwera.

Kod web2py.js wyświetla okienko wyboru daty, gdy wprowadza się dane do pola INPUT z klasą "datetime". Oto przykład:

image

Kod web2py.js wyświetla również następujace okienko wyboru czasu, gdy próbuje się edytować pole INPUT z klasą "time":

image

Po złożeniu formularza, akcja kontrolera ustawia odpowiedź z komunikatem fleszowym "record inserted". Domyślny układ renderuje ten komunikat w elemencie DIV z atrybutem CSS id="flash". Kod web2py.js jest odpowiedzialny za efekt pojawiania się i zanikania tego elementu DIV po klknięciu na nim:

image

Te i inne efekty są dostępne programowo w widokach i poprzez helpery w kontrolerach.

Efekty jQuery

efekty jQuery

Opisane tutaj podstawowe efekty nie wymagaja żadnych dodatkowych plików - wszystko co potrzeba, jest włączone w web2py_ajax.html.

Obiekty HTML/XHTML mogą być identyfikowane przez ich typ (na przykład DIV), ich klasy lub ich identyfikator. Na przykład:

1
2
<div class="one" id="a">Hello</div>
<div class="two" id="b">World</div>

Przynależą one odpowiednio do klas "one" i "two". Mają one identyfikatory równe odpowiednio "a" i "b".

W jQuery można odwoływać się do pierwszego z tych selektorów używając notacji podobnej do notacji CSS, w ten sposób:

1
2
3
4
jQuery('.one')    // adresowanie obiektu przez klasę "one"
jQuery('#a')      // adresowanie obiektu przez id "a"
jQuery('DIV.one') // adresowanie obiektu typu "DIV" z klasą "one"
jQuery('DIV #a')  // adresowanie przez obiekt typu "DIV" z id "a"

a do drugiego, tak:

1
2
3
4
jQuery('.two')
jQuery('#b')
jQuery('DIV.two')
jQuery('DIV #b')

lub można odwoływać się do obu z nich:

1
jQuery('DIV')

Obiekty znacznikowe są powiązane ze zdarzeniami, takimi jak "onclick". jQuery umożliwia połączenie tych zdarzeń z efektami, na przykład "slideToggle":

1
2
<div class="one" id="a" onclick="jQuery('.two').slideToggle()">Hello</div>
<div class="two" id="b">World</div>

Teraz po kliknieciu na "Hello", znika "World". Jeśli kliknie się jeszcze raz, "World" pojawi sie ponownie. Można wykonać ukrywanie elementu domyślnie przez nadanie mu klasy hidden:

1
2
<div class="one" id="a" onclick="jQuery('.two').slideToggle()">Hello</div>
<div class="two hidden" id="b">World</div>

Można również odnieść akcje do zdarzeń poza samym znacznikiem. Powyższy kod można zapisać następująco:

1
2
3
4
5
<div class="one" id="a">Hello</div>
<div class="two" id="b">World</div>
<script>
jQuery('.one').click(function(){jQuery('.two').slideToggle()});
</script>

Efekty zwracają wywoływalne obiekty, więc można je łączyć.

Ustawienie funkcji zwrotnej na zdarzeniu click powoduje możliwość wywoływania tej funkcji poprzez klikniecie elementu. Podobnie jest dla change, keyup, keydown, mouseover itd.

Powszechną sytuacja jest potrzeba wykonania jakiegośc kodu JavaScript dopiero po całkowitym załadowaniu dokumentu. Jest to często realizowane przez wykorzystanie atrybutu onload selektora BODY, ale jQuery dostarcza alternatywne rozwiązanie, które nie wymaga edytowania układu:

1
2
3
4
5
6
7
<div class="one" id="a">Hello</div>
<div class="two" id="b">World</div>
<script>
jQuery(document).ready(function(){
   jQuery('.one').click(function(){jQuery('.two').slideToggle()});
});
</script>

Ciało funkcji nienazwanej jest wykonywane tylko wtedy, gdy dokument jest gotowy, po jego całkowitym załadowaniu.

Oto wykaz przydatnych nazw zdarzeń:

Zdarzenia formularza

  • onchange: skrypt uruchamiany gdy element ulega zmianie,
  • onsubmit: skrypt uruchamiany gdy formularz został zgłoszony,
  • onreset: skrypt uruchamiany gdy formularz jest resetowany,
  • onselect: skrypt uruchamiany gdy element został wybrany,
  • onblur: skrypt uruchamiany gdy element traci fokus,
  • onfocus: skrypt uruchamiany gdy element uzyskuje fokus.

Zdarzenia klawiatury

  • onkeydown: skrypt uruchamiany gdy zostaje wciśnięty klucz,
  • onkeypress: skrypt uruchamiany gdy klucz zostaje wciśnięty i zwolniony,
  • onkeyup: skrypt uruchamiany gdy klucz zostaje zwolniony.

Zdarzenia myszy

  • onclick: skrypt uruchamiany gdy wykonane jest kliknięcie myszą,
  • ondblclick: skrypt uruchamiany gdy wykonane jest dwukliknięcie myszą,
  • onmousedown: skrypt uruchamiany gdy zostaje kliknięty przycisk,
  • onmousemove: skrypt uruchamiany gdy zostaje przeniesiony wskaźnik myszy,
  • onmouseout: skrypt uruchamiany gdy wskaźnik myszy opuszcza element,
  • onmouseover: skrypt uruchamiany gdy wskaźnik myszy zostaje przeniesiony na element,
  • onmouseup: skrypt uruchamiany gdy przycisk mysz zostaje zwolniony.

Oto lista przydatnych efektów zdefiniowanych w jQuery:

Efekty

  • jQuery(...).show(): czyni obiekt widocznym,
  • jQuery(...).hide(): czyni obiekt ukrytym;
  • jQuery(...).slideToggle(speed, callback): czyni obiekt rozwijajacym lub zwijającym sie,
  • jQuery(...).slideUp(speed, callback): czyni obiekt przesuwajacym się w górę,
  • jQuery(...).slideDown(speed, callback): czyni obiekt przesuwajacym się w dół,
  • jQuery(...).fadeIn(speed, callback): czyni obiekt zanikającym przez ściemnianie,
  • jQuery(...).fadeOut(speed, callback): czyni obiekt zanikającym przez rozjaśnianie.

Argument speed jest zwykle ustawiany na "slow", "fast" lub zostaje pominięty (domyślnie). Argument callback jest opcjonalną funkcją wywoływaną po zakończeniu efektu.

Efekty jQuery mogą być łatwo osadzane w helperach, dla przykładu w widoku:

1
{{=DIV('click me!', _onclick="jQuery(this).fadeOut()")}}

Inne pomocne metody i atrybuty dla obsługi wybranych elementów, to:

Metody i atrybuty

  • jQuery(...).prop(name): zwraca wartość atrybutu o nazwie name;
  • jQuery(...).prop(name, value): ustawia atrybut o nazwie name na wartość value;
  • jQuery(...).html(): bez argumentów zwraca kod HTML z wewnątrz wybranego elementu, a z argumentem łańcuchowym przyjmuje ten łańcuch jako nową zawartość znacznika i dokonuje zamiany;
  • jQuery(...).text(): bez argumentów zwraca wewnętrzny tekst wybranego elementu (bez znaczników), a z argumentem łańcuchowym przyjmuje ten łańcuch jako nowy tekst wewnątrz znacznika;
  • jQuery(...).css(name, value): gdy podano tylko parametr name, zwraca watość atrybutu style wybranego elementu, przy dwóch parametrach ustawia nową wartość dla atrybutu style;
  • jQuery(...).each(function): jest to pętla po wybranych elementach, ustawiająca i wywołująca function w każdym elemencie jako argument;
  • jQuery(...).index(): bez argumentów zwraca indeks pierwszego elementu wybranego względem swojego rodzeństwa (czyli indeks elementu LI); gdy przekazywany jest argument, to zwraca pozycję elementu wzgledem wybranego zestawu elementów;
  • jQuery(...).length: atrybut ten zwraca liczbę wybranych elementów.

jQuery jest bardzo kompaktową i zwięzłą biblioteką Ajax, dlatego web2py nie potrzebuje żadnej dodatkowej wartstwy abstrakcji na szczycie jQuery (z wyjątkiem funkcji ajax omówionej poniżej). Interfejsy API jQuery są łatwo dostępne w ich natywnej postaci gdy są potrzebne.

Proszę zapoznać się z dokumentacja jQuery i jej intefrfejsów API w celu poznania szczegółów.

Biblioteka jQuery może zostać rozszerzona przy użyciu wtyczek i User Interface Widgets. Temat ten nie został tutaj omówiony, patrz [jquery-ui] w celu poznania szczegółów.

Pola warunkowe w formularzach

Typowym zastosowaniem efektów jQuery jest formularz zmieniający wygląd na podstawie wartości swoich pól.

W web2py jest to łatwe, ponieważ helper SQLFORM generuje formularze "przyjazne dla CSS". Formularz zawiera tabelę z wierszami. Każdy wiersz zawiera etykietę, pole wejściowe i ewentualnie trzecią kolumnę. Elementy mają identyfikatory pochodzące dokładnie od nazwy tabeli i nazw pól.

Konwencją jest, że każde pole INPUT ma identyfikator ustalony według wzorca tablename_fieldname i że jest zawarte w wierszu o identyfikatorze ustalonym według wzorca tablename_fieldname__row.

Jako przykład, utworzymy formularz wejściowy, który pyta o imie podatnika i o imię żony podatnika, ale tylko wtedy, gdy jest on żonaty.

Utwórzmy aplikacje testową z następującym modelem:

1
2
3
4
5
db = DAL('sqlite://db.db')
db.define_table('taxpayer',
    Field('name'),
    Field('married', 'boolean'),
    Field('spouse_name'))

i natepujacym kontrolerem "default.py":

1
2
3
4
5
def index():
    form = SQLFORM(db.taxpayer)
    if form.process().accepted:
        response.flash = 'record inserted'
    return dict(form=form)

oraz następujacym widokiem "default/index.html":

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{{extend 'layout.html'}}
{{=form}}
<script>
jQuery(document).ready(function(){
   if(jQuery('#taxpayer_married').prop('checked'))
        jQuery('#taxpayer_spouse_name__row').show();
   else jQuery('#taxpayer_spouse_name__row').hide();
   jQuery('#taxpayer_married').change(function(){
        if(jQuery('#taxpayer_married').prop('checked'))
            jQuery('#taxpayer_spouse_name__row').show();
        else jQuery('#taxpayer_spouse_name__row').hide();});
});
</script>

Skrypt w widoku ma wpływ na efekt ukrywania wiersza zawierającego imię żony:

image

Gdy podatnik zaznaczy pole "married", w formularzu pojawi się pole dla imienia żony:

image

Tutaj "taxpayer_married" jest polem wyboru związanym z "logicznym" polem "married" w tabeli "taxpayer". Wiersz "taxpayer_spouse_name__row" zawiera pole wejściowe dla "spouse_name" w tabeli "taxpayer".

Potwierdzanie przy kasowaniu

potwierdzanie

Kolejna przydatna aplikacja, to potwierdzanie decyzji po zaznaczeniu pola wyboru "delete", takiego, które stosuje sie w formularzach edycyjnych.

Rozważmy następujący przykład i dodajmy następującą akcję kontrolera:

1
2
3
4
5
6
def edit():
    row = db.taxpayer[request.args(0)]
    form = SQLFORM(db.taxpayer, row, deletable=True)
    if form.process().accepted:
        response.flash = 'record updated'
    return dict(form=form)

i odpowiadający jej widok "default/edit.html"

1
2
{{extend 'layout.html'}}
{{=form}}
deletable

Argument deletable=True w konstruktorze SQLFORM instruuje web2py aby wyświetlił pole wyboru "delete" w formularzu edycyjnym. Ma ono wartość domyślną False.

Plik "web2py.js" w web2py zawiera następujący kod:

1
2
3
4
5
6
jQuery(document).ready(function(){
   jQuery('input.delete').prop('onclick',
     'if(this.checked) if(!confirm(
        "{{=T('Sure you want to delete this object?')}}"))
      this.checked=False;');
});

Zgodnie z konwencją pole wyboru kasowania ma klasę o wartości "delete". Powyższy kod jQuery łączy zdarzenie onclick tego pola wyboru z okienkiem dialogowym potwierdzenia (komunikat typowy w JavaScript) i odznacza pole wyboru, jeśli podatnik nie potwierdzi swojej decyzji:

image

Funkcja ajax

W web2py.js zdefiniowana jest funkcja o nazwie ajax która opiera się na funkcji jQuery $.ajax, ale nie powinna być z nią mylona. Ta ostatnia jest bardziej zaawansowana niz pierwsza i przed jej użyciem zalecamy zapoznać się z dokumentacją ref.[jquery] i ref.[jquery-b]. Jednak pierwsza funkcja jest wystarczająca dla wielu skomplikowanych zadań i jest łatwiejsza w stosowaniu.

Funkcja ajax jest funkcją JavaScript, która ma następującą składnię:

1
ajax(url, [name1, name2, ...], target)

Wywołuje ona asynchronicznie adres url (pierwszy argument), przekazuje wartości pól wejściowych z nazwą równą jednej z nazw na liście (drugi argument), następnie zapisuje odpowiedź w innerHTML znacznika z identyfikatorem równym celowi (trzeci argument).

Oto przykład kontrolera default:

1
2
3
4
5
def one():
    return dict()

def echo():
    return request.vars.name

i związanego widoku "default/one.html":

1
2
3
4
5
{{extend 'layout.html'}}
<form>
   <input name="name" onkeyup="ajax('{{=URL('default', 'echo')}}', ['name'], 'target')" />
</form>
<div id="target"></div>

Podczas wpisywania czegoś w polu INPUT, zaraz po zwolnieniu klucza(onkeyup), wywoływana jest funkcja ajax i wartość pola name="name" jest przekazywana do akcji "echo", która odsyła z powrotem tekst do widoku. Funkcja ajax odbiera odpowiedź i wyświetla echo odpowiedzi w DIV "target".

Ewaluacja celu

Trzeci argument funkcji ajax może być łańcuchem :eval. Oznacza to, że łańcuch zwracany przez serwer nie będzie osadzany w dokumencie, ale zamiast tego będzie ewaluowany.

Oto przykład kontrolera default:

1
2
3
4
5
def one():
    return dict()

def echo():
    return "jQuery('#target').html(%s);" % repr(request.vars.name)

i związanego widoku "default/one.html":

1
2
3
4
5
{{extend 'layout.html'}}
<form>
   <input name="name" onkeyup="ajax('{{=URL('default', 'echo')}}', ['name'], ':eval')" />
</form>
<div id="target"></div>

Umożliwia to tworzenie bardziej złożonych odpowiedzi, które mogą aktualizować wiele celów.

Automatyczne uzupełnianie

W web2py wbudowany jest widżet uzupełniania, opisany w rozdziale "Formularze". Tutaj zbudujemy od podstaw proste rozwiązanie automatycznego uzupełniania.

Innym zastosowaniem powyżej omawianej funkcji ajax jest automatyczne uzupełnianie. Tutaj utworzymy pole wejściowe dla wprowadzania nazw mięsiecy, takie że kiedy odwiedzajacy wpisze niepełna nazwę, wykonane zostanie automatyczne uzupełnienie tekstu poprzez żądanie Ajax. W odpowiedzi, poniżej pola wejściowego pojawi się okienko z podpowiedzia.

Można to osiągnąć poprzez następujący kontroler default:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def month_input():
    return dict()

def month_selector():
    if not request.vars.month: return ''
    months = ['January', 'February', 'March', 'April', 'May',
              'June', 'July', 'August', 'September' ,'October',
              'November', 'December']
    month_start = request.vars.month.capitalize()
    selected = [m for m in months if m.startswith(month_start)]
    return DIV(*[DIV(k,
                     _onclick="jQuery('#month').val('%s')" % k,
                     _onmouseover="this.style.backgroundColor='yellow'",
                     _onmouseout="this.style.backgroundColor='white'"
                     ) for k in selected])

i związany widok "default/month_input.html":

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{{extend 'layout.html'}}
<style>
#suggestions { position: relative; }
.suggestions { background: white; border: solid 1px #55A6C8; }
.suggestions DIV { padding: 2px 4px 2px 4px; }
</style>

<form>
 <input type="text" id="month" name="month" style="width: 250px" /><br />
 <div style="position: absolute;" id="suggestions"
      class="suggestions"></div>
</form>
<script>
jQuery("#month").keyup(function(){
      ajax('{{=URL('default', 'month_selector')}}', ['month'], 'suggestions')});
</script>

Skrypt jQuery w widoku wyzwala żądanie Ajax za każdym razem, gdy użytkownik wpisze coś w polu wejściowym month. Ta wartość pola wejściowego jest zgłaszana w żądaniu Ajax do akcji month_selector. Akcja ta odszukuje listę nazw miesięcy, która rozpoczyna się od zgłoszonego (wybranego) tekstu , buduje liste elementów DIV (każdy zawiera proponowaną nazwę) i zwraca łańcuch z serializowanymi elementami DIV. Widok wyświetla odpowiedź HTML w elemencie DIV suggestions. Akcja month_selector generuje zarówno podpowiedzi jak i kod JavaScript osadzony w elementach DIV, który musi być wykonany, gdy uzytkownik kliknie na podpowiedź. Na przykład, gdy odwiedzający wpisze "M", akcja wywołania zwrotnego zwróci:

1
2
3
4
5
6
7
8
<div>
     <div onclick="jQuery('#month').val('March')"
          onmouseout="this.style.backgroundColor='white'"
          onmouseover="this.style.backgroundColor='yellow'">March</div>
     <div onclick="jQuery('#month').val('May')"
          onmouseout="this.style.backgroundColor='white'"
          onmouseover="this.style.backgroundColor='yellow'">May</div>
</div>

Oto efekt końcowy:

image

Jeśli nazwy miesięcy są zapisane w tablicy bazy danych, tak jak tu:

1
db.define_table('month', Field('name'))

wystarczy zamienić akcję month_selector na:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def month_input():
    return dict()

def month_selector():
    if not request.vars.month:
        return ''
    pattern = request.vars.month.capitalize() + '%'
    selected = [row.name for row in db(db.month.name.like(pattern)).select()]
    return ''.join([DIV(k,
                 _onclick="jQuery('#month').val('%s')" % k,
                 _onmouseover="this.style.backgroundColor='yellow'",
                 _onmouseout="this.style.backgroundColor='white'"
                 ).xml() for k in selected])

Biblioteka jQuery dostarcza opcjonalną wtyczkę Auto-complete z dodatkowymi funkcjonalnościami, ale nie opisujemy jej tutaj.

Zgłaszanie formularza Ajax

asynchroniczność
wywołania asynchroniczne

Tutaj rozważymy stronę, która umożliwia użytkownikom wysłać komunikaty przy użyciu Ajax bez przeładowywania całej strony. W web2py dostępny jest helper LOAD stanowiący lepszy mechanizm do wykonania tej funkcjonalnosci niż sposób tu opisany. Jest on omówiony w rozdziale 12. Tutaj pokazujemy, jak wykonać omawianą funkcjonalność używając jQuery.

Nasz przykład zawiera formularz "myform" i DIV "target". Gdy zgłoszony zostaje ten formularz, serwer może go zaakceptować (i wykonać operację wstawiania do bazy danych) lub go odrzucić (ponieważ nie przechodzi walidacji). Zwracane jest powiadomienie z odpowiedzią Ajax i zostaje ono wyświetlone w DIV "target".

Zbudujmy aplikację test z następujacym modelem:

1
2
3
db = DAL('sqlite://db.db')
db.define_table('post', Field('your_message', 'text'))
db.post.your_message.requires = IS_NOT_EMPTY()

Proszę zwrócić uwagę, że każdy wpis ma pojedyncze pole "your_message", które ma wymaganie, aby nie było puste.

Edytujmy kontroler default.py i zapiszmy dwie akcje:

1
2
3
4
5
6
7
8
9
def index():
    return dict()

def new_post():
    form = SQLFORM(db.post)
    if form.accepts(request, formname=None):
        return DIV("Message posted")
    elif form.errors:
        return TABLE(*[TR(k, v) for k, v in form.errors.items()])

Pierwsza akcja tylko zwraca widok.

Druga akcja jest wywołaniem zwrotnym Ajax. Oczekuje ona zmiennych formularza w request.vars, przetwarza je i zwraca DIV("Message posted") w przypadku powodzenoa a TABLE komunikatów o błędach w przypadku niepowodzenia.

Edytujmy teraz widok "default/index.html":

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{{extend 'layout.html'}}

<div id="target"></div>

<form id="myform">
  <input name="your_message" id="your_message" />
  <input type="submit" />
</form>

<script>
jQuery('#myform').submit(function() {
  ajax('{{=URL('new_post')}}',
       ['your_message'], 'target');
  return false;
});
</script>

Proszę zwrócić uwagę, jak w tym przykładzie formularz jest tworzony ręcznie przy uzyciu HTML, ale jest przetwarzany przez SQLFORM w innej akcji niż ta, która wyświetla formularz. Obiekt SQLFORM nie jest nigdy serializowany w HTML. SQLFORM.accepts w tym przypadku nie pobiera sesji i ustawia formname=None, ponieważ nie zdecydowaliśmy się na ustawienie nazwy formularza i klucza formularza w ręcznym formularzu HTML.

Skrypt na dole widoku łączy przycisk zatwierdzania "myform" z funkcja wierszową, która zgłasza INPUT z id="your_message" używając funkcję ajax web2py i wyświetla odpowiedź wewnątrz DIV z id="target".

Głosowanie i ocena

Inną aplikacją Ajax jest głosowanie lub elementy oceniania na stronie. Tutaj zakładamy, że aplikacja, która pozwala odwiedzającym głosować na zamieszone obrazy. Aplikacja składa sie z pojedynczej strony, która wyswietla obrazy posortowane według ilości głosów. Umożliwimy odwiedzajacym głosować wiele razy, chociaż można to łatwo zmienić, jeśli odwiedzajacy są uwierzytelnieni, przez śledzenie poszczególnych głosow w bazie danych i powiązaniu ich z request.env.remote_addr głosującego.

Oto prosty model:

1
2
3
4
db = DAL('sqlite://images.db')
db.define_table('item',
    Field('image', 'upload'),
    Field('votes', 'integer', default=0))

a to jest kontroler default:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def list_items():
    items = db().select(db.item.ALL, orderby=db.item.votes)
    return dict(items=items)

def download():
    return response.download(request, db)

def vote():
    item = db.item[request.vars.id]
    new_votes = item.votes + 1
    item.update_record(votes=new_votes)
    return str(new_votes)

Akcja download jest niezbędna do umożliwienia, aby widok list_items pobierał obrazy przechowywane w folderze "uploads". Akcja votes jest wykorzystywana dla wywołania zwrotnego Ajax.

Oto widok "default/list_items.html":

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{{extend 'layout.html'}}

<form><input type="hidden" id="id" name="id" value="" /></form>
{{for item in items:}}
<p>
<img src="{{=URL('download', args=item.image)}}"
     width="200px" />
<br />
Votes=<span id="item{{=item.id}}">{{=item.votes}}</span>
[<span onclick="jQuery('#id').val('{{=item.id}}');
       ajax('{{=URL('default', 'vote')}}', ['id'], 'item{{=item.id}}');">vote up</span>]
</p>
{{pass}}

Gdy użytkownik kliknie "[vote up]" kod JavaScript przechowa item.id w ukrytym polu INPUT "id" i zgłosi tą wartość do serwera poprzez żądanie Ajax. Serwer zwiększa licznik głosów dla odpowiedniego rekordu i zwraca nową liczbę głosów jako łańcuch. Wartość ta jest następnie wstawiana w docelowym elemencie SPAN item{{=item.id}}.

Wywołania zwrotne Ajax mogą być wykorzystywane do wykonywania obliczeń w tle, ale zalecamy używanie zamiast tego cron lub przetwarzanie w tle (omówione w rozdziale 4), ponieważ serwer internetowy wymusza limit czasu w wątkach. Jeśli obliczenie trwa zbyt długo, serwer internetowy zabije taki proces. Zobacz parametry serwera internetowego aby ustawić wartość limitu czasu.
 top