UI Links#
This page explains various ways of creating bookmarkable links to pages or resources using Wicket and Igloo.
What is a bookmarkable link?#
We’ll define bookmarkable links as any non-ajax link that points directly to a HTML page or to a user download (XLS file, JPEG image, …).
This excludes in particular the subclasses of org.apache.wicket.markup.html.link.AbstractLink
that implement event handlers (onClick
or onSubmit
methods): they may redirect to a HTML page or to a user download, but they don’t point to it directly. We’ll name those action links.
The main differences between bookmarkable links and action links are that:
on the user side, the action link will render as a URL tied to the page from where the link originates, whereas bookmarkable links will renders as a URL tied to the target.
on the server side, bookmarkable link are a bit lighter to execute than event handlers implementing a redirection (since they require one request instead of two).
For more information on action links, see UI User Actions.
Link descriptor#
Link descriptors are an addition from Igloo. They offer several advantages over traditional Wicket linking:
They enforce type-safety: users provide business object models only, and they do not need to perform manual conversion to strings each and every time they create a link.
They enforce consistency: link generation and parameter extraction is done by the same object, which only has to be defined once.
They provide dynamic link generation. When you add an
AbstractLink
generated by a link descriptor to your page, you are guaranteed that if an underlying model has its value updated, the link will also be updated the next time it is rendered.
Link descriptors are in fact two things: link generators and link parameter extractors.
As link generators, they allow to generate an URL, or even a Wicket AbstractLink
tied to predefined models and that will automatically render with an up-to-date URL with each page render.
As link parameters extractors, they allow to take the content of Wicket PageParameters
, convert it to actual objects (not just primitive types) and store it in predefined models.
Those “predefined models” are in fact models that were mapped to HTTP query parameters. See below for details about link descriptor mappers.
Interfaces#
The terms “link descriptor” refer to several interfaces:
ILinkGenerator
(see above)ILinkParameterExtractor
(see above)ILinkDescriptor
(an extension of bothILinkGenerator
andILinkParameterExtractor
)IPageLinkGenerator
, an extension ofILinkGenerator
that provides some features relevant only to pagesIImageResourceLinkGenerator
, an extension ofILinkGenerator
that provides some features relevant only to resources providing image filesIPageLinkParametersExtractor
, an extension ofILinkParameterExtractor
that provides some features relevant only to pages
Examples of use#
The following examples are about using an already-created link descriptor. For information about creating a link descriptor, see the section about link descriptor builders below.
URL generation#
ILinkGenerator linkGenerator = /* ... */;
String relativeOrAbsoluteUrl = linkGenerator.url();
String absoluteUrl = linkGenerator.fullUrl();
Wicket link generation#
Note: links generated using this method are automatically disabled (no href
) when they render and their parameters fail validation. You may hide them instead by calling hideIfInvalid
as below.
// Inside a Wicket component's constructor
ILinkGenerator linkGenerator = /* ... */;
add(
linkGenerator.link("linkWicketId")
.hideIfInvalid()
.add(new TargetBlankBehavior())
);
Redirection#
IPageLinkGenerator linkGenerator = /* ... */;
throw linkGenerator.newRestartResponseException();
<img>
markup#
// Inside a Wicket component's constructor
IImageResourceLinkGenerator linkGenerator = /* ... */;
add(
linkGenerator.image("linkWicketId")
.hideIfInvalid()
.add(new TargetBlankBehavior())
);
IPageLinkGenerator linkGenerator = /* ... */;
Validity check#
Important note: validity check is normally unnecessary, as it will be performed automatically and an exception will be thrown if the link is invalid. Generally, this is want you want, because an invalid link simply should not have been used (Wicket links obtained through ILinkGenerator#link(String)
, for instance, are automatically disabled when invalid, so the user cannot click them).
If invalid links are a possibility that you want to handle as part of your business code, though, you may use code similar to the following snippet. This should be exceptional: if you’re doing this extensively in your code, you probably missed something.
ILinkGenerator linkGenerator = /* ... */;
if (linkGenerator.isAccessible()) {
throw linkGenerator.newRestartResponseException();
} else {
// Fallback code
}
Link descriptor mappers#
Link descriptor mappers are factories that take models as arguments and map them to previously incomplete link definitions in order to create a link descriptor.
They are primarily useful to separate the definition of links (list of parameters types on the Java, mapping of those Java parameters to HTTP query parameters, validations, …) from the actual parameter definition. The link descriptor mapper will then represent the incomplete link definition that only lacks parameter models in order to provide a full link descriptor.
Examples of use#
Note: the result of a link descriptor mappers’ map
method is a link descriptor that may be used in each and every way described above in the “Link descriptor” section. We only provide one such example here to avoid unnecessary repetitions.
Wicket link generation#
// Inside a Wicket component's constructor
IModel<User> userModel = /* ... */;
IOneParameterLinkDescriptorMapper<IPageLinkGenerator, User> mapper = /* ... */;
add(
mapper.map(userModel).link("linkWicketId")
.hideIfInvalid()
.add(new TargetBlankBehavior())
);
Data table link declaration#
See UI-Displaying Collections for some context about DataTableBuilder
.
IOneParameterLinkDescriptorMapper<IPageLinkGenerator, User> mapper = /* ... */;
CoreDataTablePanel<?, ?> results =
DataTableBuilder.start(dataProvider, dataProvider.getSortModel())
.addLabelColumn(new ResourceModel("business.customer.lastName"), Bindings.customer().lastName())
.withLink(mapper) // <= USE THE MAPPER HERE
.withSort(CustomerSort.LASTNAME, SortIconStyle.ALPHABET, CycleMode.NONE_DEFAULT_REVERSE)
.withClass("text text-sm")
/** Add some more columns... */
.build("results");
Link descriptor builder#
The link descriptor builder allows to build link descriptors or link descriptor mappers. It provides methods to define the mappable Java-side parameters, the mappings between those parameters and HTTP query parameters, the validations around those parameters and the target of the link.
Examples of use#
The following sections provide some examples of use. This is not an exhaustive reference, so if those examples do not match exactly your need, you may start from the closest one and use the builder’s Javadoc to find what you’re looking for.
Simple link descriptor#
This is especially useful for pages with no parameters (home page, lists, …).
@AuthorizeInstantiationIfPermission(permissions = {MyPermissionConstants.READ_CUSTOMER})
public class CustomerListPage extends MainTemplate {
public static final IPageLinkDescriptor linkDescriptor() {
return LinkDescriptorBuilder.start()
.page(CustomerListPage.class);
}
public CustomerListPage(PageParameters parameters) {
super(parameters);
/* ... */
}
}
Link descriptor mapper#
public class CustomerDescriptionPage extends MainTemplate {
public static final IOneParameterLinkDescriptorMapper<IPageLinkDescriptor, Customer> MAPPER =
LinkDescriptorBuilder.start()
.model(Customer.class).map(CommonParameters.ID).mandatory()
.permission(MyPermissionConstants.READ)
.page(CustomerDescriptionPage.class);
public CustomerDescriptionPage(PageParameters parameters) {
super(parameters);
IModel<Customer> customerModel = new GenericEntityModel<Long, Customer>();
MAPPER.map(customerModel)
.extractSafely(
parameters,
CustomerListPage.linkDescriptor(),
getString("common.error.unexpected")
);
/* ... */
}
}
Link descriptor mapper with multiple parameters#
public class CustomerDescriptionPage extends MainTemplate {
public static final ITwoParameterLinkDescriptorMapper<IPageLinkDescriptor, Customer, String> MAPPER_TAB =
LinkDescriptorBuilder.start()
.model(Customer.class)
.model(String.class)
.pickFirst().map(CommonParameters.ID).mandatory()
.pickSecond().map("tab").optional()
.pickFirst().permission(MyPermissionConstants.READ)
.page(CustomerDescriptionPage.class);
public static final IOneParameterLinkDescriptorMapper<IPageLinkDescriptor, Customer> MAPPER =
MAPPER_TAB.ignoreParameter2();
public CustomerDescriptionPage(PageParameters parameters) {
super(parameters);
IModel<Customer> customerModel = new GenericEntityModel<Long, Customer>();
IModel<String> selectedTabNameModel = new Model<>();
MAPPER_TAB.map(customerModel, selectedTabNameModel)
.extractSafely(
parameters,
CustomerListPage.linkDescriptor(),
getString("common.error.unexpected")
);
/* ... */
}
}
Link descriptor mapper with a collection parameter#
public class MyPage extends MainTemplate {
public static final IOneParameterLinkDescriptorMapper<IPageLinkDescriptor, List<Customer>> MAPPER =
LinkDescriptorBuilder.start()
.<List<Customer>>model(List.class).mapCollection("list", Customer.class).mandatory()
.permission(MyPermissionConstants.READ)
.page(CustomerDescriptionPage.class);
public MyPage(PageParameters parameters) {
super(parameters);
IModel<Customer> customerListModel = CollectionCopyModel.custom(
Supplers2.<Customer>arrayListAsList(), GenericEntityModel.<Customer>factory()
);
MAPPER.map(customerListModel)
.extractSafely(
parameters,
CustomerListPage.linkDescriptor(),
getString("common.error.unexpected")
);
/* ... */
}
}
Link descriptor mapper with custom validation condition#
public class CustomerDescriptionPage extends MainTemplate {
public static final IOneParameterLinkDescriptorMapper<IPageLinkDescriptor, Customer> MAPPER =
LinkDescriptorBuilder.start()
.model(Customer.class).map(CommonParameters.ID).mandatory()
.permission(MyPermissionConstants.READ)
.validator(DetachableFactories.forUnit(
new AbstractDetachableFactory<IModel<Customer>, Condition>() {
private static final long serialVersionUID = 1L;
@Override
public Condition create(IModel<Customer> parameter) {
return new MyCondition(parameter);
}
}
))
.page(CustomerDescriptionPage.class);
public CustomerDescriptionPage(PageParameters parameters) {
super(parameters);
IModel<Customer> customerModel = new GenericEntityModel<Long, Customer>();
MAPPER.map(customerModel)
.extractSafely(
parameters,
CustomerListPage.linkDescriptor(),
getString("common.error.unexpected")
);
/* ... */
}
}
Other examples#
See Igloo’s tests, in particular the test methods in org.iglooproject.test.wicket.more.link.descriptor.AbstractAnyTargetTestLinkDescriptor
and org.iglooproject.test.wicket.more.link.descriptor.AbstractAnyTargetTestLinkDescriptorMapper
.
Other links#
EmailLink#
EmailLink
s is a mailto:
link that automatically defines its body as the email it points to.
IModel<String> emailModel = /* ... */
add(new EmailLink("email", emailModel));