How can I use jQuery in a custom Tapestry 5 component - java

I am attempting to create a component that if given the following tml:
<t:slideout>
<p:header>Short Description of Data</p:header>
Long Details about the data here
</t:slideout>
This should initially render the block in the header parameter, when this block is clicked I want the long details section to slide out using the jQuery .slideDown() function or equivalent.
Currently I have the following class:
public class slideout
{
#Parameter(name="header", defaultPrefix = BindingConstants.LITERAL)
private Block headerBlock;
public Block getHeader()
{
return headerBlock;
}
}
and the corresponding slideout.tml file:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
xmlns:p="tapestry:parameter">
<body>
<t:delegate to="header"/>
<t:body/>
</body>
</html>
We are already making use of the tapestry5-jQuery library, this component needs be able to be used multiple times on the same page so I'm also not sure of how to ensure that there are no ID collisions when rendering the page.
I'm not sure where to progress from here, if I was doing this in raw HTML/jQuery I'd do something like
$('slideout-header').click(function() {
$(this).next('slideout-body').slideDown();
});
However I'm not sure of what the "Tapestry" way of constructing these classes would be. What is the best way to solve this problem in Tapestry 5?

You can add a Slideout.js file next to your Slideout.tml:
Tapestry.Initializer.Slideout = function (parameters) {
var yourClientId = parameters.clientId;
//your javascript init script here
};
Add to your Slideout.java:
#Import(library = {"Slidout.js"})
public class Slideout {
#Inject
private JavaScriptSupport javaScriptSupport;
#Parameter(name="header", defaultPrefix = BindingConstants.LITERAL)
private Block headerBlock;
#Property
#Parameter(value = "prop:componentResources.id", defaultPrefix = "literal")
private String clientId;
#AfterRender
private void afterRender() {
JSONObject props = new JSONObject();
props.put("clientId", clientId);
javaScriptSupport.addInitializerCall("Slideout", props);
}
public Block getHeader()
{
return headerBlock;
}
}
and your Slideout.tml (note that I removed the html so that you can use Slideout as a component)
<div id="${clientId}" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
xmlns:p="tapestry:parameter">
<t:delegate to="header"/>
<t:body/>
</div>
Disclaimer: I have not tested this code so have a play.

Related

Either did not add the component to your page at all, or that the hierarchy does not match in wicket

Currently i m working one requirement in wicket framework.i done some coding part related to that.but i got hierarchy does not match expection always.i followed the hierarchy,but do not wt wrong i did.
here is my requirement:
by clicking on tab,i would like to display progress bar.i used ajax lazy load concept here..
Here is code:
ConditionalListView<Ancillary> orderAncillariesNavigationList = new ConditionalListView<Ancillary>(
"ancillaryItems", orderAncillaryList) {
#Override
protected void populateItem(ListItem<Ancillary> item) {
final Ancillary ancillary = item.getModelObject();
// add ajax link
final AjaxLink<String> navigationLink = new AjaxLink<String>("ancillaryLink") {
private static final long serialVersionUID = 1L;
#Override
public void onClick(AjaxRequestTarget target) {
// replace data with this ancillary
replaceWithSelectedAncillary(target, ancillary);
target.appendJavascript(CLEAR_BIG_EASY_FEEDBACK);
}
};
navigationLink.add(new Label("ancillaryLinkName", ancillary.getLabel()));
//progress bar
if(ancillary.getLabel().equals("Episode / Show Descriptions")){
add(new AjaxLazyLoadPanel("lazy")
{
private static final long serialVersionUID = 1L;
#Override
public Component getLazyLoadComponent(String id)
{
// sleep for 5 seconds to show the behavior
try
{
Thread.sleep(5000);
}
catch (InterruptedException e)
{
throw new RuntimeException(e);
}
return navigationLink;
}
});
}
// set class
if (ancillary.getId().longValue() == selectedAncillary.getId().longValue()) {
item.add(new AttributeAppender("class", new Model<String>("selected"), " "));
}
item.add(navigationLink);
//item.add(TestPage());
}
};
HTML:
<div class="reset"> </div>
<div wicket:id="feedback"></div>
<div wicket:id="lazy"></div>
<div id="ancillaryNavigation">
<ul id="ancillaryTabs" class="group">
<li wicket:id="ancillaryItems">
<span wicket:id="ancillaryLinkName">Music Cue Sheets</span>
</li>
</ul> <!-- /#ancillaryTabs -->
</div> <!-- /#ancillaryNavigation -->
<form wicket:id="ancillaryManualForm" name="ancillaryManualForm" class="epForm">
<div wicket:id="ancillaryOrderEpisodes" />
</form>
</div> <!-- /#ancillaryOrders -->
Please help me out.What i did wrong here.
You might want to refer to the wicket examples for adding links to the list view. Here is my observation from your code: 1. You are loading a link in lazy load. Is the loading of your link going to take long time or the panel being loaded on click of the link is going to take long time? 2.you don't add label to the link as child component, you can set the label of the link via property model. 3. Check the sequence of your markup components and java components structures. Parent -child components hierarchy must match.
I would first start with taking small steps one at a time; looking into wicket examples for all the different components I am using.

How to avoid WSOD (blank screen) while loading long-running initialization data in Struts2?

I need to do the following:
User logs in.
Redirected to welcome screen.
Looks at the welcome screen while lots of records are loaded.
Redirected to the working screen.
I am looking for a way to do in Action class something like this:
public class LinkAction extends ActionSupport implements SessionAware {
#Autowired
private ServiceDelegate myService;
public String welcome()
{
new Runnable() {
#Override
public void run() {
myService.getLoadsOfData();
//redirect to the next action
}
}.run();
// this is where the user
// goes to look at the welcome screen
return "welcome";
}
}
May be it's a wrong approach, please tell if so, I am new at Struts.
The right way is the one already suggested by AleksandrM in comments: open the page, show an indicator while you call an ajax action (let's say with jQuery, for convenience), then render the result and remove the indicator. It's easier than you think:
public class MainAction extends ActionSupport {
public String execute() {
return SUCCESS;
}
}
public class AjaxAction extends ActionSupport {
#Autowired
private ServiceDelegate myService;
private Stuff myStuff; // with getter
public String execute() {
myStuff = myService.loadLongStuff();
return SUCCESS;
}
}
Your AJAX action can either return JSON data, a JSP snippet or a Stream of binary data. Choose the way you prefer. For example, if you map SUCCESS of AjaxAction to a JSP snippet, your JSP snippet will be:
ajaxSnippet.jsp
<%# taglib prefix="s" uri="/WEB-INF/struts-tags.tld" %>
Stuff: <s:property value="myStuff" />
Then in your main.jsp, show the indicator in the div you will overwrite with the AJAX call's result:
main.jsp
<body>
<div id="main">
<img src="/images/mesmerizingProgressBar.gif" />
</div>
<script>
$(function(){ // onDocumentReady...
$.ajax({ // call ajax action...
type : 'GET',
url : '/ajaxAction.action',
success : function(data,textStatus,jqXHR){
// then render your result in "main" div,
// overwriting the loading GIF
$("#main").html(data);
},
error : function(jqXHR, textStatus, errorThrown){
$("#main").html("Error ! " + textStatus);
}
});
});
</script>
</body>
Thank you for the AJAX idea.
However, the answer I was looking for was in fact Struts interceptor "execAndWait".
I decided to use it over AJAX because I am dealing with existing application and all the Struts plumbing is in place.
This is the Struts guide on this

Loading HTML templates via Ajax from Google Web Toolkit?

For my gwt app, I have certain html templates that are stored as static .html files on my website. e.g example.com/views/signup.html , example.com/views/foo.html , etc.
I want this to be a single page app like Twitter, so the user would navigate through all the pages without the page being refreshed.
I will have a HTMlPanel as the root element of my app on the host page. Whenever the user navigates to a different page, e.g by clicking a link in the navigation menu, I want to load the .html template for that page via ajax, and set the returned html into the HTMLPanel.
Is this a plausible solution? If so, how can I load the html templates via ajax from GWT?
Thanks.
That is precisely the case of http://gwtproject.org site.
It uses gwtquery to load an html page via ajax and insert it in a certain area of the page via the load() method.
// Load the file.html using ajax, and append the fragment with id=mid from
// the returned document inside the element with id=c in the current document.
$("#c").load("file.html #mid");
You can take a look to the GWTProjectEntryPoint.java (line 128) of the gwt-site-webapp as well.
Of course you have to handle any click on any anchor present in the inserted fragment, to do the appropriate action instead of replacing the gwt application. That can be done with the live() method of gQuery.
$("#c").live("click", new Function() {
public boolean f(Event e) {
String href = $(e).attr("href");
// Do something with href.
return false;
}
});
You may want to look at this SO Q&A.
The idea is:
You create a server call (e.g., RPC) to fetch your html template as a String.
You create a corresponding HTMLPanel that knows how to attach dynamic elements to your html template that you pass in the constructor.
You add this newly constructed HTMLPanel to your page root panel where you want it.
On this SO you can find a panel template code that may help you. The version therein does not have the "dynamic" html source, instead it is hard-coded in a ClientBundle. But it is easy to extend so the source html is dynamic, simply add a construtor like:
public HtmlPanelBase(final String htmlContentAsText)
where htmlContentAsText is your template html String from your server as follows:
public class HtmlPanelBase extends Composite
{
private String _dynPostfix = "";
protected final String id(final String staticId) { return staticId + _dynPostfix; }
private final String wrapId(final String id) { return "id=\"" + id + "\""; }
private final String wrapDynId(final String refId) { return wrapId(id(refId)); }
private String _htmlContentAsText = null;
protected String getHtmlContentAsText() { return _htmlContentAsText; }
private ArrayList<String> _idList = null;
protected HTMLPanel _holder = null;
private HTMLPanel createHtmlPanel(final boolean defineGloballyUniqueIds)
{
// HTML panel text containing the reference id's.
if (defineGloballyUniqueIds)
{
// Replace the reference id's with dynamic/unique id's.
for (String refId : _idList)
_htmlContentAsText = _htmlContentAsText.replace(wrapId(refId), wrapDynId(refId));
}
// Return the HTMLPanel containing the globally unique id's.
return new HTMLPanel(_htmlContentAsText);
}
public HtmlPanelBase(final String htmlContentAsText, final ArrayList<String> idList, final boolean defineGloballyUniqueIds)
{
_htmlContentAsText = htmlContentAsText;
_idList = idList;
this.setup(defineGloballyUniqueIds);
super.initWidget(_holder);
}
public HtmlPanelBase(final String htmlContentAsText)
{
this(htmlContentAsText, null, false);
}
private void setup(final boolean defineGloballyUniqueIds)
{
if (defineGloballyUniqueIds)
_dynPostfix = "_" + UUID.uuid().replace("-", "_");
_holder = createHtmlPanel(defineGloballyUniqueIds);
}
}
To use, fetch your htmlContentAsText on sever (could depend upon locale), upon success instantiate a derived class of the above base template passing the fetched htmlContentAsText in the constructor, and add therein all your logic modifying or adding upon the base html -- e.g., add handlers in response to user actions.

Tapestry, Get selected item from loop

I'm currently working on some project, using Apache Tapestry 5.3.6. I have issue using t:loop component. Is there any way I can get selected item after loop finishes, and page is rendered?
What I need to achieve is: Let's say I have loop like this:
<t:loop t:source="itemList" t:value="item">
<t:actionlink id="something" context="item.ID"></t:actionlink>
</t:loop>
This will work fine. But if I move this actionlink into my own component, and pass this ID through my parameter, if I click, I always get the last item from list, and not the one which is clicked.
<t:loop t:source="itemList" t:value="item">
<t:mycomponent myparameter="item.ID"></t:mycomponent>
</t:loop>
I tried putting formState="iteration", and puting ValueEncoder, but nothing helps.
Please, can anyone help me, and show me how to solve this issue, and get the selected item from the list.
Thanks in advance
Edit: Here is code of my component
public class Ocenjivanje
{
#Parameter(required=true)
#Property
private int materijalID;
private Materijal materijal;
#Inject
private Session session;
#SessionState
private User user;
#CommitAfter
public Object unesiOcenu(int ocena)
{
Materijal m = (Materijal)session.createCriteria(Materijal.class).add(Restrictions.eq("materijalID", this.materijalID)).list().get(0);
Date d = new Date();
Ocena o = new Ocena();
o.setMaterijal(m);
o.setKorisnikID(this.user.getID());
o.setDatumOcene(d);
o.setOcena(ocena);
session.save(o);
return this;
}
public void onActionFromJedan()
{
unesiOcenu(1);
}
public void onActionFromDva()
{
unesiOcenu(2);
}
public void onActionFromTri()
{
unesiOcenu(3);
}
public void onActionFromCetiri()
{
unesiOcenu(4);
}
public void onActionFromPet()
{
unesiOcenu(5);
}
}
<t:container
xmlns="http://www.w3.org/1999/xhtml"
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
xmlns:p="tapestry:parameter">
Oceni sadržaj:
<t:actionlink t:id="jedan">1</t:actionlink>
<t:actionlink t:id="dva">2</t:actionlink>
<t:actionlink t:id="tri">3</t:actionlink>
<t:actionlink t:id="cetiri">4</t:actionlink>
<t:actionlink t:id="pet">5</t:actionlink>
I'm not quite sure what you are trying to achieve but in any case you do not use the context you pass in in your actionlinks and use hardcoded int's in stead. Change your action links to <t:actionlink t:id="tri" context="materijalID">3</t:actionlink> and change your event handlers to
public void onActionFromJedan(int context)
{
unesiOcenu(context);
}

Wicket: make a generated csv available to a dygraphs JavaScript

I'm trying to figure out how to make a dynamically generated csv available to a dygraphs JavaScript.
I'm using a wicket behavior to add the dygraph (JavaScript graph) to my markup like shown in the codesample bellow. Right now I've hardcoded it to use a csv file named "dygraph.csv". I want to change this, and instead make dygraph use the values from String csv, how do I achieve this?
Any help help is greatly appreciated.
public class DygraphBehavior extends AbstractBehavior {
private static final long serialVersionUID = -516501274090062937L;
private static final CompressedResourceReference DYGRAPH_JS = new CompressedResourceReference(DygraphBehavior.class, "dygraph-combined.js");
#Override
public void renderHead(IHeaderResponse response) {
response.renderJavascriptReference(DYGRAPH_JS);
}
#Override
public void onRendered(Component component) {
final String id = component.getId();
Response response = component.getResponse();
response.write(JavascriptUtils.SCRIPT_OPEN_TAG);
response.write("new Dygraph(document.getElementById(\""+id+"\"), \"dygraph.csv\", {rollPeriod: 7, showRoller: true, errorBars: true});");
response.write(JavascriptUtils.SCRIPT_CLOSE_TAG);
}
}
public class Dygraph extends WebPage {
public Dygraph() {
String csv = "Date,ms\n20070101,62\n20070102,62";
add(new ResourceLink<File>("csv", new ByteArrayResource("text/csv", csv.getBytes())));
add(new Label("graphdiv").add(new DygraphBehavior()));
}
}
<div>
<h1>Dygraph:</h1>
<div wicket:id="graphdiv" id="graphdiv" style="width:500px; height:300px;"></div>
<a wicket:id="csv" href="#">dl generated csv</a>
</div>
public class Dygraph extends WebPage {
public Dygraph() {
String csv = "Date,ms\n20070101,62\n20070102,62";
ResourceLink<File> link = new ResourceLink<File>("csv", new ByteArrayResource("text/csv", csv.getBytes()));
add( link );
//this is the url that should be passed to the javascript code
CharSequence url = link.urlFor( IResourceListener.INTERFACE );
add(new Label("graphdiv").add(new DygraphBehavior()));
}
}
There are other solutions based on the scope of your resource, maybe a dynamic shared resource would work better (if your graph parameters can simply be passed as url parameters), but this will work.
The JavaScript needs to see the data in some way after the page has been rendered. So you have two options:
Embed the data in the page (say in a hidden div) and then let JavaScript read the data from there as text.
Create a servlet where the JavaScript can download the data from.
The second option means that your page rendering code has to pass the data somehow to the servlet. You can try to put it into the session but then, it will sit there, occupying RAM. Probably not a problem if it's just a little bit of data and you have only a few users. But if that's not true, option #1 is probably better.

Categories