You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@tapestry.apache.org by Geoff Callender <ge...@gmail.com> on 2013/04/01 06:44:44 UTC
Re: Query Mobile & Tapestry
Have you tried doing it as a single page Tapestry app with each "mobile page" as a component? It's 6 months since I last worked with jquery-mobile but I think what you have to do is leave Prototype suppressed (which is tapestry5-jquery's default). Here's an example...
I<!DOCTYPE html>
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd" xmlns:p="tapestry:parameter">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no" />
<title>My App</title>
<t:if test="productionMode">
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.1/jquery.mobile-1.1.1.min.css" />
<script type="text/javascript" src="//maps.googleapis.com/maps/api/js?sensor=true&libraries=places"></script>
<script src="http://code.jquery.com/mobile/1.1.1/jquery.mobile-1.1.1.min.js"></script>
<script src="${context:js/jquery.ui.map.full.min.js}" type="text/javascript"></script>
</t:if>
<t:if test="!productionMode">
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.1/jquery.mobile-1.1.1.css" />
<script type="text/javascript" src="//maps.googleapis.com/maps/api/js?sensor=true&libraries=places"></script>
<script src="http://code.jquery.com/mobile/1.1.1/jquery.mobile-1.1.1.js"></script>
<script src="${context:js/jquery-ui-map-3.0-rc/ui/jquery.ui.map.js}" type="text/javascript"></script>
<script src="${context:js/jquery-ui-map-3.0-rc/ui/jquery.ui.map.extensions.js}" type="text/javascript"></script>
<script src="${context:js/jquery-ui-map-3.0-rc/ui/jquery.ui.map.overlays.js}" type="text/javascript"></script>
<script src="${context:js/jquery-ui-map-3.0-rc/ui/jquery.ui.map.services.js}" type="text/javascript"></script>
<script src="${context:js/jquery-ui-map-3.0-rc/ui/jquery.ui.map.microdata.js}" type="text/javascript"></script>
<script src="${context:js/jquery-ui-map-3.0-rc/ui/jquery.ui.map.microformat.js}" type="text/javascript"></script>
<script src="${context:js/jquery-ui-map-3.0-rc/ui/jquery.ui.map.rdfa.js}" type="text/javascript"></script>
</t:if>
</head>
<body>
<t:m.IndexPage/>
<t:m.PersonsPage/>
<t:m.PersonPage/>
<!-- etc. -->
</body>
</html>
package au.com.myapp.web.pages.m;
import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.services.AssetSource;
import org.got5.tapestry5.jquery.ImportJQueryUI;
import au.com.myapp.web.annotation.PublicPage;
@PublicPage
// By specifying @ImportJQueryUI we tell tapestry5-jquery to not pull in its default theme css.
@ImportJQueryUI(theme = "context:css/empty.css")
@Import(library = "Index.js")
public class Index {
// Screen fields
@Inject
@Symbol(SymbolConstants.PRODUCTION_MODE)
@Property
private boolean productionMode;
// The code
public void onActivate() {
// System.out.println("request.isXHR() = " + request.isXHR());
}
}
...Index.js has to do one bit of trickery to do with URLs: to get from one "mobile page" to another, we generate URLs that pass parameters as query strings, but jquery-mobile doesn't want that - it wants us to provide the parameters in the page.options.pageData object - so the following code moves the parameters automatically.
// Based on https://github.com/jblas/jquery-mobile-plugins/blob/master/page-params/jqm.page.params.js .
(function($) {
$(document).on("pagebeforechange", function(event, data) {
// We only want to handle the case where we are being asked
// to go to a page by URL, and only if that URL is referring
// to an internal page by id.
if (typeof data.toPage === "string") {
var toUrlObj = $.mobile.path.parseUrl(data.toPage);
if ($.mobile.path.isEmbeddedPage(toUrlObj)) {
// The request is for an internal page, if the hash
// contains query (search) params, strip them off the
// toPage URL and then set options.dataUrl appropriately
// so the location.hash shows the originally requested URL
// that hash the query params in the hash.
// If the hash has a query string (aka "search")
var postHash = toUrlObj.hash.replace(/^#/, "");
var postHashObj = $.mobile.path.parseUrl(postHash);
if (postHashObj.search) {
if (!data.options.dataUrl) {
data.options.dataUrl = data.toPage;
}
data.options.pageData = queryStringToObject(postHashObj.search);
data.toPage = toUrlObj.hrefNoHash + "#" + postHashObj.pathname;
}
}
}
});
})(jQuery);
// Given a query string, convert all the name/value pairs
// into a property/value object. If a name appears more than
// once in a query string, the value is automatically turned
// into an array.
function queryStringToObject(qstr) {
var result = {},
nvPairs = ( ( qstr || "" ).replace( /^\?/, "" ).split( /&/ ) ),
i, pair, n, v;
for ( i = 0; i < nvPairs.length; i++ ) {
var pstr = nvPairs[ i ];
if ( pstr ) {
pair = pstr.split( /=/ );
n = pair[ 0 ];
v = pair[ 1 ];
if ( result[ n ] === undefined ) {
result[ n ] = v;
} else {
if ( typeof result[ n ] !== "object" ) {
result[ n ] = [ result[ n ] ];
}
result[ n ].push( v );
}
}
}
return result;
}
// A page can use this to get the parameters passed from the previous page.
function getUrlVars(url) {
var vars = [], hash;
var hashes = url.slice(url.indexOf('?') + 1).split('&');
for(var i = 0; i < hashes.length; i++)
{
hash = hashes[i].split('=');
vars.push(hash[0]);
vars[hash[0]] = hash[1];
}
return vars;
}
...The next page gets the parameters from the URL. Eg. in PersonPage.js:
$(document).on("pageshow", "#"+PERSON_PAGE_ID, function(event, data) {
urlVars = getUrlVars(window.location.href);
var personId = urlVars.personId;
// etc.
...This IndexPage component shows a static list of options to tap: Persons, Things, etc.:
<!DOCTYPE html>
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd" xmlns:p="tapestry:parameter">
<t:content>
<div data-role="page" id="index">
<div data-role="header" data-theme="b" data-position="fixed">
<h1>My App</h1>
</div>
<div data-role="content">
<ul data-role="listview" data-theme="c" data-inset="true" data-dividertheme="c">
<li>
<a href="#persons">Persons</a>
</li>
<li>
<a href="#things">Things</a>
</li>
<!-- etc. -->
</ul>
</div>
</div>
</t:content>
</html>
package au.com.zup.web.components.m;
import org.apache.tapestry5.annotations.Import;
@Import(library = "IndexPage.js")
public class IndexPage {
}
(function($) {
var INDEX_PAGE_ID = "index";
var indexPageInited = false; // Avoid pageinit twice (jqm bug?)
$(document).on("mobileinit", function() {
$.mobile.touchOverflowEnabled = true;
});
$(document).on("pageinit", "#"+INDEX_PAGE_ID, function(event, data) {
if (!indexPageInited) {
initThis();
initThat();
indexPageInited = false;
}
});
$(document).on("pageshow", "#"+INDEX_PAGE_ID, function(event, data) {
// Populate screen fields here...
});
})(jQuery);
...In the PersonsPage component you might want to list persons, from the database. PersonsPage.tml could have an empty content div:
<script type="text/javascript">
// We do this here, not in Tapestry.Initializer, because on page refresh Tapestry.Initializer runs AFTER the jqm page events.
var getPersonsUrl = "${getPersonsUrl}";
</script>
<!-- ...snipped... -->
<div data-role="content" id="personsContent">
</div>
<!-- ...snipped... -->
...and the PersonsPage.js could request the data (AJAX) and render it:
function requestPersons(aFilterValue) {
$.mobile.showPageLoadingMsg();
var url = getPersonsUrl;
url += "?filter=" + aFilterValue;
var parameters = {};
$.ajax({
url: url,
data: parameters,
dataType: "json",
success: function(personsData) {
renderPersonsContent(personsData);
$.mobile.hidePageLoadingMsg();
}
});
}
function renderPersonsContent(personsData) {
var $content = resolve("#"+PERSONS_CONTENT_ID);
$content.html("");
if (personsData.persons) {
var persons = personsData.persons;
var block = '';
block += '<ul id="personsList" data-role="listview" data-theme="c" data-inset="false" data-dividertheme="c" data-filter="true" data-filter-theme="d">';
$.each(persons, function(index) {
var person = persons[index];
block += '<li class="personRow">';
{
block += '<a href="#person?personId=' + person.pid + '">';
{
block += '<p>' + person.pnm + '</p>';
}
block += '</a>';
}
block += '</li>';
}
block += '</ul>';
$content.append(block);
}
else {
$content.append('<h4>Persons not found.</h4>');
}
var $page = resolve("#"+PERSONS_PAGE_ID);
$page.page();
$page.trigger("create");
}
...and PersonsPage.java can handle the AJAX request:
private static final String GET_PERSONS = "GetPersons";
public String getGetPersonsUrl() {
Link link = componentResources.createEventLink(GET_PERSONS);
return link.toAbsoluteURI();
}
@OnEvent(value = GET_PERSONS)
Object onGetPersons() {
if (!request.isXHR()) {
return Index.class;
}
List<Person> persons = null;
try {
aFilterValue = request.getParameter("filter");
persons = personFinderService.findPersons(aFilterValue);
}
catch (Exception e) {
// Ignore - defaults will be used
}
JSONObject personsData = new JSONObject();
if (person != null) {
JSONArray personsArray = new JSONArray();
for (Person person : persons) {
JSONObject personData = new JSONObject();
personData.put("pid", person.getId());
personData.put("pnm", person.getName());
personsArray.put(personData);
}
// Put the array in the JSON object to return - TODO maybe we can return the array instead
personsData.put("persons", personsArray);
}
return personsData;
}
Hope this helps,
Geoff
On 31/03/2013, at 10:07 AM, Alexander Sommer wrote:
> Hi!
>
> I am trying to build a mobile web app based on jquery mobile. For that
> reason, I need to reuse my services built with tapestry - which turned out
> not being as trivial as expected.
>
> I was reading in the forum (eg
> http://mail-archives.apache.org/mod_mbox/tapestry-users/201201.mbox/%3Ccd6ed1fa-1e5c-4cbb-a6e9-67eeb842ed42@mail-nic-00.intern.albourne.com%3E),
> that jquery is complicating (correct me if this is not true!!) things due
> to prototype used in tapestry. For that reason, I would like to try a 100%
> clean approach - my web app client should not be depended on tapestry
> (GUI!) at all, I just want to use my tapestry services.
>
> one service for example is:
>
> http://www.airwriting.com/mobile/public:getLocalGroups?lat=48.15&lng=16.3
>
> which is returning valid json. (check for non believers http://jsonlint.com/
> )
>
> BUT
>
> if I want to use this service, I run into the crossDomain problem with
> javascript, because my mobile webapp for example is (for testing purposes)
> hosted here: http://www.learnclip.com/airwritingweb/ (so not on
> airwriting.com)
>
> Now, I see two options to solve this crossDomain issue;
>
> *A.*
> Run the web app build with jquery on the same domain. -> how can I do this?
> Is it possible to copy the whole web app project just to some tapestry
> folder and tell tapestry that it should "ignore" that folder - but still
> making it accessable from the outside? eg. via
> www.airwriting.com/mobile/jquery ?
>
> *B.*
> if i got it right (http://api.jquery.com/jQuery.getJSON/), the function
>
> $.getJSON in
>
> jquery mobile needs a ?jsoncallback=? parameter, in my case
>
> http://www.airwriting.com/mobile/public:getLocalGroups?lat=48.15&lng=16.3?jsoncallback=
> ?
>
> but I really have no idea how I have to handle the jsoncallback in my
> service method:
>
> private final String CONTENT_TYPE = "application/javascript";
>
> public StreamResponse onGetLocalGroups(){
> Double latitude = Double.parseDouble(request.getParameter("lat"));
> Double longitude = Double.parseDouble(request.getParameter("lng"));
>
> List<Group> groups = groupService.getGroupsOnLocation(new
> GeoPoint(latitude, longitude).toPoint(), limit, offset);
>
> JSONArray jsGroups = new JSONArray();
> for( Group group : groups ){
> jsGroups.put(group.toJSON());
> }
> return new TextStreamResponse(CONTENT_TYPE, jsGroups.toString());
> }
> <http://www.airwriting.com/mobile/public:getLocalGroups?lat=48.15&lng=16.3>
>
> just calling (from a not airwriting.com domain)
>
> <script>
> (function() {
> var url = "
> http://www.airwriting.com/mobile/public:getLocalGroups?lat=48.15&lng=16.3";
> $.getJSON( url, {
> format: "json"
> })
> .done(function( data ) {
> $.each( data.items, function( i, item ) {
>
> alert (item);
>
> });
> });
> })();
> </script>
>
>
>
> DOES NOT WORK - so i guess it has something to do with the callback.
> Reading me deeper into the jsonP theory, my understanding focuses on the
> need for writing somekind of filter in the filterrequestchain for handling
> this kind of crossDomain request. puh.. (
> http://code.google.com/p/jsonp-java/)
>
>
> any help is highly appreciated.. thx,
> alex
>
>
> further, good links:
> http://json-p.org/
> http://alotaiba.github.com/FlyJSONP/#!/demo