sobota, 30 kwietnia 2016

JourneyPlanner #6.2 - Refaktoryzacja wyników wyszukiwania cz.2

Jest to druga część wpisu dotyczącego refaktoryzacji wyświetlania mapy i związanego z nią wyszukiwania. Dzisiaj opiszę jak zmodyfikowany został moduł Home. Bez wahania mogę powiedzieć, że zostaną wypunktowane również kolejne przeciwności losu jakie pojawiły się w wyniku tych zmian. Ale o tym na samym końcu. 


W ramach przypomnienia zamieszczę aktualną strukturę katalogów i plików wspomnianego modułu. Była ona zamieszczona pod koniec poprzedniej części:
│   config.ts
│   home.scss
│   index.ts
│
├───controllers
│       map.ts
│       searchResults.ts
│
└───templates
        index.html
        search-results.html

Pliki główne


Pliki z katalogu głównego nie zmieniły się znacząco. Plik index.ts przeszedł dość logiczne zmiany, został dodany nowy kontroler. Tak wygląda plik na obecną chwilę: 
Nie udostępniam listingu, ponieważ szkoda powierzchni strony na tak mało znaczące zmiany.
W pliku config.ts zmodyfikowałem stany, wygląda on tak:


Jak widać w linii nr 3 stan home jest podstanem zakładek, dzięki czemu nie jest konieczne ich pokazywanie w innych stanach nie związanych z zakładkami. Drugi stan i zarazem jego kontroler odpowiada za ukazywanie wyników wyszukiwania.

Szablony


W plikach szablonów również nie ma zbyt wielkiej filozofii. Założenie było takie aby podczas przebywania w akcji odpowiedzialnej za wyświetlanie mapy, w nagłówku widniała ikona lupy. Po kliknięciu w nią zostajemy przekierowani do stanu umozliwiającego wyszukiwanie za pomocą pola tekstowego umieszczonego w nagłówku. A więc mamy tutaj dwa stany: mapa oraz wyniki wyszukiwania. Jak widać ma to odzwierciedlenie w naszych plikach szablonów. W pliku index.html został usunięty znacznik odpowiedzialy za tytuł nagłówka, natomiast został dodany przycisk. Zaś wyniki wyszukiwania są całkowicie nowym szablonem. W nagłówku posiadają znane nam już pole tekstowe. Dodane zostało zdarzenie onkeyup aby wywoływać podpowiadanie podczas wpisywania fraz. Zaś w treści widnieje lista wyników pobierana z kontrolera.


Kontrolery


Zacznijmy od znanego nam już z poprzednich wpisów kontrolera HomeController


Jak widać bardzo zubożał od ostatniego razu. I słusznie! W końcu to kontroler odpowiadający za wyświetlanie mapy i niczego więcej. Wyświetla mapę i wywołuje wyszukiwanie. W przyszłości będzie trzeba dodać jedynie zdarzenie centrujące mapę w danym miejscu na podstawie wyników wyszukiwania (tak jak było to robione ostatnio). Postaram się to zrobić zdecydowanie lepiej.

Kolejny jest kontroler , który załatwia bardzo dużo rzeczy i załatwi jeszcze więcej.


loadBounds()

Po uruchomieniu kontrolera wywoływana jest akcja loadBounds(), która ma za zadanie na podstawie naszej lokalizacji ustalić granice wyszukiwania. Jest to o tyle istotne, że podczas autopodpowiadania wyszukiwarka znajduje miejsca bliższe naszym okolicom. Co za tym idzie bardziej prawdopodobne jest, że mieszkając na śląsku i wpisując "Ka" w wyszukiwarkę chodzi nam o Katowice, a nie o Kair. Wspomniania metoda tworzy okrąg o średnicy zadanej dokładnością, który ogranicza przeszukiwany obszar.

search()

Kolejną metodą jest search() wywoływany w widoku zdarzeniem keyup, o którym już wspominałem. Głowna logika działania została zaczerpnięta z przykładów zawartych w dokumentacji.
Sprawdzamy czy pole tekstowe nie jest puste, a następnie jego wartość przekazujemy do usługi wyszukiwania, z granicami uzyskanymi w metodzie loadBounds(). Uzyskane w ten sposób wyniki umieszczamy w kolekcji przetwarzanej w widoku na pozycje listy.
Ostatnia linijka
ctrl.$scope.$apply();
jest bardzo diabelnie ważna! Informuje ona framework, że nastąpiła zmiana w wartościach przestrzeni kontrolera i należy odświeżyć widoki. Bez tego mogą pojawić się problemy w odświeżaniu pozycji listy.



Końcowy rezultat bardzo mnie zadowala. Aplikacja zaczyna przypominać interfejs, który może być śmiało używany na urządzeniach mobilnych:


Przeciwności losu

Został czas na podsumowanie. Wciąż nie jestem zadowolony z wyglądu kontrolerów i łamania zasady DRY. Prawdopodobnie w przyszłości powstanie więcej usług zarządzających lokalizacją aby nie powielać kodu w przypadku np. generowania granic wyszukiwania.
W następnym poście skupię się na tym aby w wyświetlaną listę podpowiedzi tchnąć życie. I zrobić coś co już raz wykonałem czyli nanoszenie wyników na mapę. Gdy to zostanie wykonane zabiorę się za punkt trzeci priorytetu MUST czyli pogoda dla danej lokalizacji.

czwartek, 28 kwietnia 2016

JourneyPlanner #6.1 - Refaktoryzacja wyników wyszukiwania cz.1

Zgodnie z obietnicami czas na małe przemieszanie i poprawianie dotychczasowych osiągnieć w dziedzinie tworzenia mapy. Na pierwszy ogień pójdą wyniki wyszukiwania i ich prezentacja.


Reorganizacja #1


Rozpocząłem od reorganizacji widoku zakładek i paska wyszukiwania. Na zakładki aplikacji mam inny pomysł i finalnie prawdopodobnie znikną. Póki co będą użyte jako osobny moduł. Natomiast górna belka będzie zawierać w widoku mapy tylko lupę, po której kliknięciu zostaniemy przekierowani do podstrony z polem tekstowym, a wyniki podpowiedzi będą się wyświetlać w formie wygodnej listy na całym ekranie. A więc do dzieła!

Przeniesienie zakładek do osobnego modułu


Postanowiłem przenieść zakładki do osobnego modułu, ponieważ nie chcę mieć ich widocznych na każdej podstronie. Podobnie będzie teraz z paskiem wyszukiwania w nagłówku. Będzie on wstrzykiwany tylko dla konkretnych widoków. Więc pierwszy ktok to usunięcie zakładkek oraz nagłówka z wyszukiwaniem z pliku ./lib/app/index.html. Następnie utworzenie nowego modułu tabs:


i wprowadzenie go do głównego kontrolera:


W tym momencie usunąłem również zawartość metody onEnter z AppController oraz całą metodę geolocate()
Kolejną czynnością była zmiana nazw stanów stron występujących jako zakładki. Nazwa stanu została zmieniona z "app.home" na "app.tabs.home". Wygląd interfejsu nie zmienił się ani trochę, nawet jest uboższy w nagłówek z polem wyszukiwania.

Chcemy szukajkę!


Ten etap zajął więcej czasu niż mogłem się spodziewać. Po pierwsze w module Home zmieniła się organizacja struktury plików. Doszedł drugi kontroler oraz widok.
Struktura prezentuje się następująco:
│   config.ts
│   home.scss
│   index.ts
│
├───controllers
│       map.ts
│       searchResults.ts
│
└───templates
        index.html
        search-results.html
Wydzieliłem osobny katalog na kontrolery oraz na szablony. Uznałem, że nie warto robić kolejnego modułu, zajmującego się wyszukiwaniem skoro jest on związany bezpośrednio z mapą czyli modułem Home.
Szczegółowym opisem zmian jakie tu wprowadziłem podzielę się w następnym poście, ponieważ jest to dość obszerny temat. Nie chciałbym zaczynać kolejnej części opisu aby przerwać ją w połowie. To co zostało zaprezentowane powyżej jest zamkniętą całością.
Można powiedzieć, że mały cel refaktoryzacji został osiągnięty. Zakłądki zostały wydzielone do osobnego modułu co zwiększa elastyczność rozwiązania. Zapraszam was do oczekiwania na część drugą, w której zostanie opisane jak wydzieliłem wyszukiwarkę wraz z podpowiedziami do osobnego kontrolera i widoku bez zakładek.

Jeśli macie jakiekolwiek sugestie co do formy prezentowanych tutaj treści zachęcam do dzielenia się krytyką w komentarzach.

wtorek, 26 kwietnia 2016

JourneyPlanner #5 - Nanoszenie wyników wyszukiwania na mapę

To drugi wpis dotyczący wyszukiwania miejsc za pomocą API Google Maps. Dzisiaj opiszę w jaki sposób wyniki otrzymywane w podpowiedziach wyszukiwarki przeniosłem na konkretną lokalizację na mapie. Nie jest to działanie idealne ale spełnia poniekąd swoją rolę. Na końcu opiszę problemy jakie obecnie występują, które postaram się poprawić podczas wykonywania refaktoryzacji. Jest ostatnim ważnym etapem, ponieważ wyszukiwarka będzie funkcjonalnością dającą dostęp do reszty funkcji aplikacji. Z czego wynika, że wyszukiwarka wraz z mapą jest najważniejszą częścią programu. 


Moment przenoszenia wskazań mapy do wyszukiwanej lokalizacji był dla mnie najtrudniejszy. Spędziłem nad uzyskaniem zadowalającego działającego rozwiązania 3 długie wieczory. Mając pole tekstowe, do którego ładowały się lokalizacje z automatycznego podpowiadania, zostało tylko tę lokalizację przechwycić. Proste, prawda? Nic bardziej mylnego. Od początku wiedziałem, że potrzebuję Usługi (ang. Service) udostępniającej dane w różnych kontrolerach. Powód jest prosty: wprowadzanie i przechwytywanie danych z pola tekstowego odbywa się w innym kontrolerze (AppController) aniżeli wyświetlanie mapy (HomeController).

Usługa

Wygląda ona następująco i umieściłem ją w module app:


Myślę, że zawartości tej klasy nie trzeba nikomu tłumaczyć. Publiczna właściwość i setter (przydałby się getter?).
I w tym momencie zaczynają się schody. Mimo że będzie to wyglądać dziecinnie proste, bez większej wiedzy na ten temat, dojście do rozwiązania problemów zajęło mi wiele czasu. W pliku ./lib/app/index.ts wstrzyknąłem głównemu kontrolerowi moją usługę:


A następnie jej w tym kontrolerze użyłem:


W ten zgrabny sposób w konstruktorze wstrzykujemy jako parametr usługę. Następnie przekazujemy ją do głównej przestrzeni danych, która jest dostępna dla każdego kontrolera aplikacji, a nawet widoków. W metodzie onEnter() dodałem eventListener reagujący na wybranie lokalizacji z podpowiadanych w wyszukiwarce miejsc. Metoda reagująca na te zdarzenie pobiera dane o pozycji wyszukanego miejsca i przekazuje je do usługi. Proste prawda? Aż mi głupio, że zajęło mi to tyle czasu.

W kolejnym kroku dokonałem niezbędnych modyfikacji w kontrolerze modułu odpowiedzialnego za wyświetlanie mapy, czyli w HomeController:


Jak widać nie musiałem tutaj wstrzykiwać usługi. Jest ona dostępna z poziomu wspólnej przestrzeni danych $scope. Nie jestem pewien co do poprawności takiej praktyki ale póki co działa bez zarzutów.
W metodzie onEnter() natomiast dodałem nasłuchiwanie zdarzenia kliknięcia w button lupy koło pola wyszukiwania. Naciśnięcie tego przycisku ma za zadanie zmianę położenia centrum wyświetlanego na mapie. Jest to bardzo proste, pobieram pozycję z usługi i przekazuję do metody panTo centrujących mapę.

Wyszukiwanie!

Jak widać nie było to takie trudne. Po prostu kilka niewielkich zmian i gotowe :) Efekt końcowy prezentuje się następująco:

Problemy

Niedoskonałości i mankamenty zaproponowanego rozwiązania to głównie:
  • Brzydki kod i nieciekawe rozwiązania 
  • Pole tekstowe w nagłówku działa tylko na jednej z zakładek 
  • Pole tekstowe nie zapamiętuje wpisanej wartości po przejściu do innej zakładki 
  • Wyszukiwarka jest wyświetlana w niecekawy sposób 
  • Wyniki autopodpowiedzi są prezentowane w sposób niedopuszczalny dla aplikacji mobilnej

Wszystkie te i inne problemy postaram się rozwiązać w następnych wpisach dotyczących wyszukiwania.

niedziela, 24 kwietnia 2016

JourneyPlanner #4 - Podpowiadanie wyszukiwanych fraz

W dzisiejszym poście pokażę, jak zacząłem realizować punkt MUST 2 (kto nie wie o czym mowa zapraszam do wpisu JourneyPlanner #2 - Moskwa :-)), czyli wyszukiwanie miejsc na mapie Google Maps. Jest to pierwszy wpis z tego cyklu. Na razie stworzyłem podpowiadanie wyszukiwania fraz w najprostszy możliwy sposób tak aby działało i to nie idealnie. W drugiej części mam w planach nanoszenie wyników wyszukiwania na mapę, a następnie refaktoryzację kodu i sposobu prezentowania tego w szablonie.


Wyszukiwarka

Pierwszym elementem, od którego zacząłem była zmiana definicji pobierania biblioteki Google Maps oraz dodanie w nagłówkach aplikacji formularza wyszukiwania. Zostało to wykonane w ten sposób w plikach ./lib/app/index.html i ./lib/index.html

Jak widać jest to grupa przycisków umieszczona po prawej stronie nagłówka, pozwoliłem sobie dodać do inputa oraz przycisku identyfikatory, które posłużą mi w najbliższym czasie do obsługi formularza.

Kontroler

Kolejną czynnością jaką wykonałem było dodanie kontrolera głównego aplikacji. Dodałem kontroler, który wcześniej w ogóle nie istaniał. Potrzebny jest mi (a przynajmniej w tym momencie tak uważam) do obsługi wstawionego wcześniej formularza. Jako, że chcę aby wpisując dane do pola tekstowego pojawiały się podpowiedzi zgodnie z tym przykładem stworzyłem następujący kontroler:

Jak widać w samym kontrolerze, widnieje wspomniany przykład przełożony na działanie w klasie. Większość rzeczy została zachowana w identyczny sposób. Warto zwrócić uwagę na linię 16 gdzie zamieszczony jest identyfikator pola tekstowego z nagłówka strony. Do pliku index.ts została dodana definicja kontrolera oraz przypisanie go do modułu głównego, a w konfiguracji załączony został kontroler tak aby widok miał dostęp do jego zawartości. Aby mogło to zadziałać należy (zgodnie z przykładem) do pola tekstowego dodać zdarzenie focus. W przypadku frameworka AngularJs wykonałem to w następujący sposób:

W tym momencie po uruchomieniu aplikacji powinniśmy uzyskać następujący efekt podpowiedzi do wpisywanej frazy:

sobota, 16 kwietnia 2016

JourneyPlanner #3 - Pierwsza Mapa

Zgodnie z ustaleniami z poprzedniego wpisu będę stosował się do listy priorytetów. Będę wykonywał wszystko zgodnie z kolejnością i bez wdawania się w szeczegóły estetyczne. Najważniejszy jest cel, upiększanie wszystkiego zostanie zostawione na koniec. Oczywiście nie można generalizować. Mimo wszystko wciąż ważna będzie estetyka kodu i jego możliwości późniejszego utrzymania i modyfikacji.

Geolokalizacja

Posiadając utworzone zakładki na dole ekranu: 

 Mogę przejść do umieszczenia w głównej części aplikacji mapy googla. Do tego celu posłuzy nam plugin Cordovy dla geolokalizacji.
> cordova platform add cordova-plugin-geolocation --save
Szablonowa aplikacja narzuca mi korzystanie z TypeScript, a ja nie chcę tego za bardzo zmieniać. W kontrolerach napisanych jako klasy przyjmowane wartości są zawsze interfejsami. Są to tak zwane definicje TypeScript. Do każdego pluginu potrzebuję taką definicję. Oficjalne wydanie nie zawiera nic dla pluginu geolokalizacji, na szczęście znalazłem nieoficjalny interfejs (może kiedyś zrozumiem jak napisać go samemu). Załączyłem go w odpowiednim miejscu i zastosowałem w kontrolerze w następujący sposób:
   
 I do tego sprowadziło się załączanie pluginu geolokalizacji z mojej aplikacji.

Mapa

Przyszedł czas na mapę. Głównym punktem startowym było załączenie do do ładowanych skryptów API Google Maps a w kontrolerze modułu Home dodanie DIVa zawierającego mapę: 
W tym momencie byłem na dobrej drodze żeby wszystko ukończyć. Do wykonania wyświetlenia mapy wystarczyło napisać coś w kontrolerze, a dokładnie w metodzie onEnter() wywoływanej podczas załadowania podstrony: 

Opisując po kolei: Na początku definiujemy DIV z szablonu, do którego zostanie załadowana mapa. Następnie przy pomocy interfejsu pobieramy pozycję. Następnie pierwszym parametrze metody then() akcje wykonywane po pomyślnym zakończeniu pobrania pozycji, w drugim akcję błędu. Reszta kodu to już standardowe wykorzystanie API Google Maps z użyciem pobranych danych na temat pozycji użytkownika. Końcowa rzecz jaką musiałem wykonać to dodać style CSS dla kontenera mapy. W obecnym stanie będzie on zwinięty, a mapa niewidoczna. W tym celu załączyłem plik SASS:

Aplikacja

W taki oto sposób powstała aplikacja z mapą pokazującą naszą pozycję. 



 Pierwszy punkt priorytetów MUST wykonany!

środa, 13 kwietnia 2016

JourneyPlanner #2 - Moskwa :-)

Poprzedni post obejmował wprowadzenie do przykładowej aplikacji Ionic zarządzanej i kompilowanej przy pomocy Webpacka. Został również przeprowadzony niewielki wstęp odnośnie wyglądu aplikacji. Dzisiaj opiszę ile potrafi zmienić się w przeciągu trzech dni.


Od opublikowania poprzedniego postu minęły trzy dni. Od początku jego powstawania około tydzień. Muszę przyznać, że mam problemy z zajmowaniem się programowaniem i jego nauką oraz prowadzeniem bloga. Zawsze na jedno albo drugie nie poświęcę ilości czasu jakiej bym chciał.
W tak zwanym między czasie w mojej głowie dojrzewało wiele pomysłów na przebudowanie wyglądu i interfejsu programu. 
Ale nie będę tego robił w tej chwili! 
I o tym będzie ten post.
Te ostatnie kilka dni zmusiły mnie do refleksji na temat mojego podejścia do wytwarzania mojej aplikacji.

Podstawowe cele

Niedawno spotkałem się z metodą wytwarzania oprogramowania nazwaną wdzięcznie MoSCoW

O co chodzi w tej metodzie?

Chodzi o podział funkcjonalności względem pewnych priorytetów. Nazwa nie wzięła się z przypadku, a jest skrótem od nazwy każdego z priorytetów: Must (have), Should (have), Could (have), Won't (have). Znaczenia każdego z priorytetów nie będę tutaj przytaczał. W internecie na pewno znajdziecie wiele wyjaśnień tej metody i argumentów za, jak i przeciw jej stosowaniu. Pojawia się wiele słów krytyki na temat racjonalnej możliwości podzielenia zadań na takie priorytety. Szczególne kontrowersje budzi ostatni człon skrótu.
Jednak śmiem sądzić, że dla tak niewielkiej i w zasadzie słabo określonej formalnie aplikacji jak moja może się ta metoda dość dobrze sprawdzić. Pozwoli mi się skupić na głównych celach osiągania pewnych rezultatów działania. A nie tylko żmudnego dłubania w kodzie.

Odnosząc się do ostatniego zdania na temat ostatnich kilku dni. Zajmijmy się głównymi efektami. Nie dopracowywaniem mało widocznych szczegółów.

Must


  • Wyświetlanie mapy, po której można nawigować.
  • Wyszukiwarka miejsc na mapie.
  • Pogoda danego obszaru.

Should


  • Atrakcje turystyczne.
  • Obiekty takie jak kawiarnie, toalety, restauracje, kina, muzea, sklepy i tym podobne.
  • Wydarzenia publiczne (prawdopodobnie zaciągane z Facebooka).

Could


  • Tworzenie tras pomiędzy wyznaczonymi miejscami.
  • Dodawanie przerw w trasie wraz z czasem takiej przerwy.

Won't


  • Transport publiczny.
  • Dokładny plan godzinowy naniesiony na trasę.



W następnym poście postaram się pokazać pierwszą mapę.
A wy spotkaliście się już z metodą MoSCoW? Co o niej myślicie. Gdzie można ją zastosować, a gdzie wolelibyście jej w ogóle nie stosować? I czy w ogóle wartą ją stosować? Jeśli ktokolwiek czyta te posty zapraszam do dyskusji :)

niedziela, 10 kwietnia 2016

JourneyPlanner #1 - koncepcja szablonu

Przyszedł czas na pracę nad tym do czego przygotowywałem się tyle czasu. Mianowicie aplikacja JourneyPlanner pozwalająca zaplanować weekend lub całe wakacje.


Koncepcja

Wstępna koncepcja wyglądu aplikacji prezentuje się następująco

Wiem że artysta ze mnie nie będzie ale taka grafika potrafi zobrazować myśli. Dzięki czemu wiadomo na czym się skupić podczas tworzenia interfejsu.

Póki co skupiam się na wykonaniu górnej i dolnej belki z przyciskami.

Usuwanie

Całą pracę zacząłem od wyprucia przykładowej aplikacji opisanej w poprzednim poście z większości funkcji.
Usunąłem moduły network, camera, battery i loading.
Odpowiednio modyfikuję również plik ./lib/app/index.ts pozbawiając go odwołań do usuniętych modułów.
Z modułu home w kontrolerze usunąłem funkcję showLoading() i przycisk z szablonu.
W szablonie modułu menu zostawiłem tylko odwołanie do modułu home.

Po skompilowaniu aplikacja wygląda tak:


Dodawanie

Skoro odjąłem co nieco, powinienem teraz coś dodać. Na pewno przyda się dolna belka z zakładkami. Na razie nie będę implementował przycisku w górnej belce bo jeszcze nie wiem co miałby robić.

Dolne zakładki dodałem przed tagiem zamykającym </ion-side-menu-content> w ten sposób:
<ion-footer-bar>
  <ion-tabs class="tabs-positive tabs-icon-only">
 <ion-tab title="Map" icon="ion-map"></ion-tab>
 <ion-tab title="Places" icon="ion-location"></ion-tab>
 <ion-tab title="Routes" icon="ion-android-walk"></ion-tab>
  </ion-tabs>
</ion-footer-bar>

I otrzymałem taki widok

W następnym poście będę podłączał pod zakładki akcje i mam nadzieję uzyskać już pierwszy podgląd mapy! Trzymajcie się :)

Webpack #3 - Współpraca z Ionic

Dzisiaj przedstawię gotowe repozytorium dostarczające szablon aplikacji wykonanej we frameworku Ionic z wykorzystaniem Webpacka. Omówię również jego budowę i działanie niektórych jej elementów.


Wstęp


Ogromne podziękowania dla pana Julien'a Renaux'a za stworzenie szablonu aplikacji omawianego w tym poście. Szablon ten posłuży mi jako baza do stworzenia mojej aplikacji. Niniejszym ogłaszam również, że w końcu bo tych kilku tygodniach w moim GitHubowym repozytorium zaczyna się coś dziać. Wszelkie zmiany będą prowadzone na osobnych roboczych gałęziach aby nie śmiecić w gałęzi master. 

Modyfikacja


Po kilku testach z tym szablonem doszedłem do wniosku, że dobrze będzie wprowadzić kilka zmian. W pliku package.json w sekcji cordovaPlugins dodałem pluginy

  • cordova-plugin-browsersync - umożliwiający podgląd aplikacji na żywo w przeglądarce
  • cordova-plugin-geolocation - dostęp do geolokalizacji
Instalacja browser sync może dziwić. Przecież Ionic ma świetne narzędzie wywoływane poprzez
ionic serve
Tak to prawda ale w momencie tworzenia aplikacji poprzez moduły CommonJS i kompilację przez Webpack przestaje spełniać swoje zadanie. W tym momencie kombinacja jest następująca: Uruchamiam polecenie
npm run watch
uruchamiające kompilację "na żywo" tzn po każdej zmianie zawartość folderu ./www się zmienia oraz następnie
cordova run browser --live-reload
uruchamiający przeglądarkę z podglądem aplikacji.
W pliku package.json z sekcji "cordovaPlatforms" zostawiłem tylko android i browser.

Działanie


Cała logika aplikacji znajduje się w katalogu ./lib gdzie podzielona jest na moduły zgodnie z CommonJs.

Plikiem wejściowym modułów jest ./lib/index.ts. Ktoś może pomyśleć, że w rozszerzeniu znajduje się literówka. Jest to rozszerzenie plików napisanych w języku TypeScript, który jest językiem upraszczającym programowanie i w pełni kompiluje się do JavaScript.
Plik ten załącza framework Ionic i przekazuje pałeczkę do modułu "app", który jest  głównym modułem całej aplikacji.
Opiszę co robi każdy z plików tego modułu:

index.ts

Importuje kolejne moduły. Jest plikiem zbiorczym informującym Angulara o lokalizacji każdego z submodułów i dodający jego funkcjonalności.

router.config.ts

Zajmuje się inicjalizacją początkowego stanu aplikacji i jej szablonu

run.ts

Nie jestem do końca pewien ale podejrzewam, że dostarcza informacje o działaniu aplikacji do konsoli deweloperskiej urządzenia.

Menu

Kolejnym ważnym modułem jest "menu", które za pomocą linków i atrybutu ui-sref pozwala wczytywać inne moduły do głównej przestrzeni szablonu. Jako wartość tego atrybutu podawany jest identyfikator stanu aplikacji. Każdy moduł, który może zostać w ten sposób wyświetlony posiada plik config.ts, w którym do danej nazwy stanu są przypisane informacje o kontrolerze i szablonie.

Po zagłębieniu się w budowę tej z pozoru skomplikowanej struktury wszystko staje się jasne i klarowne. Aplikacja jest łatwa w modyfikacji oraz utrzymaniu. W Następnym poście postaram się usunąć przykładową aplikacje i napisać coś swojego, w drodze do utworzenia JourneyPlanner

niedziela, 3 kwietnia 2016

Webpack #2 - praktyczne przykłady

Znając zarys ogólnych funkcjonalności Webpacka postaram się pokazać kilka przykładów jego zastosowania.


Nowy projekt


Rozpoczniemy od utworzenia katalogu w odpowiadającym nam miejscu
mkdir webpack-test
Inicjalizujemy manadżera pakietów
cd webpack-test
npm init
i przechodzimy przez wszystkie instrukcje, które zostaną wyświetlone.
Teraz jesteśmy gotowi aby pobrać bibliotekę:
npm install webpack --save-dev
W ten sposób zostanie ona pobrana, a jej definicja dodana od razu do pliku packages.json jako zależność deweloperska.

Tworzymy potrzebne pliki
mkdir lib www
touch webpack.config.js
touch lib/index.js
uruchamiamy edytor
atom ./
i wypełniamy plik ./webpack.config.js następującą treścią

var webpack = require('webpack'),
    path = require('path');

module.exports = {
    debug: true,
    entry: {
        main: './lib/index.js'
    },
    output: {
        path: path.join(__dirname, 'www'),
        filename: '[name].js'
    },
    module: {
        loaders: []
    }
};

Tak skonfigurowany projekt za plik wejściowy kompilacji postrzega ./lib/index.js, a jej wynik trafi do katalogu ./www
Kompilację przeprowadzamy przy użyciu następujących komend.
Debug mode:
webpack
Production mode (pliki zostaną zminifikowane)
webpack -p

Testy

Przetestujmy działanie za pomocą modułu-nakładki na akcje konsolowe.
W katalogu lib tworzymy plik consoleAdapter.js o następującej treści:
module.exports = {
  log: function(value)
  {
    console.log(value);
  }
};
Następnie plik index.js wypełniamy taką treścią
var consoleAdapter = require('./consoleAdapter.js');
consoleAdapter.log('Hello world!');
Uruchamiamy kompilację i sprawdzamy czy wszystko działa:
webpack
node ./www/main
W konsoli powinno ukazać się Hello world!


Użycie loaderów



ECMAScript 6

Instalujemy odpowiedni loader
npm install babel-loader --save-dev
Dodajemy go do konfiguracji webpacka
loaders: [{

   test: /\.es6.js$/,
   loader: "babel-loader"
   }
]
i zmieniamy rozszerzenie pliku console.js na .es6, a samą jego treść na następującą:
module.exports = {
  log: (value) => console.log(value)
};
kolejna kompilacja powinna przejść bez problemów, a rezultat zadziałać w ten sam sposób.


CSS i SASS

Do kompilacji plików CSS i SASS przydatne będą następujące loadery.

  • css-loader
  • autoprefixer-loader - dzięki niemu nie martwimy się o dodawanie prefixów dla konkretnych przeglądarek
  • sass-loader
loaders: [{
      test: /\.css$/,
      loader: "css-loader!autoprefixer-loader"
    }, {
      test: /\.scss$/,
      loader: "css-loader!sass-loader"
    } {
      test: /\.es6.js$/,
      loader: "babel-loader"
    }
]

HTML i obrazki

  • html-loader
  • file-loader
{
 test: /\.html$/,
 loader: "html-loader"
},{
 test: /\.(png|jpg|gif)$/,
 loader: "file-loader?name=img/img-[hash:6].[ext]"
},


Globalne moduły

Czasami potrzebujemy mieć dostęp do pewnych modułów w globalnej przestrzeni naszej aplikacji w tym celu używamy expose-loader.
W tym konkretnym przypadku zależy nam aby mieć globalny dostęp do lodash'a pod znakiem _.
{
  test: require.resolve("lodash"),
  loader: 'expose?_'
}

Podsumowanie

Jak widzimy kilkoma prostymi modułami i definicjami w pliku konfiguracyjnym jesteśmy w stanie wykonać na prawdę skomplikowane operacje. I nie potrzebujemy tutaj żadnego menadżera zadań. Jak najbardziej kompilację Webpacka można przy pomocy takiego uruchamiać.
Zachęcam do eksperymentowania z loaderami i testowania ich możliwości.

sobota, 2 kwietnia 2016

Webpack #1 - wprowadzenie

Najpopularniejszymi bibliotekami do budowania modularnej aplikacji w języku JavaScript są GulpJS oraz Browserify lub RequireJS. W tym poście postaram się przedstawić nowego gracza, z którym nie miałem wcześniej przyjemności. Poczytajcie o WebpackJS.


Co już wiemy


Każdy kto choć raz spotkał się z problemem zbudowania bardziej skomplikowanej aplikacji, podzielonej na moduły musiał korzystać z menadżera zadań Grunt/Gulp i biblioteki do tworzenia wyjściowej aplikacji z modułów pokroju Browserify/RequireJs. Są to złożone biblioteki składające się z dużej ilości mniejszych modułów. Nie będę się rozwodzić na tematy jak ich używać bo nie to jest przedmiotem tego posta. W tym momencie chciałbym abyście zapomnieli o wszystkim co wiecie na ten temat i przeczytali o Webpack'u.

Webpack - nowe podejście


Jest to biblioteka napisana w monolitycznym stylu. To znaczy, cała jej funkcjonalność jest wbudowana w rdzeń, który można rozszerzać pluginami i loaderami. Podejście takie eliminuje mnogość wewnętrznych zależności.
Do uruchomienia Webpacka nie jest też konieczny żaden menadżer zadań. Wszystko co potrzebne dostarcza sam Webpack.

Wspiera on kilka rodzajów implementacji modułów. Mianowicie z tych najważniejszych są to

CommonJS

var MyModule = require('./MyModule');

// export at module root
module.exports = function() { ... };

// alternatively, export individual functions
exports.hello = function() {...};

ES6

import MyModule from './MyModule.js';

// export at module root
export default function () { ... };

// or export as module function,
// you can have multiple of these per module
export function hello() {...};

Co ciekawe, biblioteka ta radzi sobie również z plikami CSS i wspiera @import. Podchodzi również w inteligentny sposób do kompilacji modułów. 
Przykładowo jeśli nasz plik CSS jest dość krótki zostanie on dodany do pliku HTML jako inline CSS. Natomiast jeśli jest dość duży zostanie zminifikowany, dodany do głównego pliku CSS i załączony do pliku HTML.

Pluginy i loadery


W tekście pojawiły się te dwa magiczne słowa. Są one bardzo ważne dla procesu kompilacji przy użyciu Webpacka. Tłumacząc skrótowo można je opisać jako kawałki kodu, wstrzykiwane podczas procesu kompilacji w celu wykonania konkretnych działań. 
Przykładowo aby dodać obsługę ES6 dodajemy loader babelJs.
Jeśli chcemy kompilować pliki SASS dodajemy loader SASSa.
I tak dalej. Każdy loader dodaje nam konkretną funkcjonalność w procesie kompilacji

Podsumowanie


Webpack to na prawdę ciekawe narzędzie. Pojedyncza biblioteka dostarcza wszystko to co dotychczas robiło kilkadziesiąt niezależnych bibliotek i pluginów. 
A w następnym poście postaram się skupić na samym użyciu Webpacka w projekcie (nie koniecznie Ionic'owym).