В этом уроке мы создадим приложение для создания заметок с Cordova и LokiJS . Мы будем использовать Ionic Framework для обработки структуры приложения и взаимодействия с пользовательским интерфейсом. Вот как будет выглядеть финальное приложение:
Что такое LokiJS?
LokiJS — это быстрое, ориентированное на память хранилище документов для node.js, браузеров и Apache Cordova, которое предоставляет комплексный API для хранения и извлечения данных. В отличие от localStorage, в LokiJS нет необходимости разбираться для хранения и доступа. LokiJS сохраняет данные в localStorage по умолчанию, но вы можете использовать другие методы хранения с «адаптерами постоянства», включая «IndexedAdapter», который использует IndexedDB для сохранения. Для этого урока мы будем использовать адаптер FileSystem, который хранит данные с использованием файла JSON, сохраненного в FileSystem.
Настроить
Сначала установите Cordova и Ionic через npm с помощью следующей команды:
npm install -g cordova ionic
После установки создайте новый проект Ionic:
ionic start loki-notes blank
Это создает новый Ionic-проект с использованием пустого шаблона. Доступны и другие шаблоны, такие как «tabs» и «sidemenu», но мы будем придерживаться пустого.
Откройте каталог loki-notes и установите необходимые платформы.
cd loki-notes ionic platform add android
Установка зависимостей
Приложение будет зависеть от следующих библиотек, я включил инструкции по установке после каждой.
LokiJS
Используется для хранения данных приложения.
bower install lokijs --save
Lodash
Используется для усечения описания заметок.
bower install lodash --save
Локи-Cordova фс-адаптер
Используется LokiJS для хранения данных в файловой системе. К сожалению, эта библиотека не проиндексирована ни в bower, ни в npm, поэтому вам нужно скачать ее с Github и сохранить в каталоге www / js .
Кордова-Plugin-файл
Зависимость для адаптера LokiJS FileSystem.
cordova plugin add cordova-plugin-file
Кордова-Plugin-камера
Используется для захвата фотографий, прикрепленных к каждой заметке.
cordova plugin add cordova-plugin-camera
Закончив установку всех зависимостей, добавьте их в файл www / index.html после основного файла JavaScript cordova.
<script src="js/app.js"></script><!-- the main cordova JavaScript file--> <script src="lib/lokijs/src/lokijs.js"></script> <script src="lib/lokijs/src/loki-angular.js"></script> <script src="js/loki-cordova-fs-adapter.js"></script> <script src="lib/lodash/dist/lodash.min.js"></script>
Обратите внимание, что cordova-plugin-file и cordova-plugin-camera являются плагинами Cordova, поэтому нам не нужно связывать их в файле index.html . API-интерфейсы подключаемых модулей имеют глобальную область видимости, поэтому мы можем вызывать их из любого файла JavaScript.
Пока мы здесь, давайте рассмотрим контроллер и сервисы, которые мы будем создавать в ближайшее время.
<script src="js/controllers/NoteController.js"></script> <script src="js/services/NoteService.js"></script> <script src="js/services/CameraService.js"></script>
Сборка приложения
Сервисы
Сервисы инкапсулируют общую функциональность в одной функции. Это позволяет нам иметь более сжатый код при вызове различных функций из библиотек, которые мы используем. Сервисы хранятся в каталоге www / js / services .
Камера Сервис
Создайте сервис камеры ( www / js / services / CameraService.js ), отвечающий за перенос вызова в плагин камеры, и добавьте следующий код:
(function(){ angular.module('starter') .service('CameraService', ['$q', CameraService]); function CameraService($q){ var me = this; me.options = { quality: 80, correctOrientation: true }; function getPicture(){ var q = $q.defer(); me.options.encodingType = Camera.EncodingType.JPEG; me.options.sourceType = Camera.PictureSourceType.CAMERA; navigator.camera.getPicture( function(result){ q.resolve(result); }, function(err){ q.reject(err); }, me.options ); return q.promise; } return { getPicture: getPicture } } })();
Разбивая вышеприведенный код, мы сначала оборачиваем все в «IIFE» (выражение для немедленного вызова функции), чтобы избежать предоставления кода глобальной области видимости.
(function(){ ... })();
Затем мы создаем сервис камеры. При этом используется встроенный угловой сервис $q
который позволяет вызывать функции асинхронно. Это означает, что нам не нужно ждать, пока плагин камеры выдаст результат, прежде чем приложение станет полностью отзывчивым. Как только фотография выбрана в приложении камеры по умолчанию, она возвращает управление приложению.
angular.module('starter') .service('CameraService', ['$q', CameraService]); function CameraService($q){ ... }
Внутри функции CameraService
мы указываем параметры по умолчанию для камеры. Мы указываем два варианта. quality
которое позволяет нам установить качество получаемой фотографии (меньшее качество означает меньший размер файла) и correctOrientation
которая автоматически корректирует ориентацию фотографии в зависимости от ориентации камеры на момент съемки.
var me = this; me.options = { quality: 80, correctOrientation: true };
CameraService
имеет один метод, getPicture
который запускает приложение камеры по умолчанию на устройстве. В начале функции мы запускаем службу $q
, вызывая метод defer
. После этого мы указываем пару вариантов. encodingType
который устанавливает формат фотографии, и sourceType
который позволяет нам указать источник фотографии, в данном случае камеру. После указания параметров приложение камеры по умолчанию запускается путем вызова navigator.camera.getPicture
. Это принимает три аргумента: функцию обратного вызова успеха, функцию обратного вызова ошибки и параметры камеры. Затем мы открываем эту функцию для внешнего мира, используя шаблон модуля выявления.
function getPicture(){ var q = $q.defer(); me.options.encodingType = Camera.EncodingType.JPEG; me.options.sourceType = Camera.PictureSourceType.CAMERA; navigator.camera.getPicture( function(result){ q.resolve(result); }, function(err){ q.reject(err); }, me.options ); return q.promise; } return { getPicture: getPicture }
Примечание Сервис
Служба заметок ( www / js / services / NoteService.js ) инкапсулирует все вызовы функций LokiJS.
(function(){ angular.module('starter') .service('NoteService', ['$q', 'Loki', NoteService]); function NoteService($q, Loki){ var db; var notes; function initialize(){ var adapter = new LokiCordovaFSAdapter({"prefix": "loki"}); db = new Loki('notes_db', { autosave: true, autosaveInterval: 1000, adapter: adapter }); } function getNotes(){ return $q(function(resolve, reject){ db.loadDatabase({}, function(){ notes = db.getCollection('notes'); if(!notes){ notes = db.addCollection('notes'); } resolve(notes.data); }); }); } function addNote(note){ notes.insert(note); } function updateNote(note){ notes.update(note); } function deleteNote(note){ notes.remove(note); } return { initialize: initialize, getNotes: getNotes, addNote: addNote, updateNote: updateNote, deleteNote: deleteNote }; } })();
Разбивка кода выше. Мы используем сервис Loki
предоставляемый LokiJS, для взаимодействия с базой данных.
(function(){ angular.module('starter') .service('NoteService', ['$q', 'Loki', NoteService]); function NoteService($q, Loki){ ... } })();
Внутри функции NoteService
находятся две переменные. db
которой хранится ссылка на текущую базу данных, с которой мы работаем, и notes
которых хранится ссылка на текущую коллекцию. Коллекции — это просто модный термин для таблиц.
var db; var notes;
Функция initialize
инициализирует базу данных, сначала создавая новый экземпляр адаптера файловой системы Cordova. Это принимает объект, содержащий параметры, которые мы хотим передать. В этом случае мы хотим указать используемый префикс. Далее мы создаем новый экземпляр Loki. Это принимает имя базы данных в качестве первого аргумента и параметры базы данных для второго. Для параметров мы устанавливаем для autosave
значение true
. Это связано с тем, что lokiJS не сохраняет данные автоматически в выбранный вами адаптер сохраняемости. Все хранится в памяти, пока вы не saveDatabase
метод saveDatabase
в db
. Установка autosave
в true
означает, что он автоматически saveDatabase
метод saveDatabase
за кулисами. Дополнительная опция с именем autosaveInterval
требуется, когда вы устанавливаете для autosave
значение true
. Это интервал в миллисекундах, в течение которого LokiJS должен сохранять данные. В приведенном ниже примере интервал установлен на 5000, что означает, что он будет сохраняться каждые 5 секунд.
function initialize(){ var adapter = new LokiCordovaFSAdapter({"prefix": "loki"}); db = new Loki('notes_db', { autosave: true, autosaveInterval: 5000, adapter: adapter }); }
Метод getNotes
загружает данные, хранящиеся в коллекции notes
. Он создается, если коллекция заметок еще не существует. Обратите внимание, что мы заключаем все в вызов службы $q
. Синтаксис отличается от службы камеры, но идея в основном та же.
function getNotes(){ return $q(function(resolve, reject){ db.loadDatabase({}, function(){ //retrieve data from notes collection notes = db.getCollection('notes'); if(!notes){ //create notes collection notes = db.addCollection('notes'); } resolve(notes.data); }); }); }
Функции addNote
, updateNote
и deleteNote
служат оболочкой для API LokiJS для вставки, обновления и удаления данных из базы данных. Все из которых принимают в качестве аргумента объект, содержащий данные заметки.
function addNote(note){ notes.insert(note); } function updateNote(note){ notes.update(note); } function deleteNote(note){ notes.remove(note); }
Раскрыть все для внешнего мира:
return { initialize: initialize, getNotes: getNotes, addNote: addNote, updateNote: updateNote, deleteNote: deleteNote };
Контроллеры
Единственным контроллером для этого приложения является контроллер заметок, отвечающий за обработку взаимодействий пользовательского интерфейса в приложении.
(function(){ angular.module('starter') .controller('NoteController', ['$scope', '$ionicModal', '$ionicPlatform', 'CameraService', 'NoteService', NoteController]); function NoteController($scope, $ionicModal, $ionicPlatform, CameraService, NoteService){ var me = this; me.notes = []; $ionicPlatform.ready(function(){ NoteService.initialize(); NoteService.getNotes().then(function(notes){ me.notes = notes; }); }); $ionicModal.fromTemplateUrl('new-note.html', { scope: $scope, animation: 'slide-in-up' }).then(function(modal){ $scope.new_note_modal = modal; }); $ionicModal.fromTemplateUrl('image-modal.html', { scope: $scope, animation: 'slide-in-up' }).then(function(modal){ $scope.image_modal = modal; }); me.truncate = function(string){ return _.truncate(string, {length: 35}); }; $scope.deleteNote = function(note){ NoteService.deleteNote(note); }; $scope.newNote = function(){ $scope.note = {}; $scope.isUpdate = false; $scope.new_note_modal.show(); }; $scope.viewNote = function(note){ $scope.note = {}; $scope.note = note; $scope.isUpdate = true; $scope.new_note_modal.show(); }; $scope.takePicture = function(){ CameraService.getPicture().then(function(photo){ $scope.note.photo = photo; }); }; $scope.saveNote = function(){ if($scope.isUpdate){ NoteService.updateNote($scope.note); }else{ NoteService.addNote($scope.note); } $scope.note = { title: '', text: '', photo: null }; $scope.new_note_modal.hide(); }; $scope.viewImage = function(image){ $scope.note.photo = image; $scope.image_modal.show(); }; $scope.closeModal = function(modal){ $scope[modal + '_modal'].hide(); }; } })();
Разбивка кода выше. Сначала мы создаем NoteController
и импортируем все необходимые нам сервисы.
(function(){ angular.module('starter') .controller('NoteController', ['$scope', '$ionicModal', '$ionicPlatform', 'CameraService', 'NoteService', NoteController]); function NoteController($scope, $ionicModal, $ionicPlatform, CameraService, NoteService){ ... })();
Вот краткое описание каждого из них:
— $scope
: позволяет получать и устанавливать данные в текущей области. Эти данные затем могут быть использованы внутри представлений.
— $ionicModal
: используется для создания модальных.
— ionicPlatform
: используется для прослушивания ready
события для Ionic.
— CameraService
: служба камеры, созданная ранее. Это позволяет нам вызывать API, предоставляемый плагином камеры.
— NoteService
: служба заметок, которую мы создали ранее. Это позволяет нам совершать вызовы API, предоставляемого LokiJS.
Объявите переменную me
которая будет использоваться как ссылка на контроллер.
var me = this;
Прикрепите пустой массив к объекту notes
и прослушайте событие ready
в $ionicPlatform
. Когда эта функция активирована, это означает, что устройство готово. Он запускается только один раз при запуске приложения, поэтому является идеальным местом для инициализации базы данных и получения данных, хранящихся в коллекции заметок.
me.notes = []; $ionicPlatform.ready(function(){ //when the device is ready NoteService.initialize(); //initialize the database //get all the notes from the notes collection NoteService.getNotes().then(function(notes){ me.notes = notes; //assign it to the controller }); });
Инициализируйте модальные поля для создания новой заметки и просмотра изображения, прикрепленного к заметке.
//modal for creating new note $ionicModal.fromTemplateUrl('new-note.html', { scope: $scope, animation: 'slide-in-up' }).then(function(modal){ $scope.new_note_modal = modal; }); //modal for viewing image attached to note $ionicModal.fromTemplateUrl('image-modal.html', { scope: $scope, animation: 'slide-in-up' }).then(function(modal){ $scope.image_modal = modal; });
Добавьте метод усечения к контроллеру. Все это ограничивает количество символов в строке. При этом используется метод truncate
в lodash, который принимает строку, с которой вы хотите работать, в качестве первого аргумента и объект, содержащий параметры. В этом случае мы хотим ограничить количество символов до 35 символов минус 3 символа, используемых для указания того, что строка была усечена. По умолчанию в lodash используются тройные точки ( … ).
me.truncate = function(string){ return _.truncate(string, {length: 35}); };
Присоедините функцию deleteNote
к текущей области. Это принимает объект, содержащий данные для конкретной заметки в качестве аргумента. Все, что он делает, это вызывает метод deleteNote
из NoteService
.
$scope.deleteNote = function(note){ NoteService.deleteNote(note); };
Функция newNote
вызывается при каждом нажатии кнопки новой заметки . Это очищает заметку, хранящуюся в настоящее время в $scope
и устанавливает isUpdate
в false
. Это будет служить переключателем для определения того, является ли текущая заметка существующей (см. Метод saveNote
). Наконец, он открывает модальные для создания новых заметок.
$scope.newNote = function(){ $scope.note = {}; $scope.isUpdate = false; $scope.new_note_modal.show(); };
Функция viewNote
вызывается всякий раз, когда пользователь дважды нажимает на конкретную заметку. Сначала сбросьте примечание, сохраненное в области, и назначьте ему новое значение. Он устанавливает для isUpdate
значение true
потому что мы пытаемся открыть существующую заметку, поэтому понятно, что пользователь пытается ее просмотреть или обновить. Наконец мы покажем модал.
$scope.viewNote = function(note){ $scope.note = {}; $scope.note = note; $scope.isUpdate = true; $scope.new_note_modal.show(); };
Функция takePicture
вызывается, когда пользователь takePicture
значок камеры в заголовке для нового модального примечания. Он запускает приложение камеры по умолчанию и при выборе фотографии присваивает его текущей области.
$scope.takePicture = function(){ CameraService.getPicture().then(function(photo){ $scope.note.photo = photo; }); };
Функция saveNote
сохраняет и обновляет заметку в базе данных. Он использует свойство isUpdate
для определения необходимости обновления или создания текущей заметки, хранящейся в области $scope
. Как только это будет сделано, мы очищаем заметку и закрываем новую модальную заметку.
$scope.saveNote = function(){ if($scope.isUpdate){ NoteService.updateNote($scope.note); }else{ NoteService.addNote($scope.note); } $scope.note = { title: '', text: '', photo: null }; $scope.new_note_modal.hide(); };
Функция viewImage
показывает вложение изображения в полноэкранном режиме.
$scope.viewImage = function(image){ $scope.note.photo = image; $scope.image_modal.show(); };
Функция closeModal
закрывает модальный closeModal
, указанный в качестве аргумента.
$scope.closeModal = function(modal){ $scope[modal + '_modal'].hide(); };
Взгляды
В приложении есть одно представление — представление заметок, поэтому создайте www / templates / notes.html и добавьте следующее.
<ion-view title="Notes" ng-controller="NoteController as note_ctrl"> <header class="bar bar-header bar-stable"> <h1 class="title">Notes</h1> <button class="button button-clear" ng-click="newNote()">New Note</button> </header> <ion-content class="has-header padding"> <div class="card" ng-repeat="note in note_ctrl.notes"> <div class="item item-text-wrap" on-double-tap="viewNote(note)" on-drag-right="deleteNote(note)"> <strong>{{note.title}}</strong> <div class="description">{{note_ctrl.truncate(note.text)}}</div> </div> </div> <div class="card" ng-if="note_ctrl.notes.length === 0"> <div class="item item-text-wrap"> No notes yet </div> </div> </ion-content> <script id="new-note.html" type="text/ng-template"> <ion-modal-view> <ion-header-bar class="bar-stable"> <button class="button button-clear icon ion-camera" ng-click="takePicture()"></button> <h1 class="title">New Note</h1> <button class="button button-clear" ng-click="closeModal('new_note')">Close</button> </ion-header-bar> <ion-content> <div class="list"> <label class="item item-input item-stacked-label"> <span class="input-label">Title</span> <input type="text" ng-model="note.title"> </label> <label class="item item-input item-stacked-label"> <span class="input-label">Note</span> <textarea ng-model="note.text"></textarea> </label> </div> <div class="padding" ng-if="note.photo"> <button class="button button-clear icon ion-image" ng-click="viewImage(note.photo)"></button> </div> <div class="padding"> <button class="button button-positive button-block" ng-click="saveNote()"> Save </button> </div> </ion-content> </ion-modal-view> </script> <script id="image-modal.html" type="text/ng-template"> <div class="modal image-modal transparent" ng-click="closeModal('image')"> <img ng-src="{{note.photo}}" class="fullscreen-image" /> </div> </script> </ion-view>
Разбивка кода выше. Сначала мы обертываем все в <ion-view>
. Это стандартный контейнер для просмотра содержимого и любой навигационной информации и информации панели заголовка. Мы указали два атрибута, title
, который является именем, которое вы хотите дать представлению, и ng-controller
, который позволяет нам указать контроллер, используемый для этого представления. Псевдоним note_ctrl
указан так, чтобы мы могли ссылаться на любые данные или функции, подключенные к контроллеру, используя псевдоним.
<ion-view title="Notes" ng-controller="NoteController as note_ctrl"> </ion-view>
Тег <header>
создает заголовок для приложения и содержит элемент <h1>
который отображает заголовок текущей страницы и элемент <button>
для создания новой заметки. Атрибут ng-click
указывает, что при нажатии этой кнопки будет выполняться функция newNote
прикрепленная к области $scope
.
<header class="bar bar-header bar-stable"> <h1 class="title">Notes</h1> <button class="button button-clear" ng-click="newNote()">New Note</button> </header>
<ion-content>
используется для указания основного содержимого представления. В этом случае основным контентом является список заметок, сохраненных в данный момент в базе данных.
Для этого мы создаем <div>
с классом card
и используем директиву ng-repeat
чтобы повторять элемент для каждой заметки, хранящейся в массиве notes
. Внутри каждой карты есть <div>
с классом item
. Это служит контейнером для любого контента, который вы хотите добавить внутри карты. Здесь мы просто выводим название заметки и ее описание.
Атрибут on-double-tap
указывает функцию, выполняемую каждый раз, когда пользователь дважды нажимает на элемент. Это вызывает функцию viewNote
которая открывает модальное viewNote
для просмотра заметки. Атрибут on-drag-right
указывает функцию, выполняемую при перетаскивании элемента вправо. Это обычный жест для удаления чего-либо, поэтому мы прикрепили к deleteNote
функцию deleteNote
. Если в настоящее время в базе данных нет заметок, мы заявляем, что заметок еще нет.
<ion-content class="has-header padding"> <div class="card" ng-repeat="note in note_ctrl.notes"> <div class="item item-text-wrap" on-double-tap="viewNote(note)" on-drag-right="deleteNote(note)"> <strong>{{note.title}}</strong> <div class="description">{{note_ctrl.truncate(note.text)}}</div> </div> </div> <div class="card" ng-if="note_ctrl.notes.length === 0"> <div class="item item-text-wrap"> No notes yet </div> </div> </ion-content>
В Ionic модалы создаются с использованием <script>
который имеет тип text/ng-template
и обязательный атрибут id
. Затем содержимое помещается в <ion-modal-view>
. Внутри находится заголовок и основной контент. Заголовок содержит кнопку для запуска приложения камеры по умолчанию, заголовок модального окна и кнопку для закрытия модального окна. Основным содержанием является форма, содержащая текстовые поля для заголовка и описания, а также кнопка для сохранения заметки. Если к заметке прикреплена фотография, добавляется кнопка со значком. Это откроет фотографию в полноэкранном режиме при нажатии пользователем.
<script id="new-note.html" type="text/ng-template"> <ion-modal-view> <ion-header-bar class="bar-stable"> <button class="button button-clear icon ion-camera" ng-click="takePicture()"></button> <h1 class="title">New Note</h1> <button class="button button-clear" ng-click="closeModal('new_note')">Close</button> </ion-header-bar> <ion-content> <div class="list"> <label class="item item-input item-stacked-label"> <span class="input-label">Title</span> <input type="text" ng-model="note.title"> </label> <label class="item item-input item-stacked-label"> <span class="input-label">Note</span> <textarea ng-model="note.text"></textarea> </label> </div> <div class="padding" ng-if="note.photo"> <button class="button button-clear icon ion-image" ng-click="viewImage(note.photo)"></button> </div> <div class="padding"> <button class="button button-positive button-block" ng-click="saveNote()"> Save </button> </div> </ion-content> </ion-modal-view> </script>
Модальное изображение отображает прикрепленное изображение в полноэкранном режиме. Это показано, когда viewImage
функция viewImage
.
<script id="image-modal.html" type="text/ng-template"> <div class="modal image-modal transparent" ng-click="closeModal('image')"> <img ng-src="{{note.photo}}" class="fullscreen-image" /> </div> </script>
стайлинг
Поскольку Ionic Framework обрабатывает большинство стилей для нас, все, что нам нужно сделать, это добавить стили для описания заметки и полноэкранного изображения:
Добавьте следующее в www / css / style.css
.description { color: #737373; font-size: 15px; } .fullscreen-image { max-width: 100%; max-height: 100%; bottom: 0; left: 0; margin: auto; overflow: auto; position: fixed; right: 0; top: 0; }
Бутстрапирование
Чтобы собрать все вместе, откройте файл www / js / app.js и зарегистрируйте lokijs
сразу после ionic
.
angular.module('starter', ['ionic', 'lokijs'])
Затем добавьте конфигурацию для состояния приложения. Поскольку у нас есть одна страница, у нас есть только одно состояние, называемое notes
. Используя метод state
мы указываем имя состояния, URL, к которому он может быть получен, и путь к представлению (относительно каталога www ). Наконец, мы указываем URL по умолчанию, по которому нужно перейти.
.config(function($stateProvider, $urlRouterProvider) { $stateProvider .state('notes', { url: '/notes', templateUrl: 'templates/notes.html' }); // if none of the above states are matched, use this as the fallback $urlRouterProvider.otherwise('/notes'); });
Вывод
Это оно! Из этого руководства вы узнали, как использовать LokiJS для сохранения данных в приложениях Cordova. Используя LokiJS, мы выполнили основные операции CRUD (создание, чтение, обновление, удаление) в базе данных. Мы едва коснулись поверхности этого урока, поэтому вот несколько тем для изучения:
- Find : в этом руководстве мы не выполняли никаких сложных запросов, поэтому ознакомьтесь с документацией по методу find .
- API изменений : если вам нужно сохранить данные на сервере, API изменений будет полезен. Это позволяет синхронизировать изменения, сделанные в локальной копии базы данных, на удаленном сервере.
Полный код этого руководства можно найти на Github. Если у вас есть вопросы или комментарии, сообщите мне об этом ниже.