О чем эта статья. Я хочу поделиться опытом разработки мобильного приложения на phonegap. В итоге получился целый програмный комплекс с RESTfull сервером, клиентами, да еще хостится на PaaS. Поэтому я опишу отдельно архитектуру клиентского приложения (html5 single page app, завернутое в phonegap), серверного (nodejs с swagger-node-express + node-orm2), и как разместить все это на openshift PaaS.
Для нетерпеливых:
Страница проекта
Исходный код
Думаю, сайт сразу ляжет от хабраэффекта. Это триальный аккаунт с всего-лишт двумя слотами для ноды. Но об этом в конце.
Хочу сразу уточнить — проект не закончен, но уже стабильно работает, плюс можно показать основные части и архитектурные решения.
Начнем, пожалуй, с самого главного — клиента. Я использовал (ex)twitter bootstrap, я понимаю что шаблон пока не очень — но главное это js логика. Само приложение построено на require.js, хотя я сам не против минимизации всего проекта. Дело в том, что на телефоне файлы быстро будут подгружатся, а для сайта, вообще-то, планируется отдельное приложение в дальнейшем. В качестве javascript фреймворка я выбрал marionette.
Сейчас реализовано два основных модуля: Auth и Conferences.
Отключил автостарт:
this.startWithParent = false;
И вручную запускаю их после инициализации главного модуля.
require( [ 'css!bootstrap_css', 'bootstrap', 'app/modules/conferences', 'app/modules/auth', ], function () { app.start(); } );
MyConference.addInitializer(function(options){ mainLayout = new MainLayout; MyConference.mainView.show(mainLayout); var headerView = new HeaderView; headerView.MyConference = MyConference; mainLayout.header.show(headerView); MyConference.Conferences.start(); MyConference.Auth.start(); });
Auth — регистрация/авторизация. Хочу обратить внимание на социальную авторизацию. Я всегда сам реализую авторизацию и не пользуюсь сторонними агрегаторами, незнаю хорошо это или плохо. Реализовано Google, LinkedIn, Facebook, Twitter, можете просто взять мой код, если вам нужно реализовать у себя что-то похожее. Суть социальной авторизации в том, что я с помощью js на клиенте получаю Api key, а потом передаю его на сервер для проверки. Например facebook:
var afterInit = function(){ var sendAccessToken = function(response){ $.post( cfg.baseUrl + 'auth.json/facebook', {FacebookKEY: response.authResponse.accessToken}, function(data, message, xhr){ process_social_resporce(model, data, xhr); }, "text" ); } FB.getLoginStatus(function(response) { if(response.status == "not_authorized" || response.status == "unknown"){ FB.login(function(response, a) { if (response.authResponse) { sendAccessToken(response); } else { console.log(response, a) } }, {scope:'email'}); }else{ sendAccessToken(response); } }); } window.fbAsyncInit = function() { FB.init({ appId : cfg.facebookAppId, // App ID status : true, // check login status cookie : true, // enable cookies to allow the server to access the session xfbml : true // parse XFBML }); afterInit(); }; // Load the SDK asynchronously (function(d){ var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0]; if (d.getElementById(id)) {return afterInit();} js = d.createElement('script'); js.id = id; js.async = true; js.src = "//connect.facebook.net/en_US/all.js"; ref.parentNode.insertBefore(js, ref); }(document));
Для постов/лайков мне этот ключ не нужен, только узнать что это за пользователь. Так что, если вам нужно использовать offline доступ, то такой метод может не сработать из-за того, что нужно получать отдельный ключ, который js клиентам не выдают.
Отдельная история с twitter. У него нет браузерной клиентской авторизации, поэтому я реализовал серверную. Открывается окошко, там пользователь авторизируется и потом родительское окно считывает ответ с дочернего. Это может работать не во всех браузерах, так что, скорее всего, придется немного его изменить. Но в android проложении работает нормально.
var childWin = window.open(cfg.baseUrl + 'auth.json/twitter/'+Storage.get('API_KEY'), 'Twitter Auth', "height=640,width=480"); childWin.onunload = function(){ var check = function(){ if(childWin.document){ var body = childWin.document.getElementsByTagName("body")[0]; if(!model.isNew() || body.textContent.length > 0){ process_social_resporce(model, body.textContent); childWin.close(); }else{ setTimeout(check, 100); } }else{ setTimeout(check, 100); } } setTimeout(check, 100); }
Теперь перейдем к основному модулю — Conferences. Здесь, на самом деле, все очень просто. Описываю контролер з роутами.
var ConferencesController = Marionette.Controller.extend(new function(){ return { main: function(){ MyConference.mainView.currentView.header.currentView.setHeader('Conferences'); var conferencesCollection = new ConferencesCollection; var spinnerView = new SpinnerView(); spinnerView.render(); conferencesCollection.fetch({ error: function(){ console.log('error'); }, success: function(collection){ var mainView = new MainView; mainView.collection = conferencesCollection; MyConference.mainView.currentView.content.show(mainView); spinnerView.remove(); } }) }, conference: function(id){ var conferenceModel = new ConferenceModel; conferenceModel.set('id', id); conferenceModel.fetch({ error: function(){ var conferenceNotFoundView = new ConferenceNotFoundView; MyConference.mainView.currentView.content.show(conferenceNotFoundView); }, success: function(conference){ var conferenceFullView = new ConferenceFullView; conferenceFullView.model = conference; MyConference.mainView.currentView.content.show(conferenceFullView); } }); }, streams: function(conference_id){ ShowStreams( conference_id, function(){ ShowStream( MyConference. mainView. currentView. content. currentView. model. streams. at(0). get('id') ); } ); }, stream: function(id){ ShowStream(id); } } }); var MainRouter = Backbone.Marionette.AppRouter.extend({ appRoutes: { "conference/:id": "conference", "conferences": "main", "": "main", "streams/:conference_id": "streams", "stream/:id": "stream" }, controller: new ConferencesController });
Как видно, создается обычная бекбоновская модель/коллекция, получаются данные и передается в вьюху. Список конференций это обычный CollectionView. Детальнее остановлюсь для View подробного описания конференции. Поддержка OpenStreetMap и GoogleMaps реализована вручную. Можно, конечно, использовать Leaflet, но я не уверен, что гуглу нравится прямой доступ к их картинкам. Так же там картинка/pdf отображается или ссылка на файл, если есть. И вверху справа ссылка на список докладов.
Если пользователь залогинен, он видит три кнопки, «пойти» на конференцию, в избранное и отказаться. Детальное описание я наследовал не от ItemView, а от Layout, поэтому просто определил блок, куда рендерить эти три кнопки.
regions: { decision: "#decision" },
И в зависимости от статуса пользователя, показываю ту или иную вьюху.
if(MyConference.Auth.getUser().isNew()){ var view = new GuestDecisionView; }else{ var desisionModel = new DecisionModel(view.model.get('decision')); var view = new LoggedInDecisionView({model: desisionModel}); view.parent = this; } this.decision.show(view);
Осталось только собрать и запустить проет на телефоне. Если нужно додать платформу выполните «cordova platform add android»
cordova platform add android
Потом
cordova build android
Для сборки или
cordova run android
Чтобы посмотреть у себя на телефоне.
Конечной целью является размещение приложений в Google play, Apple App Store и Windows Store. Но основной моей деятельностью является web, а не мобильная, разработка, поэтому я еще не зарегистрирован как разработчик ни в одном из этих магазинов.
Надеюсь кому-то будет полезна эта статья. Я пытался не сильно ее раздувать, но обратить внимание на все основные моменты. Буду рад критике, пожеланиям, pull request’ам в репозиторий. Из-за того, что материала вышло много, я его разбил на две статьи — клиент и сервер. В следующей статье я опишу создание restfull сервера на nodejs с orm’ом автодокументацией и memcached’ом. И как я деплоил все это на PaaS от RedHat — openshift.
ссылка на оригинал статьи http://habrahabr.ru/post/200192/
Добавить комментарий