О чем эта статья. Я хочу поделиться опытом разработки мобильного приложения на phonegap. В итоге получился целый програмный комплекс с RESTfull сервером, клиентами, да еще
Для нетерпеливых:
Страница проекта
Исходный код
Думаю, сайт сразу ляжет от хабраэффекта. Это триальный аккаунт с всего-лишт двумя слотами для ноды. Но об этом в конце.
Хочу сразу уточнить — проект не закончен, но уже стабильно работает, плюс можно показать основные части и архитектурные решения.
Начнем, пожалуй, с самого главного — клиента. Я использовал (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.
Автор: peinguin