
Hello! Not so long ago, in one of the
comments, I promised to write an introductory article for a quick acquaintance with the features of the
Ionic Framework (hereinafter IF). I try to keep my promises. To begin, I would like to post a list of links that I often use when developing with IF:
')
What are we going to write? A small application that will take weather data from
openweathermap.org for Moscow and display it. Development will be carried out in
Linux Mint for the target platform
Android . To compile under
iOS, you must have a laptop or a PC from
Apple . It is assumed that the reader is familiar with the JavaScript JavaScript and the basics of AngularJS (well, or quickly understands everything).
Introduction
IF is an open source SDK for developing hybrid mobile applications using the full power of HTML5, CSS3 and JavaScript, an excellent example of the symbiosis of
Cordova and
AngularJS . Applications created with it can be viewed at
showcase.ionicframework.com . The threshold of entry is low, but to create a serious application requires advanced knowledge of
AngularJS and the features of the work of
Cordova for the target platform. So let's install it soon!
Installation
This step is simple enough if you already have a
NodeJS . To do this, type in the console:
sudo npm i -g cordova ionic
Do you have
Android SDK installed and configured? If not, this
guide will help you. For Windows developers, there is
an introductory video on installation . There is also a
Vagrant build with all pre-installed software. We will assume that you didn’t have any problems (and if they did, then the
forum definitely has a description of how to solve similar problems, mainly installation and configuration of the
Android SDK , but there are also troubles with plug-ins. You can also look at
SO , there’s a lot of information on topic). It is time to create.
Project creation
Here, too, everything is quite simple. IF offers a choice of 3 templates for the application -
blank (blank),
tabs (based on tabs) and
sidemenu (with sidebar). Intuitive enough, isn’t it? All you need is to type in the console:
ionic start MSKWeather tabs
Thus, we say IF to create a project with the name
MSKWeather and the
tabs template. Do not forget to go to your project folder and execute this command there. After creation, you can go to the folder with the template:
cd MSKWeather
I deliberately use a non-empty project in order to save my and your time and show the capabilities of the framework, because it is much easier and faster to throw out the unnecessary and change it for yourself than to program from scratch. For a quick start, this approach will come down. Nevertheless, I hope that by the end of the article you will have an understanding of the architecture of the framework and you will easily begin with a
blank template, and then thank me for my laziness. So, let's analyze the structure of the project IF.
Project structure
At the root of the project there is a
config.xml file, which is responsible for the basic settings of our application. We will return to it (as well as to other files) a little later, but for now we will go to the
www folder (by the way, I use Sublime and have already opened the project folder in it) and we will see the preparation of our application. The presence of
index.html is striking - this is the link for all components of the future application (suddenly!). Consider what the framework provides out of the box:
<meta charset="utf-8"> <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
Cool encoding and prohibiting user scaling of the application. Already connected styles and scripts, as well as binding
AngularJS in the
body tag, but most importantly:
<ion-nav-bar class="bar-stable"> <ion-nav-back-button> </ion-nav-back-button> </ion-nav-bar> <ion-nav-view></ion-nav-view>
There is a blank for the tab bar with the “Back” button, and the presentation in which our templates will be rendered (rendered). Let's now take a look at the
templates folder. We are
interested in the
tabs.html file in which there is a list of tabs for opening inside the application. Let's remove 2 unnecessary tabs:
<ion-tab title="Chats" icon-off="ion-ios-chatboxes-outline" icon-on="ion-ios-chatboxes" href="#/tab/chats"> <ion-nav-view name="tab-chats"></ion-nav-view> </ion-tab> <ion-tab title="Account" icon-off="ion-ios-gear-outline" icon-on="ion-ios-gear" href="#/tab/account"> <ion-nav-view name="tab-account"></ion-nav-view> </ion-tab>
And in the remaining replace the icon, and as a result, the contents of the file you will have the following:
<ion-tabs class="tabs-icon-top tabs-color-active-positive"> <ion-tab title="Status" icon-off="ion-cloud" icon-on="ion-cloud" href="#/tab/dash"> <ion-nav-view name="tab-dash"></ion-nav-view> </ion-tab> </ion-tabs>
It's time to see what we have done, for this, in the root folder of the project, run the command:
ionic serve
If everything is successful, you will be transferred to the browser at the address
http: // localhost: 8100 / # / tab / dash where the live-reload web version of the future application will start.
As you can see, the changes picked up and we still have one tab with a cloud icon. Now we need to delete
tab-account.html and
tab-chats.html (because they are no longer needed), and also rename
chat-detail.html to
city-detail.html , and
tab-dash.html to
tab-city.html respectively . These will be our templates for selecting a city and its details, respectively. And let's change the view name and url for access. The
tabs.html file
should now look like this:
<ion-tabs class="tabs-icon-top tabs-color-active-positive"> <ion-tab title="Status" icon-off="ion-cloud" icon-on="ion-cloud" href="#/tab/city"> <ion-nav-view name="tab-city"></ion-nav-view> </ion-tab> </ion-tabs>
Now it's time to play around with routing in our application.
Work with routes
Changes we made to the templates should be taken into account in the configuration of the routes and the IF states. To do this, find the file
app.js in the
www / js folder , and all this information is stored in it.
angular.module('starter', ['ionic', 'starter.controllers', 'starter.services'])
Here we are witnessing the creation of the main module of the application and the connection of the IF module to it, the module with controllers, and the module providing data (maybe I called it clumsily). We are mostly interested in the
config section of the application, which accepts 2 $ stateProvider and $ urlRouterProvider objects (state manager and path manager, respectively). Let's remove the excess and change the parameters of the paths. The
app.js file
needs to be
converted to this:
app.js angular.module('starter', ['ionic', 'starter.controllers', 'starter.services']) .run(function($ionicPlatform) { $ionicPlatform.ready(function() { if (window.cordova && window.cordova.plugins && window.cordova.plugins.Keyboard) { cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true); } if (window.StatusBar) { StatusBar.styleLightContent(); } }); }) .config(function($stateProvider, $urlRouterProvider) { $stateProvider .state('tab', { url: "/tab", abstract: true, templateUrl: "templates/tabs.html" }) .state('tab.city', { url: '/city', views: { 'tab-city': { templateUrl: 'templates/tab-city.html', controller: 'CityCtrl' } } }) .state('tab.city-detail', { url: '/city/:id', views: { 'tab-city': { templateUrl: 'templates/city-detail.html', controller: 'CityDetailCtrl' } } }) $urlRouterProvider.otherwise('/tab/city'); });
The attentive reader will notice that we have changed, namely, we set up
$ stateProvider for our routes, controllers and templates. The state setting is trivial - “state name”, the setting object in which the access url is specified, the view with the name of the view (the name we changed in the last step), the path to the template, and the name of the controller. In fact, there are many ways to configure the state, to the
extent that instead of the controller name, you can substitute the controller function itself, but we will not touch it (by the way, I advise you to look at
the Ionic presentation by Andrew Joslin on ngEurope ). Notice that the view name of the tab.city and tab.city-detail states is the same to load the template into the same (only) view we have configured.
$ urlRouterProvider.otherwise ('/ tab / city') provides default routing if no path is present (simply redirect the application to tab / city). If we see how the edits were applied in the browser, then visually nothing should change, except, of course, the address bar. If everything is right, then it's time to go to the goodies, namely - to receive data and their subsequent display.
Data acquisition
Now we need to open the file
controllers.js in order to further configure the application. As you remember, in the router we marked the binding of states to the
CityCtrl and
CityDetailCtrl controllers , so in this file you need to declare them:
angular.module('starter.controllers', []) .controller('CityCtrl', function($scope) { }) .controller('CityDetailCtrl', function($scope) { });
Now open the
services.js file and bring it to this view:
services.js angular.module('starter.services', []) .factory('Cities', function() { var cities = [{ id: 524901, name: '', desc: ' ', emblem: 'http://upload.wikimedia.org/wikipedia/commons/d/da/Coat_of_Arms_of_Moscow.png' }]; return { all: function() { return cities; }, get: function(id) { for (var i = 0; i < cities.length; i++) { if (cities[i].id === parseInt(id)) { return cities[i]; } } return null; } }; });
In principle, we no longer need it, so you can save and close it. In the future, all the magic will be inside
controllers.js .
With the above code, we created a data factory to use in our application. It is very simple to connect this factory; it is enough to register its name
'Cities' in the controller dependencies in order to use its methods. Write:
.controller('CityCtrl', function($scope, Cities) { $scope.cities = Cities.all(); })
So we get an array of cities and store it in the scope of the controller. Let's write in the dependencies of CityDetailCtrl what we use inside it, namely $ http (to get data using AJAX), $ stateParams (to get the parameter from the target date) and $ ionicPopup (for error message). And also we will write a request for obtaining weather for the selected city. As a result, we should have such a wonderful controller:
CityDetailCtrl .controller('CityDetailCtrl', function($scope, $http, $stateParams, $ionicPopup) { $scope.data = {}; $scope.id = $stateParams.id; $scope.showAlert = function(title, text) { $ionicPopup.alert({ title: title, template: text }); }; $scope.refresh = function() { $http.get('http://api.openweathermap.org/data/2.5/forecast/daily?id='+$scope.id) .success(function(data, status, headers, config){ $scope.data = data; $scope.$broadcast('scroll.refreshComplete'); }) .error(function(data, status, headers, config){ $scope.showAlert(status, data); $scope.$broadcast('scroll.refreshComplete'); }); }; $scope.refresh(); })
Well, we have almost everything ready! It remains to edit our templates and get the finished application =)
Editing templates
Open to start
tab-city.html . I remember that in the controller we received a list of all cities from the factory in the controller's scop. Let's implement their list with
avatars . To do this, write in the file the following structure:
<ion-view view-title=""> <ion-content class="padding"> <ion-list> <a class="item item-avatar" ng-repeat="city in cities" href="#/tab/city/{{city.id}}"> <img ng-src="{{city.emblem}}"> <h2>{{city.name}}</h2> <p>{{city.desc}}</p> </a> </ion-list> </ion-content> </ion-view>
Save the file and see in the browser:
Russia is our sacred state! Wonderful! Now for the
city-detail.html . Data is requested for the week, so we also need a list here. To understand what exactly to display, go to
api.openweathermap.org/data/2.5/forecast/daily?id=524901 , and look at the structure of the server response (I like the
jsoneditoronline.org service for viewing and formatting).
Server response { "cod": "200", "message": 0.0165, "city": { "id": 524901, "name": "Moscow", "coord": { "lon": 37.615555, "lat": 55.75222 }, "country": "RU", "population": 1000000 }, "cnt": 7, "list": [ { "dt": 1428915600, "temp": { "day": 283.84, "min": 278.31, "max": 284.09, "night": 278.31, "eve": 283.05, "morn": 281.75 }, "pressure": 1010.15, "humidity": 74, "weather": [ { "id": 802, "main": "Clouds", "description": "scattered clouds", "icon": "03d" } ], "speed": 5.02, "deg": 243, "clouds": 32 }, { "dt": 1429002000, "temp": { "day": 279.6, "min": 275.65, "max": 280.3, "night": 275.65, "eve": 279.3, "morn": 278.04 }, "pressure": 994.7, "humidity": 89, "weather": [ { "id": 500, "main": "Rain", "description": "light rain", "icon": "10d" } ], "speed": 5.01, "deg": 200, "clouds": 64, "rain": 1.86 }, { "dt": 1429088400, "temp": { "day": 277.79, "min": 273.76, "max": 278.35, "night": 277.3, "eve": 278.35, "morn": 273.76 }, "pressure": 998.51, "humidity": 73, "weather": [ { "id": 500, "main": "Rain", "description": "light rain", "icon": "10d" } ], "speed": 6.53, "deg": 221, "clouds": 76, "rain": 0.53 }, { "dt": 1429174800, "temp": { "day": 282.85, "min": 276.5, "max": 282.85, "night": 276.5, "eve": 278.69, "morn": 279.93 }, "pressure": 991.07, "humidity": 0, "weather": [ { "id": 501, "main": "Rain", "description": "moderate rain", "icon": "10d" } ], "speed": 5.36, "deg": 287, "clouds": 47, "rain": 4.22 }, { "dt": 1429261200, "temp": { "day": 280.71, "min": 274.5, "max": 280.71, "night": 274.5, "eve": 277.19, "morn": 280.17 }, "pressure": 995.12, "humidity": 0, "weather": [ { "id": 501, "main": "Rain", "description": "moderate rain", "icon": "10d" } ], "speed": 5.32, "deg": 285, "clouds": 65, "rain": 3.23 }, { "dt": 1429347600, "temp": { "day": 281.27, "min": 276.59, "max": 281.27, "night": 276.59, "eve": 278.67, "morn": 279.17 }, "pressure": 1002.53, "humidity": 0, "weather": [ { "id": 500, "main": "Rain", "description": "light rain", "icon": "10d" } ], "speed": 9.51, "deg": 359, "clouds": 67, "rain": 0.62 }, { "dt": 1429434000, "temp": { "day": 282.04, "min": 278.38, "max": 282.04, "night": 279.1, "eve": 280.23, "morn": 278.38 }, "pressure": 1006.22, "humidity": 0, "weather": [ { "id": 500, "main": "Rain", "description": "light rain", "icon": "10d" } ], "speed": 5.5, "deg": 311, "clouds": 88, "rain": 0.97 } ] }
Well, for this data you need to create a simple template, an example of which is shown below:
city-detail.html <ion-view view-title="{{data.city.name}}"> <ion-content class="padding"> <h2> : {{data.city.name}} </h2> <p> : {{data.city.country}} </p> <p> : {{data.city.population}} . </p> <p> : {{data.city.coord.lon}} : {{data.city.coord.lat}} </p> <ion-list> <a class="item item-avatar" ng-repeat="day in data.list" href="#"> <img ng-src="http://openweathermap.org/img/w/{{day.weather[0].icon}}.png"> <h2>: {{(day.temp.day - 273.15).toFixed(2)}} C</h2> <p> : <span ng-bind="day.dt*1000 | date: 'dd.MM.yyyy'"></span></p> </a> </ion-list> </ion-content> </ion-view>
There is no time to explain - we save, run and watch!
And how to make the avatar is not round? Open the
css / style.css file and write:
.item-avatar>img:first-child { border-radius: 5%; }
This will remove the standard border-radius as much as 50% (for an avatar, maybe just right, but for a coat of arms ugly). In this file, as you may have guessed, you can prescribe your own styles for components (and you can also intimately rewrite the styles of the IF itself).
I will of course attach the archive with the code to the article, so that you will have the opportunity to play with the fields in every possible way. As you can see, the development process is simple, clear and pleasant (honestly, I hope I don’t need to explain what is going on in this template?). And finally ...
Data refresh (pull-to-refresh)
I like this snack. What do you need? Just add
<ion-refresher pulling-text="-" on-refresh="refresh()"> </ion-refresher>
to the previous file at the very beginning of the
ion-content tag. And also register
$scope.$broadcast('scroll.refreshComplete');
to the
successors and
error handlers of the
$ http.get request. Try to do this and pull the page down.
Compiling and Publishing the Application
At this stage we can compile and debug our application on devices and emulators. To view the application immediately in
iOS and
Android, run:
ionic serve --lab
and see the following:
To compile the application and run it in the emulator, you need to run:
ionic platform add android ( ios) ionic build android (ios) ionic emulate android (ios)
In the case of
Apple technology
, you will need to install
ios-sym . For deployment, see the instructions for your target platform. For example, for
Android, you will need to generate a key and sign an application, which is well described
here . How to generate icons and splash to the platforms described
hereArchive with the projectConclusion
I hope I managed to bring the basic concepts of IF, and also to encourage you to try this framework for your projects. Comments and suggestions are accepted strictly in the LAN, the article will be edited and supplemented based on your feedback (I plan to make a best practices collection from this post). Thank you all for your attention and good coding!