//DEVGURU

Archives: November, 2008

Javascript, jQuery i preloading obrazków

Wednesday, November 26th, 2008

Wiemy już w czym problem, prawda? Chodzi o te pół, jedną trzecią, dwie szóste czy nawet całą, a nie daj Boże dwie i więcej sekundy, po których pojawia się obrazke zdefiniowany w css na zdarzeniu hover. Rozwiązań jest kilka, z czego na chwilę obecną przychodzi mi do głowy np. CSS Sprites, czyli działające-aczkolwiek-średnio-wygodne rozwiązanie.

Jakie rozwiązanie jest zadowalające-prawie-wszystkich a zarazem banalne w użyciu? Preloading przy użyciu jQuery.

Ot, cała filozofia: w DOMie tworzony jest obiekt img, który dostaje odpowiedni atrybut src, co powoduje wczytanie tego pliku do cache przeglądarki. Problem “opóźnionych hoverów” z głowy.

Hoptoad PHP

Tuesday, November 25th, 2008

Zalety korzystania z Hoptoada, jako “narzędzia do obsługi wyjątków w railsowych aplikacjach” wychwalał już na devguru Bartosz. Od niedawna, nie jest on jednak ograniczony do aplikacji Railsowych. Rich Cavanaugh zakodował w PHP obsługę błędów i wyjątków korzystającą z API Hoptoada.

Choć sam autor wspomina, że nie jest to jeszcze wersja finalna, postanowiliśmy skorzystać z niej na Flakerze. Jak na razie, sprawuje się na prawdę dobrze i z czystym sumieniem możemy ją polecić innym programistom PHP.

Jak zintegrować Hoptoada z naszą aplikacją PHP?

1. Wymagania

Do działania potrzebujemy Horde_Yaml i HTTP_Request.

pear channel-discover pear.horde.org
pear install horde/yaml

pear install HTTP_Request

2. Plik Hoptoad.php, który znajdziemy w repozytorium.
Do pliku musimy dodać ładowanie wspomnianych pluginów, czyli np:
require_once "HTTP/Request.php"
require_once "Horde/Yaml.php"

3. Ustawienie klucza API i przypisanie Hoptoada do obsługi błędów i wyjątków. Może to wyglądać na przykład tak:
require_once('Hoptoad.php');
define("HOPTOAD_API_KEY", "YOUR_HOPTOAD_API_KEY");
set_error_handler(array("Hoptoad", "errorHandler"));
set_exception_handler(array("Hoptoad", "exceptionHandler"));

Oczywiście poziom raportowania możemy zmienić dodając drugi parametr do set_error_handler.

Na jakie problemy, możemy napotkać?
Autor wspomina na swojej stronie o możliwych nieścisłościach w wynikach zwracanych przez metodę Hoptoad::tracer() – która generuje informacje o miejscu w którym wystąpił błąd. Na razie jednak wszystkie wyniki, które otrzymałem były precyzyjne, stąd trudno będzie problem ten potwierdzić.

Dodatkowe informacje o licencji i samym pluginie można znaleźć na stronie repozytorium.

Railsowa magia nie do końca skuteczna, czyli counter cache

Wednesday, November 19th, 2008

Magia!

Railsy są tworzone z myślą “konwencja nad konfiguracją” – czego jednym z przykładów może być counter cache. Jak to działa?

Mamy model User. User ma wiele Postów, więc Post belongs_to :user. Chcemy wyświetlić liczbę postów, jakie napisał użytkownik. Wywołując, dajmy na to, User.first.posts.count dostajemy aktualną liczbę rekordów Post w bazie. Nic zaskakującego. Jeśli zdefiniujemy w tabeli users pole “posts_count” a przy definiowaniu relacji Post belongs_to :user dodamy :counter_cache => true – liczba postów należących do użytkownika będzie zapisywana w rekordzie użytkownika (a konkretnie – inkrementowna lub dekrementowana przy tworzeniu postów), przez co (kosztem nadmiarowości (redundancja to zbyt egzotyczne, jak dla mnie, słowo) – ale to mocno dyskusyjna sprawa) oszczędzamy sobie zapytań SQL zmuszających bazę do nieustannego mielenia. Fajne.

Fajne – w standardowych przypadkach.

“Konwencja nad konfiguracją” ma to do siebie, że działa w standardowych zastosowaniach, a problemy pojawiają się, gdy chcemy rozszerzyć jakąś funkcjonalność.

Przykład: komentarze muszą zostać zatwierdzone przez moderatora, a więc nie chcemy podliczać użytkownikowi zawieszonych przez moderatora komentarzy (a jedynie te aktywne – do takich rzeczy polecam acts_as_state_machine). Counter_cache w tym przypadku odpada – będzie inkrementował licznik komnetarzy przy stworzeniu, dekrementował przy wywołaniu destroy, ale zmiana stanu na nic nie wpłynie.

Napiszmy to sami.

Odpuścimy sobie ustawianie counter_cache na true przy belongs_to i dopiszemy kilka callbacków w modelu komentarza:

(Powyższy kod zakłada, że mamy zdefiniowane named_scope :active).
Zastosowań tej metody jest więcej – możemy np. nie usuwać rekordów z bazy przy usuwaniu komentarza, a jedynie zmieniać jego stan (acts_as_state_machine!) na “deleted” i wyświetlać ładną informację pt. “ten komentarz został usunięty”, przez co nie zaburzamy toku dyskusji – after_save wywoływany przy zmianie stanu zmieni liczbę komentarzy na aktualną; działa też b. fajnie w momencie gdy obiekt nie zostaje stworzony, a jedynie przypisany parentowi (po drobnej modyfikacji, rzecz jasna).

Javascript a ładny kod: Object Literal

Tuesday, November 18th, 2008

Problem

Jak wygląda praca z Javascriptem z punktu widzenia server-side’owego developera?

Przeszliśmy pierwszą falę szału nad bibliotekami JS: Prototype, Mootools, jQuery (czy mniej buzzowe ext.js, YUI). Zachwycamy się ładnymi rzeczami tworzonymi przy użyciu canvas, które znajdujemy na oursignal.com (też przecież napisanym w JS). Widzimy, jak za pomocą jednej linii możemy schować, animować, przetransformować i zrobić milion rzeczy z DIVem, Paragrafem czy Anchorem. Wybieramy jedną z bibliotek. Czytamy dokumentację, widzimy milion fajnych selektorów, czytamy książki, w których zachwycamy się jak fajnie można rozszerzyć funkcjonalność o pluginy… Myślimy sobie: “tak, teraz, gdy JS nie jest tak paskudny i skopmlikowany jak kiedyś, mogę stać się JS-developerem!”. Piszemy więc accordion, animujemy kilka rzeczy w naszej aplikacji (żeby user widział, że się dzieje!), kilka formularzy wysyłamy AJAXem. Wszystko jest ładne i unobtrusive. I co?

Zdajemy sobie nagle sprawę, że nasz application.js ma o kilkaset linii kodu za dużo, że kod jest nieczytelny, że nie wszystko da się wrzucić do document.ready(), że kilku rzeczy moglibyśmy użyć w kilku miejscach – zaczynamy pisać więc funkcje, staramy się przyjąć jakąś konwencję nazewnictwa… I kończymy z jeszcze większym bałaganem – chociaż bałaganem rozszerzonym o DRY.

Rozwiązanie?

Dobrym punktem wyjścia jest trzymanie wszelkich akcji w jednym obiekcie – korzystając z object literal (kawałek kodu korzysta z jQuery):

Co nam teraz pozostaje? Wywołanie funkcji bob.init(); w document.ready. Korzyści? Mając jeden obiekt “bob” możemy wywoływać jego funkcje w środku odwołując się do self. Trzymając wszelkie informacje, które mogą ulec zmianie – takie, jak długość timeoutu, klasy css czy wiadomości komunikatów – w bob.config – możemy łatwo je zmienić (gdy np. mamy konflikt z koderem CSS). Zresztą kod jest całkiem “samowyjaśniający się“.

Potencjalne zastosowanie? Dajmy na to, że wszystkim input type=submit podpinamy event, który powoduje, że po kliknięciu na niego zmienia value na “ładuję…” i staje się disabled. Ok, pięknie działa dla wszystkich przycisków wygenerowanych przez aplikację. Co jednak, gdy stworzymy nowy input przez javascript? Mamy doczepić mu event w momencie tworzenia? Nie, po umieszczeniu go w DOMie wywołujemy tę samą funkcję, która jest wywoływana przy init(). Warto rozpisywać nawet najmniejsze rzeczy w osobnych funkcjach – nigdy nie wiadomo, czy nie będziemy musieli ich kiedyś użyć ponownie.

Co dalej? Walidujemy nasz JS przy użyciu JSLint, przez co oszczędzimy sobie żmudnego procesu debugowania (który nie jest, swoją drogą, zbyt przyjazny). Kilka ciekawych praktyk zawarł w swojej prezentacji Chris Heilmann (część 1, część 2, wygrzebane przez Filipa Teppera) – m.in. jak umożliwić designerowi/koderowi CSS pracę nad wyglądem strony z Javascriptem lub bez niego – korzystając jedynie z CSS.

Zresetuj CSS!

Thursday, November 6th, 2008

1. Problem

Przy pisaniu arkuszy przy nagłówkach (h1, h2, h3 itd.) czy podobnych elementach niemiłosiernie często przewija się margin: 0; padding: 0; – z wiadomych powodów: elementy te mają predefiniowane w przeglądarkach wartości margin czy padding.

2. Rozwiązanie – “the framework way”

Ktoś, kiedyś pomyślał: skoro i tak przy każdym projekcie muszę “czyścić” marginesy, to stworzę arkusz css, który zrobi to za mnie! Tak powstał plik powszechnie znany jako reset.css. Obecnie większość frameworków CSS zawiera je w sobie (przykładowo: YUI reset CSS, Blueprint reset.css) i działają naprawdę fajnie, jeśli korzystamy z tych frameworków.

3. Rozwiązanie – “the DIY way”

Jeśli nie odpowiada nam frameworkowa konwencja i chcemy jedynie zresetować wartości margin i padding dla wszytkich elementów – stosujemy bardzo ładne rozwiązanie:

I cieszymy się, że zrobiliśmy to sami – za pomocą 31 znaków.

UploadPack – łatwy i elastyczny system uploadu plików w CakePHP

Tuesday, November 4th, 2008

Mimo, że upload plików to funkcjonalność obecna w wielu aplikacjach internetowych, do tej pory trudno znaleźć rozwiązanie, które by w znaczący sposób ułatwiało obsługę tego procesu z poziomu aplikacji w CakePHP.

Widziałem i używałem już paru skryptów czy pluginów obsługujących upload po stronie serwera (napisanych nie tylko w PHP), dzięki temu mogłem stworzyć sobie wizję takiego pluginu, dostosować ją do specyfiki frameworka CakePHP i spróbować wprowadzić ją w życie.

Postawione wymagania:

  • zapis rekordu z załącznikiem nie powinien się niczym różnić od zapisu zwykłego rekordu
  • całość powinna działać przy minimalnej konfiguracji, ale również być łatwo dostosowywalna do potrzeb aplikacji
  • możliwość wykonywania dodatkowych czynności z przesłanym plikiem niż tylko zapis na dysk, np. wygenerowanie miniaturek obrazka
  • łatwy dostęp do plików, także do ich różnych wersji (miniaturek) z poziomu widoku
  • naturalna integracja z CakePHP

Efektem pracy jest UploadPack, który w chwili obecnej zawiera:

  • behavior, który podłączony do modelu zajmuje się zapisem plików na dysk i ewentualnie generowaniem miniaturek obrazków
  • helper, który ułatwia wyświetlanie url’i do przesłanych plików i wyświetlanie obrazków

Wydaje mi się, że całość działa dość zgrabnie. Jedyną rzeczą, którą trzeba zrobić jest dodanie pola w bazie danych przechowującego nazwę pliku i podpięcie behaviora do modelu. Reszta idzie automatycznie. Wszystko jest udokumentowane na stronie repozytorium UploadPack.

Na razie dostępna jest wersja 0.1, z którą jednak można już całkiem sporo zdziałać. Prace nad dalszymi funkcjonalnościami trwają :)

#devguru 2

Tuesday, November 4th, 2008
  • na rubyinside.com mamy 4 ciekawe biblioteki do Ruby/Rails pozwalające w prosty sposób wyświetlać pogodę z różnych serwisów
  • prezentacja z infoq.com na której Tobias Lütke wyjaśnia działanie memcached
  • Aptana Jaxer , czyli framework w JS. I tylko w JS.
  • Rails I18n – przewodnik na temat “internalizacji” w Railsach
  • Nginx+memcached = igvita.com
  • pojawił się agiletuning.pl, podcast poświęcony ”efektywnemu wykorzystaniu lekkich metodyk w zarządzaniu projektamiinżynierii oprogramowania”. Życzymy powodzenia!
  • Modelbaker może być super narzędziem do scaffoldingu aplikacji w #cakephp. Na razie dostępne są tylko screencasty, ale zapowiada się nieźle. widgetpress.com

Rails – ampersandy w parametrach

Tuesday, November 4th, 2008

Na początku zaznaczę, że nie mam pojęcia kto jest winien poniższego zachowania (i, po prawdzie, nie interesuje mnie to zupełnie), ale jeśli można spokojnie problem rozwiązać na poziomie Railsów, to chyba tak jest najlepiej.

Otóż, gdy przekazujemy w URLu jakieś parametry (co mnie osobiście razi i staram się tego w miarę możliwości unikać), są one rodzielone znakiem ampersandu – &. Standardy mówią, że znak ten powinien być odpowiednio zakodowany jako & i tak jest w istocie, jeśli korzysta się z railsowych helperów do restful routes. Problem pojawia się, jak zwykle, gdy zaczniemy te standardy testować w różnych przeglądarkach. O ile się nie mylę (a mylić się mogę, bo nie sprawdziłem tego dobrze), przeglądarka powinna sama, po kliknięciu takiego linku, przekodować & z powrotem na &. Jednakże, moja ulubiona (Safari) tego nie robi. Efektem tego otrzymujemy hash z parametrami postaci:

Osobiście wolałbym się odwoływać do params[:name], a nie params["amp;name"], więc zacząłem szukać rozwiązania. Jest ono proste i nieskomplikowane - Rails not handling links with & correctly. Jeżeli ktoś lubi ręcznie patchować Railsy…

Dla tych co, podobnie jak ja, odczuwają “wewnętrzne fuj” przy takich czynnościach proponuję w zamian dodać (najlepiej gdzieś w lib) prosty monkey patch: