Технология прошла долгий путь с тех пор, как человечество использовало камни, чтобы разжечь огонь. Было время, когда Интернет предназначался для обслуживания гипертекстовых документов на нескольких машинах. Но сегодня мы достигли сложного состояния, когда пульс контролируется устройством, а затем передается на ваш компьютер. И если частота сердечных сокращений не нормальная, вы можете даже увидеть Скорую помощь, ожидающую в конце спринта.
Это в значительной степени то, как мы живем в эти дни. И для реализации таких удивительных функций нам нужны удивительные технологии. В этом посте мы собираемся обсудить две такие передовые технологии, Ionic Framework и Firebase.
Что такое Ionic Framework?
Ionic — это мощная мобильная веб-платформа на основе AngularJS, которая упрощает создание гибридного мобильного приложения. Он не только обладает возможностью двухстороннего связывания данных, но и имеет отличный интерфейс для работы с RESTful API. Это делает Ionic идеальным выбором для разработки приложений и синхронизации их между устройствами.
Что такое Firebase?
Были времена, когда выделение машины для развертывания простого веб-сайта занимало недели. Затем появилась Амазонка. Вы просто указываете Amazon, какую систему вы хотите, и она предоставляет сервер для вас. Затем мы увидели рост Heroku, который предоставляет PaaS (Платформа как сервис) для размещения ваших приложений. Это позволило разработчикам уделять больше внимания приложению, а не беспокоиться о подготовке и развертывании приложения. И, наконец, у нас есть Firebase, который является самодостаточным «Сервер как услуга», управляемый хранилищем данных NoSQL. В Firebase все, что вам нужно сделать, это определить набор данных, и Firebase позаботится о том, чтобы представить его как RESTful API для вас.
Приложение Bucketlist
Я написал статью под названием Ionic Restify MongoDB — сквозное гибридное приложение , в котором объясняется, как создать сквозное гибридное приложение с использованием Restify и MongoDB в качестве сервера API и Ionic в качестве гибридного клиента. В этом посте мы увидим, как мы можем полностью исключить серверный уровень API с помощью Firebase.
Приложение Bucketlist, которое мы собираемся создать, будет иметь уровень аутентификации, позволяющий пользователям регистрироваться и входить в систему. После аутентификации пользователю предоставляется возможность создать новый элемент списка ведра.
Основной вид приложения показывает список незавершенных элементов, а второй вид — список завершенных элементов. У пользователя будет возможность пометить элемент как завершенный или удалить его.
Прежде чем мы начнем создавать приложение, вы должны:
- Взгляните на живой экземпляр приложения .
- Загрузите родной установщик .
- Отправьте готовое приложение в PhoneGap Build, подписав репо .
- Посмотрите на полный код, который мы собираемся построить.
Архитектура приложений
Наше приложение будет в основном состоять из двух слоев. Первый — это клиент ( в нашем случае, Ionic App, но это может быть любой другой клиент, который может использовать RESTful API ), а второй — сервер ( Firebase ).
Как видно из приведенной выше диаграммы, на стороне клиента у нас есть уровень Angularfire, который взаимодействует с Firebase и действует как сервисный уровень для приложения Ionic. Именно этот уровень дает возможность синхронизировать данные между Firebase и нашим клиентом Ionic.
На стороне Firebase мы настроим простой вход в систему, чтобы позаботиться об аутентификации.
Наше приложение Ionic будет иметь пять ключевых контроллеров:
- Контроллер регистрации
- Вход в контроллер
- Создать новый контроллер предметов
- Показать контроллер незавершенных предметов
- Показать контроллер завершенных предметов
Кроме того, у нас будет несколько методов, которые помогут пометить элемент как завершенный и удалить элемент.
Проектирование структуры данных
Firebase идеально используется для синхронизации данных в режиме реального времени, когда ожидается, что несколько клиентов по всему миру увидят одни и те же данные практически в один и тот же момент. Это не так с нашим приложением. Мы действительно не ищем синхронизацию нескольких устройств. Все, что нам нужно, — это чтобы Firebase позаботился о том, чтобы мы управляли данными наших списков.
Огромная часть Firebase — это то, что он предоставляет API аутентификации из коробки. Все, что нам нужно сделать, это включить его и включить клиента, а Firebase позаботится обо всем остальном за нас.
Для коллекции bucketlist нам нужна связь между пользователем и элементом bucketlist, вроде внешнего ключа. Это позволит нам показывать элементы списка ведра, созданные только пользователем.
Образец коллекции листовки показан ниже:
"BucketListCollection": [{ "item": "test", "isCompleted": false, "user": "[email protected]", "created": 1400801853144, "updated": 1400801853144 }, { "item": "tes message", "isCompleted": false, "user": "[email protected]", "created": 1401008504927, "updated": 1401008504927 }, { "item": "Just to check", "isCompleted": true, "user": "[email protected]", "created": 1401008534451, "updated": 1401008534451 }, ....]
В приведенном выше примере JSON ключ user
содержит ссылку между вошедшим в систему пользователем и его элементами. Итак, когда мы выбираем данные, мы выбираем записи, которые соответствуют зарегистрированному пользователю. И вот как мы представляем запрос, используя конечную точку RESTful:
https://bucketlist-app.firebaseio.com/bucketList/[email protected]
К сожалению, нет простого способа реализовать это в Firebase.
Согласно этому сообщению о переполнении стека , существует три способа:
- Используйте названия мест и приоритеты разумно.
- Делайте запросы на стороне клиента.
- Запустите отдельный сервер.
Эти подходы были своего рода излишним для простого API. Затем я наткнулся на этот пост переполнения стека , в котором упоминается, как вы можете перевернуть структуру данных, чтобы она была в большей степени ориентирована на пользователя, чем на функцию. Поэтому я изменил структуру данных приложения, как показано ниже.
"test@bla,com" : [{ "item": "test", "isCompleted": false, "created": 1400801853144, "updated": 1400801853144 }, { "item": "tes message", "isCompleted": false, "created": 1401008504927, "updated": 1401008504927 }....] "test2@bla,com" : [{ "item": "test2", "isCompleted": false, "created": 14008012853144, "updated": 14008012853144 }, { "item": "tes message2", "isCompleted": false, "created": 14010028504927, "updated": 14010028504927 }....]
Теперь каждый пользователь имеет свою собственную коллекцию, а не общую коллекцию списков ведра, что имеет больше смысла в нашем приложении. Итак, мы будем использовать эту структуру для управления нашими данными. И наши URL будут выглядеть так:
https://bucketlist-app.firebaseio.com/test@bla,com
Примечание. Я не уверен на 100%, повлияет ли большая пользовательская база на общее время ответа на один запрос ( больше пользователей = больше коллекций ).
Настройка Firebase
У нас есть хорошая идея относительно того, куда мы направляемся. Нашим первым шагом будет настройка учетной записи Firebase, создание нового экземпляра приложения Firebase и настройка для него аутентификации.
Перейдите на Firebase.com и создайте новую учетную запись, если у вас ее нет. Затем перейдите на страницу «Учетные записи» и создайте новое приложение. Укажите желаемое имя и URL. Как только приложение будет создано, нажмите на имя приложения, чтобы перейти на страницу данных и конфигурации. Это бэкэнд с высоты птичьего полета. Не стесняйтесь просматривать, прежде чем продолжить.
Далее мы настроим аутентификацию для нашего приложения. Нажмите на вкладку «Простой вход» в левой части страницы, и в основной области содержимого вы увидите доступные опции. В разделе « Поставщики проверки подлинности » нажмите « Электронная почта и пароль», а затем установите флажок « Включено» . Это настроит простой логин для нас.
Настройте Ионный Проект
Далее мы создадим новое приложение Ionic из пустого шаблона с использованием интерфейса командной строки Ionic (CLI). Создайте новую папку с именем myIonicFireApp
и откройте терминал / приглашение здесь. Сначала мы установим Cordova и Ionic. Выполните следующую команду:
$ npm i -g cordova ionic
Далее мы создадим новое приложение Ionic. Как правило, мне нравится держать мой код организованным. Поскольку это тестовое приложение, и мы не собираемся использовать какой-либо контроль версий для управления разработкой и производством, мы создадим две папки, myIonicFireApp/dev
и myIonicFireApp/prod
. Этот шаг не является обязательным и полностью предпочтительным. Затем cd
в папку dev
(если вы ее создали) и выполните следующую команду:
$ ionic start bucketListApp blank
bucketListApp
— это имя приложения. Это создаст для нас шаблон Ionic + PhoneGap. После завершения установки первым делом нужно переместить bucketListApp
config.xml из папки bucketListApp
папку www
( требование сборки PhoneGap ).
Затем откройте файл config.xml в своем любимом редакторе и обновите поля идентификатора виджета, имени, описания и автора. Это будут метаданные для вашего приложения, когда оно будет запущено через PhoneGap Build. Обновленный файл будет выглядеть так:
<?xml version='1.0' encoding='utf-8'?> <widget id="com.ionicfire.bucketlist" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0"> <name>BucketList App</name> <description>An Awesome App</description> <author email="[email protected]" href="http://bucketlist.com/">Arvind Ravulavaru</author> <content src="index.html" /> <access origin="*" /> <preference name="fullscreen" value="true" /> <preference name="webviewbounce" value="false" /> <preference name="UIWebViewBounce" value="false" /> <preference name="DisallowOverscroll" value="true" /> <!-- Don't store local date in an iCloud backup. Turn this to "cloud" to enable storage to be sent to iCloud. Note: enabling this could result in Apple rejecting your app. --> <preference name="BackupWebStorage" value="none" /> <feature name="StatusBar"> <param name="ios-package" value="CDVStatusBar" onload="true" /> </feature> </widget>
Обратитесь к настройке CLI PhoneGap 3 на Mac и Windows, чтобы полностью понять и настроить PhoneGap на Windows и Mac.
Чтобы добавить поддержку платформы iOS (только для Mac), выполните следующую команду:
$ ionic platform add ios
Чтобы добавить поддержку платформы Android, выполните следующую команду:
$ ionic platform add android
Далее мы создадим приложение, запустив:
$ ionic platform build ios
или
$ ionic platform build ios
Далее, чтобы эмулировать приложение, выполните:
$ ionic emulate ios
или
$ ionic emulate android
Вы можете использовать описанный выше подход для проверки вашего кода. Но вам нужно создавать код для соответствующих платформ каждый раз, когда вы вносите изменения в код в папке www
.
Учитывая мою лень, я никогда этого не сделаю. Проект Ionic поставляется с поддержкой Gulp. Давайте воспользуемся этим. Вернувшись в терминал, выполните следующую команду:
$ npm install
Это установит все зависимости, перечисленные в package.json . Далее установите gulp-connect с помощью команды:
$ npm install gulp-connect --save
Затем откройте файл gulfile.js , находящийся в корне папки bucketListApp
и замените его следующим кодом:
var gulp = require('gulp'); var gutil = require('gulp-util'); var bower = require('bower'); var concat = require('gulp-concat'); var sass = require('gulp-sass'); var minifyCss = require('gulp-minify-css'); var rename = require('gulp-rename'); var sh = require('shelljs'); var connect = require('gulp-connect'); var paths = { sass: ['./scss/**/*.scss'], www : ['www/**/*.*'] }; gulp.task('default', ['sass']); gulp.task('serve', ['connect', 'watch']); gulp.task('sass', function(done) { gulp.src('./scss/ionic.app.scss') .pipe(sass()) .pipe(gulp.dest('./www/css/')) .pipe(minifyCss({ keepSpecialComments: 0 })) .pipe(rename({ extname: '.min.css' })) .pipe(gulp.dest('./www/css/')) .on('end', done); }); gulp.task('reload', function () { return gulp.src(['www/index.html']) .pipe(connect.reload()); }); gulp.task('watch', function() { // Uncomment below line if you wish to work wit SASS //gulp.watch(paths.sass, ['sass']); gulp.watch([paths.www], ['reload']); }); gulp.task('install', ['git-check'], function() { return bower.commands.install() .on('log', function(data) { gutil.log('bower', gutil.colors.cyan(data.id), data.message); }); }); gulp.task('git-check', function(done) { if (!sh.which('git')) { console.log( ' ' + gutil.colors.red('Git is not installed.'), '\n Git, the version control system, is required to download Ionic.', '\n Download git here:', gutil.colors.cyan('http://git-scm.com/downloads') + '.', '\n Once git is installed, run \'' + gutil.colors.cyan('gulp install') + '\' again.' ); process.exit(1); } done(); }); gulp.task('connect', function() { connect.server({ root: 'www', port: '1881', livereload: true }); });
Вернувшись в терминал, запустите:
$ gulp serve
Это раскрутит сервер. Теперь все, что вам нужно сделать, это открыть http://localhost:1881
и наблюдать !.
Обратите внимание, что во время разработки cordova.js будет 404. И, поскольку мы добавили поддержку прямой перезагрузки, все, что вам нужно сделать, это внести изменения и переключиться в браузер, чтобы увидеть изменения.
Примечание. Если вы создаете приложение с собственными плагинами, такими как контакты или камера, этот подход не будет работать! Вам необходимо развернуть приложение на устройстве, чтобы протестировать его.
Наше Ионическое приложение настроено. давайте построим реальное приложение.
Ионная и Огненная база
Первое, что мы собираемся сделать, это открыть www / index.html и добавить необходимые ссылки JavaScript для Firebase, AngularFire и Firebase-simple-login.
<script src="https://cdn.firebase.com/v0/firebase.js"></script> <script src="https://cdn.firebase.com/libs/angularfire/0.5.0/angularfire.min.js"></script> <script src="https://cdn.firebase.com/v0/firebase-simple-login.js"></script>
Они указаны в CDN, но вы также можете скачать файлы и разместить их на сервере. Затем обновите значение директивы ng-app
тега body с bucketList
до bucketList
. Это будет название нашего модуля. Наконец, мы добавим поддержку кнопки «Назад». Добавьте следующий код в тело страницы:
<ion-nav-bar class="bar-stable nav-title-slide-ios7"> <ion-nav-back-button class="button-icon icon ion-chevron-left"> Back </ion-nav-back-button> </ion-nav-bar>
Законченный www / index.html будет выглядеть так:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width"> <title></title> <link href="lib/ionic/css/ionic.css" rel="stylesheet"> <link href="css/style.css" rel="stylesheet"> <!-- IF using Sass (run gulp sass first), then uncomment below and remove the CSS includes above <link href="css/ionic.app.css" rel="stylesheet"> --> <!-- ionic/angularjs js --> <script src="lib/ionic/js/ionic.bundle.js"></script> <script src="https://cdn.firebase.com/v0/firebase.js"></script> <!-- firebase --> <script src="https://cdn.firebase.com/libs/angularfire/0.5.0/angularfire.min.js"></script> <!-- angularfire --> <script src="https://cdn.firebase.com/v0/firebase-simple-login.js"></script> <!-- firebase-simple-login --> <!-- cordova script (this will be a 404 during development) --> <script src="cordova.js"></script> <!-- your app's js --> <script src="js/app.js"></script> <script src="js/controllers.js"></script> </head> <body ng-app="bucketList" animation="slide-left-right-ios7"> <ion-nav-bar class="bar-stable nav-title-slide-ios7"> <ion-nav-back-button class="button-icon icon ion-chevron-left"> Back </ion-nav-back-button> </ion-nav-bar> <ion-nav-view></ion-nav-view> </body> </html>
Обратите внимание, что мы добавили ссылку на controllers.js . Мы разрешим это через мгновение. Если вы вернетесь в браузер и зайдите в консоль разработчика, вы увидите пару 404-х и ошибку Uncaught object
. Ошибка Uncaught object
заключается в том, что мы обновили директиву ng-app
в index.html, но не в www / js / app.js. Вы можете убить задание глотком, так как мы собираемся внести немало изменений. Как только все будет сделано, мы можем перезапустить сервер.
Откройте www / js / app.js в вашем любимом редакторе. Во-первых, давайте обновим имя модуля. Затем мы добавим пару зависимостей. Обновите объявление существующего модуля:
angular.module('bucketList', ['ionic', 'firebase', 'bucketList.controllers'])
Первичная зависимость — это ионная связь , следующая пожарная база и, наконец, контроллеры.
Для разработки нашего приложения мы собираемся использовать две пары компонента ion-tabs . Первый набор вкладок будет использоваться для отображения экранов входа в систему и регистрации, а второй набор вкладок будет использоваться для отображения экранов неполных списков ведра и завершенных элементов списков ведра.
Мы собираемся обернуть наши вкладки в другую абстрактную вкладку, чтобы получить больше контроля. Это доведет общее количество наших маршрутов до шести. Внутри метода run
мы $rootScope
пару переменных и методов в переменную $rootScope
. Это будет включать в себя URL-адрес экземпляра Firebase, checkSession
, logout
и загрузчики для лучшего UX. Окончательный app.js будет
angular.module('bucketList', ['ionic', 'firebase', 'bucketList.controllers']) .run(function($ionicPlatform, $rootScope, $firebaseAuth, $firebase, $window, $ionicLoading) { $ionicPlatform.ready(function() { // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard // for form inputs) if (window.cordova && window.cordova.plugins.Keyboard) { cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true); } if (window.StatusBar) { StatusBar.styleDefault(); } $rootScope.userEmail = null; $rootScope.baseUrl = 'https://bucketlist-app.firebaseio.com/'; var authRef = new Firebase($rootScope.baseUrl); $rootScope.auth = $firebaseAuth(authRef); $rootScope.show = function(text) { $rootScope.loading = $ionicLoading.show({ content: text ? text : 'Loading..', animation: 'fade-in', showBackdrop: true, maxWidth: 200, showDelay: 0 }); }; $rootScope.hide = function() { $ionicLoading.hide(); }; $rootScope.notify = function(text) { $rootScope.show(text); $window.setTimeout(function() { $rootScope.hide(); }, 1999); }; $rootScope.logout = function() { $rootScope.auth.$logout(); $rootScope.checkSession(); }; $rootScope.checkSession = function() { var auth = new FirebaseSimpleLogin(authRef, function(error, user) { if (error) { // no action yet.. redirect to default route $rootScope.userEmail = null; $window.location.href = '#/auth/signin'; } else if (user) { // user authenticated with Firebase $rootScope.userEmail = user.email; $window.location.href = ('#/bucket/list'); } else { // user is logged out $rootScope.userEmail = null; $window.location.href = '#/auth/signin'; } }); } }); }) .config(function($stateProvider, $urlRouterProvider) { $stateProvider .state('auth', { url: "/auth", abstract: true, templateUrl: "templates/auth.html" }) .state('auth.signin', { url: '/signin', views: { 'auth-signin': { templateUrl: 'templates/auth-signin.html', controller: 'SignInCtrl' } } }) .state('auth.signup', { url: '/signup', views: { 'auth-signup': { templateUrl: 'templates/auth-signup.html', controller: 'SignUpCtrl' } } }) .state('bucket', { url: "/bucket", abstract: true, templateUrl: "templates/bucket.html" }) .state('bucket.list', { url: '/list', views: { 'bucket-list': { templateUrl: 'templates/bucket-list.html', controller: 'myListCtrl' } } }) .state('bucket.completed', { url: '/completed', views: { 'bucket-completed': { templateUrl: 'templates/bucket-completed.html', controller: 'completedCtrl' } } }) $urlRouterProvider.otherwise('/auth/signin'); });
изangular.module('bucketList', ['ionic', 'firebase', 'bucketList.controllers']) .run(function($ionicPlatform, $rootScope, $firebaseAuth, $firebase, $window, $ionicLoading) { $ionicPlatform.ready(function() { // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard // for form inputs) if (window.cordova && window.cordova.plugins.Keyboard) { cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true); } if (window.StatusBar) { StatusBar.styleDefault(); } $rootScope.userEmail = null; $rootScope.baseUrl = 'https://bucketlist-app.firebaseio.com/'; var authRef = new Firebase($rootScope.baseUrl); $rootScope.auth = $firebaseAuth(authRef); $rootScope.show = function(text) { $rootScope.loading = $ionicLoading.show({ content: text ? text : 'Loading..', animation: 'fade-in', showBackdrop: true, maxWidth: 200, showDelay: 0 }); }; $rootScope.hide = function() { $ionicLoading.hide(); }; $rootScope.notify = function(text) { $rootScope.show(text); $window.setTimeout(function() { $rootScope.hide(); }, 1999); }; $rootScope.logout = function() { $rootScope.auth.$logout(); $rootScope.checkSession(); }; $rootScope.checkSession = function() { var auth = new FirebaseSimpleLogin(authRef, function(error, user) { if (error) { // no action yet.. redirect to default route $rootScope.userEmail = null; $window.location.href = '#/auth/signin'; } else if (user) { // user authenticated with Firebase $rootScope.userEmail = user.email; $window.location.href = ('#/bucket/list'); } else { // user is logged out $rootScope.userEmail = null; $window.location.href = '#/auth/signin'; } }); } }); }) .config(function($stateProvider, $urlRouterProvider) { $stateProvider .state('auth', { url: "/auth", abstract: true, templateUrl: "templates/auth.html" }) .state('auth.signin', { url: '/signin', views: { 'auth-signin': { templateUrl: 'templates/auth-signin.html', controller: 'SignInCtrl' } } }) .state('auth.signup', { url: '/signup', views: { 'auth-signup': { templateUrl: 'templates/auth-signup.html', controller: 'SignUpCtrl' } } }) .state('bucket', { url: "/bucket", abstract: true, templateUrl: "templates/bucket.html" }) .state('bucket.list', { url: '/list', views: { 'bucket-list': { templateUrl: 'templates/bucket-list.html', controller: 'myListCtrl' } } }) .state('bucket.completed', { url: '/completed', views: { 'bucket-completed': { templateUrl: 'templates/bucket-completed.html', controller: 'completedCtrl' } } }) $urlRouterProvider.otherwise('/auth/signin'); });
Обратите внимание, что мы инициализируем сервис Firebase Auth, используя этот код:
$rootScope.baseUrl = 'https://bucketlist-app.firebaseio.com/'; var authRef = new Firebase($rootScope.baseUrl); $rootScope.auth = $firebaseAuth(authRef);
Не забудьте заменить baseURL
своим экземпляром Firebase
Теперь давайте создадим controllers.js . Создайте новый файл на www/js
и назовите его controllers.js . Как следует из названия, этот файл будет содержать все контроллеры. Затем создайте новую папку с именем templates
. Мы будем заполнять каждый шаблон по мере продвижения.
Во-первых, у нас есть контроллер регистрации. Давайте сначала создадим необходимые шаблоны. Создайте новый файл с именем auth.html в папке templates
. Это будет абстрактная вкладка для вкладок Вход и Регистрация. Заполните его следующим кодом:
<ion-tabs class="tabs-icon-top"> <ion-tab title="Sign In" icon-on="ion-ios7-locked" icon-off="ion-ios7-locked-outline" href="#/auth/signin"> <ion-nav-view name="auth-signin"></ion-nav-view> </ion-tab> <ion-tab title="Sign Up" icon-on="ion-ios7-personadd" icon-off="ion-ios7-personadd-outline" href="#/auth/signup"> <ion-nav-view name="auth-signup"></ion-nav-view> </ion-tab> </ion-tabs>
Далее давайте добавим шаблон регистрации. Создайте новый файл с именем auth-signup.html внутри папки templates
и добавьте следующий код:
<ion-header-bar class="bar-positive"> <h1 class="title">Sign Up</h1> </ion-header-bar> <ion-content class="has-header padding"> <div class="list"> <label class="item item-input"> <span class="input-label">Email</span> <input type="text" ng-model="user.email"> </label> <label class="item item-input"> <span class="input-label">Password</span> <input type="password" ng-model="user.password"> </label> <label class="item item-input"> <button class="button button-block button-positive" ng-click="createUser()"> Sign Up </button> </label> </div> </ion-content>
Когда пользователь нажимает кнопку отправки, мы вызываем createuser()
. Контроллер выглядит так:
angular.module('bucketList.controllers', []) .controller('SignUpCtrl', [ '$scope', '$rootScope', '$firebaseAuth', '$window', function ($scope, $rootScope, $firebaseAuth, $window) { $scope.user = { email: "", password: "" }; $scope.createUser = function () { var email = this.user.email; var password = this.user.password; if (!email || !password) { $rootScope.notify("Please enter valid credentials"); return false; } $rootScope.show('Please wait.. Registering'); $rootScope.auth.$createUser(email, password, function (error, user) { if (!error) { $rootScope.hide(); $rootScope.userEmail = user.email; $window.location.href = ('#/bucket/list'); } else { $rootScope.hide(); if (error.code == 'INVALID_EMAIL') { $rootScope.notify('Invalid Email Address'); } else if (error.code == 'EMAIL_TAKEN') { $rootScope.notify('Email Address already taken'); } else { $rootScope.notify('Oops something went wrong. Please try again later'); } } }); } } ])
На что обратить внимание:
-
$rootScope.show()
,$rootScope.hide()
и$rootScope.notify()
определены в app.js для отображения наложения загрузки. -
$rootScope.auth.$createUser()
отвечает за взаимодействие с Firebase и создание нового пользователя. - Обратите внимание на различные сообщения об ошибках, возвращаемые Firebase. Вы можете найти весь список здесь .
- При успешной регистрации мы перенаправим пользователя к нашему основному виду.
Далее идет контроллер Signin. Создайте новый файл с именем auth-signin.html в папке templates
и добавьте следующую разметку:
<ion-header-bar class="bar-positive"> <h1 class="title">Sign In</h1> </ion-header-bar> <ion-content class="has-header padding"> <div class="list"> <label class="item item-input"> <span class="input-label">Email</span> <input type="text" ng-model="user.email"> </label> <label class="item item-input"> <span class="input-label">Password</span> <input type="password" ng-model="user.password"> </label> <label class="item item-input"> <button class="button button-block button-positive" ng-click="validateUser()">Sign In</button> </label> </div> </ion-content>
Когда пользователь нажимает кнопку отправки, мы вызываем validateUser()
. Контроллер будет ( продолжение сверху ):
.controller('SignInCtrl', [ '$scope', '$rootScope', '$firebaseAuth', '$window', function ($scope, $rootScope, $firebaseAuth, $window) { // check session $rootScope.checkSession(); $scope.user = { email: "", password: "" }; $scope.validateUser = function () { $rootScope.show('Please wait.. Authenticating'); var email = this.user.email; var password = this.user.password; if (!email || !password) { $rootScope.notify("Please enter valid credentials"); return false; } $rootScope.auth.$login('password', { email: email, password: password }) .then(function (user) { $rootScope.hide(); $rootScope.userEmail = user.email; $window.location.href = ('#/bucket/list'); }, function (error) { $rootScope.hide(); if (error.code == 'INVALID_EMAIL') { $rootScope.notify('Invalid Email Address'); } else if (error.code == 'INVALID_PASSWORD') { $rootScope.notify('Invalid Password'); } else if (error.code == 'INVALID_USER') { $rootScope.notify('Invalid User'); } else { $rootScope.notify('Oops something went wrong. Please try again later'); } }); } } ])
На что обратить внимание:
-
$rootScope.auth.$login()
отвечает за аутентификацию Firebase. -
$rootScope.auth.$login()
возвращает обещание, которое будет выполнено после завершения запроса. - При успешной аутентификации мы будем перенаправлять на наш основной вид.
Далее, давайте создадим основной вид приложения. Создайте новый файл с именем bucket.html внутри папки templates
и добавьте следующий код:
<ion-tabs class="tabs-icon-top"> <ion-tab title="My List" icon-on="ion-ios7-browsers" icon-off="ion-ios7-browsers-outline" href="#/bucket/list"> <ion-nav-view name="bucket-list"></ion-nav-view> </ion-tab> <ion-tab title="Completed" icon-on="ion-ios7-checkmark" icon-off="ion-ios7-checkmark-outline" href="#/bucket/completed"> <ion-nav-view name="bucket-completed"></ion-nav-view> </ion-tab> </ion-tabs>
Это абстрактное представление, в котором содержится полный и неполный вид нашего списка. Затем создайте новый файл с именем bucket-list.html внутри папки templates
и добавьте следующий код:
<ion-header-bar class="bar-positive"> <button class="button button-clear" ng-click="newTask()">New</button> <h1 class="title">My Bucket List</h1> <button class="button button-clear" ng-click="logout()">Logout</button> </ion-header-bar> <ion-content class="has-header padding" has-tabs="true" on-refresh="onRefresh()"> <div class="card" ng-repeat="item in list" id="{{item.key}}" > <div class="item item-text-wrap"> <span>{{ item.item }}</span> <br/> <br/> <p class="actions padding"> <i class="ion-checkmark-circled icon-actions margin" ng-click="markCompleted('{{item.key}}')"></i> <i class="ion-trash-b icon-actions margin" ng-click="deleteItem('{{item.key}}')"></i> </p> </div> </div> <div class="card" > <div class="item item-text-wrap" ng-show="noData"> <span> No Items in your bucket List. Click <a href="javascript:" ng-click="newTask()">Here</a> and create one </span> </div> </div> </ion-content>
На что обратить внимание:
- Мы добавили новую кнопку в шапку. Откроется всплывающее окно, где пользователь может ввести описание элемента и создать его.
- Тело представления отображает карту , на которой отображаются описание элемента и значки « Удалить и пометить как выполненные» .
Контроллер выглядит так:
.controller('myListCtrl', function($rootScope, $scope, $window, $ionicModal, $firebase) { $rootScope.show("Please wait... Processing"); $scope.list = []; var bucketListRef = new Firebase($rootScope.baseUrl + escapeEmailAddress($rootScope.userEmail)); bucketListRef.on('value', function(snapshot) { var data = snapshot.val(); $scope.list = []; for (var key in data) { if (data.hasOwnProperty(key)) { if (data[key].isCompleted == false) { data[key].key = key; $scope.list.push(data[key]); } } } if ($scope.list.length == 0) { $scope.noData = true; } else { $scope.noData = false; } $rootScope.hide(); }); $ionicModal.fromTemplateUrl('templates/newItem.html', function(modal) { $scope.newTemplate = modal; }); $scope.newTask = function() { $scope.newTemplate.show(); }; $scope.markCompleted = function(key) { $rootScope.show("Please wait... Updating List"); var itemRef = new Firebase($rootScope.baseUrl + escapeEmailAddress($rootScope.userEmail) + '/' + key); itemRef.update({ isCompleted: true }, function(error) { if (error) { $rootScope.hide(); $rootScope.notify('Oops! something went wrong. Try again later'); } else { $rootScope.hide(); $rootScope.notify('Successfully updated'); } }); }; $scope.deleteItem = function(key) { $rootScope.show("Please wait... Deleting from List"); var itemRef = new Firebase($rootScope.baseUrl + escapeEmailAddress($rootScope.userEmail)); bucketListRef.child(key).remove(function(error) { if (error) { $rootScope.hide(); $rootScope.notify('Oops! something went wrong. Try again later'); } else { $rootScope.hide(); $rootScope.notify('Successfully deleted'); } }); }; })
На что обратить внимание:
- Мы будем строить Firebase Reference на основе зарегистрированного пользователя, как обсуждалось в разделе «
Designing the data structure
».
var bucketListRef = new Firebase($rootScope.baseUrl + escapeEmailAddress($rootScope.userEmail));
Мы создаем коллекцию, названную после экранирования адреса электронной почты пользователя. Вы можете добавить определение escapeEmailAddress()
внизу escapeEmailAddress()
controllers.js .
function escapeEmailAddress(email) { if (!email) return false // Replace '.' (not allowed in a Firebase key) with ',' email = email.toLowerCase(); email = email.replace(/\./g, ','); return email.trim(); }
- Далее мы будем использовать эту динамическую ссылку для извлечения всех элементов списка, используя событие прослушивания
on
дляvalue
. Это срабатывает, когда происходит изменение в коллекции ( одна из лучших частей Firebase ). - Мы проверяем, не завершен ли элемент
data[key].isCompleted == false
, и затем добавляем его в список элементов, которые должны быть показаны. - Мы также регистрируем
newTask()
, который откроет всплывающее окно «Create New
элемент». -
$scope.markCompleted()
и$scope.deleteItem()
взаимодействуют с Firebase API, чтобы обновить значениеisCompleted
в true и удалить часть данных из коллекции соответственно.
Далее мы добавим newCtrl
, отвечающий за создание нового контроллера. Создайте новый файл с именем newItem.html в папке templates
и добавьте следующий код:
<div class="modal slide-in-up" ng-controller="newCtrl"> <header class="bar bar-header bar-secondary"> <button class="button button-clear button-primary" ng-click="close()">Cancel</button> <h1 class="title">New Item</h1> <button class="button button-positive" ng-click="createNew()">Done</button> </header> <ion-content class="padding has-header"> <input type="text" placeholder="I need to do..." ng-model="data.item"> </ion-content> </div>
При нажатии Done
мы вызываем createUser()
. В controller.js добавьте следующий код:
.controller('newCtrl', function($rootScope, $scope, $window, $firebase) { $scope.data = { item: "" }; $scope.close = function() { $scope.modal.hide(); }; $scope.createNew = function() { var item = this.data.item; if (!item) return; $scope.modal.hide(); $rootScope.show(); $rootScope.show("Please wait... Creating new"); var form = { item: item, isCompleted: false, created: Date.now(), updated: Date.now() }; var bucketListRef = new Firebase($rootScope.baseUrl + escapeEmailAddress($rootScope.userEmail)); $firebase(bucketListRef).$add(form); $rootScope.hide(); }; })
На что обратить внимание:
- Мы создаем объект формы, который будет состоять из всех основных данных для создания нового элемента списка ведра.
- Мы создадим новое соединение с коллекцией пользователя и затем используем
$firebase(bucketListRef).$add(form);
мы вставляем данные в коллекцию. - После того, как данные вставлены, Firebase запускает событие
value
, что обновит наше представление элементов списка корзин.
Наконец, давайте добавим контроллер, чтобы показать все завершенные элементы списка ведра. Создайте новый файл с именем bucket-complete.html внутри папки templates
и добавьте следующий код:
<ion-header-bar class="bar-positive"> <h1 class="title">Completed Items</h1> <button class="button button-clear" ng-click="logout()">Logout</button> </ion-header-bar> <ion-content class="has-header padding" has-tabs="true" on-refresh="onRefresh()"> <div class="card" ng-repeat="item in list" > <div class="item item-text-wrap"> <span>{{ item.item }}</span> <br/> <br/> <p class="actions padding"> <i class="ion-trash-b icon-actions margin" ng-click="deleteItem('{{item.key}}')"></i> </p> </div> </div> <div class="card" > <div class="item item-text-wrap" ng-show="noData || incomplete"> <span ng-show="incomplete"> You can have not completed any of your Bucket List items yet. Try harder!! </span> <span ng-show="noData"> No Items in your bucket List. </span> </div> </div> </ion-content>
Этот контроллер аналогичен контроллеру неполного списка сегментов, за исключением « Create New
элемент» и « Mark Item Incomplete
. Вы также можете добавить их сюда, если хотите. Контроллер выглядит так:
.controller('completedCtrl', function($rootScope, $scope, $window, $firebase) { $rootScope.show("Please wait... Processing"); $scope.list = []; var bucketListRef = new Firebase($rootScope.baseUrl + escapeEmailAddress($rootScope.userEmail)); bucketListRef.on('value', function(snapshot) { $scope.list = []; var data = snapshot.val(); for (var key in data) { if (data.hasOwnProperty(key)) { if (data[key].isCompleted == true) { data[key].key = key; $scope.list.push(data[key]); } } } if ($scope.list.length == 0) { $scope.noData = true; } else { $scope.noData = false; } $rootScope.hide(); }); $scope.deleteItem = function(key) { $rootScope.show("Please wait... Deleting from List"); var itemRef = new Firebase($rootScope.baseUrl + escapeEmailAddress($rootScope.userEmail)); bucketListRef.child(key).remove(function(error) { if (error) { $rootScope.hide(); $rootScope.notify('Oops! something went wrong. Try again later'); } else { $rootScope.hide(); $rootScope.notify('Successfully deleted'); } }); }; });
Наконец, давайте добавим немного CSS. Откройте style.css в папке www/css
и добавьте следующий код:
.margin { margin-left: 9px; margin-right: 9px; } .icon-actions { font-size: 23px; } .checkbox { vertical-align: middle; } .actions { float: right; } .item-text-wrap { overflow: auto; } .ion-checkmark-circled.icon-actions.margin{ margin-right: 35px; }
Были сделаны! Давайте запустим приложение и посмотрим, как оно выглядит. В терминале запустите:
gulp serve
Это запустит сервер. Затем перейдите по http://localhost:1881
и вас встретит представление Signin. Нажмите на «Регистрация» и зарегистрируйте учетную запись. Как только регистрация прошла успешно, вы будете перенаправлены в представление списка корзин. Поиграйте с новым приложением Firebase Ionic .
Примечание. Вы можете зайти в свою учетную запись приложения Firebase и проверить там структуру данных.
Выпуск сборки PhoneGap
Мы успешно создали приложение, которое отлично работает в браузере. Давайте создадим собственный установщик и посмотрим, как приложение работает на реальном устройстве.
Примечание. Если вы новичок в PhoneGap, я бы порекомендовал прочитать Quick Start PhoneGap, прежде чем продолжить.
Шаг 1. Сначала скопируйте myIonicFireApp/dev/bucketListApp/www
и ее содержимое в myIonicFireApp/prod
. Это все, что нам нужно для выпуска сборки PhoneGap.
Шаг 2: Создайте новый IonicFirePGInstaller
GitHub с именем IonicFirePGInstaller
.
Шаг 3: cd
в папку myIonicFireApp/prod
( не в папке www
) и выполните следующие команды:
$ git init $ git add -A $ git commit -am "Initial Commit" $ git remote add origin [email protected]:sitepoint/IonicFirePGInstaller.git
Обязательно обновите путь репо, чтобы он указывал на тот, который вы создали. Наконец, проверьте код:
$ git push origin master
Это подтолкнет код к GitHub.
Шаг 4: Перейдите к PhoneGap Build и войдите в систему.
Шаг 5: Нажмите + New App
и отправьте URL репозитория GitHub ( https, а не ssh ) под открытым исходным кодом . Теперь служба PhoneGap перейдет на GitHub и получит репо. После того, как репозиторий будет загружен, вы увидите кнопку Ready to Build
. Нажмите на него, чтобы выполнить сборку PhoneGap.
После завершения сборки вы можете загрузить установщики для вашего устройства и протестировать приложение.
Вывод
На этом мы завершаем статью о создании гибридного приложения с использованием Firebase и платформы Ionic. Надеюсь, вы получили честное представление о том, как создать свой собственный.
- Вы можете найти кодовую базу, которую мы разработали на GitHub .
- Вы можете найти папку
www
которую вы также можете отправить в сборку PhoneGap на GitHub . - Вы можете скачать установщик приложения здесь .
Спасибо за прочтение!