Recommended
PPTX
Basics to framework programming
PPTX
PDF
Android activities & views
PPTX
Std 12 Computer Chapter 2 Cascading Style Sheet and Javascript (Part-2)
PDF
PDF
PPT
PDF
UDA-Componentes RUP. Formulario
PDF
Spring Boot - Uma app do 0 a Web em 30 minutos
PDF
ODP
Introduction to Progressive Web Apps (PWA)
PPTX
PPTX
Progressive Web-App (PWA)
PDF
Web Worker, Service Worker and Worklets
PDF
UDA-Componentes RUP. Autocomplete
PPTX
Web application architecture
PDF
Service Worker Presentation
PDF
PDF
PPT
PPTX
Introduction to Mobile Development
PDF
PPTX
PPTX
PDF
Data Binding and Data Grid View Classes
PPTX
Introduction to ASP.Net Viewstate
PDF
Core Location and Map Kit: Bringing Your Own Maps [Voices That Matter: iPhone...
PPT
Android User Interface: Basic Form Widgets
PDF
PPTX
[124] 하이브리드 앱 개발기 김한솔
More Related Content
PPTX
Basics to framework programming
PPTX
PDF
Android activities & views
PPTX
Std 12 Computer Chapter 2 Cascading Style Sheet and Javascript (Part-2)
PDF
PDF
PPT
PDF
UDA-Componentes RUP. Formulario
What's hot
PDF
Spring Boot - Uma app do 0 a Web em 30 minutos
PDF
ODP
Introduction to Progressive Web Apps (PWA)
PPTX
PPTX
Progressive Web-App (PWA)
PDF
Web Worker, Service Worker and Worklets
PDF
UDA-Componentes RUP. Autocomplete
PPTX
Web application architecture
PDF
Service Worker Presentation
PDF
PDF
PPT
PPTX
Introduction to Mobile Development
PDF
PPTX
PPTX
PDF
Data Binding and Data Grid View Classes
PPTX
Introduction to ASP.Net Viewstate
PDF
Core Location and Map Kit: Bringing Your Own Maps [Voices That Matter: iPhone...
PPT
Android User Interface: Basic Form Widgets
Viewers also liked
PDF
PPTX
[124] 하이브리드 앱 개발기 김한솔
PDF
[D2 오픈세미나]3.web view hybridapp
PDF
PDF
모바일앱개발 교육자료
PDF
[D2 오픈세미나]4.네이티브앱저장통신
PPTX
PPTX
PPTX
Mobile Application Development Platform "Morpheus"
PDF
PDF
PPTX
PPTX
PDF
H3 2011 하이브리드 앱의 미래, 앱스프레소 1.0
PPTX
PPTX
PDF
Native vs. Web vs. Hybrid: Mobile Development Choices
PDF
VMworld 2013: US Air National Guard - DoD Private Cloud Initiative –How Virtu...
PDF
PDF
Similar to H3 2011 하이브리드 앱 아키텍쳐 및 개발방법
PDF
PDF
PDF
2012, 대한민국 웹 표준, 그 기로에 서다
PDF
PDF
[HCI2011]모바일웹 UI패턴 및 UI플랫폼_김창겸,문승현_배포용
PDF
H3 2011 하이브리드 앱 아키텍쳐 및 개발방법_아임IN Lab팀_장동수
PDF
PDF
Mozilla 오픈 웹 모바일 플랫폼 (2012)
PDF
캠프앱 개발 사례를 통해 본 하이브리드앱 어디까지 | Devon 2012
PDF
PDF
HTML5 기반 다매체 연동형 서비스 발전 방향(티비스톰 정운교 이사)
PDF
[H3 2012] 하이브리드앱 제작 사례 공유 - 푸딩얼굴인식 3.0
PDF
[uengine.org-uEngine Day] 스마트폰과BPM의만남:프로세스터치프로젝트발표자료
PDF
PDF
Top 10 Questions about HTML5
PDF
HTML5 관점에서 2015년 웹 앱 개발 동향과 사례 및 2016년 발전 방향 저...
PDF
HTML5 관점에서 본 2014 모바일 웹 앱 개발 동향과 사례 및 발전 방향 전망
PDF
K모바일발표 111026 하이브리드ux_배포용
PDF
D4 이상찬-bridge overtroubledwater-no_movie_sbs
PDF
Javascript and Web Performance
More from KTH
PDF
H3 2011 안드로이드의 Seamless UX를 위한 Activity 활용전략
PDF
H3 2011 앱 개발에 날개를 달자, 모바일 클라우드가 꿈꾸는 미래
PDF
H3 2011 iOS5 새로운 기능들의 프로젝트 적용 사례
PDF
H3 2011 반응형 웹디자인, 진짜 할 만 한가?
PDF
H3 2011 하이브리드 클라우드 활용방안 및 도입전략
PDF
H3 2011 모바일 시대의 Search Engine Optimization 전략
PDF
H3 2011 파이썬으로 클라우드 하고 싶어요
PDF
H3 2011 대형사이트 구축을 위한 MySQL 튜닝전략
PDF
H3 2011 HTML/CSS로 만들어진 디테일이 살아있는 앱 제작의 노하우를 모두 공개합니다!
PDF
H3 2011 모바일에서의 Location API 완전정복
PDF
H3 2011 클라우드 컴퓨팅 AWS 글로벌 서비스 구축을 위한 선택
PDF
H3 2011 UX에 대한 7가지 오해와 진실
PDF
PDF
H3 2011 Google을 통해 살펴보는 분산 파일 시스템의 현재와 미래
H3 2011 하이브리드 앱 아키텍쳐 및 개발방법 1. 2. 3. 4. 하이브리드앱아키텍쳐구성요소
네이티브 하이브리드 웹
UI툴킷 웹UI툴킷 자바스크립트프레임웍/라이브러리
웹표준기술
프레임웍
HTML5 CSS 자바스크립트
네이티브라이브러리 비표준DeviceAPIs 표준DeviceAPIs
개발도구 웹브라우져“엔진” 웹브라우져“앱”
플랫폼SDK
안드로이드
iOSSDK 윈폰7SDK …⋯
SDK
4
5. 6. 7. 8. 9. 네이티브와웹의결합
Flash/Flex?
Active-X?
JavaApplet? 문제는....
다리!!
Native-Web
네이티브 Bridge 웹
9
10. 네이티브와웹의결합
WebView
WebViewClientWebChromeClient
loadUrl
addJavascriptInterface
UIWebView
UIWebViewDelegate
loadRequest
stringByEvaluatingJavascriptFromString
10
11. 네이티브와웹의결합
자바스크립트
캐시
그래봤자,
문자열~
URL 쿠키
어차피,
꼼수
그리고...
HTTP!
그림 출처: http://petticoatsandpistols.com/2010/05/12/
11
12. 13. 네이티브지향하이브리드앱
사실상네이티브,
웹은거들뿐...
· 제한적이고직관적인네이티브와웹의결합
· 웹브라우져as-aUI컴포넌트
· 도움말,앱/개발사소개,공지사항/새소식...
· 웹기반사용자인증(OAuth)...
13
14. 15. 16. 예제코드(안드로이드)
웹서버 컨텐츠 불러오기
public class NoticeActivity extends Activity {
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
WebView webView = (WebView)findViewById(R.id.webView);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebChromeClient(new WebChromeClient());
...
webView.loadUrl(“http://m.pudding.kr/pud/mNotice.kth”);
...
}
...
}
앱에 포함된 정적 컨텐츠 불러오기
public class HelpActivity extends Activity {
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
WebView webView = (WebView)findViewById(R.id.webView);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebChromeClient(new WebChromeClient());
...
webView.loadUrl(“file:///android_asset/www/
help.html”);
...
}
...
}
16
17. 예제코드(iOS)
웹 서버 컨텐츠 불러오기
@interface NoticeViewController : UIViewController {
IBOutlet UIWebView *webView;
...
@end
@implementation HelpViewController
...
- (void)viewDidLoad {
...
NSURL *url = [NSURL URLWithString:@”http://m.pudding.kr/pud/mNotice.kth”];
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
[webView loadRequest:requestObj];
...
}
...
@end 앱에 포함된 정적 컨텐츠 불러오기
@interface HelpViewController : UIViewController {
IBOutlet UIWebView *webView;
...
@end
@implementation HelpViewController
...
- (void)viewDidLoad {
...
NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
NSString *path = [bundlePath stringByAppendingPathComponent:@”/www/help.html”];
NSURL *url = [NSURL fileURLWithPath:path];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[webView loadRequest:request];
...
}
...
@end
17
18. 19. 20. 21. 예제코드(안드로이드)
링크 클릭 가로채기
...
WebView webView = (WebView)findViewById(R.id.webView);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebChromeClient(new WebChromeClient());
webView.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView webView, String url) {
if(!url.startsWith(“http://m.pudding.kr/pud/”) {
new AndroidDialog.Builder(NoticeActivity.this)
.setMessage(“딴데로 갈라구?? -_-+”)
.setPositiveButton(“아니... 여기 있을께 ㅠㅠ”, new DialogInterface.OnClickListener() {
dialog.dismiss();
})
.setNegativeButton(“갈꼬얌!”, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int witch) {
dialog.dismiss();
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
NoticeActivity.this.startActivity(intent);
}
}).show();
return false;
} else if ...
...이러쿵 저러쿵...
} else if ...
...어쩌구 저쩌구...
} else if ...
...구시렁 구시렁...
}
view.loadUrl(url);
return true;
}
});
webView.loadUrl(“http://m.pudding.kr/pud/mNotice.kth”);
...
21
22. 예제코드(iOS)
링크 클릭 가로채기
@interface NoticeViewController : UIViewControllerUIWebViewDelegate, UIAlertViewDelegate {
IBOutlet UIWebView *webView;
NSString *externalUrl;
...
@implementation HelpViewController
...
- (void)viewDidLoad {
NSURL *requestUrl = [NSURL URLWithString:@”http://m.pudding.kr/pud/mNotice.kth”];
[webView loadRequest:[NSURLRequest requestWithURL:requestUrl]];
[webView setDelegate:self]
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType {
NSString *url = [[request URL] absoluteString];
if(![url hasPrefix:@”http://m.pudding.kr/pud/mNotice.kth”]) {
self.externalUrl = url;
UIAlertView *alertView = [UIAlertView alloc] initWithTitle:nil
message:@”딴데로 갈라구?? -_-+” delegate:self
cancelButtonTitle:@”아니... 여기 있을께 ㅠㅠ“ otherButtonTitles:@”갈꼬얌!”, nil];
[alertView show];
[alertView release];
return NO;
} else if ...
...이러쿵 저러쿵...
} else if ...
...어쩌구 저쩌구...
} else if ...
...구시렁 구시렁...
}
return YES;
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if(buttonIndex == YES self.externalUrl) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:self.externalUrl];
}
}
...
22
23. 예제코드(안드로이드)
URL을 이용한 네이티브와 웹의 통신::자바스크립트
function getFieldValue(fieldId) {
var fieldValue = document.getElementById(fieldId).value;
location.href = ‘custom://getFieldValue?fieldId=’ + fieldId + ‘fieldValue=’ + fieldValue;
}
function setFieldValue(fieldId, fieldValue) {
document.getElementById(fieldId).value = fieldValue;
}
URL을 이용한 네이티브와 웹의 통신
webView.loadUrl(“javascrpt:getFieldValue(‘userName’)”); // 결과는 나중에... 비동기!! -_-;
...
webView.loadUrl(“javascrpt:setFieldValue(‘userName’, ‘“ + userName + “‘“);
...
webView.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView webView, String url) {
if(!url.startsWith(“custom://getFieldValue”) {
Uri uri = Uri.parse(url);
String fieldId = uri.getQueryParameter(“fieldId”);
String fieldValue = uri.getQueryParameter(“fieldValue”);
if(fieldId.equals(“userName”)) {
userName = fieldValue; // 결과가 도착했다! 이제 어떡하지? 비동기!! OTL
} else if ...
} else if ...
} else if ... // 나는 엘시프가 씨러요! ㅠㅠ
}
return false;
} else if ...
} else if ...
} else if ... // 나는 엘시프가 씨러요! ㅠㅠ
}
view.loadUrl(url);
return true;
}
});
...
23
24. 예제코드(iOS)
URL을 이용한 네이티브와 웹의 통신
NSString *script = [NSString stringWithFormat:@“getFieldValue(‘%@’)”, fieldId];
[webView stringByEvaluatingJavaScriptString:script];
...
NSString *script = [NSString stringWithFormat:@“setFieldValue(‘%@’, ‘%@‘)“, fieldId, fieldValue];
[webView stringByEvaluatingJavaScriptString:script];
...
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType {
NSString *url = [[request URL] absoluteString];
if(![url hasPrefix:@”custom://getFieldName”]) {
NSDictionary *params = [HttUtils decodeQueryString:[[request URL] query]];
NSString *fieldId = [params objectForKey:@”fieldId”];
NSString *fieldValue = [params objectForKey:@”fieldValue”];
if(fieldId isEqualToString:@”userName”) {
self.userName = fieldValue;
} else if ...
} else if ...
} else if ... // 나는 엘시프가 씨러요! ㅠㅠ
}
[paramArray release];
return NO;
} else if ...
} else if ...
} else if ... // 나는 엘시프가 씨러요! ㅠㅠ
}
return YES;
}
...
24
25. 웹지향하이브리드앱
사실상웹,
네이티브는거들뿐...
· 광범위하지만일관성있는네이티브와웹의결합
· 클라이언트사이드“웹앱”
· 기존웹서버+RESTfulAPI서버
· 기본적인웹컨텐츠는앱에포함
25
26. 27. 28. 29. “푸딩얼굴인식앱”소개
푸딩얼굴인식앱은...
· 600만+다운로드!
· @iolothebard와@seti222
· 5500+줄의자바스립트
· 2700+줄의CSS
· 200+줄의네이티브코드
· 앱스프레소0.9+내부개발버전
29
30. 단일페이지인터페이스
index.html #pageId
pageId.css
pageId.js
Active
Page
show/hide
30
31. 단일페이지인터페이스
웹앱도MVC가필요해!
자바스크립트가컨트롤러!
· 웹서버도없는데...페이지이동은왜?!
· (GUI)애플리케이션스타일의“상태”관리
· 빠른화면전환화면전환효과
· 체감성능UP!
· 메모리사용량UP!
· 그런데...공동작업은어떻게?-_-;
31
32. 단일페이지인터페이스
HTML마크업: section#faceFoundPage
section id=”faceFoundPage” class=”jj-page”
header
button class=”jj-left jj-back”span data-nls=”common.back”이전/span/button
button class=”jj-right jj-home”span data-nls=”common.home”홈/span/button
h1span data-nls=”faceFoundPage.title”얼굴인식 결과/span/h1
/header
article CSS스타일:faceFoundPage.css
... #faceFoundPage {
/article background-color:rgb(255,255,255);
footer }
... #faceFoundPage article, #faceFoundPage footer {
/footer background-color:rgb(138,135,136);
/section }
...
자바스크립트:faceFoundPage.js
var jj.ui = jj.require(‘jj.ui’);
export.FaceFoundPage = new jj.defclass(jj.ui.Page, {
onInit: function() {
$(this.pageNode).bind(‘onpagebeforeshow’, function() { … });
$(this.pageNode).bind(‘onpageaftershow’, function() { … });
$(this.pageNode).bind(‘onpagebeforehide’, function() { … });
$(this.pageNode).bind(‘onpageaftershow’, function() { … });
...
}, 자바스크립트를 이용한 화면 전환
... // 화면을 수동으로 초기화
}); var faceFoundPage = new FaceFoundPage(
$(‘#faceFoundPage’), // 화면을 구성하는 DOM 노드
facePickerPage, // 화면내의 .jj-back 노드를 클릭할 때 전환될 페이지
mainPage); // 화면내의 .jj-home 노드를 클릭할 때 전환될 페이지
// 화면 표시에 필요한 상태 정보를 전달
faceFoundPag.setFaceInfo(faceInfo);
// 현재 화면이 왼쪽으로 사라지면서, 새 화면이 오른쪽에서(slideleft) 나타남
faceFoundPage.show(‘slideleft’);
// 현재 화면을 유지한 채, 새 화면을 아래에서 위로(slideup) 나타남(modal)
//faceFoundPage.show(‘slideup’, true);
32
33. “Placeholder”HTMLMarkup
자바스크립트 API서버 웹서버
HTML
로컬
캐시 스토리지 캐시 앱
StaticHTMLFragment
Placeholder Data Template
+
이름 클래스 레벨 ★ ♥ 이름 클래스 레벨
iolo bard 만렙 iolo bard 만렙 foreach
장동수 개발자 쪼렙 장동수 개발자 쪼렙 ★ ♥
... ... ... ... ... ... end
33
34. “Placeholder”HTMLMarkup
웹앱도MVC가필요해!
뷰와모델의분리
· HTML마크업을위한“변수”
· 웹서버개발에서클라이언트에적용
smarty,...)을웹
널리쓰이는템플릿(velocity,
· 서버부하는DOWN!
· 데이터전송량도DOWN!
· 캐시히트율은UP!
34
35. “Placeholder”HTMLMarkup
div id=”faceFound_starTemplate” class=”jj-template”
div class=”face” data-fastclick=”true”/div
div class=”rank”/div
p class=”rate”span class=”rateText”/spansmall%/small/p
p class=”nameText” data-fastclick=”true”/p
p class=”info”/p
p class=”descriptionText”/p
/div
div id=”faceFound_star_wrapper”
div id=”faceFound_star_scroller”
ul
li class=”nomatch”
div id=”faceFound_star_nomatch”
h5 data-nls=”0ah002”닮은 연예인을 찾지 못했습니다./h5
p data-nls=”0ah003”얼굴이 가까이 나온br /정면 사진으로 다시 시도해 보세요./p
div
button id=”faceFound_retryBtn” class=”y” data-fastclick=”true”
span data-nls=”0ah005”다시 찾기/span
/button
/div
/div
/li
li
div id=”faceFound_star1” class=”jj-placeholder”
data-template=”#faceFound_starTemplate”/div
/li
li
div id=”faceFound_star2” class=”jj-placeholder” data-template=”#faceFound_starTemplate”/div
div id=”faceFound_star3” class=”jj-placeholder”/div
/li
li
div id=”faceFound_star4” class=”jj-placeholder” data-template=”#faceFound_starTemplate”/div
div id=”faceFound_star5” class=”jj-placeholder” data-template=”#faceFound_starTemplate”/div
/li
/ul
/div!-- star_scroller --
/div!-- star_wrapper --
35
36. “Marker”CSSClass
웹앱도MVC가필요해!
뷰와컨트롤러의분리
· CSS스타일시트를위한“조건문”
· 프론트엔드UI개발자와자바스크립트개발자의약속
· 화면의동적인변화를자바스크립트없이확인제어
· HTML/CSS는“쬐끔”복잡해지고...-_-;
· 자바스크립트는“쬐끔”단순해지고...-_-;
· 함께일하기는더좋아지고~^O^
36
37. “Marker”CSSClass
스타일시트
/* 일치하는 연예인이 하나라도 있으면 안내문을 표시하지 않는다 */
#faceFoundPage .nomatch {
display:none;
}
/* 일치하는 연예인이 없으면 그에 따른 안내문을 표시한다 */
#faceFoundPage.nomatch .nomatch {
display:block;
}
/* 일치하는 연예인이 없으면 carousel 페이지 인디케이터를 숨긴다 */
#faceFoundPage.nomatch ul li {
display:none;
}
/* 일치하는 연예인이 없으면 하단의 공유 버튼들을 비활성화 */
#faceFoundPage.nomatch footer button {
opacity:0.5;
}
/* 일치하는 연예인 숫자에 따라 carousel 영역(iscroll)의 너비를 조절한다
#faceFoundPage.nomatch #faceFound_star_scroller,
#faceFoundPage.match_1 #faceFound_star_scroller {
width:320px;/*320*1pages*/
}
#faceFoundPage.match_2 #faceFound_star_scroller,
#faceFoundPage.match_3 #faceFound_star_scroller {
width:640px;/*320*2pages*/
}
#faceFoundPage.match_4 #faceFound_star_scroller,
#faceFoundPage.match_5 #faceFound_star_scroller {
width:960px;/*320x3pages*/
}
자바스크립트
if(matchingCelebs 1) {
$(‘#faceFoundPage’).addClass(‘nomatch’);
} else {
$(‘#faceFoundPage’).removeClass(‘nomatch’).addClass(‘match_’ + matchingCelebs);
}
37
38. 단말해상도별최적화
ResponsiveWebDesign?
지금은곤란하니,기다려달라~
· HTML태그에마커CSS클래스추가/활용
· 기본적으로해상도에자유로운프론트엔드UI개발
· 널리쓰이는해상도는꼼꼼하게“미세”조정
· 특이한해상도는최소한의“미세”조정
· 이한몸희생해서...모두가행복할수있다면...ㅠㅠ
38
39. “Marker”CSSClass
단말 플랫폼/해상도 마커 클래스 초기화 스크립트
var platformCls = (/iOS/.test(navigator.userAgent)) ? ‘ios’ :
(/Android/.test(navigator.userAgent) ? android : ‘generic’);
var screenCls =‘screen’, screen.width, ‘x’, screen.height).join(‘’);
var orientationCls = (screen.width screen.height) ? ‘portrait’ : ‘landscape’;
$(window).addClass(platformCls, screenCls, orientationCls);
단말 플랫폼 마커 클래스를 활용한 스타일 최적화
/* 플랫폼 고유의 체크박스(on/off 스위치) 이미지를 사용 */
input[type=”checkbox”] {
background-repeat:no-repeat;
background-size:100%;
}
.ios input[type=”checkbox”] {
background-image:@url(‘img/ios/check.png’);
}
단말 해상도 마커 클래스를 활용한 스타일 최적화 .android input[type=”checkbox”] {
/* 해상도 독립적인 레이아웃 */ background-image:@url(‘img/android/check.png’);
section { width:100%; height:100%; } }
header, footer { height:10%; } ...
article { height:80%; }
article.noheader, article.nofooter { height:90%; }
article.noheader.nofooter { height:100%; }
...
/* 아이폰 해상도에 맞춰 미세 조정 */
.screen320x480 header { height:44px; }
.screen320x480 footer { height:49px; }
.screen320x480 article { height:387px;/*480-44-49*/ }
.screen320x480 article.noheader { height:436px;/*480-44*/ }
.screen320x480 article.nofooter { height:431px;/*480-49*/ }
...
/* 갤러시탭에 해상도에 맞춰 미세 조정 */
.screen600x1024 header { height:80px; }
...
39
40. 다국어처리
I18N?L10N?A11Y?
이제는선택이아닌필수!
· HTML태그에마커CSS클래스추가/활용
· “그림글자”사용최소화:국제화접근성
· 일관성있는번역어식별자
· 언어별어순/길이/너비차이고려
· 언어별UI“미세”조정
40
41. 42. 다국어처리
다국어 지원 초기화 스크립트
var lang = navigator.language.substring(0, 2);
if(lang !== ‘en’ lang !== ‘ja’ lang !== ‘zh’) { lang = ‘en’; }
$.get(‘locales/’ + lang + ‘/messages.json’, function(messages) {
$(document.documentElement).addClass(‘jj-nls-’ + lang);//언어 식별 마커 클래스 추가
$.each(document.body).find(‘*[data-nls]’).each(function(index, node) {
var key = node.attr(‘data-nls’);
var message = messages[key];
if(message) {
node.html(message);
} else {
console.error(’missing nls message:’ + key);
} 다국어 번역 텍스트 파일(locales/언어/messages.json)
}); “common.back”: “Back”,
...
“setupTwitterPage.title”: “Setup - Twitter”,
...
다국어 지원 자리 잡기 태그
buttonspan data-nls=”common.back”이전/span/button
...
h1span data-nls=”setupTwitterPage.title”설정 - 트위터/span/h1
...
다국어 번역 텍스트 치환 결과
buttonspan data-nls=”common.back”Back/span/button
...
h1span data-nls=”setupTwitterPage.title”Setup - Twitter/span/h1
...
마커 클래스를 활용한 언어별 스타일 최적화
.jj-nls-zh #intro_title {
/*locales/zh/img/intro_title.png*/
background-image:@url(‘../img/intro_title.png’);
}
.jj-nls-zh #find_cameraBtn .text,
.jj-nls-zh #find_albumBtn .text {
! width:70px; display:inline-block;
}
...
42
43. 44. Ant를이용한빌드자동화
target name=”verify_js” depends=”init”
jslint jslint=”${jslint.js}” encoding=”${js.encoding}”
options=”${jslint.options}” haltOnFailure=”${jslint.haltOnFailure}”
predef${jslint.predef}/predef
formatter type=”plain”/
filelist refid=”js.src.files”/
/jslint
/target
target name=”merge_js” depends=”verify_js” if=”build.release”
!-- pudface --
concat destfile=”${js.out.merged}” encoding=”${js.encoding}”
outputencoding=”${js.encoding}” fixlastline=”no” eol=”unix”
filelist refid=”js.src.files”/
filterchain
deletecharacters chars=”#xFEFF;”/
/filterchain
/concat
echo message=”merged into ${js.out.merged}”/
/target
target name=”compress_js_yuicompressor” depends=”verify_merged_js” if=”build.release”
!-- pudface --
java classname=”${yuicompressor.mainclass}” classpathref=”yuicompressor.classpath”
fork=”true” failonerror=”true”
arg value=”--verbose”/
arg value=”--charset”/
arg value=”${js.encoding}”/
arg value=”--type”/
arg value=”js”/
arg value=”-o”/
arg file=”${js.out.compressed}”/
arg file=”${js.out.merged}”/
/java
delete file=”${js.out.merged}”/
echo message=”compressed into ${js.out.compressed}”/
/target
44
45. Ant를이용한빌드자동화
target name=”build_debug” depends=”clean,copy_apis” if=”build.debug”
copy todir=”${out.dir}” verbose=”true”
fileset dir=”${src.dir}”
exclude name=”index.html”/
/fileset
/copy
copy todir=”${out.dir}” encoding=”${html.encoding}”
outputencoding=”${html.encoding}” verbose=”true” overwrite=”true”
fileset dir=”${src.dir}”
include name=”index.html”/
/fileset
filterchain
linecontains negate=”true”contains value=”@@RELEASE”//linecontains
/filterchain
/copy
/target
target name=”build_release” depends=”clean,compress_js,compress_css” if=”build.release”
copy todir=”${out.dir}” verbose=”true”
fileset dir=”${src.dir}”
exclude name=”index.html”/
exclude name=”js/pudface/**”/
exclude name=”css/**”/
/fileset
/copy
copy todir=”${out.dir}” encoding=”${html.encoding}”
outputencoding=”${html.encoding}” verbose=”true” overwrite=”true”
fileset dir=”${src.dir}”
include name=”index.html”/
/fileset
filterchain
linecontains negate=”true”contains value=”@@DEBUG”//linecontains
/filterchain
/copy
/target
45
46. 네이티브와의결합
사용한앱스프레소플러그인
· deviceapis.filesystem:파일입출력
· deviceapis.deviceinteraction:화면꺼짐방지/진동
· ax.ext.media:카메라/포토앨범/효과음
· ax.ext.net:업로드/다운로드
· ax.ext.ui:네이티브UI/차일드브라우져
· ax.ext.ga:구글통계
· ax.ext.admob:애드몹광고
· kth.puddingface:푸딩얼굴인식앱전용*^^*
46
47. 48. 앱스프레소플러그인구조
AppspressoPlugin Appspresso
Appspresso Project Plugin
Application
Archive
Project axplugin.xml axplugin.js
(*.axp)
res overlay
link export
run lib*.a *.jar
share
iOSNativeModule AndroidNativeModule
(XcodeStaticLibraryProject) (AndroidLibraryProject)
48
49. 앱스프레소플러그인API
AxPlugin AxPluginContext
intgetId()
/*YOURCODEHERE*/ 2
StringgetMethod()
activate(AxRuntimeContext) Object[]getParams()
deactivate(AxRuntimeContext) ...
execute(AxPluginContext) sendResult([result])
... sendError(code[,message])
2 4
AxRuntimeContext 1
자바스크립트API
getWebView()
getWidget()
...
requirePlugin(pluginId) 플랫폼확장API
executeJavaScript(script)
... Android iOS
49
50. 앱스프레소플러그인플랫폼확장API
·안드로이드전용
· Activity
· ActivityListener
· WebViewListener
· WebViewClientListener/WebChromeClientListener
· iOS전용
· UIViewController
· UIApplicationDelegate
· UIWebViewDelegate
· AxViewControllerDelegate
50
51. 앱스프레소플러그인자바스크립트API
·AxPlugin의자바스크립트“stub”
· functionexecSync(method,params)
· functionexecAsync(method,successCallback,
errorCallback,params)
· params:arrayofarguments
· successCallback:function(result){...}
· errorCallback:function(error){...}
· ax,ax.error,ax.util,ax.console,
ax.request,ax.bridge,ax.plugin,...
51
52. 앱스프레소플러그인개발실습
네이티브주소록UI플러그인
· deviceapis.pim.contact는...
· 너무어려워+너무느려+뽀대도안나...x3
· 그래서,@bluenmad 가만들었습니다~
ax.ext.contact.pickContact(function(contact) {
if(contact contact.phoneNumbers) {
var firstPhoneNumber = phoneNumbers.split(‘,’)[0];
if(confirm(contact.firstName + ‘에게 전화걸까요?’)) {
location.href = ‘tel:’ + firstPhoneNumber;
}
}
}, function(error) { ... })
· 어떻게?
52
53. axplugin.xml
?xml version=”1.0” encoding=”UTF-8”?
axplugin id=”ax.ext.contact” version=”1.0”
! descriptionContact Extension API Appspresso Plugin
! /description
! urlhttp://appspresso.com/url
! authorAppspresso Dev. Team/author
! licenseCopyright (c) 2011, KT Hitel Co., LTD. All Rights Reserved.
! /license
! feature id=”http://appspresso.com/api/ax.ext.contact“
! ! category=”Extension” /
! module platform=”android” platform-version=”8”
! ! min-platform-version=”7” max-platform-version=””
! ! class=”com.appspresso.screw.contact.ContactPlugin”
! ! property name=”permission” value=”android.permission.READ_CONTACTS” /
! /module
! module platform=”ios” platform-version=”4.1”
! ! min-platform-version=”4.0” max-platform-version=””
! ! class=”ax_ext_contact_MyPlugin”
! ! property name=”framework” value=”AddressBook.framework, AddressBookUI.framework” /
! /module
/axplugin
53
54. axplugin.js
/*jslint browser:true, confusion:true, /**
debug:true, devel:true, nomen:true, * pick a contact
plusplus:true, vars:true */ *
/** * @param {function} callback
* @fileOverview Contact Extension API * @param {function} errback
* @author blueNmad * @param {ax.ext.contact.ContactOpts} opts
* @version 1.0 * @return AxRequest
*/ * @methodOf ax.ext.contact
(function () { */
“use strict”;
function pickContact(callback, errback,
var NS_CONTACT = “ax.ext.contact”; opts) {
var PREFIX_CONTACT = “ax.ext.contact”; onPickContactCallback = callback;
/** return this.execAsync(‘pickContact’,
* Contact Extension API ax.nop, errback, [opts || {}]);
* }
* @namespace
* @name ax.ext.contact function onPickContact(contact) {
*/ if ( !! onPickContactCallback) {
onPickContactCallback(eval(contact));
/** }
* @class
* @name ContactOpts onPickContactCallback = undefined;
* @memberOf ax.ext.contact }
*/
ax.plugin(PREFIX_CONTACT, {
/** ‘pickContact’: pickContact,
* native callback for pickContact. ‘onPickContact’: onPickContact
* }, NS_CONTACT);
* @param result })();
* @memberOf ax.ext.contact
* @private
*/
var onPickContactCallback = undefined;
54
55. com...contact.ContactPlugin.java
package com.appspresso.screw.contact; return super.onActivityResult(activity,
requestCode, resultCode, data);
import android.app.Activity; }
import android.content.Intent; };
import android.provider.ContactsContract;
public void activate(
import com.appspresso.api.AxPluginContext; AxRuntimeContext runtimeContext) {
import com.appspresso.api.AxRuntimeContext; super.activate(runtimeContext);
import com.appspresso.api.DefaultAxPlugin;
... runtimeContext.addActivityListener(
중간생략 activityListener);
... }
/** public void deactivate(
* Appspresso Plugin Android Module AxRuntimeContext runtimeContext) {
* runtimeContext.removeActivityListener(
* id: ax.ext.contact activityListener);
* version: 1.0.0
* super.deactivate(runtimeContext);
*/ }
public class ContactPlugin extends
DefaultAxPlugin { public void pickContact(
private static final int AxPluginContext context) {
REQ_PICK_CONTACT = 62000; Intent intent = new Intent(
Intent.ACTION_PICK,
private ActivityListener activityListener = ContactsContract.Contacts.CONTENT_URI);
new ActivityAdapter() { runtimeContext.getActivity()
public boolean onActivityResult( .startActivityForResult(intent,
Activity activity, int requestCode, int REQ_PICK_CONTACT);
resultCode, Intent data) { context.sendResult();
if (ContactPlugin.REQ_PICK_CONTACT == }
requestCode data != null) { }
return ContactUtils.onPickContact(
runtimeContext, data);
}
55
56. com...contact.ContactUtils.java
package com.appspresso.screw.contact; String contactId =
data.getData().getLastPathSegment();
import java.util.ArrayList;
import java.util.List; JSONObject contact =
ContactUtils.getContactWithContactId(
import org.apache.commons.logging.Log; activity, contactId);
import org.json.JSONArray;
import org.json.JSONObject; if (contact != null) {
runtimeContex.invokeJavaScriptFunction(
import android.app.Activity; JS_CALLBACK_ONPICKCONTACT, contact);
import android.content.Intent; }
import android.database.Cursor; return true;
import android.provider.ContactsContract; }
...
중략 static JSONObject getContactWithContactId(
... Activity activity, String contactId) {
import android.webkit.WebView; JSONObject contact = new JSONObject();
...
import com.appspresso.api.AxLog; Cursor cursor = null;
Cursor rawContactIdsCursor = null;
class ContactUtils { try {
private static Log L = String[] rawContactIds = null;
AxLog.getLog(“ContactPlugin”); rawContactIdsCursor = activity
.getContentResolver().query(
private static final String RawContacts.CONTENT_URI,
JS_CALLBACK_ONPICKCONTACT = new String[] { RawContacts._ID },
“ax.ext.contact.onPickContact”; RawContacts.CONTACT_ID + “ = ?”,
new String[] { contactId },
public static boolean onPickContact( null);
RuntimeContext runtimeContext, ...
Intent data) { 중략
if (data == null) { ...
return false; }
} }
56
57. ax_ext_contact_MyPlugin.h
#import Foundation/Foundation.h
#import UIKit/UIKit.h
#import AddressBook/AddressBook.h
#import AddressBookUI/AddressBookUI.h
#import “AxPlugin.h”
@protocol AxContext;
@protocol AxPluginContext;
@interface ax_ext_contact_MyPlugin : NSObjectAxPlugin, ABPeoplePickerNavigationControllerDelegate {
@private
NSObjectAxRuntimeContext *_runtimeContext;
}
@property (nonatomic,readonly,retain) NSObjectAxRuntimeContext* runtimeContext;
- (void)activate:(NSObjectAxRuntimeContext*)runtimeContext;
- (void)deactivate:(NSObjectAxRuntimeContext*)runtimeContext;
- (void)execute:(NSObjectAxPluginContext*)context;
- (IBAction)presentABPeoplePickerNavigationController;
// ABPeoplePickerNavigationControllerDelegate method
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person;
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person
property:(ABPropertyID)property
identifier:(ABMultiValueIdentifier)identifier;
- (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker;
@end
57
58. ax_ext_contact_MyPlugin.m
// [self
// ax_ext_contact_MyPlugin.m presentABPeoplePickerNavigationController];
// [context sendResult];
// Copyright 2011 none. All rights reserved. }
// else {
[context sendError:AX_NOT_AVAILABLE_ERR];
#import “AxRuntimeContext.h” }
#import “AxPluginContext.h” }
#import “AxError.h”
#import “AxLog.h” -
#import “ax_ext_contact_MyPlugin.h” (IBAction)presentABPeoplePickerNavigationContr
oller {
#define JS_CALLBACK_ONPICKCONTACT dispatch_async(dispatch_get_main_queue(), ^{
@”ax.ext.contact.onPickContact” ABPeoplePickerNavigationController *picker
= [[[ABPeoplePickerNavigationController alloc]
@implementation ax_ext_contact_MyPlugin init] autorelease];
picker.peoplePickerDelegate = self;
@synthesize runtimeContext = _runtimeContext; [[self.runtimeContext getViewController]
presentModalViewController:picker
- (void)activate: animated:YES];
(NSObjectAxRuntimeContext*)runtimeContext { // [picker release];
_runtimeContext = [runtimeContext retain]; });
} }
...
- (void)deactivate: 중략
(NSObjectAxRuntimeContext*)runtimeContext { ...
[_runtimeContext release]; -
_runtimeContext = nil; (void)peoplePickerNavigationControllerDidCance
} l:(ABPeoplePickerNavigationController
*)peoplePicker {
- (void)execute:(idAxPluginContext)context { [[self.runtimeContext getViewController]
NSString* method = [context getMethod]; dismissModalViewControllerAnimated:YES];
}
AX_LOG_TRACE(@”ContactView_ios_method : %s”,
method); @end
if([method isEqualToString:@”pickContact”]){
58
59. 60. 61. 62. References
· 하이브리드모바일앱프레임웍http://slideshare.net/iolo/hybrid-mobile-application-framework
· 단일페이지인터페이스웹/앱개발http://slideshare.net/iolo/ss-7719322
· AndroidSDKWebView레퍼런스http://goo.gl/iqr9H
· iOSSDKUIWebView레퍼런스http://goo.gl/U8XGy
· Android용하이브리드앱템플릿https://github.com/iolo/hellowebapp-android
· HowtobuildAndroidAppwithHTML/CSS/JavaScripthttp://youtube.com/watch?v=uVqp1zcMfbE
· iOS용하이브리드앱템플릿https://github.com/iolo/hellowebapp-ios
· HowtobuildiOSAppwithHTML/CSS/JavaScripthttp://youtube.com/watch?v=L28lGkoSQ2c
· 푸딩얼굴인식앱(Android)https://market.android.com/details?id=com.kth.puddingface
· 푸딩얼굴인식다국어앱(iOS)http://itunes.apple.com/us/app/id378461555?mt=8
· 앱스프레소홈페이지http://appspresso.com/
· 폰갭홈페이지http://phonegap.com/
· 티타늄홈페이지http://appcelerator.com/
· jQuery홈페이지http://jquery.com/
· iScroll홈페이지http://cubiq.org/iscroll/
· JSLint홈페이지http://jslint.com/
· YUICompressor홈페이지http://developer.yahoo.com/yui/compressor/
62
63. 64.