Długo zbierałem się z tą decyzją, ale ostatecznie ją podjąłem. Wznawiam moją aktywność blogową jednak na nowym, angielskojęzycznym blogu: michalorman.com. Zapraszam wszystkich do śledzenia mojego nowego bloga.

Bardzo modnym ostatnio terminem jest Unobtrusive JavaScript . Ideą tego podejścia jest nie umieszczanie wywołań JavaScript w plikach HTML (np. w zdarzeniach onclick czy onblur) a przypinanie ich z poziomu samych JavaScriptów za pomocą odpowiednich selektorów (a jeszcze lepiej z wykorzystaniem np. jQuery). Mało jednak się mówi iż ten sam paradygmat tyczy się CSS-a, a w jego przypadku sprawa jest trochę mniej oczywista.

Zacznijmy od trywialnego przykładu, na początek w Javie, a konkretniej JSF. Któż z nas nie spotkał się z czymś podobnym:

<ice:panelGrid columns="3" style="width:100%" cellpadding="0" cellspacing="0"
               columnClasses="col15 firstcol, col35 lastcol, col50 lastcol">

    <!-- ... -->

</ice:panelGrid>

Jaki jest sens tworzenia klas typu col50? Czym to się różni od ustawienia style="width: 50%" prosto w plikach JSF? Oczywiście w przypadku JSF i komponentu panelGrid nie da się ustawić stylu dla poszczególnych kolumn, A jedynie klasy, ale niewiele to zmienia.

Przejdźmy jednak do mniej oczywistego przypadku, na który ja się ostatnio złapałem. Tworzyłem formularz, który w odróżnieniu od pozostałych, zamiast każde pole renderować w kolejnym wierszu cały renderował się w jednym. Pomyślałem, tak jak pewnie wiele osób by pomyślało, zrobię klasę inline-form. Wtedy do każdego formularzu, który ma być renderowany "w linii" dodam tę klasę i będzie śmigać. No i powstał taki kod:

= simple_form_for @language, :html => { class: 'inline-form' } do |f|
  = f.input :name, :label => false
  = f.input :level, :label => false
  = f.submit

I arkusz styli:

.inline-form {
  overflow: hidden;
  input {
    float: left;
  }
}

W arkuszu stylów standard, jakiś float, jakiś overflow.

Im dłużej się przyglądałem temu formularzowi tym bardziej mi się to przestawało podobać. Czym to się właściwie różni od "niesławnych" col35 czy col50 z poprzedniego przykładu? Idea jest dokładnie ta sama. Jakiś fragment CSS-a zamykamy w klasie i ją umieszczamy w HTML-u. Po prostu mamy mniej klepnięć w klawiaturę, no i reużycie kodu prawda?

Style powinniśmy w dokumentach HTML umieszczać w sposób unobtrusive. Aby to osiągnąć, podobnie jak w przypadku JavaScript-u, powinniśmy w arkuszu stylów odpowiednim selektorem wybrać interesujący nas formularz i zdefiniować jak on powinien wyglądać. Nie potrzebujemy żadnej klasy.

Niestety sam CSS jest zbyt ograniczony, aby osiągnąć ten efekt jednocześnie nie duplikując kodu. Jeżeli chcielibyśmy posiadać wiele takich formularzy musielibyśmy skopiować kod stylu dla każdego formularza. Dlatego warto zamiast czystego CSS-a skorzystać z takich języków jak Sass). W Sass możemy bardzo łatwo osiągnąć ten sam efekt bez dublowania kodu:

@mixin inline-form {
  overflow: hidden;
  input {
    float: left;
  }
}

#new_language {
  @include inline-form;
}

Po co ta cała zabawa? Po to aby nie mieszać odpowiedzialności (SRP?). HTML mówi co znajduje się w dokumencie, CSS określa jak wygląda a JavaScript jak się zachowuje. Zarówno JavaScript jak i CSS powinny być łączone z dokumentem HTML w sposób unobtrusive.

Testowanie operacji na plikach może być problematyczne. Z jednej strony nie chcemy zaśmiecać swojego systemu jakimiś plikami generowanymi przez testy i martwić się, aby posprzątać po testach, z drugiej mockowanie klas I/O może być uciążliwe, oraz powoduje, że testy są bardzo wrażliwe na zmianę implementacji (nawet jeśli działanie metody pozostało bez zmian). Testując operacje na plikach na prawdę chcielibyśmy tworzyć te pliki, sprawdzać ich istnienie, atrybuty czy zawartość.

Rozwiązaniem tego problemu jest FakeFS. Biblioteka ta tworzy nam sztuczny system plików w pamięci operacyjnej. Dzięki FakeFS możemy tworzyć i testować pliki jednocześnie nie zaśmiecając sobie dysku.

Ponieważ biblioteka ta modyfikuje działanie biblioteki I/O możemy natknąć się na problemy jeżeli nasz test powinien otworzyć plik z prawdziwego systemu pliku. Na szczęście FakeFS pozwala nam aktywować i deaktywować w dowolnym momencie sztuczny system plików. Przykładowo testując za pomocą Cucumber'a do pliku env możemy dodać coś takiego:

Before '@fakefs' do
  FakeFS.activate!
end

After '@fakefs' do
  FakeFS.deactivate!
end

Wtedy każdy scenariusz oznaczony przez @fakefs będzie wykonywany z użyciem sztucznego systemu plików.

Używając tej biblioteki nie potrzebujemy korzystać z jakiegoś dodatkowego API, tworzyć jakiś magicznych obiektów itp. Operujemy na plikach po prostu korzystając z biblioteki I/O.

Rails 3.1 pojawiło się na horyzoncie przynosząc nam wiele zmian (czasem dość kontrowersyjnych), a także wiele uproszeń, w tym dotyczących uwierzytelniania (ang. authentication). Teraz jeżeli chcemy część naszych widoków ukryć tylko dla administratora, możemy w prosty sposób zadeklarować uwierzytelnianie metodą HTTP Basic. Wystarczy w kontrolerze dodać:

http_basic_authenticate_with :name => "admin", :password => "secret"

I już. Dla pełności powinniśmy dodać force_ssl aby połączenie było szyfrowane. Możemy również użyć parametrów only oraz except znanych z filtrów.

To proste podejście jest jednak mało interesujące dla tych, którzy implementują jakiejś REST-owo-JSON-owe API w oparciu o Rails. W takim przypadku powinniśmy zdefiniować w kontrolerze filtr:

before_filter :authenticate!

private

def authenticate!
  authenticated = authenticate_with_http_basic do |username, passwd|
    # Tutaj proces uwierzytelniania...
  end
  request_http_basic_authentication unless authenticated
end

W ten sposób możemy posiadać bazę wielu użytkowników i uwierzytelniać ich metodą HTTP Basic. Rails czarną robotę zrobi za nas.

Załóżmy, że w naszej aplikacji tworzymy widok na którym zawartość jednej listy wyboru zmienia się w zależności od tego co wybierzemy w drugiej. Opcje listy przeładowywane są oczywiście z pomocą AJAX-a. Załóżmy również, że chcemy użytkownika poinformować o owym żądaniu wyświetlając odpowiedni obrazek postępu (tzw. spinner) tuż obok listy, która ma zostać przeładowana. Oczywiście nic nie powinno stać na przeszkodzie, aby takich list mieć wiele na jednym formularzu. Na koniec załóżmy, że aplikację tworzymy w frameworku Rails z użyciem simple_form. Jak zatem dodać spinner to pola formularza? Okazuje się, że korzystając z tej instrukcji jest to niezwykle proste.

To co chciałbym osiągnąć, to aby nasz spinner można było deklaratywnie dodawać do pola formularza (podobnie jak pozostałe komponenty):

f.association :subcategory, :spinner => true

Zatem podążając zgodnie z instrukcją modyfikujemy najpierw konfigurację /config/initializers/simple_form.rb:

SimpleForm.setup do |config|
  config.components = [ :placeholder, :label_input, :spinner, :hint, :error ]
end

require 'simple_form/spinner'

W konfiguracji tej musimy dodać nowy komponent spinner który będzie renderowany dla każdego pola formularza.

Następnie tworzymy plik /lib/simple_form/spinner.rb:

module SimpleForm
  module Components
    module Spinner
      def spinner
        if options[:spinner]
          spinner_tag(attribute_name)
        end
      end

      private
      
      def spinner_tag(attribute)
        template.image_tag 'spinner.gif', :id => "#{attribute}-spinner", :class => "spinner", :style => "display: none"
      end
    end
  end

  module Inputs
    class Base
      include SimpleForm::Components::Spinner
    end
  end
end

I to w zasadzie tyle. W metodzie spinner dla każdego pola, które posiada opcję spinner = true tworzymy spinner_tag, który jest zwyczajnym obrazkiem z unikalnym identyfikatorem. Spinner domyślnie jest ukryty i musimy go przy odpowiednim zdarzeniu (np. zmianie nadrzędnej listy) wyświetlić (np. metodą toggle z jQuery). Moduł Spinner musimy jeszcze dołączyć do klasy SimpleForm::Inputs::Base skąd będzie dostępny dla wszystkich pól formularza.

Gotowy skrypt jest dostępny tutaj: https://github.com/michalorman/simple_form_spinner.

Jest w nim też kilka drobnych usprawnień konfiguracyjnych.