Error reporting from a LoadableDetachableModel doesn't work - wicket

If an error occurs while loading a model, what is the correct way to present that to the user? It seems like adding a feedback component to the page and using error() is the correct way to do this, but when I do that, I get this error:
Caused by: org.apache.wicket.WicketRuntimeException: Cannot modify component hierarchy after render phase has started (page version cant change then anymore)
at org.apache.wicket.Component.checkHierarchyChange(Component.java:3572)
at org.apache.wicket.Component.addStateChange(Component.java:3501)
at org.apache.wicket.Component.error(Component.java:1254)
at com.prosc.wicket.LoadableDetachableModelErrorTest$1.load(LoadableDetachableModelErrorTest.java:21)
at com.prosc.wicket.LoadableDetachableModelErrorTest$1.load(LoadableDetachableModelErrorTest.java:17)
at org.apache.wicket.model.LoadableDetachableModel.getObject(LoadableDetachableModel.java:121)
at org.apache.wicket.Component.getDefaultModelObject(Component.java:1605)
Here is my Java code:
package com.prosc.wicket;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.model.LoadableDetachableModel;
public class LoadableDetachableModelErrorTest extends WebPage {
public LoadableDetachableModelErrorTest() {
add( new FeedbackPanel( "feedback" ) );
add( new Label("dynamicText", new LoadableDetachableModel<String>() {
#Override
protected String load() {
String result = "Hello World";
error( "Uh oh, some imaginary problem happened" );
return result;
}
}));
}
}
Here is my HTML markup:
<html>
<body>
<div wicket:id="feedback" />
<div wicket:id="dynamicText" />
</body>
</html>
The expected result is to see a blank page with the error message displayed to the user.

I'm not really sure what would be the 'best' way, but you can set the error on the Session. But you will still need to think about what you want to display in your components that use your model..
public class LoadableDetachableModelErrorTest extends WebPage {
public LoadableDetachableModelErrorTest() {
add( new FeedbackPanel( "feedback" ) );
add( new Label("dynamicText", new LoadableDetachableModel<String>() {
#Override
protected String load() {
if (failure) {
Session.get().error( "Uh oh, some imaginary problem happened" );
return null;
}
else
return result;
}
}));
}
}
You can also throw a nice typed Exception and handle it in a custom RequestCycle (see full info here: http://wicketinaction.com/2008/09/building-a-smart-entitymodel/)
public class MyRequestCycle extends WebRequestCycle
{
#Override
public Page onRuntimeException(Page page, RuntimeException e)
{
if (e instanceof EntityNotFoundException)
{
return new EntityNotFoundErrorPage((EntityNotFoundException)e);
}
else
{
return super.onRuntimeException(page, e);
}
}
}

When Wicket starts rendering components, they are no longer allowed to change their state.
You'll have to check for errors in your component's #onConfigure().

Related

Wicket 7 Link/Label error when using inheritance

With Wicket 7, I am working on an app that uses a base page as a template for other pages to extend.
On the base page, I want to have a label and a link that changes depending on whether the user is authenticated or not.
Here's my BasePage.html:
<div wicket:id="chromeMenu">foo</div>
<div>
<h2 wicket:id="userGreeting"></h2>
<h2><span wicket:id="loginLabel"></span> </h2>
</div>
<wicket:child/>
and the BasePage.java:
public BasePage() {
super();
add(new ChromeDropDownMenu("chromeMenu", buildMenu()));
add(new Label("pageTitle", new StringResourceModel("page.title", this, null)));
if(BasicAuthenticatedSession.get().isSignedIn()) {
// Do stuff here
} else {
add(new Label("userGreeting", "Hello Visitor"));
add(new Link("loginLink") {
#Override
public void onClick() {
setResponsePage(LoginPage.class);
}
});
add(new Label("loginLabel","Test"));
}
}
HomePage extends BasePage.
HomePage.html
<wicket:extend/>
HomePage.java
public class HomePage extends BasePage {
private static final long serialVersionUID = 1L;
public HomePage() {
super();
setPageTitle(new StringResourceModel("page.title", this, new Model<Serializable>("Admin")));
add(new Label("version", getApplication().getFrameworkSettings().getVersion()));
}
}
HomePage is the class returned by the Wicket application.
When I try to load HomePage, I get the following error:
Last cause: Unable to find component with id 'loginLabel' in [Link [Component id = loginLink]]
Expected: 'loginLink:loginLabel'.
Found with similar names: 'loginLabel'
It points to the <a><span/></a> structure from BasePage.html as the root of the problem.
I've tried a few ways to work around this, but without success. I thought maybe an add(Link).add(Label) might be needed, but that didn't work either.
Any thoughts out there on what I'm missing?
The error message says it all.
Last cause: Unable to find component with id 'loginLabel' in [Link
[Component id = loginLink]]
Expected: 'loginLink:loginLabel'.
Found with similar names: 'loginLabel'
Wicket is expecting the same component hierarchy in your Java code as you've written in the HTML. In BasePage.html you have:
<h2><span wicket:id="loginLabel"></span> </h2>
In the BasePage.java code you need to add loginLabel as a child of loginLink component.
Link loginLink = new Link("loginLink") {
#Override
public void onClick() {
setResponsePage(LoginPage.class);
}
};
add(loginLink);
loginLink.add(new Label("loginLabel", "Test"));
The problem is at
add(new Link("loginLink") {
#Override
public void onClick() {
setResponsePage(LoginPage.class);
}
});
add(new Label("loginLabel","Test"));
The Link should be the parent of the Label:
link = new Link("loginLink") {
#Override
public void onClick() {
setResponsePage(LoginPage.class);
}
};
link.add(new Label("loginLabel","Test"));
add(link);
Few extra notes:
better use BookmarkablePageLink if setResponsePage() is the only thing you need in onClick()
use AbstractLink#setBody(IModel label) instead of Link+Label

AEM 6.1 Sightly basic form submit and redirect to same page

I am trying to do the following on AEM 6.1:
Develop a simple form (3 input fields)
Process the submitted values,
And redirect to the same page with processed values/result
I am able to submit the values to a servlet, and process them (business logic), and the result to a requestparamter so i can retrieve them on the UI. But i am stuck at these:
Redirecting to the same page
And retrieving the request parameters and display them using Sightly.
Code Snippets:
Servlet
#SlingServlet(
methods = { "POST","GET" },
name="com.tti.tticommons.service.servlets.LeadTimeTrendsServlet",
paths = { "/services/processFormData" }
)
public class TTICommonServlet extends SlingAllMethodsServlet{
...
#Override
protected void doPost(SlingHttpServletRequest request,SlingHttpServletResponse response) throws ServletException,IOException {
String result;
try {
Enumeration<String> parameterNames = request.getParameterNames();
Map<String, String> formParametersMap = new HashMap<String, String>();
while (parameterNames.hasMoreElements()) {
paramName = parameterNames.nextElement();
paramValue = request.getParameter(paramName);
.......
.......
}
request.setAttribute("result",result);
response.sendRedirect("/content/ttii/en/**posttest.html**");
}
}
Can anyone please help on ho to retireve the above "result" in posttest.html using sightly.
After lot of research and several trials, i finally had the code working. I had to pick up related info from several answers in stackoverflow. Thanks to all the authors. Posting my solution here so beneficial for others.
Result Form with response from webservice:
Process flow
Submit form data to Servlet's POST method
In Servlet, get the values entered by the user from the request
Make the necessary webservice calls. Get the response(json)
I added the response-json as a parameter to the request
Using Wrapper, forward to the necessary page
Define a WCMUse class for use with Sightly.
Assign the 'request' to the Use-class and process it there
Use the assigned values from the Use-class to the UI using sightly
Code snippets - HTML
<form name="userRegistrationForm" method="post" action="/services/processFormData">
<input type="hidden" name=":redirect" value="posttest.html" />
<input type="submit" title="Submit" class="btn submit btn-success" value="Submit" tabindex="25" name="bttnAction">
<div data-sly-use.model="${'com.abccommons.service.helpers.PostServiceHelper' # slingreq=request }">
**${model.getRawJson}**
</div>
Code snippets - Servlet
#SlingServlet(
label = "ABC - Common Servlet",
metatype = true,
methods = { "POST" },
name="com.abccommons.service.servlets.ABCPostServlet",
paths = { "/services/processFormData" }
)
public class ABCPostServlet extends SlingAllMethodsServlet{
#Override
protected void doPost(SlingHttpServletRequest request,SlingHttpServletResponse response) throws ServletException,IOException {
log.info("\n\n----- ABCPostServlet POST: ");
String paramName;
String paramValue;
String osgiService="";
try {
Enumeration<String> parameterNames = request.getParameterNames();
Map<String, String> formParametersMap = new HashMap<String, String>();
while (parameterNames.hasMoreElements()) {
paramName = parameterNames.nextElement();
paramValue = request.getParameter(paramName);
if (paramName.equals("osgiService")) {
osgiService = paramValue;
} else if (paramName.equals(":cq_csrf_token")) {
//TODO: don't add to the map
} else if (paramName.equals("bttnAction")) {
//TODO: dont' add to the map
} else {
//log.info("\n---ParamName="+paramName+", value="+paramValue);
formParametersMap.put(paramName, paramValue);
}
}
String parametersInJSON = JSONHelper.toJson(formParametersMap);
log.info("\n\n----------- POST paramters in json="+parametersInJSON);
String json = webServiceHelper.getJSON(osgiService, parametersInJSON, request, response);
log.info("\n\n----------- POST json from web service="+json);
request.setAttribute("jsonResponse",json);
//String redirectPage = request.getParameter(":redirect");
//RequestDispatcher dispatcher = request.getRequestDispatcher("/content/en/"+redirectPage);
RequestDispatcher dispatcher = request.getRequestDispatcher("/content/en/postformtest.html");
GetRequest getRequest = new GetRequest(request);
dispatcher.forward(getRequest, response);
} catch (Exception e) {
log.error("SlingServlet Failed while retrieving resources");
} finally {
//TODO
}
}
/** Wrapper class to always return GET for AEM to process the request/response as GET.
*/
private static class GetRequest extends SlingHttpServletRequestWrapper {
public GetRequest(SlingHttpServletRequest wrappedRequest) {
super(wrappedRequest);
}
#Override
public String getMethod() {
return "GET";
}
}
Code snippets - PostServiceHelper - WCMUSe class
public class PostServiceHelper extends WCMUse {
protected final Logger log = LoggerFactory.getLogger(PostServiceHelper.class);
private SlingHttpServletRequest httpRequest;
private String rawJson;
#Override
public void activate() throws Exception {
log.info("\n\n========= PostServiceHelper.activate():"+get("slingreq", SlingHttpServletRequest.class));
this.httpRequest = get("slingreq", SlingHttpServletRequest.class);
//this.resourceResolver = getResourceResolver();
//log.info("\n\n========= getRequest()="+getRequest());
SlingHttpServletRequest tRequest;
Set<String> keys = new HashSet<String>();
Enumeration<?> attrNames = this.httpRequest.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attr = (String) attrNames.nextElement();
//log.info("\n--- Key="+attr);
if (attr.equals("jsonResponse")) {
this.setRawJson((String)this.httpRequest.getAttribute(attr));
//log.info("\n---rawJson is SET with : "+this.rawJson);
}
}
}
public void setRawJson(String json) {
this.rawJson = json;
}
public String getRawJson() {
return this.rawJson;
}
}
This is actually a rather tricky pattern to achieve in Sling. You may be better served by submitting the form asynchronously and updating your HTML dynamically via JavaScript.
If you do need to submit your form in the manner you specify, then your servlet needs to produce the HTML response. To produce a response made up of a rendering of the page identified by the requested path your servlet will need to dispatch the request to the appropriate rendering mechanism. You can reference Get JSP output within Servlet in AEM for information concerning how that can be accomplished. Upon dispatch your page and its components should have access to the submitted form values as well as the attributes set on the request.

Wicket: tell the browser to scroll to certain tag (anchor)

We are using Wicket and our generated pages are quiet long (a lot of vertical scrolling). Some links or form's onSubmit methods invoke just perform some actions on the database and show the same page again:
public class MyPage extends WebPage {
public MyPage(PageParameters parameters) {
....
final Form<Void> form = new StatelessForm<Void>("formId") {
protected void onSubmit() {
// some database stuff
...
setResponsePage(getClass(), getPageParameters());
}
};
...
}
}
How can I make the setResponsePage invocation cause the browser scroll to the form, so the page is not just showing the top? Maybe some JavaScript-injection?
I think a nice Wicket-y solution combines stuff that is already in Michael's answer, with a Behavior, so you can just add this to your form.
form.add( new ScrollToTopBehavior());
The behaviour itself would like something like this:
public class ScrollToTopBehavior extends Behavior
{
#Override
public void renderHead( Component component, IHeaderResponse response )
{
super.renderHead( component, response );
response.render( JavaScriptHeaderItem.forReference( Application.get().getJavaScriptLibrarySettings().getJQueryReference() ) );
component.setOutputMarkupId( true );
String script = String.format("doSomeJavaScriptStuff('%s')", component.getMarkupId());
response.render( OnDomReadyHeaderItem.forScript( script ) );
}
}
UPDATE:
For scrolling to a specific ID / ANCHOR only once, you can follow this answer:
https://stackoverflow.com/a/3163635/461499
JS of course.
This would be something like (with JQuery usage):
var scrollPosition = $('#scrollToMarkupId').offset().top;
$('html, body').animate({ scrollTop: " + scrollPosition + " }, 'slow');
where scrollToMarkupId is wicket component's markup id, which could be obtained by calling component.getMarkupId() method.
I'm not pro in JS, so you can try to google better impl may be.
Now, about wicket:
1) As for me, I prefer AJAX invocations for such behavior ( note that if you use such approach your page won't be stateless ):
// do not override your form's `onSubmit()` method
final Form<Void> form = new Form<Void>("formId");
// adding ajax behavior with `onSubmit()` method overriding.
form.add ( new AjaxFormSubmitBehavior ("submit")
{
protected void onSubmit ( AjaxRequestTarget target )
{
// your submit logic
// then insert js, descriped above:
target.appendJavaScript ("..." + componentToScroll.getMarkupId() + "..");
}
});
This approach won't reload your page at all but also post your data.
/----------------------------------------------------------------------------------------------------------------------------------/
2) You also could execute JS after page loading, by overriding renderHead method:
public class YourPage extends WebPage
{
...
#Override
public void renderHead ( final IHeaderResponse response )
{
//replace `...` by your script.
response.render ( OnDomReadyHeaderItem.forScript ( "..." );
}
...
}
Such script will be invoked after page is renedered (and setResponsePage method will render your page). You can use this approach for any components and panels too.
I've now use following JavaScript injecting code:
add(new Behavior() {
#Override
public void renderHead(Component component, IHeaderResponse response) {
super.renderHead(component, response);
response.render(new HeaderItem() {
#Override
public Iterable<?> getRenderTokens() {
return Collections.singletonList("javascript-anchor");
}
#Override
public void render(Response response) {
response.write("<script type=\"text/javascript\">\n");
response.write("window.location.href='#rules';\n");
response.write("</script>\n");
}
});
}
});
Feel free to comment (I'm a complete JS-noob with only very limited experience in Wicket).

Processing a ListView inside a form

Please consider the following signup:
<form wicket:id="form">
<div wicket:id="fooList">
<input wicket:id="fooList.quxField" type="text" size="10"/>
</div>
<button wicket:id="submit"><wicket:message key="submitText"/></button>
</form>
And these two classes (I am assuming setters, getters etc.)
class FooClazz {
String quxField;
}
class BarClazz {
List<FooClazz> fooList;
}
And this form (models are wrapped in CompoundPropertyModel):
class BarForm extends Form<BarClazz> {
public BarForm(String id,final IModel<BarClazz> model){
super(id,model);
add(new ListView<FooClazz>("fooList"){
#Override
protected void populateItem(final ListItem<FooClazz> item){
item.add(new TextField<String>("fooList.quxField"));
}
}
}
}
Now the above code is generating a runtime exception for me:
2011-12-11 16:33:46 ERROR [org.apache.wicket.DefaultExceptionMapper] Unexpected error occurred org.apache.wicket.WicketRuntimeException: The expression 'quxField' is neither an index nor is it a method or field for the list class java.util.ArrayList
I can change the TextField to include a Model like this:
item.add(new TextField<String>("fooList.quxField", new Model<String>(model.getObject().getFooList().getQuxField())));
This resolves the error, but when I submit the form (with an Ajaxbutton) I never get to see the values entered into the form fields.
So how can I keep the TextField models connected to my form model? Am I overlooking the obvious?
(This is of course just a very shortened version of my original code ...)
Need to add: all my models are wrapped in CompoundPropertyModels.
Thanks in advance for any tips how to fix this.
I found it. I need to include a model for the TextField that has implementations for both getObject() and of course setObject(). So I really was missing the obvious.
#Override
protected void populateItem(final ListItem<Taste> item) {
final TextField<String> quxField = new TextField<String>("tastes.quxField", new Model<String>() {
#Override
public String getObject() {
return item.getModel().getObject().getquxField();
}
#Override
public void setObject(final String value) {
item.getModel().getObject().setquxField(value);
}
});
item.add(quxField);
}

How to implement content assist's documentation popup in Eclipse RCP

I have implemented my own editor and added a code completion functionality to it. My content assistant is registered in source viewer configuration like this:
public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) {
if (assistant == null) {
assistant = new ContentAssistant();
assistant.setDocumentPartitioning(getConfiguredDocumentPartitioning(sourceViewer));
assistant.setContentAssistProcessor(getMyAssistProcessor(),
MyPartitionScanner.DESIRED_PARTITION_FOR_MY_ASSISTANCE);
assistant.enableAutoActivation(true);
assistant.setAutoActivationDelay(500);
assistant.setProposalPopupOrientation(IContentAssistant.PROPOSAL_OVERLAY);
assistant.setContextInformationPopupOrientation(IContentAssistant.CONTEXT_INFO_ABOVE);
}
return assistant;
}
When I press Ctrl + SPACE inside the desired partition, the completion popup appears and works as expected.
And here's my question.. How do I implement/register a documentation popup that appears next to completion popup? (For example in java editor)
Well,
I'll answear the question myself ;-)
You have to add this line
assistant.setInformationControlCreator(getInformationControlCreator(sourceViewer));
to the configuration above. Then when creating CompletionProposals, the eighth (last) parameter called additionalProposalInfo of the constructor is the text, which will be shown in the documentation popup.
new CompletionProposal(replacementString,
replacementOffset,
replacementLength,
cursorPosition,
image,
displayString,
contextInformation,
additionalProposalInfo);
More information about can be found here.
Easy, isn't it.. if you know how to do it ;)
For the styled information box (just like in JDT).
The DefaultInformationControl instance need to received a HTMLTextPresenter.
import org.eclipse.jface.internal.text.html.HTMLTextPresenter;
public class MyConfiguration extends SourceViewerConfiguration {
[...]
public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) {
if (assistant == null) {
[...]
assistant.setInformationControlCreator(getInformationControlCreator(sourceViewer));
}
return assistant;
}
#Override
public IInformationControlCreator getInformationControlCreator(ISourceViewer sourceViewer) {
return new IInformationControlCreator() {
public IInformationControl createInformationControl(Shell parent) {
return new DefaultInformationControl(parent,new HTMLTextPresenter(false));
}
};
}
}
Proposals can then use basic HTML tags in the string from method getAdditionalProposalInfo().
public class MyProposal implements ICompletionProposal {
[...]
#Override
public String getAdditionalProposalInfo() {
return "<b>Hello</b> <i>World</i>!";
}
}