(c) Robert Tinney |
Mam
już schemat jak wyciągnąć komentarze z dowolnego bloga, to teraz
czas na część wizualną. Tak jak poprzednio, najpierw z grubsza
opiszę poszczególne kroki. Film z podsumowaniem dam na końcu.
Jako, że film byłby dość długi gdyby pokazywać tworzenie kodu
to pokażę w nim tylko jak wygląda struktura pakietu i kod. Z
poprzedniej części mam już w zasadzie napisaną podstawę
pojedynczej funkcji, która odczyta komentarze z podanego adresu URL
i zwróci tablicę komentarzy. Najpierw jednak opiszę jak utworzyć
komponent w Seaside.
Otwieram
System Browser i zaczynam od utworzenia kategorii (paczki obiektów).
Prawy myszki na liście kategorii i klikam "Add category...".
Jako, że tworzę pod portal dobreprogramy.pl, utworzę kategorię
DobreprogramyAPI.
Tworzenie
komponentu głównego
Ten
komponent będzie odpowiadać za zawartość strony głównej. To
wszystko o czym będę pisać jest także dostępne dostępne w
dokumentacji
Seaside.. Kasą główną komponentów jest klasa WAComponent. Po
niej odziedziczę funkcjonalność na potrzeby mojej nowej klasy
"DPRoot":
WAComponent
subclass: #DPRoot instanceVariableNames:
'' classVariableNames:
'' poolDictionaries:
'' category:
'DobreprogramyAPI' |
Pierwsze
co trzeba zrobić, to utworzyć metodę "initialize":
initialize super
initialize. |
Metoda
"initialize" jest metodą wywoływaną zaraz po utworzeniu
obiektu, m.in. po wywołaniu metody "new". Dobrą praktyką
jest wywołać metodę "initialize" obiektu nadrzędnego za
pomocą metody "super". Zielona strzałka do góry, która
się pokaże obok nazwy metody mówi, że moja metoda nadpisuje
metodę z klasy nadrzędnej.
Kolejnym
krokiem będzie utworzenie metody, która wyświetli coś na stronie.
Na chwilę obecną dam jakiś prosty tekst byle zobaczyć, czy
działa. Metoda ta posiada określona nazwę, bo Seaside przeszukując
obiekty i wyświetlając zawartość szuka właśnie jej:
renderContentOn:
html html
paragraph:
'Alibaba' . |
Jako,
że obiekt DPRoot jest obiektem głównym, trzeba zaznaczyć to
tworząc metodę, która poinformuje framework Seaside. W części
klasowej trzeba utworzyć metodę:
canBeRoot ^
true . |
Dzięki
niej Seaside będzie widzieć obiekt w przeglądarce aplikacji.
Rejestracja
komponentu jako aplikacji
W
przeglądarce przechodzę pod adres http://localhost:8080/config .
Klikam w Add na pasku menu. W pole wpisuję 'dp' i wybieram typ jako
Application. OK. W sekcji General na kolejnej stronie jako 'Root
class' wybieram DPRoot. Klik w Apply. Teraz przechodząc pod adres
localhost:8080/dp powinien się wyświetlić Alibaba... Jest. ;)
To
samo można zrobić wywołując w Workspace polecenie:
WAAdmin
register: DPRoot
asApplicationAt: 'dp' . |
Tworzenie
elementu, który wyświetli pojedynczy komentarz
Komponent
tworzę tak samo jak komponent główny, z jednym wyjątkiem.
Komponent będzie posiadać prywatne pola, przechowujące dane nicku,
logo i treść komentarza:
WAComponent
subclass: #DPKoment instanceVariableNames:
'nick
imgUrl text' classVariableNames:
'' poolDictionaries:
'' category:
'DobreprogramyAPI' |
Teraz
trzeba utworzyć gettery i settery. Najlepiej zaznaczyć obiekt
DPKoment, potem prawym myszki klik, wybrać Refactor class ->
Accessors i kliknąć Accept. OK. Można teraz modyfikować zmienne
instancji po utworzeniu obiektu. Aby oszczędzić sobie zachodu
niepotrzebnym późniejszym pisaniem trzeba jeszcze utworzyć
konstruktor na postawie tych zmiennych. Tworzę go w części
klasowej obiektu:
nick:
aNick
imgUrl:
aImgUrl
text:
aText . ^
( self
new) nick: aNick ;
imgUrl: aImgUrl ;
text: aText . |
Czas
na metodę renderContentOn:. Będzie dość prymitywna. Upiększanie
będzie dalej w części z CSS. Teraz tylko oznaczę odpowiednio
klasy tagów.
renderContentOn:
html html
div class: 'komentarz' ; with:
[ html
div class: 'nick' ;
with: self
nick. html
div class: 'image' ;
with: [ html
image url: self
imgUrl]. html
div class:
'text' ;
with: self
text. ]. |
Użyłem
tu kilku słów kluczowych takich jak: div, class image. Odpowiadają
one odpowiednikom z HTML zgodnie z tym
wykazem pod kolumną 'Factory Selector'.
Zmieniam
metodę renderującą klasy DPRoot na taką, by wypróbować czy
działa:
renderContentOn:
html | tmp
| tmp
:= DPKoment
nick: 'test
nick'
imgUrl:
'http://localhost:8080/files/WAWelcomeFiles/seasidestar.png' text: 'tekst' . html
paragraph: tmp . |
Wygląda
to mniej więcej tak:
W AComponent
subclass: #DPKomentList instanceVariableNames:
'' classVariableNames:
'' poolDictionaries:
'' category:
'DobreprogramyAPI' |
DPkomentList
będzie mieć konstruktor, który za parametr bierze listę z
komentarzami
fromList:
aList ^
( self
new)
comments: aList . |
oraz
metodę instancji i zmienną która przechowa przekazane komentarze
do wyświetlenia: 'comments'. Mając te rzeczy można napisać metodę
renderującą:
renderContentOn:
html |
dpk
| ( self
comments) do: [ : koment
|
dpk
:= DPKoment
nick: ( koment
at: #nick )
imgUrl: ( koment
at: #img )
text: ( koment
at: #text ). html
paragraph: dpk . ] |
Jako,
że komentarze będą aktualizowane z jednego miejsca utworzę
zmienną klasową w obiekcie DPRoot o nazwie blogData. Oczywiście
tworzę metody dostępu do tej zmiennej. blogData ma postać listy,
której elementy to obiekty Dictionary:
pseudo
kod: OrderedCollection : ( Dictionary : #url, #name,
#data). |
Po
stronie klasy tworzę metodę, która wyciąga z adresu url bloga
komentarze. Metoda ma taki sam wygląd jak w poprzednim wpisie o
Pharo:
commentsFromUrl:
aUrl | zupa
root komentIDregX komentClass imgClass nickClass komentSoups
komenty koment
| zupa
:= Soup
fromUrl: aUrl . root
:= zupa
findTagByID: 'ctl00_phContentLeft_panUpdateComment' . komentIDregX
:= 'komentarz_[0-9]+' . komentClass
:= 'text-h75
tresc' . imgClass
:= 'border
small float-left' . nickClass
:= 'text-h65
font-heading display-inl_blk nick' . komentSoups
:= root
findAllTagsByIDregX: komentIDregX . komenty
:= OrderedCollection
new. komentSoups
do: [ : ks
| koment
:= Dictionary
new. koment
at: #nick
put: ( ks
findTagByClass: nickClass )
text. koment
at: #img
put: ( ks
findTagByClass: imgClass )
src. koment
at: #text
put: ( ks
findTagByClass: komentClass )
text. komenty
add: koment . ]. ^
komenty . |
To
jest metoda statyczna dostępna z dowolnego obiektu. Wykorzystam ją
w metodzie inicjalizacyjnej DPRoot, którą rozszerzę do postaci:
initialize |
tmp
| super
initialize. ( DPRoot
blogData) ifNil: [ DPRoot
blogData: OrderedCollection
new.]. tmp
:= OrderedCollection
new.
DPRoot
blogData do: [ : data
| data
at: #data
put: ( DPRoot
commentsFromUrl: ( data
at: #url )). tmp
add: data . ]. DPRoot
blogData: tmp . |
Metoda
ta uzupełnia pole #data w słowniku na liście na podstawie pola
#url. Słowniki przy dodawaniu bloga do listy wyświetlania posiadają
zainicjowane tylko pola #url i #name. Pole #name to czytelna nazwa
bloga.
Metodę
renderContentOn: z tej klasy zmienia poniższą:
renderContentOn:
html html
div script:
( html
jQuery new tabs selected:
selectedTab; onSelect:
( html
jQuery ajax callbackTabs:
[ : event
| selectedTab :=
event
at: #index
])); with:
[ html
unorderedList: [ DPRoot
blogData do: [ : blog
| html
listItem: [ html
anchor url:
( html
jQuery ajax html :
( DPKomentList
fromList: ( blog
at: #data
)); fullUrl); with:
( blog
at: #name )
]]]] |
Jest
to lekko przerobiona metoda z przykładu do jQuery (
http://demo.seaside.st/javascript/jquery-ui/tabswidget ). Służy ona
do wyświetlania kilku wpisów jako listę z zakładkami. Jest tu
dodatkowa zmienna, która służy do zapamiętywania aktualnie
wybranej zakładki: selectedTab. Przy zapisie metody, gdy edytor
zapyta się co z nią zrobić, zaznaczam 'declare instance'. Zanim
przejdę do wyświetlania komentarzy dodam sobie dwa testowe blogi do
blogData. W Workspace wpisuję to i daję "Do it":
DPRoot
blogData: ( OrderedCollection
new). e
:= Dictionary
new. f
:= Dictionary
new. e
at: #url
put:
'http://...(cut)... .html' ;
at: #name
put: 'Od
kuchni' ;
at: #data
put: nil. f
at:
#url put:
'http://...(cut)... .html' ;
at: #name
put: 'Etui' ;
at: #data
put: nil. DPRoot
blogData add: e ;
add: f . |
Po
przejściu na localhost:8080/dp widać to:
Zwykła
lista bez jQuery. Oczywiście nic się nie wyświetli, bo skrypty
jQuery nie są dodane do konfiguracji. Dodać je można wchodząc w
Configure na dolnym pasku. W sekcji General - Libraries te biblioteki
trzeba ustawić w takiej kolejności:
Po
modyfikacji powinno być widać to:
a
po kliknięciu w przycisk, to:
Na
dzisiaj wystarczy. W kolejnej części będzie o tym jak dorobić
formularz z możliwością edycji obserwowanych blogów.
I
jeszcze film na koniec z podglądem jak to wszystko wygląda w
Seaside:
Wracając do dyskusji rozpoczętej w innym portalu, podaję link do ciekawego artykułu:
OdpowiedzUsuńhttp://www.chris-granger.com/2012/10/05/all-ideas-are-old-ideas/
Przypomniał mi się jeszcze jeden edytor Acme -- napisany przez jednego z twórców języka Go.
http://acme.cat-v.org/
http://research.swtch.com/acme
Używał go również Dennis Ritchie -- twórca języka C.
Pozdrawiam
Edytor, który współgra z kodem jest nie do przecenienia, bo umożliwia szybkie jego testowanie. Light Table posiada Instarepl, gdzie można testować całe bloki kodu nie tylko jedną funkcję na raz. Bardzo to ułatwia pracę z zagadkami, które rozwiązuje się na stronie 4clojure.com.
UsuńW Light Table podoba mi się także instalacja pomysłu, który porzuca edytowanie kodu na całych plikach ma rzecz nawigacji po pojedynczych funkcjach jak w Smalltalk.
Co do testowania całych bloków kodu ostatnio odkryłem nakładkę na IDLE -- Pythonowy shell + edytor kodu -- o nazwie IdleX (http://idlex.sourceforge.net). Zastosowano tam fragmenty kodu "Subcode", oddzielone komentarzami jak na screenie:
Usuńhttp://idlex.sourceforge.net/static/idlex1.png
Pozwala to uruchomić dowolny subcode, a nawet więcej. Widoczne pod menu podstawowe operatory mogą być pomocne przy testowaniu dowolnego fragmentu kodu. Wystarczy postawić kursor w miejscu gdzie znajduje się zmienna, która ma być np. zwiększana o 1 lub mnożona przez 1.1 i klikać wybrany operator, a wskazany fragment kodu będzie kolejno wykonywany.
Testowanie bloków kodu jest też od dawna w IPython (http://ipython.org/). Z resztą nie tylko testowanie. Jest tam wiele innych magicznych funkcji jak %history, %save, %pastebin, które mogą działać na fragmentach kodu, który został wprowadzony, np.
In [8]: %pastebin 6-7
Out[8]: 'https://gist.github.com/3119227'
Krótka prezentacja:
https://docs.google.com/a/tynecki.pl/viewer?url=https://speakerd.s3.amazonaws.com/presentations/50055cd882ba61000204cacf/IPython.pdf
Ciekawostka: IPython właśnie dostał $1.15M dofinansowania, więc można się spodziewać, że to jeszcze bardziej przyspieszy rozwój.
Faktycznie, ten IPython to bardzo ciekawa bestia. Ci ludzie są na dobrej drodze do Smalltalka, tylko jeszcze trochę tam za dużo pisania w konsoli. Musze się temu przyjrzeć trochę bliżej.
UsuńOgólnie w Pythonie przydałby się browser obiektów taki jak w Smalltalk. Wtedy łatwiej byłoby się poruszać po obiektach i je modyfikować. Tego w Python brakuje. Bardzo utrudnia to jego naukę i wymaga nauki wielu standardowych funkcji na pamięć lub ciągłej pracy z dokumentacją. W Smalltalku po prostu mogę sobie podejrzeć jak coś działa i z czego się bierze bezpośrednio. Z tego co widzę, w IP jest jakiś trik, który próbuje wyciągnąć kod źródłowy funkcji, ale coś dużo przy tym pisania. Nie lubię języków, które zmuszają ludzi do klepania, bo robią z mich maszynistów, jednak dobrze widzieć krok w dobrym kierunku.