You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@tapestry.apache.org by Alexander Sommer <al...@gmail.com> on 2013/03/31 00:07:18 UTC
Query Mobile & Tapestry
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
Re: Query Mobile & Tapestry
Posted by Thiago H de Paula Figueiredo <th...@gmail.com>.
On Sat, 30 Mar 2013 20:07:18 -0300, Alexander Sommer
<al...@gmail.com> wrote:
> Hi!
Hi!
> 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.
Have you tried tapestry-jquery with the suppress Prototype configuration
turned on? This will make Tapestry work completely free of Prototype. This
way, you don't need to throw the baby together with the bath water. ;)
> 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 this is something which is a pure JavaScript security issue, not
related to Tapestry.
--
Thiago H. de Paula Figueiredo
---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org
Re: Query Mobile & Tapestry
Posted by Geoff Callender <ge...@gmail.com>.
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