Reputation: 891
I'm wondering if something like this is possible in wicket:
<span wicket:id="label"><a wicket:id="link"></a></span>
Where the link can be rendered on the desired place of the label (without the need of splitting it).
For example, consider the following label: "The [LINK]roses[/LINK] are red" If 'The' and 'are red' are dynamic, we have to use multiple labels:
<span wicket:id="firstPartLabel"/><a wicket:id="link"/><span wicket:id="lastPartLabel">
Which is pretty ugly, we cannot insert the link dinamicaly so that, considering the label
The ${link} are red
and replace the placeholder with a proper Link?
Thanks
Upvotes: 1
Views: 2630
Reputation: 548
One easy way is to build up the link as a String
yourself using standard <a href=""> </a>
tags. Your Java code can then supply this String
as a Label
with the attribute .setEscapeModelStrings(false)
. Your html markup is a standard Wicket label.
In the markup, I suggest underlining the Lorem ipsum text (which will not be seen after Wicket replaces the label). This can help to remind you that this label is going to be a link, when you are designing your markup.
Example markup:
<span wicket:id="mylabel"><u>Lorem ipsum dolor</u></span>
Example Java:
this.createLink("mylabel", "The", "ROSES", "are red", "https://testurl.com");
}
private Void createLink(String label, String textBefore, String textLink, String textAfter, String url)
{
String htmlLink = "<a href='" + url + "'>" + textLink + "</a>";
String labelText = textBefore + htmlLink + textAfter;
this.add(new Label(label, labelText).setEscapeModelStrings(false));
}
Upvotes: 0
Reputation: 10896
If what you want to do is something like Twitter's hashtag/username links (to process plain text and add links to special segments), you can extend Label to do that. Just search the text for the special words (here I used a regex Pattern, but you could use any other method) and replace them with the HTML for the link.
In this example, isInternalRef
creates a link to an Ajax behavior. It could be also used to create links to resources, pages, and other types of components (some handle direct links, some don't).
This class is a very simple example, it's not intended to support all of Twitter's syntax.
public class TweetLabel extends Label {
private static final Pattern USERNAME_HASHTAG_PATTERN = Pattern.compile("\\B([@#*])([a-zA-Z0-9_]+)", Pattern.CASE_INSENSITIVE);
private transient CharSequence body;
public TweetLabel(String id) {
super(id);
}
public TweetLabel(String id, String label) {
super(id, label);
}
public TweetLabel(String id, Serializable label) {
super(id, label);
}
public TweetLabel(String id, IModel<?> model) {
super(id, model);
}
protected void onLinkClicked(AjaxRequestTarget target, String word) {
target.appendJavaScript("alert('You clicked on \"" + JavaScriptUtils.escapeQuotes(word) + "\", and the server knows...');");
}
@Override
protected void onConfigure() {
super.onConfigure();
// process text here, because we can add behaviors here, but not at onComponentTagBody()
body = processText(getDefaultModelObjectAsString());
}
@Override
public void onComponentTagBody(MarkupStream markupStream, ComponentTag openTag) {
replaceComponentTagBody(markupStream, openTag, body);
}
/**
* based on Matcher.replaceAll()
*/
private CharSequence processText(String text) {
Matcher m = USERNAME_HASHTAG_PATTERN.matcher(text);
boolean result = m.find();
if (result) {
StringBuffer sb = new StringBuffer();
do {
final String replacement = getLinkedTextAndAddBehavior(text, m.start(), m.end());
m.appendReplacement(sb, replacement);
result = m.find();
} while (result);
m.appendTail(sb);
return sb;
}
return text;
}
private String getLinkedTextAndAddBehavior(String fullText, int start, int end) {
final String matchedString = fullText.substring(start, end);
final int length = matchedString.length();
final String prefix = matchedString.substring(0, 1);
final String identifier = matchedString.substring(1, length);
final boolean isUsername = prefix.equals("@");
final boolean isHashtag = prefix.equals("#") && (start == 0 || fullText.charAt(start - 1) != '&');
final boolean isInternalRef = prefix.equals("*");
final String replacement;
if (isUsername) {
final String url = "https://twitter.com/" + identifier;
replacement = "<a href='" + url + "'>" + matchedString + "</a>";
} else if (isHashtag) {
final String url = "https://twitter.com/search?src=hash&q=" + UrlEncoder.QUERY_INSTANCE.encode(matchedString, "UTF-8");
replacement = "<a href='" + url + "'>" + matchedString + "</a>";
} else if (isInternalRef) {
final LinkedWordBehavior behavior = getOrAddBehavior(new LinkedWordBehavior(identifier));
final String rawFunction = behavior.getCallbackScript().toString();
replacement = String.format("<a href='#' onclick='%s;return false;'>%s</a>", rawFunction, matchedString);
} else {
replacement = matchedString;
}
return replacement;
}
/**
* Verify if the behavior was already added, add if not.
*/
private LinkedWordBehavior getOrAddBehavior(LinkedWordBehavior behavior) {
final List<LinkedWordBehavior> behaviors = getBehaviors(LinkedWordBehavior.class);
final int index = behaviors.indexOf(behavior);
if (index > -1) {
return behaviors.get(index);
} else {
add(behavior);
return behavior;
}
}
private final class LinkedWordBehavior extends AbstractDefaultAjaxBehavior {
private final String word;
public LinkedWordBehavior(String word) {
this.word = word;
}
@Override
protected void respond(AjaxRequestTarget target) {
final String word = TweetLabel.this.getRequest().getRequestParameters().getParameterValue("word").toString();
if (!Strings.isEmpty(word)) {
TweetLabel.this.onLinkClicked(target, word);
}
}
protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
super.updateAjaxAttributes(attributes);
attributes.getExtraParameters().put("word", word);
}
@Override
public int hashCode() {
return word.hashCode();
}
@Override
public boolean equals(Object obj) {
return word.equals(((LinkedWordBehavior) obj).word);
}
}
}
UPDATED changed the example to handle ajax requests with Behaviors. There are other ways to do it. For example, you could have a single behavior to handle all links, or use a resource, but this way looked cleaner to me.
Upvotes: 0
Reputation: 891
I solved this creating a custom Panel called LinkLabel. The component still uses "hard-coded markup" for both the labels, but at least its working.
You can use it this way:
LinkLabel myLabel = new LinkLabel("description",
"First ${link} second", "my link", new ILinkListener() {
private static final long serialVersionUID = 1L;
@Override
public void onLinkClicked() {
// Your desired onClick action
}
});
The java code for the component is the following:
public class LinkLabel extends Panel {
private static final long serialVersionUID = 1L;
public LinkLabel(String id, String model, String linkName,
ILinkListener linkListener) {
super(id);
setRenderBodyOnly(true);
String[] split = model.split("\\$\\{link\\}");
if (split.length == 2) {
Label first = new Label("first", split[0]);
Label second = new Label("second", split[1]);
add(first);
add(second);
add(generateLink(linkListener, linkName));
} else if (split.length == 1) {
Label first = new Label("first", split[0]);
Label second = new Label("second");
second.setVisible(false);
add(first);
add(second);
add(generateLink(linkListener, linkName));
} else {
throw new UnsupportedOperationException(
"LinkLabel needs the ${link} placeholder!");
}
}
private Link<?> generateLink(final ILinkListener linkListener,
String linkName) {
Label linkLabel = new Label("linkLabel", linkName);
Link<String> link = new Link<String>("link") {
private static final long serialVersionUID = 1L;
@Override
public void onClick() {
linkListener.onLinkClicked();
}
};
link.add(linkLabel);
return link;
}
}
Markup for the component:
<wicket:panel>
<span wicket:id="first"></span>
<a wicket:id="link"><span wicket:id="linkLabel"></span></a>
<span wicket:id="second"></span>
</wicket:panel>
Upvotes: 0