Jaxer – moje wrażenia
Autor: Zbyszek
Niedawno miałem okazję popełnić nieskomplikowaną aplikację webową (bazodanową) na bazie Jaxer i EXT JS.
Wyboru technologii dokonałem sam – chciałem się przekonać jakie są wady i zalety takiego połączenia oraz co można z tym zdziałać (a aplikacja była na tyle skomplikowana, że wychodziła poza typowe przykłady, tutoriale itp., a na tyle prosta, że nie bałem się użyć czegoś nowego).
Rezultat jest dosyć ciekawy, prawdopodobnie w niedalekiej przyszłości przedstawię mój sposób na integrację między nimi (sam kod). Ale najpierw opiszę co to jest Jaxer, co to jest EXT JS i podzielę się wrażeniami z pisania kodu na tej bazie.
Co to jest Jaxer?
Jaxer jest to moduł serwera aplikacji internetowych (najczęściej instalowany i standardowo napisany jako moduł Apache, ale da radę zintegrować też z Tomcatem czy z .NET) który zapewnia dodatkową warstwę przetwarzania po stronie serwera w oparciu o język JavaScript. W zasadzie jest to zupełne środowisko programistyczne pozwalające na to, na co pozwalają języki programowania aplikacji webowych po stronie serwera (jak np. PHP). Jaxer został stworzony przez firmę Aptana z Kanady, która to firma wcześniej napisała (i dalej tworzy) bardzo znane IDE wspomagające pracę programistów JS i PHP. Programiści z Aptana wzięli silnik JavaScript oraz DOM z Mozilla Firefox (jeden z najlepszych, jeśli nie najlepszy z dostępnych) i na jego bazie napisali API pozwalające na pisanie backendu aplikacji webowych w JavaScript (także z wykorzystaniem mechanizmów sesji, dostępu do bazy, wysyłania email, komunikacji sieciowej). Pomysł sam w sobie intrygujący – ten sam język programowania i u klienta w przeglądarce i na serwerze.
Jakie są zalety takiego podejścia?
Na pewno jest nią możliwość napisania kodu, który będzie wykonywany i po stronie klienta i po stronie serwera (napisania tylko raz w 1 języku). To się przydaje przy walidacji – można napisać kod walidacji raz i mieć pewność, że pola formularza przejdą sprawdzenie u klienta, a jeśli klient ma wyłączony JavaScript, to na serwerze też przejdą walidację tym samym kodem.
Kolejną zaletą jest prosta integracja backendu z frontendem poprzez wywołania asynchroniczne AJAX. Dzięki Jaxer piszemy funkcje proxy wykonywane na serwerze i są one dostępne do wywoływania po stronie klienta. Nie trzeba się męczyć z pisaniem kodu wiążącego, to jest robione “w tle” w sposób przeźroczysty dla programisty. Można się skupić na implementowaniu logiki aplikacji.
Jeszcze jedna zaleta to możliwość wykorzystania bibliotek JavaScript po stronie serwera. jQuery po stronie klienta daje dosyć duże możliwości, użycie jej po stronie serwera jeszcze je zwiększa.
Dostęp do struktury DOM w sposób łatwy i przyjemny (który można sobie jeszcze ułatwić i uprzyjemnić poprzez użycie ulubionej biblioteki JS) po stronie serwera to także znaczne udogodnienie.
Zaletą też jest to, że w natywny sposób zarówno klient jak i serwer używają JSON. Łatwiej się jest więc im “dogadać”. Nie trzeba tłumaczyć JSON na obiekty docelowego języka. Poza tym np. dane wyciągnięte z bazy przez API Jaxera mają już ten format, można je więc przekazać bezpośrednio do frontendu i kod który tam napiszemy zrozumie te dane.
Ogólnie z Jaxerem zaoszczędza się sporo pisania kodu, a więc i czas.
Przejdźmy do wad:
Wygenerowany kod nie koniecznie spełnia standardy W3C w 100% (chociaż pewnie będzie – w miarę rozwoju Jaxera, obecnie dziedziczy niektóre wady engine DOM Firefoxa). Z jednej strony czasem trudno zmusić go do wygenerowania niepoprawnego kodu (pewne rzeczy sam poprawia), a z drugiej niektóre psuje (czasem kasuje domknięcie tagu, np. mamy < … /> i kasuje to “/”).
Dużą wadą jest brak wypracowanego wzorca jak chodzi o sensowny podział kodu. Leży to w gestii programisty, ale nie ma tutaj struktury MVC, do tego dochodzą kolejne wady, których efektem ubocznym jest utrudnienie sensownej strukturalizacji kodu.
Brak szablonowania na pewno jest utrudnieniem w wielu sytuacjach. W wielu frameworkach podejście jest takie, że mamy pliki szablonów (czy to JSP/JSF w JEE, czy to TPL w PHP+Smarty, czy pliki widoków w CakePHP/Code Igniter itp.) oraz klasy/pliki kontrolera (które ładują i przetwarzają dane, a na koniec ładują odpowiedni widok przekazując do niego parametry). Rozwiązania są tutaj różne, ale bardzo uogólniony model podziału jest jeden. W Jaxer go brakuje. Oczywiście można go nie potrzebować wcale (np. używając tylko i wyłącznie EXT JS do budowy całości aplikacji) albo użyć szablonowania z jakiejś biblioteki JS (np. Dojo – Dijit ma coś takiego) lub też samemu napisać sobie szablonowanie.
Typowe podejście “zamiast” może być takie, że mamy plik html i po prostu poprzez odpowiednie selektory lub trawersowanie DOM z oddzielnego kodu modyfikujemy strukturę dokumentu. Jest to dobre rozwiązanie (ma swoje zalety – np. kod html jest czysty i nie ma w nim wstawek z języka server side, są tylko elementy o odpowiednich nazwach klas i id, po których są rozpoznawane i wypełniane), tyle, że wymaga zapewne zmiany nawyków.
Kolejną wadą jest brak możliwości stworzenia klas proxy z dziedziczeniem (lub prototypowaniem), pewną ilością metod itd. Warstwa proxy musi pozostawać czy to funkcyjna, w dodatku cache’owane są tylko funkcje i obiekty stanu (sesji, aplikacji itp. – ale jak sprawdziłem nie da się w nich przemycić prototypu obiektu lub instancji zawierającej metody, przynajmniej mi się nie udało sprawić, by działały i miały dostęp do bazy danych). A więc z poziomu JS klienta da radę wywoływać funkcje proxy, ale każda z nich jest gołą funkcją, a nie metodą jakiegoś obiektu. Na pierwszy rzut oka to może nie wydawać się takie straszne, w końcu można sobie te funkcje pogrupować w pliki, ale jednak trochę mozliwości wynikających z chociażby wykorzystania dziedziczenia zostaje zabrane. Czyli jeśli chce się napsiać CRUD dla dostępu do pewnego zestawu tabel, to trzeba sporo kopiuj-wklej wykorzystać (ewentualnie można z jednych funkcji proxy wywoływać inne, więc można sobie ogólniejsze API napisać w kilku funkcjach, a dalej go używać w poszczególnych funkcjach dla tabel, jednak to rozwiązanie nie jest ani tak eleganckie ani tak szybkie do napisania jak ORM w dobrym frameworku PHP czy JEE).
Przy skomplikowanym projekcie trudno sensownie zarządzać kodem w Jaxer.
Czym jest EXT JS?
EXT JS jest to biblioteka JavaScript (chociaż może należałoby powiedzieć raczej framework), która pozwala na szybkie pisanie rozbudowanych interface AJAX opatych o JavaScript.
Jeśli pisze się aplikację w całości w EXT JS (a nie tylko wstawia pojedyncze komponenty), to różnica w sposobie pisania jest bardzo duża.
HTML z podstawowego jezyka opisu staje się gościem rzadko pojawiającym się w kodzie – wszystko w EXT JS buduje się w JavaScript, a dokładniej w formacie JSON.
Widoki to albo wielokrotnie zagnieżdżony JSON albo wiele plików z klasami rozszerzającymi inne klasy (biblioteka ma własny mechanizm dziedziczenia). Optymalnie coś pomiędzy – nie za dużo zagnieżdżeń i nie za dużo plików. Biblioteka wprowadza coś w rodzaju drugiego MVC po stronie klienta (o ile sensownie się dzieli kod, bo można też nie dzielić i wtedy nie jest to tak zauważalne) – mamy kod widoków opisujący co, gdzie i jak wygląda, mamy kod kontrolera (głównie zdarzenia oraz domyślny poprzez sposób działania komponentów i ich parametry) oraz mamy kod modelu (chodzi o klasy typu Store, Reader itp. – do komunikacji z zapleczem, zarządzania danymi). Całość buduje się z gotowych komponentów (które można rozszerzać) o bardzo dużych możliwościach (np. tablicę z sortowaniem, stronicowaniem, konfigurowalną przez klienta ilością, szerokością i kolejnością kolumn oraz ładowaniem danych przez AJAX z użyciem JSON lub XML można zrobić w moment).
Zalety ma ogromne, a wady chyba tylko trzy:
- brak narzędzia do wizualnej edycji widoków (ale wkrótce ma podobno być)
- widoki pisane w JSON, raczej ciężko wpłynąć na to jaki będzie HTML wygenerowany, a także trudno dostosować do pewnego założonego wyglądu przez to, że kod html wygenerowany jest nieco skomplikowany (chociaż można jeśli koniecznie trzeba – przez CSS)
- nie działa bez JS w przeglądarce klienta
Moje wrażenia
Moim zdaniem EXT JS daje się sensownie użyć wraz z Jaxer i jest to udane połączenie.
Jaxer ma dosyć znaczące wady, ale udało mi się zniwelować najważniejsze z nich (oprócz tej z szablonowaniem, bo ono i tak raczej nie jest potrzebne przy EXT JS) dopisując trochę kodu (na pewno kosztem prędkości działania – choć testów z obciążeniem nie robiłem – ale dałoby się ten mój kod jeszcze usprawnić i zoptymalizować, także pod tym względem; pewnie niedługo zaprezentuję ten kod tutaj, ale najpierw muszę go lepiej opisać komentarzami).
Napisałem kod, który pozwala po stronie serwera zdefiniować zestaw klas opisujących logikę aplikacji – klas modelu. Tych klas są dwa rodzaje, jeden dziedziczy po klasie ogólnej Model (dziedziczącej po klasie ogólnej Controller), a drugi rodzaj dziedziczy bezpośrednio po Controller. Controller w sumie nic sensownego na ten moment nie zawiera, bo na razie nie miałem potrzeby pisać żadnych metod ogólnych dla kontrolerów obu rodzajów. Można uznać, że to klasa pusta. Jej jedynym zadaniem jest utrzymywanie dla kodu autogenerującego listy klas dziedziczących po nim (przerobiłem nieco plugin jQuery inheritance tak, by klasa nadrzętna pamiętała jakie klasy po niej dziedziczą). Model zaś zawiera ogólną implementację CRUD z możliwością ustawiania różnych “where”, “limit”, “order by” itp. (ale przy pobieraniu bez uwzględnienia joinów między tabelami – na razie joiny zrealizowałem jako widoki w bazie – views). Klasy dziedziczące zawierają zaś kilka pól opisujących jaka to tabela w bazie, jakie ma pola, czy jest “read only” (widoki są “read only” – nie mają metod związanych z zapisem i update), do tego zawierają ewentualne dodatkowe metody. Nie jest to zupełny ORM, daleko mu do tego, ale pewne podstawowe zadania da radę z nim zrobić.
Do każdej metody jest parametr określający poziom uprawnień jaki trzeba mieć by móc wywołać metodę przez AJAX. Jest też parametr ogólny dla klasy (metody CRUD po nim “dziedziczą” ten parametr, ale można dla nich poustalać odrębne parametry).
Kolejny kawałek kodu wykonuje autogenerację kodu proxy (z cache’owaniem – nie przy każdym wywołaniu, ale tylko, jeśli były modyfikacje, można też wygenerować i wyłączyć sprawdzanie). Kod proxy dzieli się na dwie warstwy. Jedną jest kod funkcji proxy Jaxer wywoływanych po stronie serwera. W każdej z nich następuje sprawdzenie uprawnień i jeśli jest ok, użytkownik w sesji zalogowany i o odpowiednim poziomie uprawnień, to ładowane są klasy ogólne (Controller i ewentualnie Model) oraz klasa której metody dotyczy funkcja proxy i wywoływana jest odpowiednia metoda tej klasy z wysłanymi parametrami. Jeśli nie – zwracany jest błąd.
Drugą warstwą jest kod proxy po stronie klienta. Ma on postać klas odpowiadającym swoim interface klasom po stronie serwera (metody tych klas wywołują funkcje proxy, różnica jest też taka, że po stronie serwera klasy są stateless, a te tutaj stateful – trzymają dane w odpowiednim polu “data”).
Napisałem jeszcze klasę JaxerProxy dla EXT JS, dzięki czemu mogę wraz z JsonReader i Store tworzyć zaplecze dla gridów.
Stworzenie grida dla tabeli z bazy to jest moment: tworzę plik modelu (kilka linijek kodu), tworzę instancję Store z JsonReader i JaxerProxy. Podaję listę kolumn dla JsonReadera (lub nie jeśli typy to int lub string – wtedy automatycznie mi weźmie z modelu, wpisać muszę tylko pola o innych typach, np. TIMESTAMP), podaję mapowanie kolumn dla grida (by wiedział jak wyświetlać nazwy kolumn), podaję parametr “model” dla proxy (którym jest instancja klasy proxy, np. model: new Uzytkownik ()). I mam w pełni działającego grida z stronicowaniem, ładowaniem przez AJAX, do tego zabezpieczonego logowaniem (id sesji, poziom uprawnień).
Podobnie wywoływanie CRUD przez AJAX (JSON) z kodu klienta. Mam coś w rodzaju prostego ORM po stronie klienta z zabezpieczeniem poprzez logowanie od razu po napisaniu kilku linijek kodu modelu. Dosyć mocna rzecz moim zdaniem – a wszystko dzięki mechanizmowi proxy Jaxer, nieco podrasowanemu o klasy i dziedziczenie.
Pewnie jeszcze będę rozwijał ten pomysł (ale najpierw lepiej udokumentuję to co zrobiłem).
Należałoby to rozwiązanie jeszcze zoptymalizować, dodać obsługę joinów między tabelami dla CRUD (szczególnie dla “R”), pomyśleć nad lepszym dziedziczeniem (obsługiwaniem wielopoziomowego dziedziczenia, np. by jeden model/kontroler mógł dziedziczyć po innym, a nie tylko po jednej z 2 klas ogólnych) i wywoływaniem wzajemnym kodu między klasami modelu, pomyśleć nad mechanizmem przenoszenia kodu, który da się przenieść (nie wywołuje innych metod) do metod proxy podczas autogeneracji (żeby nie trzeba było ładować całej klasy w takiej sytuacji – to na pewno znacznie obniża prędkość działania).
Połączenie Jaxer – EXT JS to dosyć potężne narzędzie, ale samo w sobie jest jak nienaostrzony nóż. Trzeba trochę popracować by go “naostrzyć” – czyli wyeliminować pewne niedogodności, które normalnie deklasują to rozwiązanie wobec wielu znanych frameworków.
Nie testowałem wydajności rozwiązania, sam Jaxer podobno plasuje się gdzieś pomiędzy PHP a RoR, ale myślę, że po tego typu sztuczkach z dziedziczeniem to zostaje znacznie pogorszone. Więc to podejście może mieć raczej wąskie zastosowanie – np. do aplikacji intranetowych pracujących pod niedużym obciążeniem.
No chyba, że zastosuje się skalowanie na więcej serwerów – ale nie wiem jak to jest z tym w Jaxer (on nie jest stateless tak jak PHP, chociażby przez to, że tworzy cache funkcji proxy, więc może być różnie).
Jakoś nie widzę Jaxera w zastosowaniu do standardowych aplikacji internetowych (np. portale, cmsy, fora etc.) – co najwyżej jako dodatkowa warstwa przetwarzania na serwerze (gdy podstawą jest jakiś klasyczny framework). Ale może być silnym narzędziem jak chodzi o aplikacje intranetowe z interface w 100% ajaxowym – tutaj ma potencjał.
Integracja Kohana – EXT JS
Autor: Zbyszek
Ostatnio stanąłem przed problemem zintegrowania funkcjonalności gridów EXT JS (tablic z funkcjonalnością sortowania, zarządzania kolumnami, pobierania danych przez AJAX itp.) z kodem po stronie serwera napisanym w PHP na bazie frameworku Kohana.
Kohana jest forkiem Code Ignitera (framework PHP) i ma pewne wady oraz zalety względem tego bardzo znanego frameworku.
Największą wadą Kohana jest brak tak dobrej dokumentacji, jaką ma CI. Najnowsza wersja Kohany (3.x) ma bardzo skromną dokumentację (prawie wcale), nieco wcześniejsza (2.x) ma już lepszą, zawiera też tutoriale, ale nadal daleko jej do jasności, przejrzystości i przyjazności dokumentacji CI.
Największe zaś zalety, które powodują, że warto rozważać użycie tego frameworku to:
- pełna zgodność z PHP 5 (w tym 5.3 z którym CI w ogóle nie chce działać)
- o niebo lepszy ORM (w Kohana deklarujemy klasę o odpowiedniej nazwie oraz pola opisujące relacje i mamy pełnowartościową i dosyć potężną klasę ORM na której można wykonywać nie tylko CRUD, ale także metody uwzględniające relacje między tabelami, w tym w prosty sposób relacje wiele do wielu z tabelą pośredniczącą), a w CI warstwa modelu leżała (w zasadzie CI daje Active Records, ale to nie jest ORM tylko namiastka lub fundament na którym trzeba sobie samemu zbudować odpowiednią funkcjonalność dostępu do bazy)
- lepsza obiektowość oraz możliwość lepszej implementacji z dziedziczeniem (w CI można było zrobić co najwyżej jedną ogólną klasę kontrolera po której mogły dziedziczyć inne klasy, nie można było zrobić żadnego bardziej skomplikowanego dziedziczenia lub jeśli można było – to na pewno było to trudne i nie polecane przez twórców, w Kohana można tworzyć klasy kontrolera systemowe po których mogą dziedziczyć klasy będące już szczegółowymi implementacjami)
Niżej opisane działania dotyczą Kohana w wersji 2.3+ (używałem wersji 2.3.4).
Wracając do problemu: by zitegrować gridy EXT JS z backendem postanowiłem stworzyć sobie odrębną systemową klasę kontrolera na wzór Templace_controller (w katalogu system/controllers), dzięki której mógłbym w prosty sposób generować metody kontrolera/kontrolerów dziedziczących po tej klasie. Te metody służyłyby do generowania danych w postaci JSON dla takich gridów (na początek z pominięciem CRUD – zwykły grid, a nie “editable”).
Oto klasa, która powstała:
<?php defined('SYSPATH') OR die('No direct access allowed.'); /** * JSON Controller dla EXT JS. * * Podstawowe API do tworzenia metod dla wywołań pobierania danych przez JSON Store * lub JSON Reader dla klasy Grid z EXT JS * * Użycie: * `class Your_Controller extends Json_Controller` * * $Id: json.php 2009-09-16 Zbyszek Matuszewski $ * * @package Core * @author Zbyszek Matuszewski */ abstract class Json_Controller extends Controller { // dane protected $data = FALSE; // opcje grida protected $gridOptions = FALSE; // ilość całkowita protected $total = FALSE; // wynik protected $result = FALSE; // błąd protected $error = FALSE; // czy wywołano przez AJAX protected $ajax = FALSE; // to do auto-rendering or not to do (default true) public $auto_render = TRUE; // na podstawie danego modelu ORM, domyślnych wartości // oraz struktury opisującej translację kolumn bazy na kolumny grida // tworzy wynik ($this->result) i ilość łączną ($this->total) protected function __prepareData ($model,$default,$translation) { $this->total = $model->count_all(); $this->__receiveGridParams ($default,$translation); $this->__updateGridOrm ($model); $this->data = $model->find_all(); // generujemy wynik na bazie translacji z kolumn bazy (a dokładniej // z pól obiektu ORM) na kolumny dla EXT JS JSON Store dla grida $this->result = array (); foreach ($this->data as $r) { $res = array(); foreach ($translation as $key => $val) { eval('$res[$key] = $r->' . str_replace(".", "->", $val).';'); } $this->result[] = (object) $res; } } // przeprowadza update parametrów ORM na bazie opcji grida private function __updateGridOrm ($orm) { if ($this->gridopts->start > 0) $orm = $orm->offset($this->gridopts->start); if ($this->gridopts->limit > 0) $orm = $orm->limit($this->gridopts->limit); $orm = $orm->orderby($this->gridopts->sort,$this->gridopts->direction); return $orm; } // pobiera parametry grida z POST // a jeśli nie ma - to domyślne (a jeśli i tych brakuje, to // domyślne sortowanie to po id ASC private function __receiveGridParams ($default = FALSE,$translation = array()) { // kierunek $direction = $this->input->post('dir'); if (!isset($direction)) if (isset($default->direction)) $direction = $default->direction; else $direction = 'ASC'; // kolumna sortowania $sort = $this->input->post('sort'); if (!isset($sort)) if (isset($default->sort)) $sort = $default->sort; else $sort = 'id'; // translacja kolumny sortowania // uwzględnia zamianę na klucz obcy // o nazwie według reguł ORM dla Kohana if (isset($sort) && isset($translation[$sort])) $sort = $translation[$sort]; $x = strpos($sort, "."); if ($x > 0) $sort = substr($sort, 0, $x) . '_id'; // limit do stronicowania $limit = $this->input->post('limit'); if (!isset($limit)) if (isset($default->limit)) $limit = $default->limit; else $limit = 0; // start do stronicowania $start = $this->input->post('start'); if (!isset($start)) if (isset($default->start)) $start = $default->start; else $start = 0; // zapisanie opcji w polu-obiekcie gridopts $this->gridopts = (object) array( 'direction' => $direction, 'sort' => $sort, 'limit' => $limit, 'start' => $start ); } // ładowanie i startup public function __construct() { parent::__construct(); $data = $this->input->post('data'); // jeśli AJAX to na pewno nie renderujemy layoutu // i zapisujemy info o tym w polu ajax if (request::is_ajax()) { $this->auto_render = FALSE; $this->ajax = TRUE; } // dekodujemy dane z JSON na obiekt PHP jeśli są if (isset($data)) $this->$data = json_decode($data); // ładujemy szablon jeśli trzeba if ($this->template) $this->template = new View($this->template); Event::add('system.post_controller', array($this, '_render')); } // renderujemy szablon lub tworzymy JSON lub renderujemy widok danych // dla grida które normalnie trafiłyby do JSON (to ostatnie dla // uprzyjemnienia debugowania) public function _render() { if ($this->auto_render == TRUE) $this->template->render(TRUE); if ($this->result !== FALSE || $this->error!==FALSE) { // jeśli mamy result lub error // to jeśli AJAX to zwracamy w JSON tablicę z rezultatem i ewentualnie // danymi do debugowania (np. gridopts) // jeśli zaś normalne wejście synchroniczne to zwracamy // w formie czytelniejszej if ($this->ajax) { $res = array (); if ($this->total !== FALSE) $res['total'] = $this->total; if ($this->result !== FALSE) $res['result'] = $this->result; if ($this->error !== FALSE) $res['error'] = $this->error; // if ($this->gridopts !== FALSE) $res['gridopts'] = $this->gridopts; echo json_encode($res); } else { echo '<div>'; if ($this->total !== FALSE) echo '<b>Total</b>: ' . $this->total . '<br/><br/>'; if ($this->result !== FALSE) { echo '<b>Result</b>: '; if (count($this->result) > 0) { echo '<br/><table><tr>'; foreach ($this->result[0] as $x => $y) { echo '<th>' . $x . '</th>'; } echo '</tr>'; foreach ($this->result as $r) { echo '<tr>'; foreach ($r as $x => $y) echo '<td>'.$y.'</td>'; echo '</tr>'; } echo '</table><br/><br/>'; } else echo '[]<br/><br/>'; } if ($this->error !== FALSE) echo '<b>Błąd</b>: ' . $this->error . '<br/><br/>'; if ($this->gridopts !== FALSE) { echo '<b>Opcje grida</b>:<br/>'; foreach ($this->gridopts as $key => $val) { echo $key . ' => ' . $val . '<br/>'; } } echo '</div>'; } } } }
Najważniejsza jest tutaj z punktu widzenia użytkowania metoda __prepareData ($model,$default,$translation).
Wywołując ją z odpowiednimi parametrami z poziomu metody kontrolera dla szczegółowej implementacji powoduję naszykowanie odpowiedzi dla grida oraz zarejestrowanie metody która wykona “render” widoku lub JSON (dla grida to drugie) po wykonaniu metody kontrolera.
Parametry jakie akceptuje to:
- klasa ORM modelu
- obiekt zawierający dane domyślne jak chodzi o sortowanie, limit
- tablica translacji opisująca jak kolumny JSON Store lub JSON Reader mapują się na pola ORM
Przykładowa metoda kontrolera main (class Main_Controller extends Json_Controller), metoda z parametrami w celu testowania i debugowania przez wywołania synchroniczne, równie dobrze domyślne parametry mogłyby być wspólne dla wszystkich metod albo w ciele metody (kwestia decyzji projektowej):
public function getSignals ($sort = 'Time', $direction = 'desc',$start = 0,$limit = 0) { $this->auto_render = FALSE; // wybieramy ORM i tworzymy instancję; model ten jest powiązany kluczami zewnętrznymi z dwoma innymi modelami: product, scope $model = new Signal_Model(); // domyślne parametry brane z parametrów metody - dzięki temu można je przekazać przez GET, w celu debugowania tylko $default = (object)array( 'sort' => $sort, 'direction' => $direction, 'start' => $start, 'limit' => $limit ); // translacja kolumn $translation = array ( 'Time' => 'time', 'Symbol' => 'product.symbol', 'Scope' => 'scope.name', 'Signal' => 'signal', 'Params' => 'params', 'Type' => 'type' ); // wywołanie, które automagicznie załatwi resztę $this->__prepareData($model,$default,$translation); }
Baza (struktura; SQL dla MySQL):
CREATE TABLE IF NOT EXISTS `products` ( `id` int(11) NOT NULL AUTO_INCREMENT, `symbol` varchar(20) COLLATE utf8_polish_ci NOT NULL, `name` varchar(100) COLLATE utf8_polish_ci NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_polish_ci AUTO_INCREMENT=4 ; CREATE TABLE IF NOT EXISTS `scopes` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(30) COLLATE utf8_polish_ci NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_polish_ci AUTO_INCREMENT=5 ; CREATE TABLE IF NOT EXISTS `signals` ( `id` int(11) NOT NULL AUTO_INCREMENT, `product_id` int(11) NOT NULL, `scope_id` int(11) NOT NULL, `signal` enum('MINUS','ZERO','PLUS') COLLATE utf8_polish_ci NOT NULL, `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `params` varchar(500) COLLATE utf8_polish_ci DEFAULT NULL, `type` varchar(100) COLLATE utf8_polish_ci NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_polish_ci AUTO_INCREMENT=243 ;
Sam grid:
SignalGrid = Ext.extend(Ext.grid.GridPanel, { initComponent : function() { Ext.apply(this, { columns: [ {header: "Czas", tooltip: 'Czas wystąpienia sygnału', width: 100, dataIndex: 'Time', sortable: true, xtype: 'datecolumn', format:"Y-m-d H:i:s"}, {header: "Symbol", tooltip: 'Symbol produktu którego sygnał dotyczy', width: 50, dataIndex: 'Symbol', sortable: true}, {header: "Zakres", width: 50, dataIndex: 'Scope', sortable: true}, {header: "Sygnał", tootip: 'Kierunek sygnału', width: 50, dataIndex: 'Signal', sortable: true}, {header: "Parametry", width: 400, dataIndex: 'Params', sortable: true}, {header: "Typ sygnału", width: 100, dataIndex: 'Type', sortable: true} ], sm: new Ext.grid.RowSelectionModel({singleSelect: true}), store: new Ext.data.JsonStore({ root: 'result', totalProperty: 'total', idProperty: 'id', remoteSort: true, id: 'signalStore', fields: [ {name: 'Time', type: 'date', dateFormat: 'Y-m-d H:i:s'}, 'Symbol', 'Scope', 'Signal', 'Params', 'Type' ], proxy: new Ext.data.HttpProxy({ url: base_url +'main/getSignals/' }) }), viewConfig: { forceFit: true } }); SignalGrid.superclass.initComponent.call(this); this.store.setDefaultSort('Time', 'desc'); } }); Ext.reg('signalGrid', SignalGrid);
Jak widać na poziomie EXT JS definiujemy typy kolumn (domyślnie string), przyjazne nazwy, szerokości. Podajemy też namiary na naszą metodę: base_url +’main/getSignals/’. (base_url jest zmienną którą definiuję gdzie indziej, w tagu script gdzieś w nagłówku strony – zmienna ta zawiera bazowy adres aplikacji webowej, adres wzięty z wywołania funkcji frameworku baseUrl() która z kolei przekazuje adres podany w konfiguracji).
W metodzie po stronie backendu z kolei definiuję jakiego ORM grid dotyczy, jakie są domyślne parametry sortowania jeśli nie podano żadnych oraz jak oczekiwane przez JSON Reader nazwy pól w obiekcie JSON tłumaczą się na pola ORM.
Tyle powinno wystarczyć bardziej ogólnej metodzie by pobrała z ORM odpowiednie dane, mapowała kolumny i zwróciła obiekt JSON.
Powiązania z zewnętrznymi tabelami są brane pod uwagę dwojako:
1) do sortowania brany jest klucz zewnętrzny, tak było prościej – to tymczasowe rozwiązanie które należałoby udoskonalić (minus taki, że jeśli weźmiemy sortowanie po kolumnie z tabeli powiązanej kluczem zewnętrznym to są one sortowane według klucza głównego tej tabel, czyli właśnie tego klucza zewnętrznego, a nie po wybieranej z niej kolumny -np. sortowanie po “scope” odbędzie się nie po “scope.name” ale po “scope.id” mimo, że w gridzie wyświetlany jest “scope.name”). Translacja odbywa się w ten sposób, że jak mamy nazwę tabeli powiązanej, to wiemy, że pole zawierające klucz zewnętrzny to ta nazwa małymi literami + “_id” – bo taka jest konwencja w ORM Kohana (i tak należy zaprojektować bazę pod ten ORM, takie ma wymagania by automagiczne metody działały, bez tego mamy tyle, co w Code Igniter).
2) do pobierania jest brana odpowiednia kolumna z tabeli powiązanej kluczem zewnętrznym (tutaj konwencja dla ORM to orm.tabela.pole gdzie tabela to nazwa tabeli powiązanej, a pole to nazwa jej kolumny).
Podsumowując: myślę, że plusem takiego podejścia na pewno jest uproszczenie budowy warstwy kontrolera dla metod AJAX dla gridów (szczególnie w aplikacji gdzie jest dużo gridów). W każdej metodzie określamy ORM, domyślne parametry i translację, wywołujemy metodę, a klasa-rodzic wykonuje resztę.
Ale nie jest to rozwiązanie dopracowane – należałoby jeszcze ulepszyć je między innymi w kwestii sortowania po kolumnach z tabel powiązanych, obsługi editable grid.
Czekam na komentarze na temat takiej metody integracji.
jQuery – podstawy
Autor: Zbyszek
Chciałbym podzielić się tekstem na temat jQuery, który napisałem na jedno z forów. Jest to jedna z moich ulubionych bibliotek JavaScript, a w tekście tym przedstawiam pewne podstawowe zagadnienia potrzebne do wykorzystania tej biblioteki.
Podstawowe informacje
jQuery jest biblioteką stosunkowo prostą w wykorzystaniu i jednocześnie mającą przy tym dosyć potężne możliwości (do tego ma rewelacyjną dokumentację).
Zakres zastosowań tej biblioteki można podzielić na 4 obszary:
- Operacje na modelu dokumentu DOM (strukturze strony)
- Animacje (to w sumie specyficzny rodzaj operacji z punktu 1)
- Asynchroniczne połączenia z serwerem (asynchroniczne = bez przeładowania strony)
- Obsługa zdarzeń
Ogólnie rzecz biorąc jej zadaniem jest uproszczenie i ulepszenie tego, co i normalnie można zrobić w JavaScript (ale trzeba do tego dużo żmudnej pracy i sporo kodu). Dzięki jej wykorzystaniu można łatwiej tworzyć kod, który jest zgodny z wszystkimi głównymi przeglądarkami, jest efektywny, a także jego działanie jest efektowne (dzięki ładnym animacjom).
Nietypowa (względem tego jak normalnie pisze się kod JS) jest składnia, lub raczej sposób pisania kodu, używany w skryptach na bazie jQuery.
Przykład trochę skomplikowanej animacji (omówię na nim składnię):
$(document).ready(function () { $('#x').fadeIn(2000); $('#x').animate({ width: '300', top: '200', left: '200' },{ queue:false, duration:2000, complete: function () { $(this).animate({ width:'200', height: '200', top: '100', left: '200' },{ duration: 'slow', easein: 'linear', complete: function() { $(this).fadeOut('normal',function () { $(this).show('normal',function () { $(this).text("KONIEC ANIMACJI"); }); }); } }); } }); });
<div id="x" style="position:absolute;width:100px;height:100px;background:#FF1100;display:none"></div>
Ten kod wykonuje sekwencję animacji na prostokątnej czerwonej ramce (pojawienie się przez fade z jednoczesnym przesunięciem na pozycję 200,200 i zmianą szerokości do 300, potem zmiana w kwadrat 200×200, potem fade out, na koniec pojawienie się przez rozciągnięcie) i na koniec wpisanie tekstu “KONIEC ANIMACJI” w tą ramkę.
Funkcja jQuery – $
Pierwszą rzeczą, która rzuca się w oczy jest $(document) i $(’#x’).
$ jest skrótem do głównej funkcji jQuery, która to funkcja zwraca obiekt jQuery (który daje nam całe API do wykorzystania). Funkcja ta bierze za parametr dowolny typ zmiennej i na podstawie jej typu i zawartości odgaduje co ma z tą zmienną zrobić. Zmienną może być: ciąg znaków, element DOM, tablica, obiekt itp., przy czym każde z nich jQuery inaczej potraktuje.
Jak chodzi o ciąg znaków to domyślnie zostanie potraktowany jako selektor (omówię go w dalszej części). Ale na przykład dając za parametr tablicę dostaniemy obiekt pozwalający na wykonanie różnych operacji na niej w prostszy sposób, na przykład iterowanie $(tablica).each(function () {…}) gdzie w funkcji wpisujemy kod odnoszący się do “this” które tutaj będzie elementem tablicy.
JSON
Zanim przejdę do selektorów muszę jeszcze wspomnieć o jednej rzeczy: wiele parametrów ma postać JSON (JavaScript Object Notation). Przykładem jest pierwszy parametr metody animate.
Jest to nic innego jak najbardziej skrótowa składniowo forma definiowania obiektów w JavaScript (która w wielu zastosowaniach wypiera XML – przy pomocy JSON można to samo zapisać z mniejszym narzutem kodu nie niosącego informacji, a jedynie opisującego strukturę, np. alternatywą do XML-RPC jest JSON-RPC).
W JavaScript nie ma klas jako takich, są natomiast obiekty. Ogólnie zasada jest taka, że obiekt można zdefiniować i stworzyć poprzez zawarcie w nawiasach klamrowych {} listy typu nazwa pola: wartość oddzielonych przecinkami. Do tego dochodzi jeszcze sposób definiowania tabel poprzez zamknięte w nawiasy kwadratowe zbiorów wartości. Możliwe jest też wstawienie do JSON funkcji JavaScript (to wykorzystuje się w praktyce dosyć często, ale tylko w JavaScript, można jako parametr przekazać obiekt zawierający w swoich polach wiele funkcji, które potem mogą zostać wywołane). To tworzy pewną konwencję zapisu.
Przykład:
var moj_json = { x: 123, y: 31.4, ciag: 'to jest jakiś ciąg znaków', funkcja: function () { window.alert('a to jakaś funkcja'); }, tablica: ['tablice','też','można'] }
W ten sposób mamy w zmiennej moj_json obiekt z 5 polami. I możemy się na przykład odwołać do trzeciego elementu tablicy poprzez moj_json.tablica[2] – indeksowanie tablic w JavaScript zaczyna się od 0 (pierwszy element jest na pozycji 0).
Selektory
Wracając do selektorów i jQuery: w przykładzie ‘#x’ jest rodzajem selektora. Selektory są jedną z tych rzeczy, które czynią tą bibliotekę potężną. Pozwalają one wybrać element (lub zestaw elementów) z dokumentu i wywołać na nich operacje (przy czym poza standardowymi operacjami mamy dodatkowo specjalne API do modyfikowania, animacji czy AJAX, pod pojęciem operacji należy także rozumieć przypisanie funkcji obsługujących do zdarzeń, np. kliknięcia na danym elemencie).
Ogólnie budowa selektora może być czasem skomplikowana, jest to dosyć silne narzędzie (pozwala np. stworzyć selektor co drugiego obrazka typu gif w określonej ramce div “#d1 img[src$='.gif']:odd”). Ale najczęściej używa się tylko kilku rodzajów:
‘#x’ – oznacza wybór elementu o id = x
‘.x’ – oznacza wybór elementów o klasie (class) x
‘td’ – oznacza wybór elementów danego typu (tutaj wszystkich komórek tabeli)
‘X Y’ – gdzie X i Y to selektory, oznacza “X and Y”, złączenie obu selektorów, np. ‘td .klasa’ oznacza wszystkie komórki tabeli mające klasę klasa.
‘X > Y’ – gdzie X i Y to selektory, oznacza wszystkie Y mające za rodzica X, czyli np. ‘#ramka > .klasa’ oznaczać może wszystkie elementy klasy klasa mające za rodzica element o id “ramka”.
Tutaj operujemy na ramce div o id =”x”, więc selektorem jest ‘#x’.
Poza tym występuje $(document) – oznacza to, że tworzymy obiekt jQuery na bazie całego dokumentu. Jest to bardzo częste wywołanie i ma na celu przypisanie funkcji obsługi zdarzenia gotowości struktury dokumentu DOM.
Zdarzenie gotowości struktury dokumentu
Wszystkie operacje na elementach zazwyczaj chcemy wykonywać nie wcześniej niż wtedy, gdy są one już załadowane. Z kolei często nie chcemy czekać tak długo aż załadują się wszystkie obrazki (bo tak działa standardowe zdarzenie onload, np. <body onload=”zrobTo()”> – jest ono wywoływane dopiero jak wszystko się załaduje, razem z obrazkami). Stąd wprowadzone w jQuery nowe zdarzenie “ready”.
A więc wywołanie $(document).ready(jakas_funkcja) przypisuje nam funkcję “jakas_funkcja()” do zdarzenia gotowości struktury dokumentu DOM -kiedy to możemy już wykonywać operacje na tej strukturze.
Od wersji 1.3 jQuery istnieje też skrót do tej funkcji, wystarczy $(function () {…}) by stworzyć przypisanie funkcji do zdarzenia “ready” dokumentu.
jQuery pozwala na przypisanie wielu funkcji do jednego zdarzenia, więc można stworzyć wiele bloków $(document).ready(…) i one wszystkie zostaną wywołane w momencie, gdy dokument będzie gotowy by wykonywać na nim zmiany.
Wykorzystanie funkcji anonimowych i funkcje “callback”
W tym momencie pojawia się kolejna ciekawa rzecz – w kodzie pisanym na bazie jQuery bardzo często wykorzystuje się tak zwane funkcje anonimowe. Język JavaScript pozwala na definiowanie funkcji, które nie mają nazwy, ale są przekazywane jako parametr do innych funkcji. Wywołanie ma postać zrobCos(function() {…}) gdzie zrobCos to jakaś funkcja. Przekazujemy taką funkcję jako parametr do innej funkcji by tamta mogła ją wywołać w odpowiednim momencie przy odpowiednich warunkach.
W jQuery większość funkcji dotyczących animacji ma jako jeden z parametrów, zwykle albo ostatni albo we wnętrzu parametru obiektu opisującego opcje, funkcję (nazywa się to “callback”) i ona jest wywoływana po zakończeniu działania funkcji wywoływanej. Pozwala to na łączenie animacji w ciągi tak, by wywoływane były jedna po drugiej.
Jest to jeden z 2 sposobów tworzenia ciągów wywołań operacji na określonym elemencie, drugi sposób polega na tym, że z praktycznie każdej metody jQuery zwracany jest obiekt jQuery na którym oryginalnie została wywołana metoda. Na pierwszy rzut oka wydaje się to dziwne, ale pozwala na skrócenie zapisu jeśli potrzebujemy zrobić wiele wywołań różnych metod na obiekcie jQuery zwróconym na bazie np. jednego selektora. Prostsze jest $(’#x’).fadeIn().animate(…).text(’xyz’) niż wywołanie 3 razy selektora albo zdefiniowanie zmiennej var x = $(’#x’) i potem na niej wywoływanie kolejnych operacji.
Możliwe jest nawet modyfikowanie oryginalnego selektora w trakcie kolejnych wywołań oraz cofanie takich modyfikacji. Ale w praktyce chyba rzadko się tego używa, ja w ogóle tego unikam, bo kod w tym momencie staje się mniej czytelny.
W wczesnych wersjach jQuery było tak, że ciągi tworzone przez funkcje callback były wywoływane zawsze po zakończeniu działania danej funkcji, a ciągi tworzone na bazie wielu wywołań na tym samym obiekcie zwracanym przez kolejne funkcje były wywoływane jednocześnie (a to dzięki temu, że funkcje nie wykonujące się natychmiastowo, np. animacje, tak na prawdę tylko rejestrują wywołanie animacji i jej parametry i kończą działanie, za wykonywanie animacji tak na prawdę odpowiedzialny jest oddzielny moduł, można o nim myśleć jak o oddzielnym wątku w programie wielowątkowym – po zakończeniu danego zadania wywołuje on callback). W obecnej wersji nie jest to tak oczywiste – jak chodzi o metodę animate pozwalającą na animację niektórych stylów CSS istnieje dodatkowy parametr “queue” który ustawiony na false powoduje nie kolejkowanie kolejnych wywołań, domyślnie obecnie są one kolejkowane przy obu sposobach wywołania.
Powyższy kod to ilustruje – dzięki parametrowi (a dokładniej to opcji w obiekcie będącym drugim parametrem i opisującym opcje) queue:true wejście fadeIn oraz animacja pozycji i rozmiaru wykonują się jednocześnie (chociaż w zasadzie do tego nie potrzeba było 2 metod, można było w jednej animacji dodatkowo animować atrybut CSS “opacity”). Jeśli ten parametr skasujemy to fadeIn oraz pierwsza animacja wykonają się jedna po drugiej (bez niego animacja czeka na zakończenie innych wcześniej dodanych animacji). Jeśli używa się callback (tak jak w powyższym kodzie przy ostatnich etapach animacji) to wywołanie nastąpi dopiero po zakończeniu wcześniejszej animacji czy operacji (do której przekazano callback jako parametr) – nie zależnie od użycia lub nie opcji queue.
Sposoby iterowania po elementach
Operacje na elementach objętych obiektem jQuery (jQuery jest tutaj wrapperem – zawiera listę elementów lub obiektów do których się odnosi i pozwala na wykonanie na nich dodatkowych operacji) możemy wywoływać na dwa sposoby.
Pierwszy to bezpośrednie wywołanie operacji – wtedy zostanie ona powtórzona dla wszystkich elementów/obiektów, które obejmuje dany obiekt jQuery. Np. jeśli wywołamy $(’.x’).fadeIn() to wszystkie elementy o klasie x zostaną pokazane (poprzez animację przeźroczystości z domyślną prędkością). Widzieliśmy ten sposób wywołania w przykładowym skrypcie.
Drugi sposób jest wykorzystywany gdy do wykonania operacji potrzebujemy wiedzieć coś o każdym z poszczególnych elementów. Czyli na przykład potrzebujemy pobrać dane z jakiegoś ich pola i coś z tymi danymi zrobić itp. Wtedy wykorzystujemy metodę “each”. Bierze ona za parametr funkcję, która zostanie wykonana na każdym z elementów. We wnętrzu tej funkcji “this” oznacza aktualny element, ale nie objęty przez jQuery, nie będący obiektem jQuery – trzeba użyć $(this) by dostać obiekt jQuery.
Przykład:
$(document).ready(function () { $('.x').each(function () { $(this).animate({ width: $(this).text() },{ queue:false, duration:3000 }); }); });
<div class="x" style="width:10px;height:50px;background:#FF1100;border:1px">50</div> <div class="x" style="width:10px;height:50px;background:#00FF11;border:1px">200</div> <div class="x" style="width:10px;height:50px;background:#1100FF;border:1px">350</div> <div class="x" style="width:10px;height:50px;background:#FF11FF;border:1px">150</div>
Mamy tu 4 paski (ramki div) zawierające w tekście pewną liczbę i mające klasę “x”.
Skrypt ma za zadanie każdy z tych pasków rozszerzyć w animowany sposób do szerokości określonej przez tą liczbę.
Istotne tutaj jest wywołanie $(’.x’).each które przeiteruje po wszystkich elementach o klasie x i wywoła podaną mu funkcję (np. anonimową funkcję podaną za parametr, ale może to być wsześniej zdefiniowana funkcja, wtedy jeśli funkcja była zdefiniowana function xyz() {…} to podaje się parametr xyz).
Kilka innych użytecznych metod
W samej funkcji użyłem 2 metod jQuery: $(this).animate(…) oraz $(this).text().
Ta pierwsza pojawiła się już wcześniej i służy do animowania stylów CSS (przy czym w czystym jQuery tylko niektórych – trzeba ściągnąć odpowiednie pluginy z jQuery UI by móc praktycznie wszystkie parametry animować, w tym kolory). Jej pierwszym parametrem jest obiekt JSON opisujący jak ma zmienić się CSS. Drugim parametrem są opcje. Animate ma także alternatywną formę która ma inne parametry (parametry css, czas trwania, sposób animacji, callback), ale nie wszystko można przy jej pomocy zrobić (bo nie ma niektórych opcji, np. queue). Co ważne parametry CSS które zawierają znak “-” muszą być podane bez tego znaku, ale z dużą literą po tym znaku. Czyli np. ‘border-color’ musi być zapisany jako ‘borderColor’.
Druga metoda “text” wywołana bez parametru pobiera zawartość elementu (wszystko co jest wewnątrz tagu) jako tekst – z wyrzuceniem wszystkich ewentualnych tagów html.
Jest także metoda “html” (np. $(’#x’).html() i $(’#x’).html(’<p>OK</p>’)) która robi to samo, ale bez usuwania tagów html.
Istotne są jeszcze metody “val” – wartość elementu, głównie dla pól formularza oraz “attr” – wartość atrybutu elementu. Tą ostatnią wywołuje się albo z jednym parametrem określającym nazwę atrybutu i wtedy zwróci wartość albo z dwoma parametrami i wtedy przypisze wartość drugiego parametru do atrybutu o danej nazwie. Na przykład jak mamy obrazek img o id=”xim” to wykonując $(’#xim’).attr(’src’,'images/obrazek.jpg’) zmieniamy atrybut src, a co za tym idzie pokazywany obrazek.
Z funkcji animacji używa się także często fadeIn, fadeOut, slideIn, slideOut oraz show i hide. Są to różne sposoby pokazywania i ukrywania elementów.
Zdarzenia, przypisywanie funkcji obsługi zdarzeń
Kolejną istotną rzeczą są zdarzenia. Zdarzeniom można przypisywać funkcje obsługi poprzez wywołania poszczególnych metod jQuery dla zdarzeń (np. click, dblclick, keypress itp.), które to metody akceptują funkcję obsługi za parametr, albo poprzez ogólną metodę “bind”. Do tej ostatniej przekazujemy nazwę zdarzenia dla funkcji w pierwszym parametrze i funkcję w drugim (lub w trzecim, wtedy drugi jest przekazywany do funkcji jako jedno z pól obiektu zdarzenia; funkcja obsługująca powinna akceptować 1 parametr którym jest obiekt zdarzenia, zawiera on pewne dane związane z zdarzeniem, na przykład przy zdarzeniu keypress jest tam informacja jaki klawisz wciśnięto).
Jest jeszcze funkcja unbind – likwiduje ona przypisanie funkcji do zdarzenia.
$(document).ready(…) także jest przykładem przypisania funkcji obsługi do zdarzenia.
Przykład:
$(document).ready(function () { $('#x').bind('mouseover',function (e) { var pos = $(this).position(); var koniec = false; while (!koniec) { var przesun = losujWektor(); przesun = normalizujWektor (przesun, 150); polozenie = {left: pos.left+przesun.left, top: pos.top+przesun.top}; koniec = czyNaEkranie(polozenie); } $(this).animate(polozenie,{queue:false,duration:400}); }) $('#x').click(function (e) { $(this).unbind('mouseover') .css({background: '#00DD22',color: '#FFFFFF', width: 120}) .text('GRATULACJE'); }); }); // funkcja zmienia wektor w taki o podanej dlugosci, ale zachowujac kierunek function normalizujWektor (wektor, dlugosc) { var vlen = Math.sqrt(wektor.left*wektor.left + wektor.top*wektor.top); wektor.left = dlugosc*wektor.left / vlen; wektor.top = dlugosc*wektor.top / vlen; return wektor; } // losuje wektor function losujWektor () { return { left: 2*Math.random()-1, top: 2*Math.random()-1}; } // sprawdza czy po dodaniu polozenie miesci sie w zakresie ekranu function czyNaEkranie (polozenie) { return (polozenie.left > 0) && (polozenie.top > 0) && (polozenie.left < 800) && (polozenie.top < 500); }
<div id="x" style="position:absolute;left:200px;top:200px;width:90px;height:40px;background:#FF1122;border:1px;text-align:center;">KLIKNIJ MNIE</div>
Jest to prosta gra polegająca na tym, że jest kwadracik z napisem “KLIKNIJ MNIE” i trzeba go złapać. Kwadracik oczywiście ucieka w losowych kierunkach, dosyć szybko. Po kliknięciu na niego przestaje uciekać, zmienia kolor i pojawia się w nim tekst “GRATULACJE”.
Żeby zagrać ponownie – trzeba odświeżyć stronę (by nie komplikować przykładu nie dawałem tego typu przycisku).
Poza funkcjami i kodem do obliczeń, które służą głównie do tego, by każda kolejna pozycja była w zakresie obszaru 800 x 500 oraz każdy skok był o tą samą długość, ale w różnych losowych kierunkach, jest tutaj wykorzystana metoda “bind” jQuery oraz metoda “undbind”.
Metodą bind przypisujemy tutaj 2 funkcje do 2 zdarzeń: najechania myszką (funkcja która powoduje obliczenie nowej pozycji i wywołanie animacji) oraz kliknięcia (funkcja kasująca przez undbind obsługę zdarzenia najechania myszką oraz modyfikująca nieco ramkę, w tym zmieniająca tekst).
Ostatni zakres jaki obejmuje ta biblioteka, to jest asynchronczne połączenia z serwerem (AJAX), opiszę innym razem.
Witaj świecie!
Autor: Zbyszek
Witam!
Postanowiłem zrobić coś sensownego z swoją stroną internetową i przeniosłem ją na gotową już platformę jaką jest Wordpress. Przez długi czas przymierzałem się do tego, by napisać sobie funkcje blogowania od zera, ale w końcu poddałem się – ciągle są ważniejsze rzeczy na głowie niż napisanie tej funkcjonalności, a jeśli nic ważniejszego aktualnie nie ma, to zazwyczaj jest to po dniu pracy pełnym programowania i po prostu mi się nie chce pisać kolejnego kodu – wolę odpocząć.
Layout strony nie zmienił się znacznie – połączyłem go z istniejącym już tematem “Skinbu”, dodałem do tego jQuery (licencja GPL na to pozwala). Nie zrobiłem tego idealnie dokładnie tak, by odwzorować stary layout – stwierdziłem, że nie ma sensu. Style Skinbu też mi się podobają, chciałem tylko zachować tło i nagłówek.
Nowością będą na pewno wpisy, które będę zamieszczał od czasu do czasu w wolnych chwilach. Głównym tematem będzie tworzenie aplikacji webowych – czyli AJAX, PHP, JS, JAXER, MySQL, jQuery, EXT JS, Code Igniter, Kohana, CakePHP – by wymienić główne skróty i nazwy, jakie będą się często przewijać.
Prawdopodobnie będę często korzystał z tego miejsca jak z “notatnika” – planuję zamieszczać tu rozwiązania problemów na jakie natrafiłem czy informacje typu “jak to zrobić”.
Planuję też dodać galerię, ale muszę się najpierw zastanowić jak najefektywniej ją zintegrować z Wordpressem (oraz czy nie ma już tego typu pluginu – lepiej nie wymyślać ponownie koła).
Życzę przyjemnego czytania – jak już coś więcej napiszę.



Wrzesień 30th, 2009