Im trying to use mountPage in wicket to mount "/" url as my MainPage url.
My WebApplication looks like this now:
public class WicketApplication extends WebApplication
{
#Override
public Class<HomePage> getHomePage()
{
return HomePage.class;
}
#Override
public void init()
{
super.init();
mountPages();
}
private void mountPages()
{
mountPage( "/home", HomePage.class );
mountPage( "/about", AboutCompany.class );
mountPage( "/prices", Prices.class );
mountPage( "/gallery", Gallery.class );
mountPage( "/contact", Contact.class );
mountPage( "/offer", Offer.class );
}
}
and actually works great, but I'd like to change "/home" into "/", yet it messes a lot with CSS for example, not transfering it, but whole HomePage instead. How can I make this work the way I want to?
Since wicket 1.5 you can apply HomePageMapper , it is designed to map requests to root path. Actually the HomePageMapper will be applied by wicket automatically for the page provided in your WebApplication's Class<? extends Page> getHomePage() so that your homepage will be mounted to path root
Related
I am using Activities and Places.
I have a LoginPlace.
The url displayed when I navigate to that place has this at the end:
#LoginPlace:login
How can I change this to just #login or something else?
My tokenizer looks like this:
public class LoginTokenizer implements PlaceTokenizer<LoginPlace> {
private LoginPlace loginPlace;
public LoginTokenizer() {
}
#Override
public LoginPlace getPlace(String token) {
return new LoginPlace(token);
}
#Override
public String getToken(LoginPlace place) {
loginPlace = place;
return loginPlace.getLoginToken();
}
}
And navigation to the LoginPlace is done through the PlaceController:
clientFactory.getPlaceController().goTo(new LoginPlace("login"));
Where can I manipulate the format of the URL?
The mapping is done by the PlaceHistoryMapper.
You can have an implementation generated by GWT based in PlaceTokenizers, but then it's based on a prefix/suffix. The #Prefix allows you configure the prefix (which otherwise defaults to the place class' name).
Or you can implement the interface yourself and have complete control over the process.
Rename the Place class from LoginPlace to Login.
Pass an empty token:
new LoginPlace("")
We are using Wicket and our generated pages are quiet long (a lot of vertical scrolling). Some links or form's onSubmit methods invoke just perform some actions on the database and show the same page again:
public class MyPage extends WebPage {
public MyPage(PageParameters parameters) {
....
final Form<Void> form = new StatelessForm<Void>("formId") {
protected void onSubmit() {
// some database stuff
...
setResponsePage(getClass(), getPageParameters());
}
};
...
}
}
How can I make the setResponsePage invocation cause the browser scroll to the form, so the page is not just showing the top? Maybe some JavaScript-injection?
I think a nice Wicket-y solution combines stuff that is already in Michael's answer, with a Behavior, so you can just add this to your form.
form.add( new ScrollToTopBehavior());
The behaviour itself would like something like this:
public class ScrollToTopBehavior extends Behavior
{
#Override
public void renderHead( Component component, IHeaderResponse response )
{
super.renderHead( component, response );
response.render( JavaScriptHeaderItem.forReference( Application.get().getJavaScriptLibrarySettings().getJQueryReference() ) );
component.setOutputMarkupId( true );
String script = String.format("doSomeJavaScriptStuff('%s')", component.getMarkupId());
response.render( OnDomReadyHeaderItem.forScript( script ) );
}
}
UPDATE:
For scrolling to a specific ID / ANCHOR only once, you can follow this answer:
https://stackoverflow.com/a/3163635/461499
JS of course.
This would be something like (with JQuery usage):
var scrollPosition = $('#scrollToMarkupId').offset().top;
$('html, body').animate({ scrollTop: " + scrollPosition + " }, 'slow');
where scrollToMarkupId is wicket component's markup id, which could be obtained by calling component.getMarkupId() method.
I'm not pro in JS, so you can try to google better impl may be.
Now, about wicket:
1) As for me, I prefer AJAX invocations for such behavior ( note that if you use such approach your page won't be stateless ):
// do not override your form's `onSubmit()` method
final Form<Void> form = new Form<Void>("formId");
// adding ajax behavior with `onSubmit()` method overriding.
form.add ( new AjaxFormSubmitBehavior ("submit")
{
protected void onSubmit ( AjaxRequestTarget target )
{
// your submit logic
// then insert js, descriped above:
target.appendJavaScript ("..." + componentToScroll.getMarkupId() + "..");
}
});
This approach won't reload your page at all but also post your data.
/----------------------------------------------------------------------------------------------------------------------------------/
2) You also could execute JS after page loading, by overriding renderHead method:
public class YourPage extends WebPage
{
...
#Override
public void renderHead ( final IHeaderResponse response )
{
//replace `...` by your script.
response.render ( OnDomReadyHeaderItem.forScript ( "..." );
}
...
}
Such script will be invoked after page is renedered (and setResponsePage method will render your page). You can use this approach for any components and panels too.
I've now use following JavaScript injecting code:
add(new Behavior() {
#Override
public void renderHead(Component component, IHeaderResponse response) {
super.renderHead(component, response);
response.render(new HeaderItem() {
#Override
public Iterable<?> getRenderTokens() {
return Collections.singletonList("javascript-anchor");
}
#Override
public void render(Response response) {
response.write("<script type=\"text/javascript\">\n");
response.write("window.location.href='#rules';\n");
response.write("</script>\n");
}
});
}
});
Feel free to comment (I'm a complete JS-noob with only very limited experience in Wicket).
my map is
mountPage("/page/#{code}/#{name}", Page.class);
but when I click on the link
localhost/page/10/toy?2
wicket add also one parameter like a counter, when I refresh the page I have
localhost/page/10/toy?3
why?
This is because your page are stateful, Wicket manages its own states to your page by appending this "counter". This way, when your user navigate backward using its browser built-in functionnality, the page is displayed has it has been previously.
If you don't want such a parameter in your URL, you'll need to dig out and eradicate every stateful component in your pages.
You can create
public class MountedMapperWithoutPageComponentInfo extends MountedMapper {
public MountedMapperWithoutPageComponentInfo(String mountPath, Class<? extends IRequestablePage> pageClass) {
super(mountPath, pageClass, new PageParametersEncoder());
}
#Override
protected void encodePageComponentInfo(Url url, PageComponentInfo info) {
}
#Override
public Url mapHandler(IRequestHandler requestHandler) {
if (requestHandler instanceof ListenerInterfaceRequestHandler) {
return null;
} else {
return super.mapHandler(requestHandler);
}
}
}
and map page on Application class like this
mount(new MountedMapperWithoutPageComponentInfo("/page/#{code}/#{name}", Page.class));
I have a linktree for navigation over data which shall replace a component in another div.
For this in the tree I have the treestatelistener implementing
public void nodeSelected(Object node) {
log.debug("nodeSelected: " + node+ " class: " + node.getClass());
Component content = new Label("content");
content.replaceWith(new Label("content", "Hello World"));
}
}
}
All I get is the message:
"This method can only be called on a component that has already been
added to its parent."
The Label with Id "content" is added to the page on init, so it is there before. What do I do wrong?
You are creating a new parent Component every time your treenode listener is called, without adding the 'content' label.
What I would suggest is to try something like this -
public MyPage extends WebPage {
private Component parent = new WebMarkupContainer("parent");
public WebPage() {
parent.add(new Label("content", "hi");
}
...
public void nodeSelected(Object node) {
log.debug("nodeSelected: " + node+ " class: " + node.getClass());
parent.replaceWith(new Label("content", "Hello World"));
}
}
}
I always have issues with just calling Component.replaceWith(). Since I am doing this with AJAX, I always need to call a target.add().
My solution is for now to have a class MyTree extend LinkTree and in that class override the newNodeComponent().
Since in my application everything happens on the IndexPage.class and there I just replace components, I added a method handleSelection() to the IndexPage.Class(extends BasePage extends webpage). There I decide, based on the clicked nodeObject, how to replace a component in the IndexPage
Public Class MyTree extends Linktree{
protected Component newNodeComponent(String id, IModel<Object> model) {
#Override
protected void onNodeLinkClicked(Object node, BaseTree tree, AjaxRequestTarget target) {
super.onNodeLinkClicked(node, tree, target);
((IndexPage)getPage()).handleSelection(target, (TreeNode)node);
}
#Override
protected ResourceReference getResourceFolderOpen(Object node) {
return getResourceForNode(node);
}
#Override
protected ResourceReference getResourceFolderClosed(Object node) {
return getResourceForNode(node);
}
#Override
protected ResourceReference getResourceItemLeaf(Object node) {
return super.getResourceItemLeaf(node);
}
#Override
protected ResourceReference getImageResourceReference(BaseTree tree, Object node) {
return getResourceForNode(node);
}
}
}
I hope this is explained propperly, else: questions or comments wanted!
We have a web application that needs a different theme for each major client. The original developer did this by looking at the URL in javascript and adding a stylesheet to override the default theme.
One problem with this is the site has the default look for a few seconds then suddenly swaps to the correct theme. Another is that it seems to waste a lot of bandwidth/time.
My current idea is to create a "default" ClientBundle with our default look and feel extend that interface and override each entry (as needed) with the client's images using the various annotations like #ImageResouce and pointing to a different location.
Has anybody had experience doing this? One problem I forsee is not being able to use the uibinder style tags as they statically point to a specific resource bundle.
Any ideas?
Overriden bundles
Yes you can.
I've did the override thing with ClientBundles and works fine. One thing you MUST do is inherit the types of the properties too. By example:
BigBundle {
Nestedundle otherBundle();
ImageResource otherImage();
Styles css();
}
And then you must inherit this way:
OtherBigBundle extends BigBundle {
OtherNestedBundle otherBundle(); // if you want to change it
ImageResource otherImage(); // of you want to change it
OtherStyles css(); // of you want to change it
}
and OtherNestedBundle extends NestedBundle
and OtherStyles extends Styles
At least with css's: if the properties are declared NOT USING the child interface they will produce styles for the same CSS classname and all will be mixed. So declare overriden styles with the child interfaces :)
Flexible UIBinders
You can set from outside the bundle to use if you use UiField(provided=true) annotation. In this way you first set the bundle and then call the uibindler. It will use the resource field assuming it's already created.
Deferred binding
You could use GWT.runAsync for loading just the correct bundle.
Some example
The ui.xml
<ui:with field='res' type='your.package.TheBundle'/>
the corresponding class
#UiField(provided=true) TheBundle bundle;
private void createTheThing() {
this.bundle = factory.createBundle();
MyUiBindler binder = GWT.create(MyUiBindler.class);
this.panel = binder.createAndBindUi(this);
...
}
Some bundle interfaces
interface TheBundle extends ClientBundle {
#ImageResource("default.png")
ImageResource image1();
#Source("default.css")
TheCss css();
}
interface Theme1Bundle extends TheBundle {
#ImageResource("one.png")
ImageResource image1(); // type: imageresource is ok
#Source("one.css")
OneCss css(); // type: OneCss => use other compiled css class-names
interface OneCss extends TheCss { // inner-interface, just for fun
// don't need to declare each String method
}
}
If you don't override something it's ok
Options for the bundle factory
1) just altogether
if (...) {
return GWT.create(TheBundle.class);
} else if (...) {
return GWT.create(Theme1Bundle.class);
}
2) runAsync (just load the needed part... but after the initial part is executed)
if (...) {
GWT.runAsync(new RunAsyncCallback() {
public void onSuccess() {
return GWT.create(TheBundle.class);
}
// please program the onFailure method
});
} else if (...) {
GWT.runAsync(new RunAsyncCallback() {
public void onSuccess() {
return GWT.create(Theme1Bundle.class);
}
// please program the onFailure method
});
}
3) use deferred-binding and generators for autogenerating factory in compile-time based on annotated bundles like #ThemeBundle("one")
This example is from the real world. I use a DynamicEntryPointWidgetFactory (DEPWidgetFactory for short) for creating widget based on an identifier string. Each widget is an application screen and each main menu ítem has the widgetName it has to create.
In your case the id will be the theme to create.
Important: if you use runAsync you cannot create the resourcebundle just before creating the UI like in the sample code before. You must ask for the theme and when it's ready (in the callback) pass it to your widget constructor and your widget can assign it to its field.
The factory interface:
public interface DynamicEntryPointWidgetFactory
{
public void buildWidget(String widgetName, AsyncCallback<Widget> callback);
}
The annotation for widgets to generate:
#Target(ElementType.TYPE)
public #interface EntryPointWidget
{
/**
* The name wich will be used to identify this widget.
*/
String value();
}
The module configuration:
It says: the implementation for the Factory will be generated with this class (the other option is to use replace-with, but in our case we don't have predefined options for each locale or browser, but something more dynamic).
<generate-with class="com.dia.nexdia.services.gwt.rebind.entrypoint.DynamicEntryPointFactoryGenerator">
<when-type-assignable class="com.dia.nexdia.services.gwt.client.entrypoint.DynamicEntryPointWidgetFactory" />
</generate-with>
The generator:
public class DynamicEntryPointFactoryGenerator extends Generator {
#Override
public String generate(TreeLogger logger, GeneratorContext context,
String typeName) throws UnableToCompleteException {
PrintWriter pw = context.tryCreate(logger,
"x.services.gwt.client.entrypoint",
"DynamicEntryPointWidgetFactoryImpl");
if (pw != null) {
// write package, imports, whatever
pw.append("package x.services.gwt.client.entrypoint;");
pw.append("import x.services.gwt.client.entrypoint.DynamicEntryPointWidgetFactory;");
pw.append("import com.google.gwt.core.client.GWT;");
pw.append("import com.google.gwt.core.client.RunAsyncCallback;");
pw.append("import com.google.gwt.user.client.rpc.AsyncCallback;");
pw.append("import com.google.gwt.user.client.ui.Widget;");
// the class
pw.append("public class DynamicEntryPointWidgetFactoryImpl implements DynamicEntryPointWidgetFactory {");
// buildWidget method
pw.append(" public void buildWidget(String widgetName, final AsyncCallback<Widget> callback) {");
// iterates over all the classes to find those with EntryPointWidget annotation
TypeOracle oracle = context.getTypeOracle();
JPackage[] packages = oracle.getPackages();
for (JPackage pack : packages)
{
JClassType[] classes = pack.getTypes();
for (JClassType classtype : classes)
{
EntryPointWidget annotation = classtype.getAnnotation(EntryPointWidget.class);
if (annotation != null)
{
String fullName = classtype.getQualifiedSourceName();
logger.log(TreeLogger.INFO, "Entry-point widget found: " + fullName);
pw.append("if (\"" + annotation.value() + "\".equals(widgetName)) {");
pw.append(" GWT.runAsync(" + fullName + ".class, new RunAsyncCallback() {");
pw.append(" public void onFailure(Throwable t) {");
pw.append(" callback.onFailure(t);");
pw.append(" }");
pw.append(" public void onSuccess() {");
pw.append(" callback.onSuccess(new " + fullName + "());");
pw.append(" }");
pw.append(" });");
pw.append(" return;");
pw.append("}");
}
}
}
pw.append("callback.onFailure(new IllegalArgumentException(\"Widget '\" + widgetName + \"' not recognized.\"));");
pw.append(" }");
pw.append("}");
context.commit(logger, pw);
}
// return the name of the generated class
return "x.services.gwt.client.entrypoint.DynamicEntryPointWidgetFactoryImpl";
}