sobota, 7 maja 2016

JourneyPlanner #7 - Informacja pogodowa

To ostatni obowiązkowy post zdefiniowany przez regulamin. Ale nie ostatni w ogóle. Dzisiaj opiszę jak zrealizowałem dostęp do danych pogodowych. Jest to również ostatni punkt priorytetu MUST. Bardzo dobrze się składa, że wszystkie wymagania z najwyższym priorytetem udało zrealizować się w założonym regulaminowym czasie konkursu. 


Wyświetlanie informacji pogodowej z danego obszaru mapy było jednym z kluczowych funkcji niniejszej aplikacji. Moja wizja interfejsu jest taka, że oprócz pogody będzie jeszcze kilka innych kontrolek odpowiadających za pozostałe funkcje opisane we wspomnianym poście. Przełączając się pomiędzy nimi na zasadzie inputa typu RADIO, będziemy wyświetlać odpowiednie informacje ze wskazanego obszaru mapy. W obecnej formie funkcja pogody nie zadowala mnie (a jakże by inaczej) ale działa! Głównie chodzi (jak za każdym razem) o sposób prezentacji danych czyli wygląd GUI.

Interfejs


Rzeczą, od której zacząłem to sam przycisk uruchamiający funkcję informacji pogodowej. W tym celu w dobrze znanym już widoku mapy w górnej belce utworzyem button:


Już śpieszę z wyjaśnieniami. Samej budowy buttonu nie ma co opisywać. Natomiast kolejne atrybuty jak najbardziej.
  • ng-click - kliknięcie wywołuje metodę z kontrolera z parametrem weather (tutaj w przyszłości dobre byłyby jakieś stałe z ENUMa)
  • ng-class - dzięki temu mogę nadać przyciskowi klasę active co wpływa na jego wygląd. W tym przypadku klasa active jest nadana zawsze poza momentem naciśnięcia wybrania funkcji pogoda. Jaki to wygląd pokażę w screenie po opisaniu kontrolera.


Kontroler


Kontroler to ostatnie miejsce, w którym wprowadziłem zmiany.

Obsługa GUI



Do obsługi GUI wystarczy tylko tyle. Jak widać został dodany nowy atrybut kontrolera informujący o tym jaka operacja na mapie jest obecnie aktywna. Przyjmuje string lub null. Oraz metoda, za pomocą której ten atrybut ustawiamy. To wszystko doprowadza nas do następującego zachowania przycisku. Domyślnie jest on "przygaszony", a po kliknięciu w niego "zapala" się, co informuje nas o aktywowaniu funkcji pogodowej:

Pobieranie informacji pogodowej


Aby funkcja pogodowa zaczęła działać dodałem event click w obszarze mapy, zaraz po jej wywołaniu i metodę uruchamiającą aktywną funkcje programu:


Jak widać sprawdzam czy funkcja pogody jest aktywna, pobieram dane geograficzne zaznaczonego punktu i wykonuję zapytanie do OpenWeather API, które przy bezpłatnym planie daje dość duże możliwości, w postaci ograniczenia 60 zapytań na minutę. W dalszym kodzie przechwytuję odpowiedź, odpowiednio ją przetwarzam i dodaję marker w postaci ikony pogodowej na mapie. Niestety wadą tego rozwiązania jest konieczność dodawania dymków z pogodą, które w aplikacji mobilnej nie są najlepszym pomysłem. Prawdopodobnie w późniejszym etapie dodam wyświetlanie modala z dokładnymi informacjami. W tym momencie jest tylko ikonka, a reszta danych wyświetlana jest w konsoli:


Co dalej?


W dalszym ciągu będę starał się dodawać posty z postępu prac. Bardzo możliwe, że nie tak często jak dotychczas. Muszę przyznać, że jest to dla mnie dość dużym obciążeniem ale również daje mi pewną satysfakcję. Jedno co mogę zagwarantować, że aplikacja będzie rozwijać się dalej nawet jeśli nie uda mi się tych zmian regularnie opisywać.
Kolejne etapy to:
  • Usprawnienie funkcji pogodowej
  • Realizacja priorytetu SHOULD

środa, 4 maja 2016

JourneyPlanner #6.3 - Refaktoryzacja wyników wyszukiwania cz.3

Witam cię drogi czytelniku w trzecim i ostatnim wpisie traktującym o refaktoryzacji funkcji wyszukiwania. Zostaną opisane tutaj zmiany jakie wykonałem aby możliwe było naniesienie wybranych wyników wyszukiwania na mapę. A więc do dzieła!


Widok wyszukiwania

Ostatnim razem zatrzymałem się w momencie, w którym wyszukiwanie odbywa się w osobnym widoku, a jego wyniki prezentowane są pod postacią przyjaznej urządzeniom dotykowym listy. Lista ta nie ma podpiętej póki co żadnej akcji pozwalającej na jakiekolwiek działanie. Należałoby ją w takim razie dodać. Zrealizowałem to poprzez dodanie atrybutu ng-click na elemencie listy. Jako wartość zadałem metodę kontrolera, do której przekazuję wybrany element. A wszystko to oczywiście w pliku szablonu ./lib/home/templates/search-results.html


Posiadając już taką konstrukcję trzeba tę metodę utworzyć. Dylematem był tutaj dla mnie sposób rozwiązania. W poprzedniej wersji wyszukiwania do usługi przekazywałem już gotową lokalizację. Tutaj niestety obiekt, który metoda otrzymuje posiada tylko identfikator. Przekazuje się go do dedykowanej usługi API Google Maps PlacesService aby uzyskać szczegółowe informacje na temat miejsca np. jego lokalizację. Wymagało to zmiany podejścia ponieważ wspomniana usługa Google Maps przyjmuje również w konstruktorze obiekt samej mapy, niedostępny w przestrzeni kontrolera wyszukiwania. Zmodyfikowałem zatem moją usługę wyszukiwania tak aby przyjmowała tylko identyfikator miejsca zamiast jego lokalizacji:


A co za tym idzie w kontrolerze wyszukiwania przekazuję do niej tylko identyfikator i wykonuję przekierowanie na akcję zawierającą mapę.


Widok mapy

Kontroler odpowiadający za wyświetlanie mapy, nie zmienił się zbyt wiele. Jedynie przybyło mu objętości. Inicjalizacja mapy odbywa się tak samo jak dotychczas, po jej wywołaniu dodałem fragment kodu w warunku:


Następuje tutaj wywołanie centrowania mapy na elemencie zawartym w usłudze wyszukiwania (jeśli jest zadany) i dodanie markera. Kod jest żywcem wzięty z przykładu zawartego w dokumentacji API Google Maps i działa bardzo dobrze :)

Rezultat

Rezultat jest bardzo przyjemny, a jego efekty spełniają moje oczekiwania: 
Podsumowując, punkt drugi priorytetu MUST wykonany. Pozostał jeszcze jeden dotyczący nanoszenia na mapę informacji o pogodzie. Dobrze się składa ponieważ okres publikacji postów do konkursu kończy się dla mnie w tym tygodniu. Oczywiście termin ten można przekroczyć, a posty publikować w dalszym ciągu co mam zamiar uczynić. Dumnie muszę powiedzieć, że udawało mi się dotychczas publikować minimum dwa posty tygodniowo. Zgodnie z regulaminem konkursu aby mój blog był rozpatrywany, poza tym postem powinienem do 8 maja opublikować jeszcze jeden wpis. O czym będzie traktował już wspomniałem - punkt trzeci priorytetu MUST czyli "Informacje pogodowe dla wybranego obszaru".

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). 

niedziela, 27 marca 2016

Ionic i AngularJS - Architektura aplikacji

Po wstępnych testach możliwości frameworka, pierwszych niezbyt skomplikowanych aplikacjach czas na zabranie się za właściwy projekt od prawidłowej strony. Na pierwszy ogień pójdzie architektura aplikacji.


Już przy pierwszej aplikacji zaproponowanej w poradniku autorów Ionic'a zwróciłem uwagę na pewne problemy. Przede wszystkim na brak większej architektury takiej aplikacji. Wiadomo, że nie była to ani duża aplikacja ani aplikacja, która będzie miała jakiś cykl życia. Wszystko było ładowane w dwóch plikach: index.html i app.js . Nie trudno sobie wyobrazić jakie konsekwencje i problemy może nieść taki układ przy rozwijaniu większych programów. Będąc szczerym nigdy nie miałem przyjemności z AngularJS, natomiast przewinął mi się kiedyś ReactJS. Muszę przyznać, że obydwie te biblioteki niosą ze sobą zupełnie inne podejście do pisania aplikacji. ReactJS wpływa bezpośrednio na strukturę HTML i ją tworzy, natomiast w Angularze to struktura HTML korzysta z biblioteki. Działa to w zupełnie przeciwnych kierunkach. Przyjemnie pracowało mi się z React'em, zobaczymy jak sprawa będzie wyglądać w przypadku Angulara.

Podczas tworzenia tego posta posiłkowałem się następującymi wpisami ludzi zdecydowanie bardziej kompetentnych w tym temacie ode mnie, mianowicie:


Architektura

Tak, każdy z nas kocha to magiczne słowo. Słowo, które jest obszerne i bardzo pojemne. Z racji mnogości rzeczy, które może określać wyjaśnię co będzie określać w tym poście. 
Wyraz architektura w kontekście tego posta będzie oznaczać organizację struktury katalogów oraz plików w projekcie. Najpopularniejszym podziałem aplikacji w większości języków jest model MVC. Abstrahując od tego czym jest MVC, ponieważ zakładam, że większość jak nie wszyscy wiecie o co chodzi. Jeśli nie to pewnie jest wiele zgrabniejszych opisów niż ten który mógłbym sporządzić, a który nie jest tematem tego posta.
Taki model aplikacji wymusza podział naszych plików na trzy kategorie. Inaczej pisząc wykorzystujemy podział sort-by-type czyli rozdzielamy pliki według ich typu, ich przeznaczenia.
We wszystkich podlinkowanych artykułach występuje propozycja żeby zrobić krok dalej. Wykonać podział o wdzięcznej nazwie sort-by-feature. Znaczy to nie więcej, nie mniej, że nasz dotychczasowy podział przenosimy poziom niżej i stosujemy w katalogach przeznaczonych na konkretną funkcjonalność naszej aplikacji.


Jest kilka zalet takiej organizacji struktury projektu:
  • Liczba plików w katalogu jest ograniczona tylko do kilku, pojedynczych z każdego typu.
  • Nawigowanie i wyszukiwanie plików potrzebnych do zmian w danej funkcjonalności jest proste.
  • Pracujemy bezpośrednio z daną funkcjonalnością.
  • Wiemy co reprezentuje każdy katalog. Wszystko jest jasno i precyzyjnie nazwane.

Julien Renaux, autor pierwszego z podanych artykułów proponuje konkretne rozwiązania i narzędzia potrzebne do zorganizowania i wykonania aplikacji skonstruowanej w ten sposób.
Jego podstawowymi postulatami są:
  • Nie używaj Bowera.
  • Używaj npm.
  • Nie używaj Gulp'a.
  • Stosuj CommonJS - standard modularnego organizowania aplikacji
  • Używaj Webpacka - biblioteka budująca aplikację z modułów i zdefiniowanych zależności
O ile zrezygnowanie z Bowera na rzecz npm'a nie jest żadnym problemem, zrezygnowanie z Gulpa może być czymś ciekawym. Dotychczas chwaliłem sobie tą bibliotekę do tworzenia zróżnicowanych tasków zadań. Stosowanie CommonJS mamy już w miarę ogarnięte przez stosowanie reguły sort-by-feature. Natomiast Webpack jest dla mnie czymś zupełnie nowym i powoduje u mnie pewną ekscytację.
W zapoznaniu się z biblioteką pomocna będzie zapewne ich strona domowa ale również wprowadzający w temat post osoby, która o nim wspomniała. W następnym poście postaram się opisać trochę wszystko czego udało mi się na jej temat dowiedzieć.


sobota, 26 marca 2016

Szybka aplikacja dla brata

Dzisiaj techniczny post ale nie bezpośrednio o aplikacji wykonywanej na potrzeby konkursu, a która jeszcze nie powstała bo wciąż zbieram wiedzę. Dzisiaj napiszę o aplikacji, spersonalizowanej pod wymagania członka mojej rodziny.


Przy niedzielnym obiedzie pochwaliłem się, że rozpocząłem naukę tworzenia aplikacji na telefony. I od razu zostałem zasypany pomysłami takich aplikacji. Jeden pomysł mi się spodobał bo był w miarę konkretny.

Aplikacja do obliczania wagi materiału usypanego przez podajnik w żwirowni

Brzmi skomplikowanie? Opisze pokrótce kontekst. Mamy żwirownie, która wydobywa materiały w postaci żwiru i piasku. Często pojawia się potrzeba oszacowania wagi materiału zgromadzonego na hałdzie w kształcie stożka. Otrzymujemy tutaj trzy dane wejściowe: obwód hałdy, wysokość i rodzaj materiału.
Proste obliczenia. Aplikacja również. Wystarczy, że będzie składać się z dwóch pól do podania wartości numerycznej i listy do wyboru materiału.
Wiedza zdobyta podczas tworzenia aplikacji ToDo, jest wystarczająca aby wykonać tego typu prostą aplikację przy pomocy frameworka Ionic i AngularJs. Aplikacja została nazwana roboczo BingCalc. Bing – z angielskiego hałda, a Calc chyba nie muszę nikomu tłumaczyć.

Do dzieła

Pierwszym krokiem jest stworzenie projektu. W tym celu w katalogu z naszą aplikacją wywołujemy komendę

ionic start BingCalc blank

Tworzy to standardowy pusty projekt z nagłówkiem, który od razu został przeze mnie zmieniony.
Wywołujemy komendę

ionic serve


Uruchomi się przeglądarka z podglądem naszej aplikacji.

Dalsze budowanie wyglądu aplikacji to wklejanie kodów z przykładów użycia komponentów CSS na stronie Ionic'a.
Pierwszy komponent to Card. Umieszczamy kod przykładu w tagi <ion-content>

Następnie zamiast przykładowego tekstu wstawiamy formularz. Ja wybrałem ten konkretny przykład: http://ionicframework.com/docs/components/#forms-stacked-labels
<form>
  <div class="list">
 <label class="item item-input item-stacked-label">
   <span class="input-label">Obwod</span>
   <input type="number" min="5" value="5">
 </label>
 <label class="item item-input item-stacked-label">
   <span class="input-label">Wysokosc</span>
   <input type="number" min="5" value="5">
 </label>
 <label class="item item-input item-select item-stacked-label">
   <span class="input-label">Material</span>
   <select>
  <option value="">
    -- Wybierz --
  </option>
   </select>
 </label>
  </div>
  <div>
 <button type="submit" class="button button-block button-positive">Licz</button>
  </button>
</div>
</form>

I to w zasadzie jest kompletny szkielet naszej aplikacji. Teraz należałoby tchnąć w nią „życie”.
W tym celu używamy atrybutów udostępnianych przez framework AngularJS do obsługi formularzy.
Odpowiednio modyfikujemy tagi FORM, INPUT oraz SELECT tak aby wykonywały pewne akcje i przyjmowały konkretne wartości.
<form ng-submit="calculate(input)"> <!-- funkcja calculate() zostanie wywołana po zatwierdzeniu formularza -->

<input type="number" ng-model="input.ambit" min="5" value="5"> <!-- pole przyjmie wartość atrybutu input.ambit -->
<input type="number" ng-model="input.hight" min="5" value="5">

<!-- lista przyjmie wartość konkretnego atrybutu i wyświetli opcje zawarte w tablicy materials -->
<select ng-model="input.material">
<option ng-repeat="material in materials" value="{{material.code}}">
  {{material.name}}
</option>
</select>

I otrzymujemy taki wygląd aplikacji

Po takim zmodyfikowaniu formularza przystępujemy do stworzenia back-endu aplikacji.
Jednak wcześniej modyfikujemy tag BODY
<body ng-app="calc" ng-controller="MainCtrl">

Zmieniając identyfikator aplikacji oraz kontroler odpowiedzialny za dany obszar strony.
Następnie w pliku js/app.js modyfikujemy linię
angular.module('calc', ['ionic'])

oraz dodajemy pod nią nasz kontroler
.controller('MainCtrl', function($scope, $timeout) {})

W ciele funkcji kontrolera dodajemy zmienną przechowującą materiały wyświetlanie w wybieralnej liście
$scope.materials = [{
  code: "gravel",
  name: "Zwir"
}, {
  code: "sand",
  name: "Piasek"
}];
W tym momencie powinny się one pojawić na liście.

Kolejno wartości domyślne dla formularza
$scope.input = {
  ambit: 5,
  hight: 5,
  material: "gravel"
};

Obiekt zawierający dane na temat przelicznika metrów sześciennych na kilogramy danego materiału
$scope.units = {
  gravel: 1700,
  sand: 1520
};

Oraz funkcję przetwarzającą podane przez nas dane
$scope.calculate = function(input) {
  console.log(input);
  var r = input.ambit / (2 * Math.PI);
  var baseField = 2 * Math.PI * r * r;
  var bulk = (1 / 3) * baseField * input.hight;

  var result = bulk * $scope.units[input.material];
  result = result / 1000;
  result = result.toFixed(1);

  alert(result + "t");
};

Po tych zabiegach powinniśmy uzyskać działającą aplikację.


Aby stworzyć z niej plik .apk wywołujemy kolejno komendy

ionic platform add android
ionic build android –-release

Nasz plik .apk powinien znajdować się w katalogu platforms/android/output/build/

Efekt końcowy


Czas pracy: 2h
Mina brata: Bezcenna