How to use BorderBehavior with ajax rerendering - java

I want to use a BorderBehavior to add additional markup around different components.
MyBorderBehavior extends BorderBehavior {
}
<wicket:border>
<div class="myBorderBehavior">
<wicket:body />
<!-- some more HTML code -->
</div>
</wicket:border>
So at some point, I add a new MyBorderBehavior to a component.
MyComponent myComponent = new MyComponent().add(new MyBorderBehavior());
But when I want to refresh myComponent via ajax
ajaxRequestTarget.add(myComponent)
The Html markup of MyBorderBehavior is drawn again without removing the already existing markup of MyBorderBehavior in the dom. As a result, the markup of MyBorderBehavior is shown twice or more often in the browser.
How can I add a border to a component which can be re-rendered with ajax?
A working solution I found so far is to remove the markup of MyBorderbehavior manually via JavaScript:
MyBorderBehavior extends BorderBehavior {
#Override
public void onComponentTag(Component component, ComponentTag tag) {
super.onComponentTag(component, tag);
IValueMap attributes = tag.getAttributes();
attributes.put("class", attributes.getString("class", "") + " hasMyBorderbehavior");
}
}
Wicket.Event.subscribe('/dom/node/removing', function(a, attributes, c, d, e) {
var component = $('#' + attributes['id']);
if (component.hasClass("hasMyBorderbehavior"))
{
component.closest(".myBorderBehavior").replaceWith(component);
}
});
But this seems to be very hacky.
There are three cases I found so far which are relevant for me:
The component with the BorderBehavior is rerendered via ajax
A parent component of the component with the BorderBehavior is rerendered via ajax
The whole page is rerendered

You can make your Behavior temporary and this will overcome the problem when re-painting with Ajax, but might break it when re-rendering the whole page.
A better solution probably is to override beforeRender of BorderBehavior and do nothing when this is an Ajax request:
#Override public void beforeRender(Component component) {
if (!RequestCycle.get().find(AjaxRequestTarget.class).isPresent()) {
super.beforeRender(component);
}
}
Same for afterRender().
The code above is for Wicket 8.x where RequestCycle.get().find(Class<T>) returns Optional<T>. If you use older version then you need to check for null instead: if (RequestCycle.get().find(AjaxRequestTarget.class) != null)

Related

Wicket - AjaxEventBehavior not rendered properly

Look at the following snippet.
add(new AjaxEventBehavior("onclick") {
private boolean toggle = false;
#Override
protected void onEvent(AjaxRequestTarget target)
{
log.debug("onEvent: " + toggle);
if (toggle)
{
toggle = false;
target.prependJavaScript("toogle(true)");
}
else
{
toggle = true;
target.prependJavaScript("toogle(false)");
}
}
});
But after the page rendering [no errors, warnings], I could see no event associated to the DOM [verified by means of firebug]. Even the debug log was never printed.
Is there any option in wicket to verify the behavior is added or not?
You should iterate trougth Behaviors added to this component to verify your one is added:
for (Behavior behavior : component.getBehaviors()) {
if(AjaxEventBehavior.class.equals(behavior.getClass())) {
// it works
}
}
The behavior won't contribute its JavaScript if the component it is attached on is either invisible or disabled.
BTW both of your prependJavaScript() calls use the same content toggle(true).
You should (almost) never have HTML ids in your markup:
For Wicket these ids take precedence over generated unique markup ids. If the id is present multiple times on the page (e.g. if you use a component multiple times), all Ajax handlers will attach to the first markup tag with that id.

Communicating with parent component

I have the MyPage.tml page and MyComponent.tml component.
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd">
<body>
<t:mycomponent />
</body>
</html>
I need to display some data on MyPage based on what has happened in MyComponent. How can I make some data from MyComponent available to MyPage? Is there something like "reverse" parameters (child passing parameter to parent)?
Your component is available to you within your page as a variable where you can access the variables required from within your page like so:
#Component(id = "myComponent")
private MyComponent myComponent;
#SetupRender //or any other render event method
private void setup() {
Object compVariable = myComponent.getYourVariable();
}
More elegant if you ask me is to use event bubbling as it makes it easer to refactor some logic out to a deeper component if needed.
Component:
#Inject
private ComponentResources resources;
#SetupRender //or any other lifecycle event method
private void triggerEvent() {
Object yourVariable = new Object();
resources.triggerEvent("YOUR_EVENT_NAME", new Object[]{yourVariable}, null);
//add an event callback if needed where I use null here
}
Page:
#OnEvent(value = "YOUR_EVENT_NAME")
private void handleComponentEvent(Object yourVariable) {
//do something with yourVariable
//even return something which would then can be handled by your component callback handler
}
You can use usual tapestry parameter.
<t:mycomponent value="myValue"/>
If this value will be changed on the component side, it will be available on the container side and vice versa.
I've used all three of these approaches, depending on context. I generally prefer event bubbling, where that makes sense.

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?)

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

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>

Set focus on a component with Apache Wicket?

How do you set focus on a component with Apache Wicket? Searching leads to very little information, mostly on setting the default field. I do not want to set a default field, rather I am looking to set focus when, for example, a specific radio button is selected.
I suggest using the native org.apache.wicket.ajax.AjaxRequestTarget#focusComponent(). For example:
/**
* Sets the focus in the browser to the given component. The markup id must be set. If
* the component is null the focus will not be set to any component.
*
* #param component
* The component to get the focus or null.
*/
org.apache.wicket.ajax.AjaxRequestTarget#focusComponent(Component component)
Once you create your behavior to set the focus, you should be able to add it to the component on any event, just make sure that component is part of the AjaxRequestTarget. I don't see why this wouldn't work...
myRadioButton.add(new AjaxEventBehavior("onchange") {
#Override
protected void onEvent(AjaxRequestTarget target) {
myOtherComponent.add(new DefaultFocusBehavior());
target.addComponent(myForm);
}
});
Here's a link that shows how to create the default focus behavior if you do not have one already:
http://javathoughts.capesugarbird.com/2009/01/wicket-and-default-focus-behavior.html
If you only want to setFocus through javascript and don't want to reload a form or a component, you can use the following code:
import org.apache.wicket.Component;
public class JavascriptUtils {
private JavascriptUtils() {
}
public static String getFocusScript(Component component) {
return "document.getElementById('" + component.getMarkupId() + "').focus();";
}
}
And then in any Ajax Method you can use:
target.appendJavascript(JavascriptUtils.getFocusScript(componentToFocus));
For a pop-up like modalWindow my workaround solution was to use the attribute "autofocus" on the first input tag.
An easy solution is to add it to the html directly.
<input ..... autofocus>
Another solution is to add it to the modalWindow itself:
#Override
public void show(AjaxRequestTarget target) {
super.show(target);
setUpFocus();
}
protected void setUpFocus() {
DeepChildFirstVisitor visitor = new DeepChildFirstVisitor() {
#Override
public void component(Component component, IVisit<Void> iVisit) {
if (isAutofocusable(component)) {
component.add(new AttributeAppender("autofocus", ""));
iVisit.stop();
}
}
#Override
public boolean preCheck(Component component) {
return false;
}
};
this.visitChildren(FormComponent.class, visitor);
}
protected boolean isAutofocusable(Component component) {
if (component instanceof TextArea ||
component instanceof DropDownChoice ||
// component instanceof RadioChoice ||
component instanceof AjaxCheckBox ||
component instanceof AjaxButton ||
component instanceof TextField) {
return true;
}
return false;
}
RadioChoice is commented out because this solution is not working on that. For RadioChoice i would recommend to implement a FocusedRadioChoice:
public class FocusedRadioChoice<T> extends RadioChoice<T> {
//constructors...
#Override
protected IValueMap getAdditionalAttributes(int index, T choice) {
super.getAdditionalAttributes(0, choice);
AttributeMap am = new AttributeMap();
am.put("autofocus", "");
return am;
}
}
Is there a way to achieve the same without JavaScript?
(I am implementing a form with a feedback-Panel that only comes up when Javascript is turned off, so it would not make sense to depend on JavaScript there...,-)
I could only find answers which use JS .focs()... maybe Wicket 1.5 will provide a method Component.setFocus()...
If you happen to be using an Ajax button, you can simply call target.focusComponent(myComponent); in the button's onSubmit method.
#martin-g 's solution was the only solution that got it working for my scenario - a modal/pop up.
Note:
I think autofocus embedded explicitly in HTML only works on page load, not modal load so any efforts to skillfully set the autofocus attribute in the HTML of a modal just fail miserably - always.
Here I lay out the steps for setting the focus on an input field called 'myInput' using the full power of Wicket (no JS!):
In onInitialize:
// Make sure the field has an ID in markup
myInput.setOutoutMarkupId(true);
Provide an overridden show method where you call the focusComponent method:
public void show(AjaxRequestTarget target)
{
// Make sure you call the super method first!
super.show(target);
target.focusComponent(myInput);
}
This does require that your component is an attribute of your modal content class so that you can access it in the show method. To avoid creating a class attribute for your input component you could blend this solution with the solution from BlondCode by replacing that solution's
component.add(new AttributeAppender("autofocus", ""));
with
target.focusComponent(component);
This also works!

Categories