How to disable / change style of wicket button link in onClick() - java

In a Wicket app, I have a bunch of <button> elements to which I'm attacking a Link component. Now in the onClick() method of the component I want to disable or change the style of the button. How can I do that? Calling setEnabled(false) has no effect.

Repeated uses of onClick() are operating on the same object in memory. If you're not using Ajax, you can still maintain some state in an anonymous subclass of Link. Then, you can use onBeforeRender() and onComponentTag() to change how it is displayed each time.
Link<Void> link = new Link<Void>("myLink") {
private String customCSS = null;
private boolean customEnabled = true;
public void onClick() {
if (/* test to determine disabled */) {
customCSS = "disabled";
customEnabled = false;
} else {
customCSS = null;
customEnabled = true;
}
}
#Override
protected void onComponentTag(ComponentTag tag) {
super.onComponentTag(tag);
if (customCSS != null)
tag.put("class", customCSS);
}
#Override
public boolean isEnabled() {
return super.isEnabled() && customEnabled;
}
};
AttributeModifiers (or other behaviors) aren't good for this case because, if you add them in the onClick() method, they will begin stacking on the same link for each click - since they are maintained as part of the Link's state.
Your Link can keep track of all manner of state, allowing your onClick() method to enable/disable/change/etc with repeated clicks.
You can also override onBeforeRender(), isVisible(), and other methods that are run each time the link is displayed on the page. The constructor, onConfigure(), and others are run just once, regardless of how many times you click the button.

I don't think this is an entirely good idea in Wicket. Of course it could be done by trickery, but it's far simpler to either:
Override the isEnabled() method to return a value derived from the model of the form/component.
Attach an AttributeModifier when you create the component, and use a model for it which returns a value derived as above.
Whichever you choose, the principle is to let Wicket "pull" rendering information in rather than pushing it explicitly.

The answer provided by Michael Borgwardt is nearly correct.
The problem is that you use Link. Disabled Links use <span> instead of
<a>/<button> and are surrounded with <em> by default. Using Button
component will set 'disabled' attribute in the element.
I would like to add, that you need to use HTML button element instead of <a> (link). Original answer can be counfusing, because Link and Button also exist in Wicket.

I think AjaxCallDecorator should be the class you need to use to disable/change style of the button.

The problem is that you use Link. Disabled Links use <span> instead of <a>/<button> and are surrounded with <em> by default.
Using Button component will set 'disabled' attribute in the element.

Take a look at SimpleAttributeModifier and AttributeAppender. Depending on your actual requirements one of those should do the trick. SimpleAttributeModifier adds or replaces an attribute of any HTML-Tag that has a prepresentation in wicket (replaces the css class), while AttributeAppender appends to the attributes (adds another css class). This should work for enabling/disabling buttons as well but I haven't tried that.
Example:
Label label = new Label("id", "Some silly text.")
add(label);
label.add(new SimpleAttributeModifier("class", "my-css-class");
For Ajax you'll have to add the component to the target as well.
More detailed example:
Java code:
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.behavior.SimpleAttributeModifier;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.model.Model;
public class DemoPage extends WebPage {
public DemoPage() {
Form form = new Form("form");
add(form);
final WebMarkupContainer wmc = new WebMarkupContainer("greenText");
form.add(wmc);
form.add(new Link("redLink"){
#Override
public void onClick() {
wmc.add(new SimpleAttributeModifier("class", "redText"));
}});
final Button boldButton = new Button("boldButton"){
#Override
public void onSubmit() {
wmc.add(new AttributeAppender("class", true, new Model<String>("boldText"), " "));
}};
form.add(boldButton);
Link disabler = new Link("buttonDisabler") {
#Override
public void onClick() {
boldButton.add(new AttributeAppender("disabled", true, new Model<String>("disabled"), " "));
}
};
form.add(disabler);
}
}
corresponding HTML:
<html>
<head>
<style>
.redText {
color: red;
}
.greenText {
color: green;
}
.boldText {
font-weight: bold;
}
</style>
</head>
<body>
<form wicket:id="form">
<div class="greenText" wicket:id="greenText">This is Green.</div><br />
Make it red<br />
<input type="submit" wicket:id="boldButton" value="Make it bold" /><br />
Disable the button
</form>
</body>
</html>

Related

Controlling visibility of wicket component via AbstractAjaxTimerBehavior

I'm using Wicket 8.10.
I have a Wicket Component that I want to dynamically hide or show depending on some external condition. For that I have the following code:
var mccc = new MyCoolCustomComponent("component"); // Custom component I wrote
mccc.setOutputMarkupId(true);
mccc.setOutputMarkupPlaceholderTag(true);
mccc.setVisible(false); //Should be hidden initially
var container = new WebMarkupContainer("container");
container.setOutputMarkupId(true);
container.setOutputMarkupPlaceholderTag(true);
container.add(mccc);
add(container);
var updateTimer = new AbstractAjaxTimerBehavior(Duration.seconds(1)) {
#Override
protected void onTimer(AjaxRequestTarget target) {
if(FooSingleton.instance().isBar()) {
mccc.setVisible(true);
} else {
mccc.setVisible(false);
}
target.add(mccc);
}
};
container.add(updateTimer);
The corresponding HTML looks like this:
<div wicket:id="container" >
<div wicket:id="component"/>
</div>
What I would expect to happen: The component is hidden initially. When isBar() returns true the component is shown and once it returns false again it is hidden.
What is actually happening: The component is hidden initially. It is shown once isBar() becomes true but does not become invisible once isBar() returns false.
I also thought about using an AttributeModifier to use the CSS display property, but I can't find how to change the value of the modifier.
I solved it with CSS:
mccc.add(new AttributeModifier("style",
() -> {
if (FooSingleton.instance().isBar()) {
return "";
}
return "display: none;";
}));
I'm sure the solution is not great, but it works for now.

onfocus event getting called recursively in Apache wicket

I am understanding how to handle JS events like onfocus and onblur in apache wicket. I have a simple form in which there is a textfield. "onfocus" event on this I am trying to set the textfield to a value. I have observed on running the code that onfocus is called again and again (recursively it seems). I fail to understand why and what I have done wrong.
Below is the code :
HTML:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
<head>
<title>Wicket Examples - component reference</title>
<link rel="stylesheet" type="text/css" href="style.css"/>
</head>
<body>
<form wicket:id="form">
<INPUT wicket:id="input" type="text" name="input" style="WIDTH: 800px" />
</form>
Java:
package com.poc.pages;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.form.ChoiceRenderer;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.Model;
public class IndexPage extends WebPage
{
/**
* Constructor
*/
public IndexPage()
{
Form form = new Form("form");
TextField<String> TextInput = new TextField<String>("input",Model.of(""));
TextInput.add(new AjaxFormComponentUpdatingBehavior("onfocus"){
#Override
protected void onUpdate(AjaxRequestTarget target) {
String thisValue =
this.getComponent().getDefaultModelObjectAsString();
thisValue = "ChangedNormally";
this.getComponent().setDefaultModelObject("ChangedViaDefaultSetModel");
target.add(this.getComponent());
System.out.println("onfocus"+thisValue);
}
});
form.add(TextInput);
add(form);
}
}
When I focus on textfield here, ChangedViaDefaultSetModel is set and on console onfocusChangedNormally gets print continuously. I fail to undertsand few things :
Why does onfocus gets called again and again printing onfocusChangedNormally evertytime on console?
How can I get the value of actual model and not default model.
WHy does the normal value doesnot get reflected in model whereas on doing setDefaultModel() it works?
Thanks For Help
1) This behavior, you implmented, is a little tricky. When you focus your TextField and add this component to the target, then AjaxRequest is updating it. When update is over, component must return its state, thats why focus gained and your behavior method onUpdate is called again.
To resolve this problem you must use some kind of blocking. The easiest way is to use boolean field and check if focus was already gained to prevent another update. (see the code below).
2) Second and third question is about models, and this is very large subject to discuss. You can learn more by reading this and this articles, or "Wicket in Action" and "Wicket cookbook" books.
In my solution I used PropertyModel, that reflects the value of the field you set as a model of the Component. This code:new PropertyModel<String>(this, "modelValue") means that Wicket must search field with name "modelValue" in this (IndexPage) object and set as the model of TextField. That's why now you can only change this object field to set another value to TextField (don't forget to update component after that).
Comments for code:
I have used AjaxEventBehavior but it's ok to use AjaxFormComponentUpdatingBehavior.
I have added onblur behavior to return old value of the TextField just to show possibilities of this solution.
I have changed TextInput to textInput because code conventions said so.
package com.poc.pages;
import org.apache.wicket.ajax.AjaxEventBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.PropertyModel;
public class IndexPage extends WebPage {
private String modelValue = "ChangedNormally";
private boolean focusGained = false;
public IndexPage() {
Form form = new Form("form");
TextField<String> textInput = new TextField<String>("input", new PropertyModel<String>(this, "modelValue"));
textInput.add(new AjaxEventBehavior( "onfocus" ) {
#Override
protected void onEvent(AjaxRequestTarget target) {
if (!focusGained) {
modelValue = "ChangedViaDefaultSetModel";
target.add(this.getComponent());
focusGained = true;
System.out.println( "focus gained " + getComponent().getDefaultModelObject() );
}
}
});
textInput.add(new AjaxEventBehavior( "onblur" ) {
#Override
protected void onEvent(AjaxRequestTarget target) {
modelValue = "ChangedNormally";
target.add(this.getComponent());
focusGained = false;
System.out.println( "focus lost " + getComponent().getDefaultModelObject() );
}
});
form.add(textInput);
add(form);
}
}
Hope this helps.

Wicket: Preventing form submission by a TextField within a Wizard step

Question relates to Wicket 1.6
I have a wizard step, which includes a Textfield component. When I press the Enter key, this is being handled by the default button of the Wizard bar ('Next'), and it advances to the next step in the Wizard. I don't want this to happen. When I hit Enter on the Textfield I just want the value to be updated, but remain on the same page.
I tried overriding the onBeforeRender() method of my Wizard class, which as you can see sets the default button of the containing form to null. However this now results in the 'Prev' button being triggered when I hit Enter, so the wizard goes back to the previous step.
public class ConfigurationWizard extends Wizard {
....
#Override
protected void onBeforeRender()
{
super.onBeforeRender();
Component buttonBar = getForm().get(BUTTONS_ID);
if (buttonBar instanceof IDefaultButtonProvider)
{
getForm().setDefaultButton(null);
}
}
}
So the basic question is, how do I disable the default button behaviour of the Wizard?
My approach (with a nice Wicket behavior)
Usage
TextField<String> myField = new TextField<String>("myField", myModel());
myField.add(new PreventSubmitOnEnterBehavior());
Behavior
public class PreventSubmitOnEnterBehavior extends Behavior
{
private static final long serialVersionUID = 1496517082650792177L;
public PreventSubmitOnEnterBehavior()
{
}
#Override
public void bind( Component component )
{
super.bind( component );
component.add( AttributeModifier.replace( "onkeydown", Model.of( "if(event.keyCode == 13) {event.preventDefault();}" ) ) );
}
}
This has nothing to do with the wizard buttons.
The TextField <input> is doing a form submit when the Enter key is pressed. This is standard behaviour for the <input> element.
The solution is to catch the Enter key press for the <input> and prevent the default behaviour
This bit of javascript magic does the trick for me:
<script type="text/javascript">
$(document).ready(function() {
$("#gridDiv").delegate("input","keypress",function(e){
if(e.originalEvent.keyCode == 13){
e.preventDefault();
}
});
});
</script>
where 'gridDiv' is the id of the <div> containing the TextField
I prefer another approach:
I use AjaxButtons for every button needed, with the specific submit code in the overrided onSubmit():
AjaxButton linkSubmit = new AjaxButton("linkSubmit")
#Override
public void onSubmit(AjaxRequestTarget target, Form form) {
super.onSubmit();
// Submit code goes here....
// ...
setResponsePage(new NewPage());
}
#Override
public void onError(AjaxRequestTarget target, Form form) {
}
};
My form doesn't need a "onSubmit()" method.
And the markup doesn't have any submit buttons. All buttons are coded like this:
With this approach you don't need to mess with javascript codes. The page simply will do nothing if you press Enter. You'll have to click your buttons to submit each one.
Hope this can help you.

Call javascript function from wicket 6, Link's "onclick ()"

I have the following java and html code:
this.leakageModel = new PropertyListView<Leakage> ( "leakage", new ArrayList<Leakage> ()) {
private static final long serialVersionUID = 1L;
#Override
protected void populateItem (final ListItem<Leakage> item) {
Link<String> brandLink = new Link<String> ("brandLink") {
private static final long serialVersionUID = -480222850475280108L;
#Override
public void onClick () {
//change another model in the page to update
//another table when the link is clicked
}
};
brandLink.add (new Label ("brand"));
item.add (brandLink);
} };
add (this.leakageModel);
html file:
<tr wicket:id="leakage" class="testClass">
<td class="testClass">
<a wicket:id="brandLink" href="#">
<span wicket:id="brand"></span>
</a>
</td>
</tr>
What I want to do is to be able to call a javascript function from inside the onClick() method.
The model update that I currently do inside the onClick method works well and updates another table on the page.
However everything I have tried to call a javascript function or change the css style has failed.
For instance:
Adding a css class:
add (new AttributeAppender("class", new Model("anotherclass"), " "));
Using an AjaxLink type instead, and a number of other things I have tried to no avail.
On a related note, my original intention is to hide all rows in the table except the one I have clicked. Maybe I can do this just from the Java code and have no need for Javascript at all, but updating the css as above doesn't work.
Any suggestions as to what am I doing wrong?
On a related note, my original intention is to hide all rows in the
table except the one I have clicked.
Instead of answering your question, I will try to provide a solution to your problem :).
It makes perfect sense to hide the table row via javascript. I would suggest doing it with Jquery as described in Hiding all but first table row with jQuery:
$("#myTbl tr:not(nth-child(3))").hide();
Now, you have to execute the above javascript snippet each time a user clicks your Wicket link. For this, you can for example create your own link class like this:
public class JavascriptLink extends Label{
public JavascriptLink(String id, String label) {
super(id, label);
add(new AttributeAppender("onclick", "...your javascript here..."));
}
}
I leave it to you to combine the jquery with the JavascriptLink to meet your requirements. It should work going in this direction.

Setting a custom onClick on one of the widgets in a GWT DisclosurePanel's caption

I have the following ui situation:
<g:DisclosurePanel width="100%" ui:field="disclosurePanel">
<g:customHeader>
<g:HorizontalPanel width="100%" ui:field="tableRow">
<g:cell width="16px" horizontalAlignment="ALIGN_CENTER">
<g:Image url="images/plus-icon.gif" ui:field="icon"></g:Image>
</g:cell>
<g:cell width="20%">
<g:Label ui:field="productName"></g:Label>
</g:cell>
<g:cell>
<g:Anchor ui:field="info"><ui:msg>More info...</ui:msg></g:Anchor>
</g:cell>
</g:HorizontalPanel>
</g:customHeader>
<g:VerticalPanel width="100%" ui:field="details">
<!-- details panel here -->
</g:VerticalPanel>
</g:DisclosurePanel>
And I would like to bind an event handler method to the Anchor info. However every widget I have in the header opens and closes the disclosurepanel, even if I hook something on the info by:
#UiHandler("info")
public void onInfoClicked(ClickEvent event)
{
// do something custom, but do not open/close the disclosurepanel
}
I hope that this can be achieved without making a custom composite or stuff. Can you help me?
DisclosurePanel's header is private inner class ClickableHeader:
private final class ClickableHeader extends SimplePanel {
private ClickableHeader() {
// Anchor is used to allow keyboard access.
super(DOM.createAnchor());
Element elem = getElement();
DOM.setElementProperty(elem, "href", "javascript:void(0);");
// Avoids layout problems from having blocks in inlines.
DOM.setStyleAttribute(elem, "display", "block");
sinkEvents(Event.ONCLICK);
setStyleName(STYLENAME_HEADER);
}
#Override
public void onBrowserEvent(Event event) {
// no need to call super.
switch (DOM.eventGetType(event)) {
case Event.ONCLICK:
// Prevent link default action.
DOM.eventPreventDefault(event);
setOpen(!isOpen);
}
}
}
assuming your code:
#UiField
DisclosurePanel p;
//call this somewhere once on widget creation to
//prevent header's default click handler
private void myInit()
{
p.getHeader().unsinkEvents(Event.ONCLICK);
}
#UiHandler("info")
public void onInfoClicked(ClickEvent event)
{
//trigger "standard" click handler if you want
if(someCondition) {
//convert GwtEvent descendant to NativeEvent descendant;
p.getHeader().onBrowserEvent(event.getNativeEvent().<Event> cast());
}
// do something custom, but do not open/close the disclosurepanel
}
Well it works as it is designed. Each element you put inside the DisclosurePanel has a click handler which opens/ closes it. So inside your header there should be only images and or text, basically onyl elements with no atteched logic. I would consider arranging your html elements different, so the link isn't inside the header...
If you really, really have to put it inside the header, you can add this to your Anchor click event:
disclosurePanel.setOpen(!disclosurePanel.isOpen());
This will restore the previous state of the DisclosurePanel. The good part is, that it is so fast, that you don't even see the DisclosurePanel opening/closing, the bad part is, that this is really bad design....
Addition:
the DisclosurePanel uses Anchors to be displayed. An anchor allowes per definition only block elements, so you shouldn't use it like this at all! (See Is putting a div inside a anchor ever correct?)

Categories