html / css와 자바 스크립트로 만든 모바일 앱을 만들고 싶습니다. JavaScript로 웹 앱을 빌드하는 방법에 대한 적절한 지식을 가지고 있지만 jquery-mobile과 같은 프레임 워크를 살펴볼 수 있다고 생각했습니다.
처음에는 jquery-mobile이 모바일 브라우저를 대상으로하는 위젯 프레임 워크에 불과하다고 생각했습니다. jquery-ui와 매우 유사하지만 모바일 세계에 적합합니다. 그러나 나는 jquery-mobile이 그 이상이라는 것을 알았습니다. 많은 아키텍처와 함께 제공되며 선언적 HTML 구문으로 앱을 만들 수 있습니다. 따라서 가장 쉽게 생각할 수있는 앱의 경우 한 줄의 자바 스크립트를 직접 작성할 필요가 없습니다 (우리 모두가 덜 일하는 것을 좋아하기 때문에 멋지죠?)
선언적 html 구문을 사용하여 앱을 만드는 접근 방식을 지원하려면 jquery-mobile과 knockoutjs를 결합하는 것이 좋습니다. Knockoutjs는 WPF / Silverlight에서 알려진 MVVM 슈퍼 파워를 JavaScript 세계로 가져 오는 것을 목표로하는 클라이언트 측 MVVM 프레임 워크입니다.
저에게 MVVM은 새로운 세상입니다. 나는 이미 그것에 대해 많이 읽었지만 실제로 전에는 실제로 사용한 적이 없습니다.
따라서이 게시물은 jquery-mobile과 knockoutjs를 함께 사용하여 앱을 아키텍처하는 방법에 관한 것입니다. 내 생각은 내가 몇 시간 동안 살펴본 후 생각 해낸 접근 방식을 적고 jquery-mobile / knockout yoda에 댓글을 달도록하여 이것이 왜 짜증나는지, 왜 처음에 프로그래밍을하지 말아야하는지 보여주는 것이 었습니다. 장소 😉
HTML
jquery-mobile은 페이지의 기본 구조 모델을 제공합니다. 나중에 ajax를 통해 페이지를로드 할 수 있다는 것을 잘 알고 있지만 모든 페이지를 하나의 index.html 파일에 보관하기로 결정했습니다. 이 기본 시나리오에서 우리는 두 페이지에 대해 이야기하고 있습니다.
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<link rel="stylesheet" href="libs/jquery-mobile/jquery.mobile-1.0a4.1.css" />
<link rel="stylesheet" href="app/base/css/base.css" />
<script src="libs/jquery/jquery-1.5.0.min.js"></script>
<script src="libs/knockout/knockout-1.2.0.js"></script>
<script src="libs/knockout/knockout-bindings-jqm.js" type="text/javascript"></script>
<script src="libs/rx/rx.js" type="text/javascript"></script>
<script src="app/App.js"></script>
<script src="app/App.ViewModels.HomeScreenViewModel.js"></script>
<script src="app/App.MockedStatisticsService.js"></script>
<script src="libs/jquery-mobile/jquery.mobile-1.0a4.1.js"></script>
</head>
<body>
<!-- Start of first page -->
<div data-role="page" id="home">
<div data-role="header">
<h1>Demo App</h1>
</div><!-- /header -->
<div data-role="content">
<div class="ui-grid-a">
<div class="ui-block-a">
<div class="ui-bar" style="height:120px">
<h1>Tours today (please wait 10 seconds to see the effect)</h1>
<p><span data-bind="text: toursTotal"></span> total</p>
<p><span data-bind="text: toursRunning"></span> running</p>
<p><span data-bind="text: toursCompleted"></span> completed</p>
</div>
</div>
</div>
<fieldset class="ui-grid-a">
<div class="ui-block-a"><button data-bind="click: showTourList, jqmButtonEnabled: toursAvailable" data-theme="a">Tour List</button></div>
</fieldset>
</div><!-- /content -->
<div data-role="footer" data-position="fixed">
<h4>by Christoph Burgdorf</h4>
</div><!-- /header -->
</div><!-- /page -->
<!-- tourlist page -->
<div data-role="page" id="tourlist">
<div data-role="header">
<h1>Bar</h1>
</div><!-- /header -->
<div data-role="content">
<p><a href="#home">Back to home</a></p>
</div><!-- /content -->
<div data-role="footer" data-position="fixed">
<h4>by Christoph Burgdorf</h4>
</div><!-- /header -->
</div><!-- /page -->
</body>
</html>
자바 스크립트
이제 재미있는 부분 인 JavaScript에 대해 알아 보겠습니다!
앱 계층화에 대해 생각하기 시작했을 때 몇 가지 사항 (예 : 테스트 가능성, 느슨한 결합)을 염두에 두었습니다. 파일을 분할하기로 결정한 방법을 보여주고 이동하는 동안 내가 왜 다른 것을 선택했는지에 대해 설명합니다.
App.js
var App = window.App = {};
App.ViewModels = {};
$(document).bind('mobileinit', function(){
// while app is running use App.Service.mockStatistic({ToursCompleted: 45}); to fake backend data from the console
var service = App.Service = new App.MockedStatisticService();
$('#home').live('pagecreate', function(event, ui){
var viewModel = new App.ViewModels.HomeScreenViewModel(service);
ko.applyBindings(viewModel, this);
viewModel.startServicePolling();
});
});
App.js는 내 앱의 진입 점입니다. App 개체를 만들고 뷰 모델에 대한 네임 스페이스를 제공합니다 (곧 제공 될 예정). jquery-mobile이 제공 하는 mobileinit 이벤트를 수신 합니다.
보시다시피, 저는 일종의 ajax 서비스 (나중에 살펴볼 것)의 인스턴스를 만들고 “service”변수에 저장합니다.
또한 서비스 인스턴스를 전달받는 viewModel의 인스턴스를 만드는 홈페이지에 대한 pagecreate 이벤트를 연결합니다 .이 점은 저에게 필수적입니다. 누군가 생각한다면, 이것은 다르게해야합니다. 여러분의 생각을 공유 해주세요!
요점은 뷰 모델이 서비스 (GetTour /, SaveTour 등)에서 작동해야한다는 것입니다. 그러나 나는 ViewModel이 그것에 대해 더 이상 알기를 원하지 않습니다. 예를 들어, 우리의 경우 백엔드가 아직 개발되지 않았기 때문에 모의 ajax 서비스를 전달하고 있습니다.
내가 언급해야 할 또 다른 것은 ViewModel이 실제 뷰에 대한 지식이 없다는 것입니다. 그래서 pagecreate 핸들러 내에서 ko.applyBindings (viewModel, this)를 호출 합니다. 보다 쉽게 테스트 할 수 있도록 뷰 모델을 실제 뷰와 분리하여 유지하고 싶었습니다.
App.ViewModels.HomeScreenViewModel.js
(function(App){
App.ViewModels.HomeScreenViewModel = function(service){
var self = {}, disposableServicePoller = Rx.Disposable.Empty;
self.toursTotal = ko.observable(0);
self.toursRunning = ko.observable(0);
self.toursCompleted = ko.observable(0);
self.toursAvailable = ko.dependentObservable(function(){ return this.toursTotal() > 0; }, self);
self.showTourList = function(){ $.mobile.changePage('#tourlist', 'pop', false, true); };
self.startServicePolling = function(){
disposableServicePoller = Rx.Observable
.Interval(10000)
.Select(service.getStatistics)
.Switch()
.Subscribe(function(statistics){
self.toursTotal(statistics.ToursTotal);
self.toursRunning(statistics.ToursRunning);
self.toursCompleted(statistics.ToursCompleted);
});
};
self.stopServicePolling = disposableServicePoller.Dispose;
return self;
};
})(App)
객체 리터럴 구문을 사용하는 대부분의 knockoutjs 뷰 모델 예제를 찾을 수 있지만 저는 ‘self’헬퍼 객체와 함께 전통적인 함수 구문을 사용하고 있습니다. 기본적으로 맛의 문제입니다. 그러나 하나의 관찰 가능한 속성이 다른 속성을 참조하도록하려면 객체 리터럴을 한 번에 적어 둘 수 없으므로 대칭성이 떨어집니다. 이것이 제가 다른 구문을 선택하는 이유 중 하나입니다.
다음 이유는 앞서 언급했듯이 매개 변수로 전달할 수있는 서비스입니다.
이 뷰 모델에는 내가 올바른 방법을 선택했는지 확실하지 않은 한 가지가 더 있습니다. 서버에서 결과를 가져 오기 위해 주기적으로 ajax 서비스를 폴링하고 싶습니다. 그래서 저는 startServicePolling / stopServicePolling 메서드를 구현 하기로 선택했습니다. 아이디어는 pageshow에서 폴링을 시작하고 사용자가 다른 페이지로 이동할 때 중지하는 것입니다.
서비스를 폴링하는 데 사용되는 구문을 무시할 수 있습니다. RxJS 마법입니다. 내가 그것을 폴링하고 있는지 확인하고 Subscribe (function (statistics) {..}) 부분 에서 볼 수 있듯이 반환 된 결과로 관찰 가능한 속성을 업데이트하십시오 .
App.MockedStatisticsService.js
네, 보여 드릴 것이 하나 남았습니다. 실제 서비스 구현입니다. 여기서 자세히 설명하지는 않겠습니다. getStatistics 가 호출 될 때 일부 숫자를 반환하는 모의입니다 . 앱이 실행되는 동안 브라우저 js 콘솔을 통해 새 값을 설정하는 데 사용하는 또 다른 mockStatistics 메서드가 있습니다 .
(function(App){
App.MockedStatisticService = function(){
var self = {},
defaultStatistic = {
ToursTotal: 505,
ToursRunning: 110,
ToursCompleted: 115
},
currentStatistic = $.extend({}, defaultStatistic);;
self.mockStatistic = function(statistics){
currentStatistic = $.extend({}, defaultStatistic, statistics);
};
self.getStatistics = function(){
var asyncSubject = new Rx.AsyncSubject();
asyncSubject.OnNext(currentStatistic);
asyncSubject.OnCompleted();
return asyncSubject.AsObservable();
};
return self;
};
})(App)
좋아, 처음에 쓸 계획이었던만큼 더 많이 썼다. 손가락이 아팠고, 개들이 산책을시켜달라고하는데 지쳤습니다. 여기에 빠진 것이 많고 오타와 문법 실수를 많이 넣었을 것입니다. 명확하지 않은 내용이 있으면 소리를 지르면 나중에 게시물을 업데이트하겠습니다.
게시물이 질문으로 보이지 않을 수도 있지만 실제로는 그렇습니다! 내 접근 방식에 대한 귀하의 생각을 공유하고 그것이 좋거나 나쁘다고 생각하거나 내가 놓친 것이라면 공유하고 싶습니다.
최신 정보
이 게시물이 큰 인기를 얻었고 여러 사람이 요청했기 때문에이 예제의 코드를 github에 넣었습니다.
https://github.com/cburgdorf/stackoverflow-knockout-example
뜨거울 때 사세요!
답변
참고 : jQuery 1.7부터이
.live()
메서드는 더 이상 사용되지 않습니다..on()
이벤트 핸들러를 연결하는 데 사용 합니다. jQuery를 이전 버전의 사용자는 사용해야.delegate()
에 우선.live()
.
나는 똑같은 일을하고 있습니다 (knockout + jquery mobile). 내가 배운 것에 대한 블로그 게시물을 작성하려고하는데 그동안 몇 가지 지침이 있습니다. 나는 또한 knockout / jquery 모바일을 배우려고 노력하고 있다는 것을 기억하십시오.
보기 모델 및 페이지
jQuery Mobile 페이지 당 하나의 뷰 모델 객체 만 사용하십시오. 그렇지 않으면 여러 번 트리거되는 클릭 이벤트에 문제가 발생할 수 있습니다.
모델보기 및 클릭
보기 모델 클릭 이벤트에는 ko.observable-fields 만 사용하십시오.
ko.applyBinding 한 번
가능한 경우 : 모든 페이지에 대해 ko.applyBinding을 한 번만 호출하고 ko.applyBinding을 여러 번 호출하는 대신 ko.observable을 사용합니다.
pagehide 및 ko.cleanNode
pagehide에서 일부 뷰 모델을 정리하는 것을 잊지 마십시오. ko.cleanNode는 jQuery Mobiles 렌더링을 방해하는 것 같습니다. 이로 인해 html이 다시 렌더링됩니다. 페이지에서 ko.cleanNode를 사용하는 경우 데이터 역할을 제거하고 렌더링 된 jQuery Mobile html을 소스 코드에 삽입해야합니다.
$('#field').live('pagehide', function() {
ko.cleanNode($('#field')[0]);
});
페이지 숨기기 및 클릭
클릭 이벤트에 바인딩하는 경우 .ui-btn-active를 정리해야합니다. 이를 수행하는 가장 쉬운 방법은 다음 코드 스 니펫을 사용하는 것입니다.
$('[data-role="page"]').live('pagehide', function() {
$('.ui-btn-active').removeClass('ui-btn-active');
});