jQuery Mobile and Dynamic Page Generation
jQuery Mobile은 default click hijacking behavior나 매뉴얼로 $.mobile.changePage()
를 call 하는 것을 통해 다이나믹하게 DOM으로 page이 당겨져 올 수 있도록 해 줍니다. 이것은 서버사이드에서 HTML pages/fragments 를 generate 하는데 아주 좋습니다. 하지만 가끔 JSON이나 다른 포맷으로부터 클라이언트 사이드에서 page content를 다이나믹하게 generate 할 필요가 있습니다. 아마도 네트워크 광역폭이나 퍼포먼스적인 이유로 그런 일이 필요할 겁니다. 혹은 서버와 서로 소통하기 위해 서버가 선택할 데이터 포맷일 수도 있겠죠.
클라이언트 사이드에서 page markup을 generate 할 필요가 있는 어플리케이션을 위해 $.mobile.changePage() call 하는 동안 트리거되는 notification에 대해 이해하고 있는것이 중요합니다. 왜냐하면 해당 시간에 여러분의 content를 generate 할 수 있도록 navigation system 에 hook 하는것을 가능하게 해 주기 때문입니다.
changePage()를 call 하게 되면 대개 아래와 같은 event notification이 trigger 됩니다.
pagebeforechange
- Fired off before any page loading or transition.
- NOTE: This event was formerly known as "beforechangepage".
pagechange
- Fired off after all page loading and transitions.
- NOTE: this event was formerly known as "changepage".
pagechangefailed
- Fired off if an error has occurred while attempting to dynamically load a new page.
이런 notification들은 페이지의 parent container element(
$.mobile.pageContainer
)에서 trigger 됩니다. 그리고 document element와 window에까지 계속 떠 다니게 될 겁니다. JSON이나 in-memory JS object 같은 non HTML data를 이용해서 어플리케이션에 page를 inject 하기를 원한다던가 아니면 빠르게 현재 존재하는 페이지의 내용을 modify 하기를 원한다면 pagebeforechange
event 가 아주 유용할 겁니다. pagebeforechange
event는 URL이나 page element들을 analyzing 하기위해 hook 할 수 있도록 해 줍니다. 어플리케이션은 load 하거나 switch 할거냐고 질문을 받게 되고 pagebeforechange
event에서 preventDefault()를 call 함으로서
default changePage()
behavior가 short-circuit 할지에 대해서도 질문을 받게 됩니다.
이 기술을 이용하기 위해 working sample을 한번 살펴 보시기 바랍니다. 이 샘플에서는 유저가 navigate 할 수 있는 카테고리 리스트로 메인페이지가 시작합니다. 각 카테고리의 실제 아이템들은 메모리의 javaScript object에 저장되어 있습니다. 이 데이터들은 어떤 곳에서든지 올 수가 있습니다.
var categoryData = {
animals: {
name: "Animals",
description: "All your favorites from aardvarks to zebras.",
items: [
{
name: "Pets"
},
{
name: "Farm Animals"
},
{
name: "Wild Animals"
}
]
},
colors: {
name: "Colors",
description: "Fresh colors from the magic rainbow.",
items: [
{
name: "Blue"
},
{
name: "Green"
},
{
name: "Orange"
},
{
name: "Purple"
},
{
name: "Red"
},
{
name: "Yellow"
},
{
name: "Violet"
}
]
},
vehicles: {
name: "Vehicles",
description: "Everything from cars to planes.",
items: [
{
name: "Cars"
},
{
name: "Planes"
},
{
name: "Construction"
}
]
}
};
이 어플리케이션은 어플리케이션에게 어떤 카테고리 아이템이 display 되어야 하는지를 말해주는 hash를 포함한 url이 있는 링크를 사용합니다.
<h2>Select a Category Below:</h2>
<ul data-role="listview" data-inset="true">
<li><a href="#category-items?category=animals">Animals</a></li>
<li><a href="#category-items?category=colors">Colors</a></li>
<li><a href="#category-items?category=vehicles">Vehicles</a></li>
</ul>
내부적으로 이 링크 중 하나를 클릭하면 어플리케이션은 internal $.mobile.changePage()
call을 intercept 합니다. 이 $.mobile.changePage() 는 프레임워크의
default link hijacking
behavior에 의해 invoke 된 것이죠. 그러면 이제 로드되기 위해 페이지에 대한 URL을 analyze 하게 됩니다. 그리고 나서 이것이 로딩 자체만을 위한 것인지 아니면 일반적인 changePage() 코드로 처리해야 될 것인지에 대해 판단하게 되죠.
어플리케이션은 도큐먼트 레벨에서 pagebeforechange
event로 binding 됨으로서 changePage() 로 그 자체를 insert 할 수 있게 됩니다.
// Listen for any attempts to call changePage().
$(document).bind( "pagebeforechange", function( e, data ) {
// We only want to handle changePage() calls where the caller is
// asking us to load a page by URL.
if ( typeof data.toPage === "string" ) {
// We are being asked to load a page by URL, but we only
// want to handle URLs that request the data for a specific
// category.
var u = $.mobile.path.parseUrl( data.toPage ),
re = /^#category-item/;
if ( u.hash.search(re) !== -1 ) {
// We're being asked to display the items for a specific category.
// Call our internal method that builds the content for the category
// on the fly based on our in-memory category data structure.
showCategory( u, data.options );
// Make sure to tell changePage() we've handled this call so it doesn't
// have to do anything.
e.preventDefault();
}
}
});
왜 도큐먼트 레벨에서 listen할까요? 간단히 말하면 deep-linking 때문입니다. 우리는 jQuery Mobile 프레임워크가 initialize 하기전에 active 되기위해 binding이 필요합니다. 그리고 어플리케이션을 invoke 한 initial URL을 어떻게 process 할지 결정하게 됩니다.
pagebeforechange
binding에 대한 callback이 invoke 됐을 때 callback에 대한 두번째 argument는 initial $.mobile.changePage()
call에 pass 될 argument들을 포함한 data object가 될 겁니다. 이 object의 프로퍼티들은 아래와 같습니다.toPage
- transition 될 페이지를 포함한 jQuery collection object가 될 수도 있고 로드되거나 transition 될 페이지에 대한 URL reference가 될 수 있습니다.
- transition 될 페이지를 포함한 jQuery collection object가 될 수도 있고 로드되거나 transition 될 페이지에 대한 URL reference가 될 수 있습니다.
- options
$.mobile.changePage()
function 함수의 caller에 의해 pass 된 옵션들을 포함한 Object- 옵션의 리스트는 여기에서 찾아 보실 수 있습니다.
changePage()
calls 에 대해서만 관심이 있습니다. 그래서 우리의 callback이 하는 첫번째 일은 toPage의 type을 체크하는 겁니다 그 다음으로는 어떤 URL parsing 유틸리티의 도움을 받아서 우리가 스스로 handling 하기위해 관심을 가지고 있는 hash를 포함한 URL인가에 대해 체크를 합니다. 만약 그렇다면 showCategory()라는 어플리케이션 함수를 call 합니다. 이 함수는 URL hash에 의해 명시된 카테고리에 대한 content를 다이나믹하게 create 할 겁니다. 그리고 이벤트에서 preventDefault()를 call 할 겁니다. pagebeforechange
event에서 preventDefault()를 call 하는 것은 다른 작업 없이 exit 하기 위해
$.mobile.changePage()
call 을 유발하게 됩니다. 이 이벤트에서 preventDefault()
method를 call 하는 것은 changePage()
request를 여러분 스스로 핸들링하게되는 jQuery Mobile과 같다고 말할 수 있습니다.preventDefault()가 call 되지 않는다면
changePage()는 평상시에 하던대로의 작업을 계속 이어나갈 겁니다. 우리의 callback에 pass 된 data object에 대해 짚고 넘어갈 부분은 여러분이 toPage 프로퍼티나 options 프로퍼티에 어떤 change를 했던지 간에
preventDefault()가 call 되지 않으면
changePage()
processing에 영향을 줄 것이라는 겁니다. 예를 들어 다른 internal/external 페이지에 특정 URL을 redirect하거나 map 하고 싶다면 우리의 callback은 그 URL이나 redirect 될 페이지의 DOM 엘리먼트로 callback에 data.toPage 프로퍼티를 set 해야 합니다. 마찬가지로 우리는 우리 callback 안의 어떤 옵션에 대한 set이나 un-set을 할 수 있습니다. 그러면 changePage()는 새로운 세팅을 사용하게 될 겁니다.
이제 우리는 어떻게 changePage()
call을 intercept 하는지 알게 됐습니다 이제 이 샘플 소스가 그 페이지에 대한 markup을 실제로 어떻게 generate 하는지 자세히 살펴 보겠습니다. 우리의 샘플 소스는 각 카테고리를 display 하기 위해 같은 페이지를 사용하거나 재 사용합니다. 우리의 special link 가 클릭 될 때마다 showCategory()
가 invoke 됩니다.
// Load the data for a specific category, based on
// the URL passed in. Generate markup for the items in the
// category, inject it into an embedded page, and then make
// that page the current active page.
function showCategory( urlObj, options )
{
var categoryName = urlObj.hash.replace( /.*category=/, "" ),
// Get the object that represents the category we
// are interested in. Note, that at this point we could
// instead fire off an ajax request to fetch the data, but
// for the purposes of this sample, it's already in memory.
category = categoryData[ categoryName ],
// The pages we use to display our content are already in
// the DOM. The id of the page we are going to write our
// content into is specified in the hash before the '?'.
pageSelector = urlObj.hash.replace( /\?.*$/, "" );
if ( category ) {
// Get the page we are going to dump our content into.
var $page = $( pageSelector ),
// Get the header for the page.
$header = $page.children( ":jqmData(role=header)" ),
// Get the content area element for the page.
$content = $page.children( ":jqmData(role=content)" ),
// The markup we are going to inject into the content
// area of the page.
markup = "<p>" + category.description +
"</p><ul data-role='listview' data-inset='true'>",
// The array of items for this category.
cItems = category.items,
// The number of items in the category.
numItems = cItems.length;
// Generate a list item for each item in the category
// and add it to our markup.
for ( var i = 0; i < numItems; i++ ) {
markup += "<li>" + cItems[i].name + "</li>";
}
markup += "</ul>";
// Find the h1 element in our header and inject the name of
// the category into it.
$header.find( "h1" ).html( category.name );
// Inject the category items markup into the content element.
$content.html( markup );
// Pages are lazily enhanced. We call page() on the page
// element to make sure it is always enhanced before we
// attempt to enhance the listview markup we just injected.
// Subsequent calls to page() are ignored since a page/widget
// can only be enhanced once.
$page.page();
// Enhance the listview we just injected.
$content.find( ":jqmData(role=listview)" ).listview();
// We don't want the data-url of the page we just modified
// to be the url that shows up in the browser's location field,
// so set the dataUrl option to the URL for the category
// we just loaded.
options.dataUrl = urlObj.href;
// Now call changePage() and tell it to switch to
// the page we just modified.
$.mobile.changePage( $page, options );
}
}
위 샘플에는 우리가 handle 하는 URL의 해쉬는 아래 두가지 부분을 포함하고 있습니다.
#category-items?category=vehicles
? 전에 있는 첫번째 부분은 content를 write할 page의 id입니다. ? 다음 부분은 어떤 데이터가 사용되고 언제 페이지에 대한 markup을 generate 할지에 대해 알 수 있는 정보 입니다. showCategory()가 하는 첫번째 일은 이 content 를 write 할 페이지의 id를 추출하기 위해 hash를 deconstruct(해채) 하는 겁니다. 그리고 카테고리 이름은 우리의
in-memory JavaScript category object로부터 올바른 data 세트를 찾아올 때 사용됩니다. 어떤 카테고리 데이터를 사용할 지에 대한 작업이 끝난 이후 그 카테고리에 대한 markup을 generate 합니다. 그리고 그것을 그 페이지의 header와 content 부분에 inject 합니다. 그 element에 이전에 있었던 markup들은 모두 씻겨 나갑니다.
이 markup을 inject 한 이후에 막 inject 된 markup list를 잘 사용하기 위해 적당한 jQuery Mobile widget을 call 하게 됩니다. 이 과정은 styled listview에 list markup이 적용되는 일반적인 과정입니다.
$.mobile.changePage()를 call 합니다. 그리고 우리가 방금 modify 한 페이지의 DOM 엘리먼트를 pass 합니다. 이 페이지가 보여지기를 원한다고 프레임워크에게 말하기 위해서죠. 이제 이 부분에서 흥미로운 부분은 jQuery Mobile은 보여지는 페이지와 연관된 URL과 함께 브라우저의 location hash를 update 한다는 겁니다. 이러한 일이 일어 날 수 있는 이유는 우리가 각 카테고리에 대해 같은 페이지를 재사용했기 때문입니다. 이것은 이상적인 상황만은 아닙니다. 왜냐하면 그 페이지에 대한 URL은 그것과 관련된 특정 category 정보를 가지고 있지 않기 때문입니다. 이 문제를 해결하기 위해서는
showCategory()가
changePage()에 pass 한 options object에 대한 dataUrl 프로퍼티를 세팅해 주는 일입니다. 우리의 오리지날 URL 대신에 이것을 display 해 달라고 얘기하기 위해서죠.
여기까지가 샘플입니다. 이 샘플이 아주 좋은 예제는 아닙니다. 특히 grade가 낮아서 JavaScript가 turn off 됐을 때에는요. 이 의미는 C-Grade 브라우저에서는 제대로 작동하지 않을 거라는 겁니다. 이렇게 낮은 grade의 브라우저에서 어떻게 제대로 동작할 수 있도록 하는지에 대해서는 나중에 글을 올릴겁니다. 업데이트 된 내용을 보시려면 여기를 참조하세요.
'jQuery Mobile > JQM Tutorial' 카테고리의 다른 글
JQuery Mobile - Toolbar types (0) | 2012.07.21 |
---|---|
JQuery Mobile - Theming Page (0) | 2012.07.12 |
JQuery Mobile - touchOverflow 기능 (0) | 2012.07.09 |
JQuery Mobile - PhoneGap apps (1) | 2012.07.09 |
JQuery Mobile - Scripting pages (1) | 2012.07.05 |
JQuery Mobile - Ajax, hashes & history 02 - (0) | 2012.06.27 |
JQuery Mobile - Ajax, hashes & history 01 - (0) | 2012.06.26 |
JQuery Mobile Tutorial : Components 05 - Prefetching & caching pages - (2) | 2012.05.16 |
JQuery Mobile Tutorial : Components 04 - Dialogs - (0) | 2012.05.04 |
JQuery Mobile Tutorial : Components 03 - Page transitions - (0) | 2012.05.04 |