Reusing wicket component in a form - wicket

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'

Related

Calling Apex Class from Lwc only it is getting Saved

I have create one custom object. Using a LWC component, I try to create one record but when try to save it from apex, only ID is getting printed not the Name.
I am not getting why only Id is getting printed not the name.
Could anybody please help me ? Would be Appreciable.
LWC Component
import { LightningElement, track, api } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import insertDe from '#salesforce/apex/insertEvent.insertDe';
import Detail_OBJECT from '#salesforce/schema/Detail__c';
export default class insertEvent extends LightningElement {
// #api childName;
#track conRecord = Detail_OBJECT;
handleChildNameChange(event) {
this.conRecord.childName = event.target.value;
}
createRec() {
insertDe({
de: this.conRecord
})
.then(result => {
// Clear the user enter values
this.conRecord = {};
// Show success messsage
this.dispatchEvent(new ShowToastEvent({
title: 'Success!!',
message: 'Contact Created Successfully!!',
variant: 'success'
}), );
})
.catch(error => {
this.error = error.message;
});
}
}
<template>
<lightning-card title="Create Contact Record">
<template if:true={conRecord}>
<div class="slds-m-around--xx-large">
<div class="container-fluid">
<div class="form-group">
<lightning-input
label="Child Name"
name="childName"
type="text"
value={conRecord.childName}
onchange={handleChildNameChange}
></lightning-input>
</div>
</div>
<br />
<lightning-button label="Submit" onclick={createRec} variant="brand"></lightning-button>
</div>
</template>
</lightning-card>
</template>
Apex code
public with sharing class insertEvent {
#AuraEnabled
public static void insertDe(Detail__c de) {
try {
insert de;
} catch (Exception e) {
System.debug('--->'+e);
}
}
}
If you're using an LWC component then I suggest to also use Lightning Data Service.
To answer your specific issue, after an insert DML, only the Id field is returned. If you need other fields, then you need to run a query. This is because trigger / workflow / process builder can change some field value.
My suggestion if you want insert record directly from LWC component, you should use Lightning Data Service. But you need to execute some custom code or insert record from apex method, then you should pass only the data LWC component and create object in apex method then insert it.
public static void insertDe(String name) {
Detail__c obj = new Detail__c();
obj.childName = name;
try {
insert obj;
} catch (Exception e) {
System.debug('--->'+e);
}
}
Only pass the name from lwc component according to your posting code.

Wicket get button component text

I would like to get the text of a button submitProceed and will use it in my code/logic.
HTML
<button wicket:id="submitProceed" type="button" class="btn btn-sm btn-primary btn-save" disabled="disabled">Submit</button>
Is it possible to get the button text Submit? Also, how to I change this to Proceed?
This is how I initialize my button component:
private Component m_btnSubmit;
...
private Component createForm() {
Form<Void> result = new Form<>("form");
...
result.add(m_btnSubmit = createSubmit("submit"));
...
return result;
}
private Component createSubmit(String wicketId) {
AjaxButton result = new AjaxButton(wicketId) {
private static final long serialVersionUID = 1L;
#Override
protected void onConfigure() {
super.onConfigure();
...
setOutputMarkupId(true);
}
#Override
protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
super.updateAjaxAttributes(attributes);
...
}
#Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
super.onSubmit(target, form);
// TODO: Get button text here
// Check button text if either `Submit` or `Proceed`
// Action depending on button text (Also change button text)
}
#Override
protected void onError(AjaxRequestTarget target, Form<?> form) {
super.onError(target, form);
...
}
};
...
return result;
}
Solution:
First of All, I would like to thank #Andrea and #martin for their solution, I just tweak it a bit for fit my existing code.
Since I need a span containing the text Submit and later be changed to Proceed... I need to add a span inside button tag like this:
<button wicket:id="submit" type="button"><span wicket:id="labelSubmit">Submit</span&lg;</button>
the problem with this, I am getting an error that seems it would not allow button to have a nested component.
Error is something like this:
org.apache.wicket.markup.MarkupException: Expected close tag for '<button wicket:id="submit" type="button">' Possible attempt to embed component(s) '<span wicket:id="labelSubmit">' in the body of this component which discards its body.
To fix this, I need to change from button to a so it would look like this:
<a wicket:id="submit" type="submit"><span wicket:id="labelSubmit">Submit</span&lg;</a>
...
IModel m_labelModel = Model.of("Submit");
Label m_labelSubmit = new Label("labelSubmit", m_labelModel);
m_labelSubmit.setOutputMarkupId(true);
...
In my button's onSubmit:
m_labelModel.setObject("Proceed");
target.add(this);
Note that I only did change m_labelModel, but I need to add the current button (this) so that the change will reflect in the UI.
For those, having the same issue or setup... hope this helps :)
If you use new AjaxButton(String, IModel) constructor, as Andrea Del Bene suggested, then the model will be used to set the value attribute of the button:
<button value="Submit"></button>
If you need to manipulate the textContent of the <button>, i.e. <button>!!!THIS!!!</button> then you can add a Label component as a child:
IModel<String> labelModel = Model.of("Submit");
Label label = new Label("labelId", labelModel);
label.setOutputMarkupId(true);
button.add(label);
...
In AjaxButton#onSubmit(AjaxRequestTarget target) you can update it:
labelModel.setObject("New value");
target.add(label);
you should use button's constructor that takes also a model as button's label:
IModel labelModel = Model.of("Submit");
new Button<>("submit", labelModel);
Than you can use the model to get/set this value

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

unit-testing Wicket input components

I just wrote my first Wicket component :) It contains a ListView with some Radio input fields. Now I want to unit test if a selected value makes its way to the model.
As WicketTester.newFormTester("myForm") expects a form, I try to create a form on the fly:
public void testDataBinding()
{
Model model = ...
MyRadioComponent myRadioComponent = new MyRadioComponent (...);
Form form = new Form("myForm", ...);
form.add(myRadioComponent);
WicketTester wicketTester = new WicketTester();
wicketTester.startComponentInPage(form);
// FormTester formTester = wicketTester.newFormTester("myForm");
// ...
}
Now wicketTester.startComponentInPage(form) results in:
Failed: Component [myForm] (path = [0:x]) must be applied to a tag of type [form],
not: '<span wicket:id="myForm" id="myForm3">'
Any idea how to fix this and/or how to test such an input component the right way?
OK, in detail the solution now looks like this:
public FormTester createFormTester(Component c) {
final WicketTester wicketTester = new WicketTester();
final FormPage page = new FormPage(c);
wicketTester.startPage(page);
return wicketTester.newFormTester(page.getPathToForm());
}
private static class FormPage extends WebPage implements IMarkupResourceStreamProvider {
private final Form<Void> form;
private final Component c;
private FormPage(final Component c) {
this.c = c;
add(form = new Form<>("form"));
form.add(c);
}
public String getPathToForm() {
return form.getPageRelativePath();
}
#Override
public IResourceStream getMarkupResourceStream(MarkupContainer container, Class<?> containerClass) {
return new StringResourceStream(
"<html><body>"
+ "<form wicket:id='" + form.getId() + "'><span wicket:id='" + c.getId() + "'/></form>"
+ "</body></html>");
}
}
I believe startComponentInPage uses a <span> for its component. Wicket checks that Forms are attached to <form> tags which is why you get this error.
You need to create your own test page with a <form> inside it. See org.apache.wicket.markup.html.form.NumberTextFieldTest for an example of inline markup. Otherwise, create a Form test page class with associated html markup file.

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