Będzie krótko, bo jutro święto jakieś :)
Od bardzo dawna przy wielu stronach natrafiałem na problem w jaki sposób ostylować listę definicji opisów (patrz komentarze) dl, tak aby elementy dt wyświetlały się na lewo od elementów dd. Przy czym kod musiał spełniać warunki:
dt i dd,dt. Elementy dd miały się rozszerzać do maksymalnej dostępnej szerokości.Nigdy nie miałem ochoty się w ten problem zagłębiać i jeśli już stałem pod ścianą to każdy "wiersz" takiej listy pakowałem do osobnego elementu dl, co rozwiązywało najtrudniejszy problem, czyli odpowiedniego ułożenia elementów w pionie. W sumie, to z tego niezbyt pięknego rozwiązania korzystałem często, bo w ten sposób używam listy definicji do tworzenia formularzy.
Ostatnio postanowiłem jednak rozwiązać swój odwieczny problem i po wielu próbach — między innymi z różnymi kombinacjami floatów, display:inline-block i wieloma dziwactwami — udało mi się znaleźć kilka rozwiązań, które działały, ale np. do momentu kiedy nie rozszerzyło się całej listy do sporej szerokości, albo nie nastąpił jakiś przypadek stosunków szerokości. Wpadłem jednak przed chwilą na rozwiązanie, które działa w każdym interesującym mnie przypadku i aż chcę się pochwalić :).
Skrócony kod HTML:
<dl> <dt>Widelec</dt> <dd>Jest biały i leży na moim biurku.</dd> <dt>Widelec</dt> <dd>Jest biały i leży na moim biurku.</dd> ... </dl>
I skrócony kod CSS:
dl { width: 70%; min-width: 40em; overflow: hidden; ... } dt { float: left; width: 15em; clear: both; ... } dd:after { content: '\00a0'; display: block; clear: left; float: right; height: 0; width: 0; } dd { margin: 0 0 0.5em 18em; clear: right; ... }
Cała magia zawiera się w regule dla dd:after. To ona powoduje, że jeśli element dt jest wyższy od następującej po nim definicji, to następna definicja jest wyświetlana w odpowiednim miejscu (nie przeskakuje ponad swój temat).
Wesołego jajka. Idę spać :)
Blog WTFJS o dziwactwach w JavaScriptcie widział już pewnie każdy. Niektóre przykłady są naprawdę fajne :) Co więcej — niektóre działają mi w sposób przedstawiony przez autorów tylko czasami, choć to pewnie nie problem JavaScriptu, a Firebuga :).
Jako, że lubię wiedzieć dlaczego tak to działa, a nie inaczej, staram się wyjaśnić sobie o co chodzi w każdym przykładzie. Niektórych nadal nie rozumiem, część jest oczywista, część ma rozwiązania podane na stronie, a te ciekawsze postaram się wyjaśnić w tym (i może następnych) wpisach.
typeof "abc" == "string" // true typeof String("abc") == "string" // true String("abc") == "abc" // true -- same types get casted to equal each other String("abc") instanceof String // false -- hmmm... (new String("abc")) instanceof String // true String("abc") == (new String("abc")) // true -- wait, wtf?
new String() to nie String(), a "string" to nie "object". Proste? :) Trochę wolniej:
Pierwsza linia jest oczywista, ale warta zapamiętania — typem literału stringa jest "string". W drugiej mogłoby się wydawać że tworzymy obiekt, ale nie — konstruktor String wywołany bez new po prostu rzutuje na stringa. Trzecia linia tylko to potwierdza.
Czwarta linia to nawiązanie do tytułu jaki nadałem tej sekcji — Jestem prawie obiektowy
. Autor tego przykładu zdziwił się skąd takie zachowanie, ponieważ pewnie nie wiedział o typach prostych w JavaScriptcie. Otóż wartości: 1, "a", true nie są typu "object" tylko odpowiednio "number", "string", "boolean". Nie są z tego powodu instancjami swoich podobnie brzmiących z nazwy klas (ale uwaga — czerpią z ich prototypów). Jest to zachowanie całkiem uzasadnione (choć niefajne w działaniu), ponieważ w innym przypadku (gdyby typy proste nie istniały) nie działało by porównanie ===.
Wyobraźmy sobie sytuację, w której pisząc "a" tworzymy nowy obiekt typu "string". Porównanie dwóch zmiennych, które są obiektami za pomocą operatora === sprawdza, czy zmienne te odnoszą się do dokładnie tego samego obiektu w pamięci. Tak więc w naszej hipotetycznej sytuacji otrzymalibyśmy:
"a" === "a"; // -> false
Uups. Po lewej obiekt, po prawej obiekt, ale to nie te same obiekty. Na szczęście teraz jest tak:
"a" === "a"; // -> true new String("a") === new String("a"); // -> false true === true; // -> true new Boolean(true) === new Boolean(true); // -> false
Nie twierdzę, że jestem fanem takiego rozwiązania. Nie jestem pewien, ale chyba w Javie jest podobnie, za to na przykład już w Rubym jest to rozwiązane lepiej (ktoś potwierdzi?). Cóż — nic nie jest idealne ;).
Wróćmy jednak do przykładu. W piątej linii wreszcie tworzymy obiekt typu String. Dla przypomnienia:
typeof new String("a"); // -> "object" typeof String("a"); // -> "string"
Ostatnia linia powinna być już oczywista jeśli powiem, że operator == przeprowadza konwersję zmiennych stojących po obu jego stronach do jednego typu (którego, tego nie wiem — pewnie zmiennej po lewej — kto ma ochotę niech sprawdzi w specyfikacji i napisze w komentarzu :). Tak więc z porównania String("abc") == (new String("abc")) otrzymujemy porównanie "a" == "a". Dla porównania:
String("abc") === (new String("abc")); // -> false
Widać więc, że trzeba uważać na krótki operator porównania i stosować go z głową. Polecam przyzwyczajenie się do używania potrójnego operatora i tylko w szczególnych wypadkach, kiedy na przykład znamy typy zmiennych (typeof x == "string"), używanie podwójnego.
Kolejny przykład będzie tylko rozwinięciem poprzedniej części. Oto on:
(1) === 1; // true
Number.prototype.isOne = function () { return this === 1; }
(1).isOne(); // false!
Number.prototype.reallyIsOne = function () { return this - 1 === 0; }
(1).reallyIsOne(); // true
Pierwsza linia powinna być oczywista — nawias nie ma znaczenia. Druga i trzecia to ścisłe nawiązanie do poprzedniej części. W isOne typeof this; // -> "object". Mimo, że wywołujemy metodę na zmiennej typu "number", to jest ona po cichu rzutowana na "object" — dziwne, ale do zrozumienia — ciężko żeby this nie było obiektem.
Dwie ostatnie linie, to dynamiczne rzutowanie. Operacja odejmowania stara się rzutować obie zmienne na typ liczbowy. this jest rzutowane na 1 i reszta jest jasna. Równie dobrze można byłoby zapisać reallyIsOne w ten sposób:
Number.prototype.reallyIsOne = function () { return +this === 1; };
Poprzedni przykład przekleiłem na czysto z WTFJS. Jeśli będziecie go w tej postaci wklejać do Firebuga, to możecie się spotkać z latającymi wyjątkami. Skąd ten problem? Na końcu drugiej i czwartej linii brakuje średników i przynajmniej firefoksowy silnik wysiada. Rada na przyszłość — średniki dobre są.
Tego już nie wytłumaczę :). Choć — obstawiałbym, że problem leży gdzieś w standardzie reprezentacji liczb zmiennoprzecinkowych, ale to tylko mój domysł.
Number.MAX_VALUE*1.0000000000000001 === (1/0) // false Number.MAX_VALUE*1.0000000000000002 === (1/0) // true
Opublikowane na licencji CC przez Trufflepig.
W poprzednim artykule o obiektach w JavaScriptcie poruszyłem kwestię ich tworzenia, dostępu do właściwości, kontekstu wywoływania metod oraz wspomniałem o kilku cechach. Czyste literały obiektów same w sobie są już w tym języku bardzo przydatne — przekonał się pewnie o tym każdy kto korzystał z jakiejś biblioteki typu MooTools czy Prototype. Pozwalają na przykład w dość wygodny sposób na podanie dowolnej liczby nazwanych argumentów funkcji:
new Ajax.Request('/your/url', { parameters: { id: 12, action: 'sth' }, onSuccess: function (transport) { alert(transport.responseText); }, evalJS: false });
Pisałem ostatnio o tym, że w JavaScriptcie nie ma klas. Skąd więc w przykładzie słówko kluczowe new, które w typowym języku służy do tworzenia instancji obiektu na bazie jakiejś klasy? Otóż okazuje się, że w JavaScriptcie wprowadzono twór zwany konstruktorami obiektów.
Zwyczajnie zaczyna się od definiowania klasy jakiegoś kotka, samochodu, czy figury geometrycznej. Nudy. Chciałem wymyślić coś bardziej konstruktywnego i spośród wszystkich dostępnych na moim biurku obiektów wybrałem butelkę po miodzie pitnym (pustą niestety). Myślę, że konstruktory dla napoju alkoholowego i butelki będą wystarczająco oryginalne :). Do dzieła:
var Alkohol = function (nazwa, ilosc_procentow, kolor) { this.nazwa = nazwa; this.ilosc_procentow = ilosc_procentow; this.kolor = kolor; }; var ButelkaAlkoholu = function (alkohol, pojemnosc, rok_produkcji, kraj_produkcji) { this.alkohol = alkohol; this.pojemnosc = pojemnosc; this.rok_produkcji = rok_produkcji; this.kraj_produkcji = kraj_produkcji; };
Oj tak. JavaScript to dziwoląg. Miały być konstruktory obiektów, a tu znowu pojawiły się funkcje. Z drugiej strony pojawiło się też słowo kluczowe this, więc cały przykład przypomina dwa wycięte z definicji klas napisanych w normalnym języku konstruktory. Na szczęście obiekty tworzymy już normalnie:
var miod = new Alkohol('Miód pitny', 13, '#850'); var butelka_miodu = new ButelkaAlkoholu(miod, 750, 2009, 'Polska'); console.dir(miod); // -> screenshot poniżej console.dir(butelka_miodu); // -> screenshot poniżej butelka_miodu.alkohol.nazwa; // -> "Miód pitny"

Utworzyliśmy w ten sposób dwa obiekty, których struktura została wylistowana przy pomocy Firebuga i którą przedstawiłem na screenie.
W poprzedniej części artykułu pisałem o tym, że obiekt może mieć swoje metody (inaczej funkcje przypisane jako właściwości obiektu). Musi więc być sposób aby w konstruktorze obiektu zdefiniować takie metody. Dodajmy więc kilka linii do konstruktora ButelkaAlkohol:
var ButelkaAlkoholu = function (alkohol, pojemnosc, rok_produkcji, kraj_produkcji) { //... this.pelna = true; this.oproznij = function () { this.pelna = false; }; }; var butelka_miodu = new ButelkaAlkoholu(miod, 750, 2009, 'Polska'); butelka_miodu.pelna; // -> true butelka_miodu.oproznij(); butelka_miodu.pelna; // -> false
Jak widać, aby utworzyć coś co możemy nazwać metodą, musimy przypisać funkcję do właściwości przyszłego obiektu. Uczulam znowu na działanie słowa this, o czym pisałem już poprzednim razem.
Naszym celem jest utworzenie obiektu o zadanych przez nas wartościach właściwości. Pamiętając, że najprostszym sposobem na utworzenie obiektu jest skorzystanie z literału możemy dojść do następującej konstrukcji: stwórzmy funkcję zwracającją obiekt zapisany za pomocą literału:
var Alkohol2 = function (nazwa, ilosc_procentow, kolor) { var obj = { nazwa: nazwa, ilosc_procentow: ilosc_procentow, kolor: kolor, pelna: true, oproznij: function () { this.pelna = false; } }; return obj; }; var piwo = Alkohol2('piwo Tyskie', 5.6, '#FE3'); piwo; // -> Object nazwa=piwo Tyskie ilosc_procentow=5.6 kolor=#FE3
Tak więc skorzystaliśmy z funkcji w jej czysto funkcyjnym wymiarze. To jednak nie koniec. Dawno już nie było mowy o żadnym JavaScriptowym dziwactwie. Pora na następne. Otóż ten zapis również zadziała:
var piwo2 = new Alkohol2('piwo Tyskie', 5.6, '#FE3'); piwo2; // -> Object nazwa=piwo Tyskie ilosc_procentow=5.6 kolor=#FE3
Zonk.
new z funkcjąKiedy dowiedziałem się o powyższym od razu zacząłem się zastanawiać jak w zasadzie JavaScript traktuje new w kontekście funkcji. Pierwsze co ciśnie się na palce, to modyfikacja przykładu z konstruktorem Alkohol2 tak by zwracał "nieobiekt":
var Cons = function () { return 5; }; var obj = new Cons(); obj; // -> Object
Nic ciekawego — jakiś pusty obiekt otrzymaliśmy. Dodajmy więc właściwość (pierwszy sposób tworzenia konstruktorów):
var Cons = function () { this.a = 'a'; return 5; }; var obj = new Cons(); obj; // -> Object a=a
Ciekawe, choć do wytłumaczenia — return 5 jest pomijane. Połączmy teraz dwa sposoby tworzenia konstruktorów:
var Cons = function () { this.a = 'a'; return { b: 'b' }; }; var obj = new Cons(); obj; // -> Object b=b
Uups. JavaScript pomija właściwości zadeklarowane przy użyciu słowa this. W ten sposób dochodzimy do algorytmu, którym kieruje się ten język (a przynajmniej Firefox — może komuś się chce zajrzeć do specyfikacji? :):
new Cons() zwraca ten obiekt.this.Porównując obydwa sposoby pisania konstruktorów można dojść do wniosku, że lepsza jest ta druga, (ze zwracaniem gotowego obiektu), ponieważ działa na obydwa sposoby: new Cons() i Cons(). Osobiście uważam jednak, że ta metoda jest gorsza — dlaczego? O tym później w tej i w następnej części artykułu.
Umiemy już tworzyć konstruktory obiektów, które można porównać do klas w zwyczajnym języku. Obiekty tworzone za ich pomocą posiadały do tej pory jednak tylko publiczne właściwości i metody. JavaScript nie udostępnia mechanizmu modyfikatorów dostępu, można jednak skorzystać z jego cech aby oprogramować w prosty sposób podobne konstrukcje.
var Cons = function () { var private = 'jestem prywatna'; this.getPrivate = function () { return private; }; }; var obj = new Cons(); obj.getPrivate(); // -> "jestem prywatna" obj.private; // -> undefined
Dlaczego to działa? Wszystko opiera się o kolejny dziwoląg, czyli JavaScriptowego scope'a, a także o closures (BTW. fajne tematy na kolejne artykuły). W skrócie — funkcja zadeklarowana w innej funkcji ma dostęp do zmiennych dostępnych w swoim "rodzicu". Do tego mechanizm domknięć powoduje, że funkcja getPrivate "zapamiętuje" środowisko, w którym została zadeklarowana.
var Cons = function () { this.nonstatic = 'nie jestem statyczna'; }; Cons.static = 'jestem statyczna'; Cons.static; // -> "jestem statyczna" Cons.nonstatic; // -> undefined
Chyba nic nie trzeba wyjaśniać. Konstruktor Cons jak każda funkcja jest obiektem, więc można dodawać mu właściwości, co robimy w czwartej linii.
Dla jasności dodam, że w identyczny sposób tworzymy prywatne i statyczne metody obiektów i klas.
Każdy obiekt posiada właściwość constructor, która daje dostęp do konstruktora (porównywalne do Javowego Object.getClass()):
var Cons = function () { this.sth = 'sth'; }; var obj = new Cons(); obj.constructor; // -> function() obj.constructor.toString(); // -> "function () { this.sth = "sth"; }"
A teraz niespodzianka. Dlaczego drugi sposób tworzenia konstruktorów (przez literał obiektu) uważam za gorszy? O to jeden z powodów:
var Cons2 = function () { return { sth: 'sth' }; }; var obj2 = Cons2(); var obj3 = new Cons2(); obj2.constructor; // -> Object() obj3.constructor; // -> Object() obj2.constructor.toString(); // -> "function Object() { [native code] }" obj3.constructor.toString(); // -> "function Object() { [native code] }"
Zdefiniowaliśmy konstruktor, który teoretycznie działa jak ten z poprzedniego przykładu (zwraca obiekt o tej samej właściwości). Okazuje się jednak, że kiedy spróbujemy dostać się do konstruktora tego obiektu, to nie jest nim funkcja Cons2, a zwykła, natywna Object. Dla porównania:
({}).constructor.toString(); // -> "function Object() { [native code] }"
Umiemy już posługiwać się obiektami. Umiemy też tworzyć ich konstruktory. Pora więc na dziedziczenie. O tym za czas jakiś w następnej części :)
Opublikowane na licencji CC przez Trufflepig.
Do tego czteroczęściowego (jak zapowiem, to może napiszę :) artykułu natchnęło mnie szkolenie z zaawansowanego JavaScriptu które, w miniony weekend, zorganizował we Wrocławiu Damian Ferrante Wielgosik. Już na początku chciałbym mu podziękować za wiedzę, którą się podzielił, ponieważ przed szkoleniem byłbym wstanie napisać tylko część tego artykułu.
Tego nie da się ukryć. JavaScript należy do wąskiej grupy języków z rodziny ECMAScriptu (JS, ActionScript, E4X), którą cechuje to, że ich obiektowość oparta jest na prototypach. Oznacza to, że w JavaScriptcie nie istnieje pojęcie klasy. Nie oznacza to jednak tego, że w JavaScriptcie nie da się programować obiektowo z wykorzystaniem np. konkretyzacji, dziedziczenia, czy właściwości prywatnych. Da się. Co więcej — da się uzyskać dużo więcej funkcjonalności klasycznego języka obiektowego, choć, o czym na sam koniec, niekoniecznie jest po co.
W języku z obiektowością klasową wypada zacząć od definicji klasy. Tak przynajmniej zaczynali autorzy książek, które czytałem. W przypadku JavaScriptu mielibyśmy jednak pewien problem, bo klas tutaj nie ma. Trzeba więc zacząć od utworzenia obiektu. Oto najprostszy:
var obj = {}; typeof obj; // -> "object"
Tak, to w JavaScriptcie jest najprostszy obiekt. Zadeklarowany w przykładzie jest pusty i dziedziczy po obiekcie Object. Możemy sprawdzić to za pomocą poniższego kodu (jak działa i co to znaczy, o tym później):
var obj = {}; obj.__count__; // -> 0, działa tylko w Firefoksie obj.__proto__.constructor; // -> Object()
Obiekt możemy również utworzyć za pomocą konstruktora Object(). Nie jest to jednak sposób polecany, ponieważ... zmienną Object można nadpisać. Lepiej pozostać przy literałach obiektów, które są bezpieczniejsze i ładniejsze.
var obj = new Object(); // -> Object Object = 5; var obj2 = Object(); // -> TypeError: Object is not a constructor
Nic nam jednak z pustego obiektu. Pora dodać do niego dane i metody. Możemy skorzystać ze składni literału obiektu:
var obj = { text: 'Jestem obiektem', saySth: function () { alert(this.text); } };
Bądź stworzyć pusty obiekt i dodać mu właściwości:
var obj = {}; obj.text = 'Jestem obiektem'; obj.saySth = function () { alert(this.text); };
W obu przypadkach efekt będzie ten sam. Kiedy wywołamy obj.saySth(); dostaniemy alerta z tekstem Jestem obiektem
.
Jeszcze słowo wtrącenia o literałach. Trzeba uważać na przecinek po ostatniej właściwości. Firefox i chyba wszystkie normalne przeglądarki trzymają się specyfikacji ECMAScript 5 pkt 11.1.5 (bądź to specyfikacja trzyma się tych przeglądarek ;), a kochany IE6, który zawsze musi być inny ("inny nie znaczy gorszy" w tym wypadku nie działa ;) trzyma się specyfikacji ECMAScript 3 pkt 11.1.5. Tak więc przecinek po ostatniej właściwości zostanie zaakceptowany przez przeglądarki z rodziny normalnych, zaś IE6 wywali cichaczem błąd. Nie wiem co prawda jak sprawa wygląda w IE7 i IE8, ale radzę po prostu tego przecinka nie stawiać. Koniec dygresji.
W Javie, czy PHP metoda obiektu jest czymś zupełnie innym niż jego właściwość. W JavaScriptcie metoda jest po prostu funkcją przypisaną do właściwości obiektu. Widać to najlepiej w trzeciej linii ostatniego przykładu: obj.saySth = function () {};. Wynika to z tego, że w JavaScriptcie funkcja też jest obiektem. Dzięki temu możemy ją przypisywać do zmiennych, zwracać w innych funkcjach, czy... wywoływać na niej metody. Ta właściwość JavaScriptu jest naprawdę potężna i myślę, że język ten sporo teraz zyskał w oczach osób lubiących Rubiego, Pythona, czy jakiś język funkcyjny :).
thistypeof obj.saySth; // -> "function" var sayNothing = obj.saySth; typeof sayNothing; // -> "function" sayNothing(); // -> alert undefined
Z pierwszych trzech linii widać, że możemy sobie dowolnie operować funkcją. Czwarta może być zaskoczeniem. Dlaczego this.text === undefined? Zauważcie, że funkcja sayNothing nie została wywołana w kontekście obiektu obj, tylko w kontekście... no właśnie — czego? Żeby to sprawdzić dodajmy dwie linie kodu do poprzednich przykładów:
window.text = 'Jestem globalnym scopem'; sayNothing(); // -> alert "Jestem globalnym scopem"
Teraz widać, że funkcja sayNothing(); wywołana jest w globalnym kontekście. Możemy ją także wywołać w odpowiednim kontekście używając metody call() działającej na funkcji (tak jak pisałem wcześniej — funkcja też jest obiektem, więc ma też swoje metody):
sayNothing.call(obj); // -> alert "Jestem obiektem"
Więcej na ten temat u Ferrante w "this" w JavaScript.
Do właściwości obiektu możemy się również dostać za pomocą operatora [], czyli w taki sposób jak do elementów tablicy. Jest to przydatne kiedy chcemy uzyskać właściwość, której nazwa trzymana jest w zmiennej:
var obj = { a: 'A', b: 'B', m: function () { return 'M'; } }; obj['a']; // -> "A" var name = 'b'; obj[name]; // -> "B" obj['m'](); // -> "M"
Po właściwościach obiektu można też iterować za pomocą konstrukcji for (i in obj). Trzeba jednak uważać, bo, w zależności od tego jakim obiektem dysponujemy, możemy dostać niespodziewane (przynajmniej na razie) rezultaty. Póki co jednak wystarczy nam:
var obj = { a: 'A', b: 'B', c: 'C', d: 'D' }; for (i in obj) { alert('obj[' + i + '] = ' + obj[i]); }
Obiekty można też zagłębiać. Jest to własność oczywista, ale wypada dla jasności o niej wspomnieć.
var obj = { obj: { obj: { hidden: 'Zonk' } } }; obj.obj.obj.hidden; // -> "Zonk"
Samym obiektem programista żyć nie może. Klas jednak w JavaScripcie nie uświadczymy, trzeba więc wymyślić coś innego. Z pomocą przyjdą nam konstruktory obiektów, ale zrobią to dopiero w następnej części :).
Poza tym w trzeciej części mam zamiar napisać o prototypach, czyli JavaScriptowym dziedziczeniu. W czwartej chciałbym zebrać wszystko do kupy, pokazać może jakieś wzorce i napisać kilka uwag o programowaniu w JavaScriptcie. W zasadzie to uwagi mam już napisane, bo zacząłem od końca :D
Sesja się już dla mnie skończyła, można więc coś napisać. Dzisiaj będzie jednak krótko, bo i temat prosty.
No właśnie. Każdy wie co to są frameworki programistyczne, ale CSSowy? Według wiki:
A CSS framework, also known as a web design framework is a pre-prepared library that is meant to allow for easier, more standards-compliant styling of a webpage using the Cascading Style Sheets language. Just like programming and scripting language libraries, CSS frameworks (usually packaged as external .css sheets inserted into the header) package a number of ready-made options for designing and outlaying a webpage.
Czyli, tak jak w językach programowania, framework jest biblioteką (w tym wypadku grupą reguł CSS) która ułatwia implementowanie jakichś standardowych, często powtarzających się funkcjonalności (w tym wypadku pewnych schematów w layoutcie).
Co, według mnie, frameworkiem CSS nie jest? Pisałem w listopadzie o parserze/kompilatorze/procesorze CSS - LESS, który rozszerza składnię CSS o kilka świetnych rzeczy. Między innymi - zmienne, zagnieżdżone reguły, obliczenia, wielokrotne wykorzystywanie reguł. Jakby nie było - nie podchodzi to pod definicję z Wikipedii, ale niestety znajduje się w linkach do przykładowych frameworków. Dla ustalenia uwagi - LESS nie jest frameworkiem.
Pod mą lupę wziąłem pierwsze linki zwrócone przez Google. Przyjrzałem się chwilkę frameworkom: Blueprint, Elastic CSS, YUI 2 Grids. Prawdopodobnie są lepsze rozwiązania od tych i z chęcią się im przyjrzę jeśli linki pojawią się w komciach :).
Wypada jeszcze wtrącić, dla niezorientowanych, co to za poroniony pomysł ten Grid system. Otóż wpadł ktoś na pomysł, że wygodnie będzie wszystkim (grafikom, programistom) jak ustalimy sobie, że strona składa się z X kolumn po Ypx szerokości każda, między którymi są marginesy po Zpx. Przyznam, że obaj z grafikiem byliśmy zachwyceni, aż do... pierwszego projektu. Życzę np. sporo zabawy z cieniami wychodzącymi poza granice kolumn, w przypadku kiedy nie możemy użyć box-shadow. Szlag trafia wszystkie piękne, okrągłe wartości.
Najpierw kawałek kodu z YUI 2 Grids:
<div id="yui-main"> <div class="yui-b"> <div class="yui-g"> <div class="yui-g first"> <div class="yui-u first"></div> <div class="yui-u"></div> </div> <div class="yui-g"> <div class="yui-u first"></div> <div class="yui-u"></div> </div> </div> </div> </div>
Tyle się kiedyś mówiło o semantyce kodu i oddzieleniu wyglądu od treści. Ponad pięć lat temu, kiedy zaczynałem naukę HTMLa i CSSa, trwała walka o to aby programiści zaczęli pisać porządny kod. Wydawało mi się, że teraz już nie powinno być z tym problemów, a tu zonk. Patrzę na ten przykład i co widzę?
Tak więc w żadnym wypadku nie widzę sensu w używaniu frameworków do budowy układu strony. Żeby jednak nie było, że w ogóle nie umiem wykorzystywać zewnętrznego kodu - uważam, że przydatne są frameworki poprawiające typografię, bądź też pod niektórymi względami formularze. Muszą jednak bazować na selektorach używających tagów, a nie klas, czy nie daj Boże identyfikatorów.
Zadałem jakiś czas temu na blipie pytanie dotyczące artykułu Label Placement in Forms. Interesowało mnie czy może jest jakiś powód dla którego niektórzy (np. Marek Kasperski w swojej książce Projektowanie stron WWW) piszą aby labele do pól formularzy umieszczać na lewo od samych pól i wyrównane do prawej, a dlaczego inni (choćby autor tego artykułu) uważają, że najlepiej opis pola umieścić ponad polem, zmniejszając tym samym liczbę fiksacji wzroku? Czy Ci pierwsi nie znają badań? Czy kierują nimi jakieś inne przesłanki (np. praktyczne), o których ja nie wiem? Rzecz jasna, nie udało mi się tak doprecyzować pytania (ciężko się było w 160 znakach zmieścić :D).
Dostałem dwie odpowiedzi. Skopiowałem sobie kiedyś tylko ich treść, nie podam niestety linka, bo w blipowym archiwum nie mam szansy tego znaleźć. Tak wyglądała rozmowa:
reinmar: #usability labele nad inputami, czy na lewo od nich (wyrównane do prawej)? Za każdym razem piszą inaczej (choć to pewnie bez znaczenia)
PanA > reinmar: nie bez znaczenia, zależy co chcesz osiągnąć. U góry się szybciej czyta niż obok, mniej fiksacji
PanB > reinmar: [blip] tak jak mówi ^PanA, najlepiej nad ale pola muszą być udzielone, mogą też być po lewej ale z wyrównaniem do prawej
Zaciekawiło mnie co miał PanA na myśli pisząc: zależy co chcesz osiągnąć
:
reinmar > PanA: To co mogę osiągnąć umieszczając je po lewej? Więcej fiksacji, więc umieszczać je tam jak chcę zniechęcić użytkownika? ;P
PanA > reinmar: czasami użytkownikowi nie ma być łatwiej - możemy chcieć zmusić go do zastanowienia się nad tym co wpisuje, etc.
reinmar > PanA: Dlatego odciągamy go od zastanawiania utrudniając ogarnięcie formularza? Czy dajemy mu więcej czasu na zastanowienie się
Uhh... Urocze powtarzanie usłyszanych od branżowych guru i wyczytanych z książek zdań. Zresztą - już moje pytanie było zadane złośliwie, bo po zależy co chcesz osiągnąć
w miarę wiedziałem czego się mogę spodziewać.
Jednak nie do samej rozmowy chciałem bić w tym wpisie (dlatego też ocenzurowałem rozmówców - nie chcę tu dyskusji :P), a do dwóch błędów, które moim zdaniem wiele osób popełnia podczas projektowania.
Generalnie sytuacja ta odnosi się do każdej branży. Młodzi adepci Czegoś siadają do książek, chłoną, chłoną i napuchli wiedzą stają się w ich mniemaniu pełnowartościowymi specjalistami od Czegoś. Znają dziesiątki strasznie brzmiących terminów, mogą toczyć poważne dyskusje z ważnymi osobami ze świadka Czegoś. Zabawa kończy się jednak kiedy "specjalista" wkracza w granice praktyki. I tutaj, w zależności od osobowości i życiowej mądrości, z pokorą zbiera doświadczenie i weryfikuje posiadaną już wiedzę, bądź też dalej wygłasza znaną sobie teorię, która zupełnie nie ma zastosowania w praktyce.
Żeby była jasność - nie neguję wartości znajomości teorii i nie twierdzę, że ktoś kto poznał teorię może być dobrym praktykiem.
PanB > reinmar: osobiście nad daje gdy jest mało pól, przy większej ilości daje z lewej
O. I to jest ciekawa odpowiedź. Rzeczywiście - ciężko przy 20 polowym formularzu dawać opisy ponad pola, ponieważ formularz ten rośnie nam do ogromnych rozmiarów. Przypomniała mi się jednak przenośnia (?), którą kiedyś wymyśliłem aby zobrazować w firmie jaki popełniamy błąd projektując funkcjonalności jednego serwisu. Wiele osób działa tak:
Weźmy tort, bo przecież każdy lubi tort. Tort jest fajny!
Weźmy znaczek Nike. Przecież jest teraz trendy, na pewno każdemu się spodoba.
Ja chcę jeszcze płytę DVD. Przecież jest taka funkcjonalna. Płyta DVD jest fajna!
I co dostajemy? Tort z przyklejonym z boku znaczkiem Nike i wbitą na sztorc płytą DVD. Dzieło sztuki po prostu. Niestety tak działa wiele osób - dobierając funkcjonalności serwisu wybierają (np. z konkurencyjnych rozwiązań) te, które są fajne, nie myśląc o tym, czy razem będą tworzyły spójną całość. Projektując interfejs zagłebiąją się w statystyki, badania, wybierają atomowo najlepsze, najszybsze, najczytelniejsze rozwiązania i nie patrzą na ogół serwisu. Nagminne.
Nie, nie uważam się za specjalistę od użyteczności, nie przeczytał nawet 5 książek. Co więcej - możliwe, że PanA ma rację, a ja jestem zbyt ograniczony by go zrozumieć.
Tak samo - sam nie jestem wolny od wyśmiewanych przeze mnie błędów. Niestety ;)
BTW. muszę wreszcie stworzyć sobie jakiś design do tego blogaska.
Przez mój czytnik i mojego blipa przewija się masa linków. Część ze znalezionych informacji dodaję do swojego del.icio.us, jednak mimo to o tych najważniejszych / wartych najwięcej uwagi czasem zapominam. Pomyślałem więc, że będę publikował co jakiś czas najciekawsze moim zdaniem znaleziska z zakresu web developowania i usability. Raz, że samemu będzie mi łatwiej do tych informacji wrócić, dwa, że może komuś zaoszczędzę trochę czasu i podsunę coś czego jeszcze nie czytał.
Wiele z poniższych linków macie szansę znać, wiele to starsze informacje, ale to pierwszy wpis w tym cyklu (tak, jeśli będę miał o czym pisać, to może zacznę to robić regularnie :) - następne będą aktualniejsze i zgrabniejsze. Na dodatek aby znaleźć te linki niestety musiałem przeglądać historię i wiele ciekawych rzeczy mogłem pominąć.
display:none jest złe – Sam odpowiedzi nie znałem. Artykuł o tym jak to zrobić lepiej.Póki co na tym koniec. Od teraz co ciekawszy znaleziony link będę sobie odkładał, więc następne wydanie przeglądu prasy będzie aktualniejsze.
Uwaga: wyszedł mi "odrobinkę" przydługi wstęp. Zabieganych zapraszam od razu do konkretów.
Nie da się ukryć że frontend developerzy (czy inaczej "ci od cięcia grafiki" :) łatwego życia nie mają. Pierwsza sprawa to niedoskonałości samych języków (CSS, xHTML, Javascript+DOM), druga to różnice i bugi (zwane inaczej ficzerami) w obsłudze przez przeglądarki. Każdy z tych języków na szczęście się rozwija (CSS3, HTML5, ECMAScript 5), a implementacja standardów jest, nawet w Internet Explorerze, sukcesywnie polepszana. Trwa to jednak strasznie długo, dlatego warto pokombinować samemu.
Ostatnio opisałem plugin do edytorów tekstów dzięki któremu można przyspieszyć pisanie HTMLa. Dzisiaj pod lupę chciałbym wziąć CSS. Osobiście na tempo pisania listy właściwości nie narzekam - mam własny zestaw snippetów, który umila mi życie. Inaczej ma się sprawa w stosunku do selektorów. Już w niedużym projekcie potrafią się zrobić cuda typu:
#content .news_list .item .title {...} #content .news_list .item .date {...} #content .news_list .item .date .day {...}
Albo:
.s1 { width:120px; float:left; font-size:1.1em; margin-left:10px; color:#FA0; } /*gdzieś kawałek dalej:*/ .s2 { width:120px; float:left; font-size:1.1em; margin-left:10px; color:#C14; }
Gdzie wypada to połączyć do pary selektorów i dla każdego z osobna jeszcze kolor ustawić. Niestety to najprostszy przypadek z tej rodziny, czasami trzeba znaleźć części wspólne dla kilku reguł po kilkunaście właściwości, albo olać wszystko.
Dalej można wymieniać:
co to był za kod tego pomarańczowego, który używam do podkreśleń?
-moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px;
Co więcej - to są problemy które pojawiają się już na etapie stron o średniej wielkości i kodowania w pojedynkę. W przypadku sporych serwisów (długo i wieloetapowo rozwijanych) i większego zespołu potrafią urosnąć do &*$#$%. To jest jak rzeźba w gównie. Trzeba przepisać cały ten bajzel
. Sam ostatnio tak opisałem jeden commit do bazy.
Jakiś czas temu dostałem od kogoś link do LESS CSS - parsera/kompilatora (nie wiem jak to nazwać) rozszerzającego możliwości CSSa o kilka interesujących rzeczy typu: zmienne, obliczenia, reguły zagnieżdżone, itd. O temacie jednak zapomniałem (mało ostatnio koduję) i przypomniał mi kilka dni temu o nim Occulkot, który podrzucił mi Sassa. Obydwa kompilatory napisane są w Rubym. Zrobiłem mały research i znalazłem jeszcze podobne cudo, tyle że w Pythonie - Clever CSS.
Po krótkim wczytywaniu się w przykłady zdecydowałem się na LESSa. Dodaje do CSSa trochę mniej ficzerów, ale przynajmniej nie zmienia jego składni (plik CSS jest poprawnym plikiem LESS) i nie wymusza na mnie jedynego, właściwego formatowania kodu (Sass i Clever CSS mają składnię czerpiącą z języków w jakich zostały napisane - odpowiednio Ruby i Python - wymagają więc wcięć).
Polecam przeczytać Haml-sass, czyli tragedia w dwóch aktach. Przy okazji tej lektury ja również odradzam Hamla, który jest w teorii cudownym (zen i te sprawy) systemem szablonów HTMLa. Tylko, że kompletnie to nieczytelne, brzydkie i bez sensu.
Po tym przydługim wstępie (chciałem usunąć, ale żal jak się już napisało) przejdźmy do konkretów.
Instalacja, dla posiadających gema (ci nieposiadający najlepiej niech go zainstalują), jest banalna:
$ sudo gem install less
Ewentualnie jeśli ktoś koduje w Railsach, to może go zainteresować ten plugin.Najprostszym sposobem skorzystania z LESSa jest jednak przy pomocy konsoli:
$ lessc style.less
Wykonanie tego polecenia spowoduje utworzenie pliku style.css zawierającego wynik działania kompilatora.
Kompilując taki kod:
@brand_color: #4D926F; #header { color: @brand_color; } h2 { color: @brand_color; }
Otrzymamy:
#header, h2 { color: #4d926f; }
Jak widać, poza podstawieniem zmiennej, LESS połączył też reguły o tej samej liście właściwości. To informacja dla miłośników oszczędzania na bajtach transferu :).
CSSowe DRY :).
.rounded_corners (@radius: 5px) { -moz-border-radius: @radius; -webkit-border-radius: @radius; border-radius: @radius; } h1 .stronger { font-weight:bold; font-size:1.1em; } #header { .rounded_corners; h1 .stronger; } #footer { .rounded_corners(10px); }
Po kompilacji:
h1 .stronger { font-weight: bold; font-size: 1.1em; } #header { -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; font-weight: bold; font-size: 1.1em; } #footer { -moz-border-radius: 10px; -webkit-border-radius: 10px; border-radius: 10px; }
Przy czym definicja reguły musi wystąpić przed jej użyciem. Przykład z wykorzystaniem h1 .stronger nie ma uzasadnienia w praktyce (jest w zasadzie błędny), ale chciałem tylko pokazać, że można wykorzystywać ponownie dowolne reguły.
To chyba mój ulubieniec. Dzięki temu rozszerzeniu unikniemy pisania co chwilę mega długich selektorów. Dziwię się w ogóle, że nie ma tego w oficjalnym CSSie.
#header { color: red; a { font-weight: bold; text-decoration: none; } > p { color: blue; } }
Po kompilacji:
#header { color: red; } #header a { font-weight: bold; text-decoration: none; } #header > p { color: blue; }
Chyba nie muszę tłumaczyć.
@the-border: 1px; @base-color: #111; #header { color: @base-color * 3; border-left: @the-border; border-right: @the-border * 2; } #footer { color: (@base-color + #111) * 2; }
Po kompilacji:
#header { color: #333333; border-left: 1px; border-right: 2px; } #footer { color: #444444; }
To by było na tyle. Jeśli kogoś temat zainteresował to zapraszam do specyfikacji, gdzie autor omawia jeszcze kilka dodatków.
W każdym razie - cztery małe ficzery, a jak cieszą, prawda? :)
Zastanawiałem się tylko jak usprawnić kompilację po wprowadzeniu zmian do pliku LESS. Najfajniej gdyby udało mi się w moim VIMie podłączyć wykonanie $ lessc pod operację zapisania, ale to nie na moje umiejętności. Mcv zaproponował mi takiego Makefile'a:
%.css: %.less lessc $<
Wykonujemy go uruchamiając: $ make plik1.css plik2.css. To już powinno mi się udać podpiąć w VIMie pod zapis pliku. Ewentualnie można spróbować tak:
all: *.css %.css: %.less lessc $<
Co zadziała automatycznie dla wszystkich istniejących już plików CSS po wykonaniu prostego $ make. Przy czym, jeśli się mylę, to proszę mnie poprawić, bo nigdy żadnego makefile'a sam nie napisałem.
EDIT: w komentarzach pojawiły się rozwiązania mojego problemu.
Snippety do htmla? Nieeee. Zobaczcie Zen Coding. W skrócie działa to tak. Wpisuję w edytorze CSSową składnią:
div#content>h1+p
Wciskam jakąś kombinację klawiszy i dostaję:
<div id="content"> <h1></h1> <p></p> </div>
Sprytne, nie? :) Ale to jeszcze mało. Spróbujcie tego:
div#top>h1>a[title=Do strony głównej]{Moja strona}<ul#menu>li.pos$*3>a
<div id="top"> <h1> <a href="" title="Do strony głównej">Moja strona</a> </h1> <ul id="menu"> <li class="pos1"> <a href=""></a> </li> <li class="pos2"> <a href=""></a> </li> <li class="pos3"> <a href=""></a> </li> </ul> </div>
Chyba nie muszę mówić jak bardzo taka pomoc przyspiesza pracę. Napisanie z palca kodu z drugiego przykładu zajęłoby mi przypuszczalnie więcej niż 2 minuty. Gdybym użył snippetów (do których przy HTMLu nie mogę się przyzwyczaić) może skróciłbym ten czas dwukrotnie. Zaś używając wynalazku Zen Coding całość naklepałem w 20s (i to nie mając wprawy). Tak więc gorąco polecam.
Gdyby ktoś szukał wtyczki do VIMa, to powstał plugin, którego twórca zainspirował się Zen Codingiem.
W zeszły czwartek (tj. 12 listopada) odbyła się Wrocławska część WUD Tour, na której miałem przyjemność być. Nawiasem mówiąc była to pierwsza konferencja poświęcona usability, na którą dotarłem, bo naście poprzednich albo przegapiłem, albo z przyczyn niezależnych dotrzeć nie mogłem. Tym razem też nie było idealnie, bo po kilku pierwszych prezentacjach musiałem udać się na zajęcia.
Wprowadzenie do tematyki usability- nic szczególnego, ani tym bardziej zaskakującego - raczej dla tych, którzy pomylili salę i potrzebowali wprowadzenia od zera.
Akcelerator Designu- prezentacja pracowników Urzędu Miasta Wrocławia dotycząca inicjatywy mającej na celu promowanie idei wzornictwa przemysłowego i użyteczności wśród przedsiębiorców (itd., itd.). Temat póki co mnie nie prawie nie dotyczący, więc było trochę nudno. Sytuację poprawił dopiero trzeci z prowadzących będący z pochodzenia Włochem, który swoją łamaną polszczyzną bardzo zgrabnie przedstawił nam pewnego włoskiego designera. Kto był ten wie o czym mówię ;D
Co chcą zobaczyć klienci sklepów elektronicznych?- O tej prezentacji za chwilę. W skrócie powiem tylko, że była całkiem ciekawa i tak "politechnikowo" przedstawiona :P
Możliwość używania- drugi biegun względem poprzedniej ekipy (prosta graficznie prezentacja, ale ładnie opracowana typograficznie versus prezentacja "specyfikacyjna" :). Prelekcja przeprowadzona przez Krzysztofa Kubaska - pracownika ASP i projektanta wzornictwa zawodu, dotycząca łączenia użyteczności z designem. Krzysztof z powodzeniem pokazywał, że te dwie rzeczy mogą iść ze sobą w parze i współpraca designera z inżynierem jest jedną z podstaw dobrego produktu. Przy okazji pokazał kilka przykładów zabawnie zaprojektowanych przedmiotów codziennego użytku, jak np. widelec w kształcie samolotu (leci samolocik... AM! :P), czy kubków z dziobami/pyskami zwierząt. Krzysztof zrobił dobre wrażenie i myślę, że taki był główny cel tej prezentacji. Według mojej spiskowej teorii - śmiech -> dobre wrażenie -> więcej klientów ;). Zwróciłem też uwagę, że specjalistów od usability uważa za informatyków. Ciekawe :)
Później niestety musiałem już sobie iść. Najciekawsze prelekcje opuściłem i czekam z niecierpliwością na video z konferencji.
Wracając do prelekcji dotyczącej sklepów internetowych. Marcin Kuliński przedstawił nam narzędzie służące do badania preferencji użytkowników dotyczących rozmieszczenia elementów interfejsów (w tym przypadku sklepów internetowych). Narzędzie to składało się z dwóch części - pierwszej - bardzo prostej, pozwalajacej za pomocą drag&dropa zaprojektować z dostępnych elementów własny interfejs. Druga służyła do przeglądania wyników tego badania. Nie pamiętam wszystkich funkcji tego narzędzia, ale na pewno można było filtrować badanych, porównywać wyniki dla grup, itd.
Drugą część prezentacji prowadziła Katarzyna Jach, która przedstawiła wyniki badań na grupie, jeśli dobrze pamiętam, 500 studentów IZetu. Zostali oni zapytani gdzie umieściliby przedstawione im elementy interfejsu. Dla każdego z predefioniowanych elementów serwisu (logo, kategorie, zdjęcie produktu, itd) zobaczyliśmy "mapę cieplną" gdzie badani widzieli tę funkcjonalność. Wyników nie pamiętam i nie udało mi się ich znaleźć. Dotarłem tylko do screenów i dema aplikacji nazwanej pieszczotliwie microSzu.
Na zakończenie z sali padły dwa pytania. Jedno dotyczyło tego po co powstała ten system skoro można przeprowadzić testy A/B przy pomocy Google Website Optimizera (który jest bezpłatny, w przeciwieństwie do microSzu). Dla mnie odpowiedź jest oczywista (a pytanie bez sensu) - testy A/B pozwalają nam jedynie udoskonalać istniejącą już stronę i tylko element po elemencie. Wcześniej trzeba przecież tę stronę zaprojektować, czyli między innymi rozmieścić na niej elementy interfejsu. Badanie przeprowadzone przez microSzu pozwala nam na optymalne dla użytkowników zaprojektowanie strony. No właśnie - czy na pewno optymalne?
Tego dotyczyło kolejne pytanie, a w zasadzie stwierdzenie - "użytkownik nie jest dobrym projektantem"
. W pierwszym momencie stwierdziłem, że i to pytanie jest bez sensu. Nie dajemy przecież użytkownikowi projektować, tylko badamy jego preferencje. To przecież część UCD. Później (między innymi po przeczytaniu relacji Agnieszki Matysiak-Szóstek, której prelekcji nie widziałem, czego bardzo żałuję) zacząłem się zastanawiać, czy na pewno mam rację. W końcu badanie polegało nie tylko na odpowiedzi na pytanie gdzie szukałaby Pani/Pan listy kategorii?
, ale na zaprojektowaniu całej strony na raz, czyli zmierzeniu się np. z problemem - ten róg mam już zajęty, gdzie ja teraz ciapnę to to?
.
Myślę, że właśnie w treści pytania i zadaniu do wykonania leży cały problem. Gdyby udało się uzyskać od badanego wszystkie informacje, bez zmierzenia go z problemem całej strony jednocześnie, to uzyskane dane byłyby bardzo przydatne. Możnaby stworzyć proste wytyczne, w którym miejscu (bądź miejscach) możemy umieścić dany element by był łatwo znajdowalny (jeśli chodzi o region, bo pozostają użyte nazwy, czy kolor/wielkość/etc.) przez użytkownika. I nie musi to od razu ograniczać inwencji i talentu projektanta, który może zawsze zaryzykować jakąś zmianę względem szablonu. To jest tylko propozycja, a czy skorzystam, to zależy np. od ilości czasu jaką mogę projektowaniu poświęcić.
Tak przynajmniej myślę ja u początków mojej przygody z użytecznością :)