Wicket 7 Link/Label error when using inheritance - wicket

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

Related

NavbarDropDownButton displaying blank links

I'm trying to use Wicket-Bootstrap Navbar and NavbarDropDownButton inside my Wicket Application to display a list of links.
Web application is built with Wicket 8.7.0 and Wicket-Boostrap-core 3.0.0-M13.
The component I added to the Wicket Panel seems to work correctly, but once I click on the main link to display the dropdown content, all links are blank. Anyway I can click the links and they made their job.
Here's the picture taken from the browser:
Here's the HTML used:
....
<div class="navbar" wicket:id="utenteNavbar">
</div>
</div>
while this is my Java code:
Model<String> cambioPasswordStringModel = Model.of(getString("cambioPassword"));
Navbar navbar = new Navbar("utenteNavbar");
INavbarComponent dropDownComponent = new INavbarComponent() {
#Override
public ComponentPosition getPosition() {
return Navbar.ComponentPosition.LEFT;
}
#Override
public Component create(String markupId) {
NavbarDropDownButton dropdownButton = new NavbarDropDownButton(Model.of(welcomeMessage)) {
#Override
protected List<AbstractLink> newSubMenuButtons(String buttonMarkupId) {
final List<AbstractLink> linksList = new ArrayList<AbstractLink>();
AjaxLink<String> cambioPasswordLink = new AjaxLink<String>(buttonMarkupId,
cambioPasswordStringModel) {
#Override
public void onClick(AjaxRequestTarget target) {
setResponsePage(CambioPasswordPage.class);
}
};
linksList.add(cambioPasswordLink);
return linksList;
}
};
return dropdownButton;
}
};
navbar.addComponents(dropDownComponent);
add(navbar);
add(welcomeMessageLabel);
Where's the mistake? Maybe I'm missing something on the CSS side?
Hope everything is clear, thanks for any help.

Error reporting from a LoadableDetachableModel doesn't work

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().

Reusing wicket component in a form

I have built a wicket component that contains input/labels and methods to change presentation (required, enabled, etc.). The components render fine, but what happens is when the form submits I see only 1 form parameter 'input', and it's the last InputRow component.
InputRow.html
<html xmlns:wicket="http://wicket.apache.org">
<head>
<link rel="stylesheet" type="text/css" href="style.css"/>
</head>
<body>
<wicket:panel>
<label wicket:id="label">abc: <span class="req">*</span></label>
<span class="input">
<input wicket:id="input" type="text" id="name"></input>
</span>
<span wicket:id="input_feedback"></span>
</wicket:panel>
</body>
</html>
InputRow.java
package com.wicket;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.feedback.FeedbackMessage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.Model;
public class InputRow extends Panel{
#SuppressWarnings("unused")
private String id;
public InputRow(String id, String label) {
super(id);
this.id = id;
Label memberIdLabel = new Label("label",label);
memberIdLabel.setEscapeModelStrings(false)
.add(new AttributeAppender("for", new Model<String>(id),""));
add(memberIdLabel);
TextField<String> name = new TextField<String>("input");
name.setType(String.class)
.setMarkupId(id)
.setOutputMarkupId(true);
add(name);
add(new Label("input_feedback",""));
}
public InputRow disable()
{
get("input")
.setEnabled(false)
.add(new AttributeAppender("class", new Model<String>("disabled"),""));
get("label")
.add(new AttributeAppender("class", new Model<String>("disabled"),""));
return this;
}
public InputRow required()
{
Model model = (Model)get("label").getInnermostModel();
StringBuffer label = new StringBuffer((String)model.getObject());
label.append(" <span class=\"req\">*</span>");
model.setObject(label);
((TextField)get("input")).setRequired(true);
return this;
}
#Override
protected void onBeforeRender() {
super.onBeforeRender();
Label feedback = (Label)get("input_feedback");
if (get("input").getFeedbackMessage() != null)
{
feedback.setDefaultModel(new Model<String>("Required"));
}
}
}
Adding to the form component
add(new InputRow("name","Name:").required());
edit
I didn't set up a ListView or repeater since I know what rows / fields I want to add to the form at build time.
Your InputFields are missing their models. This way, wicket doesn't know where to store the formdata. If you add models to the fields they will be populated automatically.
There's not just one form parameter submitted. The submits are of the named like name:input, name2:input, ...
But as Nicktar suggests in the comment you should use a model to bind the value of the form component to your entity object. You have to accept an IModel in the constructor and use it in the constructor of TextField.
A better approach to what you are trying to do is to write a Behavior which adds decorating markup for your FormComponent. That way it works for more than just simple text input fields and you can fully customize the instances of your FormComponents.
It could look like this:
public class FormComponentBehavior extends Behavior {
#Override
public void bind(Component component) {
if (!(component instanceof FormComponent)) {
throw new IllegalArgumentException();
}
}
#Override
public void beforeRender(Component component) {
FormComponent<?> fc = (FormComponent<?>) component;
Response r = component.getResponse();
r.write("<label" + (fc.isRequired() ? " class='required'" : "") + ">");
r.write(fc.getLabel().getObject());
r.write("</label>");
r.write("<span class='input'>");
}
#Override
public void afterRender(Component component) {
component.getResponse().write("</span>");
// if feedback errors write them to markup...
}
}
Then you have to add this behavior to your FormComponent instances.
Maybe the problem with your form is that your input text fields have all the same id. Try using attribute 'name' instead of 'id'

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);
}

Why is the PagingNavigator not generating the urls?

I have a problem with PagingNavigator in Wicket and I don't understand why am I having it. Here is the thing, I wanted to use a PagingNavigator with a Dataview
dataView = new DataView("pageableTicketsList", provider){
protected void populateItem(final Item item) {
//Somes codes here
};
navigator = new PagingNavigator("navigator", dataView);
dataView.setItemsPerPage(30);
addOrReplace(dataView);
addOrReplace(navigator);
In the html file, I simply have :
<wicket:enclosure child="navigator">
<div class="navigator">
<span wicket:id="navigator" ></span>
</div>
</wicket:enclosure>
When I test in a webBrowser, in fact, I have the pages number shown as :
<< < 1 2 3 4 5 6 7 8 9 10 > >>
BUT any of them are clickable.
I saw in FireBug that the urls are not properly generated like this :
<a href="?wicket:interface=:13:mypage:navigator:navigation:0:pageLink:4:ILinkListener::"><span>1</span>
</a>
Instead, I'm just having
<span>1</span>
I don't get it, what am I doing wrong ?
Here is the code of my provider
public class MyProvider implements IDataProvider {
private List<Ticket> ticketsList;
public MyProvider(TicketService ticketService // and some paramaters){
ticketsList = ticketService.getListBy(//the parameters);
}
public Iterator iterator(int first, int count) {
return ticketsList.subList(first, first + count).iterator();
}
public IModel model(final Object object) {
return new LoadableDetachableModel() {
#Override
protected Object load() {
return (Ticket)object;
}};
}
public int size() {
return ticketsList.size();
}
public void detach() {
}
public List<Ticket> getTicketsList() {
return ticketsList;
}
public void setTicketsList(List<ListTicketsExtranetView> ticketsList) {
this.ticketsList = ticketsList;
}
}
The method size() returns the right value and navigator.isEnabled() returns true
Well, after a whole day of digging, I finally found out where my problem came from :
I have a WebMarkupContainer that was added to my page, if I remove that WebMarkupContainer, the PagingNavigator works fine. There is no dependencies beetween the 2 of them though, I use the WebMarkupContainer to show a message if a list of Tickets is empty or not.
So WHY is the WebMarkupContainer having influence on the PagingNavigator ?
Can you post the code of your class that implements IDataProvider, e.g. SortableDataProvider? If the size() method isn't returning the right value, you might encounter the behaviour you're seeing.
Your code seems right. Did you set anything to disabled with
setEnabled(false);
That has effect on components below that in the hierarchy. If that does not make a difference, try posting some more code.