I'm trying to create a link that will hide or show a part of my page. The link should be reusable and display one of two images, depending on state.
Adding the two subcomponents on every page where I use the link is kind of clunky so I wanted to create a component that behaves like a link while automatically adding its content.
This is the Link component:
public class ToggleVisibilityLink extends AjaxFallbackLink<Boolean>
{
public ToggleVisibilityLink(final String id, final IModel<Boolean> model)
{
super(id, model);
setOutputMarkupId(true);
add(new Image("collapseImage")
{
#Override
public boolean isVisible()
{
return !getModelObject();
}
});
add(new Image("expandImage")
{
#Override
public boolean isVisible()
{
return getModelObject();
}
});
}
#Override
public void onClick(final AjaxRequestTarget target)
{
setModelObject(!getModelObject());
if (target != null)
{
target.add(this);
send(this.getParent(), Broadcast.EXACT, target);
}
}
}
And this is how I currently use it in HTML (this is added to the page or panel where I use the link):
<a href="#" wicket:id="collapseExpandLink" class="collapseExpandLink">
<wicket:link>
<img src="collapse.png" wicket:id="collapseImage" class="collapseExpandImage collapse">
</wicket:link>
<wicket:link>
<img src="expand.png" wicket:id="expandImage" class="collapseExpandImage expand">
</wicket:link>
</a>
And the corresponding Java call:
add(new ToggleVisibilityLink("collapseExpandLink", new PropertyModel(this, "hidden")));
But I want to be able to skip the body inside the link as one would have to know about the internals of ToggleVisibilityLink.
I experimented with IMarkupResourceStreamProvider, using Dynamic markup in Wicket as a starting point. By googling I found another example where the poster was only able to get that to work when using a Panel, and I was able to do that as well. But I'd really like to keep the link and not package it inside a Panel, as I would not be able to style the link in the markup.
I'm also open to alternatives to encapsulate the link and its body.
I was able to get this to work using setBody(), even though I was trying to sabotage myself quite badly (I had duplicate libraries, my own incompatible jQuery library import and a custom resource versioning strategy).
Here is the current ToggleVisibilityLink:
public class ToggleVisibilityLink extends AjaxFallbackLink<Boolean>
{
static {
Application.get().getSharedResources().add("ToggleVisibilityLinkCollapse",
new MyPackageResource(ToggleVisibilityLink.class, "collapse.png"));
Application.get().getSharedResources().add("ToggleVisibilityLinkExpand",
new MyPackageResource(ToggleVisibilityLink.class, "expand.png"));
}
public ToggleVisibilityLink(final String id, final IModel<Boolean> model)
{
super(id, model);
setOutputMarkupId(true);
setEscapeModelStrings(false);
setBody(new BodyModel(model));
}
#Override
public void onClick(final AjaxRequestTarget target)
{
setModelObject(!getModelObject());
if (target != null)
{
target.add(this);
send(this.getParent(), Broadcast.EXACT, target);
}
}
private static final class BodyModel extends AbstractReadOnlyModel<String>
{
private final IModel<Boolean> model;
private BodyModel(final IModel<Boolean> model)
{
this.model = model;
}
#Override
public String getObject()
{
return this.model.getObject() ?
"<img src=\""
+ RequestCycle.get().urlFor(new SharedResourceReference("ToggleVisibilityLinkExpand"), null)
+ "\" class=\"collapseExpandImage expand\">"
:
"<img src=\""
+ RequestCycle.get().urlFor(new SharedResourceReference("ToggleVisibilityLinkCollapse"), null)
+ "\" class=\"collapseExpandImage collapse\">";
}
}
}
Where MyPackageResource is a simple Implementation of PackageResource (why is that constructor protected?).
Then one can simply add the ToggleVisibilityLink to a container:
super.add(new ToggleVisibilityLink("collapseExpandLink", new PropertyModel(this, "hidden")));
and
<a wicket:id="collapseExpandLink" class="collapseExpandLink"></a>
and get notified via Event when the link is clicked.
Related
I use Wicket 1.5
When i change color it is really changed on the page only after refreshing using F5. How to refresh it in backend?
I use this lines for changing color:
dateDescription.add(AttributeModifier.replace("style", "color:red;"));
add(dateDescription);
UPDATE #1
Now i use AJAX but still have to refresh page for changing color. Could you tell me what i did wrong?
// in page class
public class FilterUpdateBehavior extends AjaxFormComponentUpdatingBehavior {
public FilterUpdateBehavior(String event) {
super(event);
}
#Override
protected void onUpdate(AjaxRequestTarget target) {
RefreshResult result = getResult(target);
if (result.getStatus() == RefreshResultStatus.DATE_NOT_SET) {
dateIntervalFilterPanel.setAlarmDateStatus(true);
} else {
dateIntervalFilterPanel.setAlarmDateStatus(false);
}
}
}
// in date panel class
dateDescription.add(new AttributeModifier("style", new AbstractReadOnlyModel<String>() {
private static final long serialVersionUID = 1L;
#Override
public String getObject() {
String cssClass = null;
if (isAlarmDateStatus()) {
cssClass = "color:red;";
} else {
cssClass = "color:black;";
}
return cssClass;
}
}));
add(dateDescription);
UPDATE #2
public RefreshResult getResults(AjaxRequestTarget target) {
// ... somewhere here additional logic of getting particulate RefreshResult
target.add(table);
target.add(paging);
target.add(loadingPanel);
return new RefreshResult(resultType);
}
UPDATE #3 FINAL (IT HELPED ME)
I miss this code line when i change isAlarmDateStatus, now it works fine. Thanks to Andrea!
target.add(dateDescription);
your code line looks right but you must use AJAX to reflect your changes without reloading the entire page. Unfortunately Wicket 1.5 is really outdated and there are few resources online to provide you an example of AJAX support. You might try to look into the old 1.5 AJAX examples code here:
https://github.com/apache/wicket/tree/build/wicket-1.5.17/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin
I would like to know
Am I doing things (the following) too complicated?
Is there a better way to update the main content of an activity that allows me to bookmark the event calendar of a store via URL like #MainPlace:eventCalendar?storeId=<id>?
I'm having this ActivityMapper here
public class AppActivityMapper implements ActivityMapper {
private ClientFactory clientFactory;
private MainActivity mainActivity;
// ..
#Override
public Activity getActivity(Place place) {
if (place instanceof LoginPlace) {
return new LoginActivity((LoginPlace) place, clientFactory);
} else if (place instanceof MainPlace) {
if(this.mainActivity == null) {
this.mainActivity = new MainActivity((MainPlace) place, clientFactory);
} else {
this.mainActivity.updateMainContent(((MainPlace) place).getMainContentToken());
}
return this.mainActivity;
}
return null;
}
}
and a MainActivity that controls my MainView that is just a menu ond the left side and the main content on the right side.
I want to decouple my views like in Best Practices for Architecting GWT App which is why I'm trying to control the main content by using events that get fired as something gets clicked in my MenuView.
Therefore I am initializing some event handlers in my MainActivity that react to clicks on the buttons in my menu to delegate the update to the MainView.
public class MainActivity extends AbstractActivity implements MainView.MainPresenter {
#Override
public void start(AcceptsOneWidget panel, EventBus eventBus) {
this.mainView = this.clientFactory.getMainView();
this.mainView.setPresenter(this);
this.mainView.initialize();
this.eventBus = eventBus;
this.eventBus.addHandler(HomeClickedEvent.TYPE, new HomeClickedHandler() {
#Override
public void onHomeClicked(HomeClickedEvent event) {
goTo(new MainPlace("home"));
}
});
this.eventBus.addHandler(EventCalendarClickedEvent.TYPE, new EventCalendarClickedHandler() {
#Override
public void onEventCalendarClicked(EventCalendarClickedEvent eventCalendarClickedEvent) {
goTo(new MainPlace("eventCalendar?storeId=" + eventCalendarClickedEvent.getStoreId()));
}
});
panel.setWidget(this.mainView.asWidget());
}
#Override
public void goTo(Place place) {
this.clientFactory.getPlaceController().goTo(place);
}
#Override
public void updateMainContent(String currentMainContentToken) {
this.mainView.updateMainContent(currentMainContentToken);
}
}
this event gets fired by MenuPresenter.clickedEventCalendar() that reacts to a click on the corresponding menu entry of the MenuView:
public class MenuPresenter implements MenuView.MenuPresenter {
// ..
#Override
public void clickedEventCalendar(Long storeId) {
this.eventBus.fireEvent(new EventCalendarClickedEvent(storeId));
}
}
One of the things I really don't like is this where I append parameters to the token e.g. to display the event calendar of a store given by storeId:
#Override
public void onEventCalendarClicked(EventCalendarClickedEvent eventCalendarClickedEvent) {
goTo(new MainPlace("eventCalendar?storeId=" + eventCalendarClickedEvent.getStoreId()));
}
is there a cleaner solution for a problem like this in GWT? I don't like the fact that I'd have to parse that string in my actual event calendar. Am I using the ActivityMapper wrong or is there simply no other way to do this?
This question should really be split into several separate ones, but that's maybe something to keep in mind for the future. If you're asking one thing then it's easier to answer thoroughly and others can find the answer easier too.
Anyway, I can see a few improvements:
use EventBinder to get rid a bit of the cruft when handling and creating new events.
if you just want to let the presenter know that a button was pressed on in the view (associated with that presenter) sending a custom event over the event bus is a bit of an overkill. Depending on your needs you can expose the button in your view's interface:
public interface Display {
HasClickHandlers getButton();
}
And then just register the ClickHandler in your presenter.
Or, if you need to do something view- and presenter- related on the click, register the ClickHandler in your view and call the presenter:
// In MainView:
#UiHandler("button")
void handleClick(ClickEvent event) {
// Do some stuff with view,
// like hide a panel or change colour
panel.setVisible(false);
// Let the presenter know that a click event has been fired
presenter.onEventCalendarClicked();
}
you're right - creating MainPlace like you are proposing is wrong. You are creating the token too soon - that's what the tokenizer associated with the place is for. You should create MainPlace by passing just the storeId to the constructor - why should MainPresenter (or any other class using this place) should know how to create the token? MainPlace should look more like this:
public class MainPlace extends Place {
private final Long storeId;
public MainPlace(Long storeId) {
this.storeId = storeId;
}
public Long getStoreId() {
return storeId;
}
public static class Tokenizer implements PlaceTokenizer<MainPlace> {
#Override
public MainPlace getPlace(String token) {
return new MainPlace(Long.valueOf(token));
}
#Override
public String getToken(MainPlace place) {
return "eventCalendar?storeId=" + place.getStoreId();
}
}
}
Now, it's the Tokenizer's responisibily to create and parse the token. Just remember to register it on your PlaceHistoryMapper.
i needed an Widget to display text properly, containing HTML elements. Therefore i used the GWT HTML-Widget like that.
HTML text= new HTML(new SafeHtml() {
#Override
public String asString() {
return "<b>TestText</b>";
}
});
Now i would like to select text displayed by that widget, and somehow get the String.
I would like to right click the marked text, and do something with that String
It's also no problem if your ideas making use of other gwt widgets, i am not too focused on that HTML one.
I also have access to Sencha GXT libarys.
Any ideas would be appreciated.
I'm assuming you want the user to select text and then retrieve the selected text on right click. Am I right? I don't recall any way of retrieving selected text in GWT, so I would use pure javascript for that. There is already a thread explaining how to do that with javascript, so you can grab that code and wrap it in a JSNI method:
public class MyClass implements IsWidget {
private final HTML text;
public MyClass() {
text = new HTML(SafeHtmlUtils.fromTrustedString("<b>Some text</b>"));
text.addDomHandler(new ContextMenuHandler() {
#Override
public void onContextMenu(ContextMenuEvent event) {
String test = getSelection();
Window.alert(test);
}
}, ContextMenuEvent.getType());
}
private native String getSelection() /*-{
var text = "";
if ($wnd.getSelection) {
text = $wnd.getSelection().toString();
} else if ($doc.selection && $doc.selection.type != "Control") {
text = $doc.selection.createRange().text;
}
return text;
}-*/;
#Override
public Widget asWidget() {
return text;
}
}
You can use sth like this:
final Label label = new Label("Some text");
label.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
label.getElement().getStyle().setBackgroundColor("#ff0"); //sth. like select
String txt = label.getText(); //get the String
Window.alert(txt); //do sth. with text
}
});
But it works on left click. If you have to use right click, you can use native JS code using eg. jQuery click.
And do not use b tag. It is deprecated in HTML5.
I've actually found a GWT-Libary that can get the selected text.
Watch this https://code.google.com/p/gwt-selection/
After installing the libary i just had to
String currentSelection = Selection.getBrowserRange().getText();
Thank you for answering though - you helped me a lot
I have a component inside an <a/> tag that opens a popup window on click. It's an "add to favourite" link which works on KML files. My KML file has a field named "favourite[boolean]". Now I'd like to hide or show my "add to favourite" link. The KML list is generated with a table:
public class CustomTracksAjaxDataTable<T> extends CustomAjaxDataTable<T> {
public CustomTracksAjaxDataTable(String id, List<IColumn<T>> iColumns,
ISortableDataProvider<T> tiSortableDataProvider, int rowsPerPage) {
super(id, iColumns, tiSortableDataProvider, rowsPerPage);
}
protected void onEventHandler(AjaxRequestTarget ajaxRequestTarget,
KMLFile file) {
setKMLData(file); // it just update map, dont care about it
add(new FavouriteStarIconState(file.isSaved()));
}
}
I tried to add a behavior thus:
public class FavouriteStarIconState extends AbstractDefaultAjaxBehavior {
private boolean isFavourite;
public FavouriteStarIconState(boolean isFavourite) {
super();
this.isFavourite = isFavourite;
}
#Override
protected void respond(AjaxRequestTarget target) {
if (isFavourite) {
target.appendJavascript("jQuery('.map_container_star').css(
{'display' : 'none' });");
} else {
target.appendJavascript("jQuery('.map_container_star').css(
{'display' : 'block' });");
}
}
#Override
public void renderHead(IHeaderResponse response) {
response.renderOnLoadJavascript(getCallbackScript().toString());
}
}
The part of the HTML containing the component:
<div id="map_container">
<a wicket:id="favourite_star" class="map_container_star"></a>
</div>
This isn't working. I got the same result with component.setVisible(false). How can I get hiding to work?
Well it finds out that I make a terrible mistake and put javascript appending in wrong place. AJAX request was not rendered. The proper class was CustomAjaxDataTable being extended by my class CustomTracksAjaxDataTable. I just add
new AjaxEventBehavior( "onclick" )
and override
protected void onEvent( AjaxRequestTarget ajaxRequestTarget )
and it works great now
You could use a CSS class like this
.hiddenClass
{
visibility:hidden;
}
then with AttributeModifier you add the class to the element
component.add(new AttributeModifier("class", "hiddenClass"));
or add the style directly to the style attribute
component.add(new AttributeModifier("style", "visibility:hidden;"));
Sorry if this was already answered before. I did a little searching and found nothing that could solve my problem. I created an application with Spring Roo, then converted to a GWT app.
All the code generated by Spring Roo is only for CRUD. Now i want to add a Calendar for make appointments, so i need to move to another page.
I´ve added this code to
ScaffoldDesktopShell.java()
public ScaffoldDesktopShell() {
initWidget(BINDER.createAndBindUi(this));
startButton.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
RootLayoutPanel.get().add(new NovoPainel());
}
});
}
...
Then created a new UIbinder, called it NovoPainel() and added this code:
public NovoPainel() {
initWidget(uiBinder.createAndBindUi(this));
botao.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
RootLayoutPanel.get().clear();
RootLayoutPanel.get().add (new ScaffoldDesktopShell());
}
});
}
Everything goes fine moving from my root panel to NovoPainel, but when i need to go back to rootPanel the page doesn´t render correctly.
EX: Doesn´t show ** ValuePicker ** to click on left panel and render on center.
This is my RootPanel
and this image is when navigate from rootPanel to NovoPainel
and finally this one is returning from NovoPainel to RootPanel
You have to integrate with Roo generated architecture so that you can still benefit from Roo scaffolding.
Roo generated code hides most of behavior in _Roo_Gwt classes and it is because GWT doesn’t currently support ITDs. So changes have to be made in derived classes by overriding methods from _Roo_Gwt class.
To navigate application use Places, ActivityMapper and ActivitiManager (you can find good read on #Thomas Broyer posterous and GWT help).
If you take a look in ScaffoldDesktopShell.ui.xml - page is devided in three main areas.
ApplicationMasterActivities class is responsible for master area.
masterActivityManager.setDisplay(shell.getMasterPanel());
proxyListPlacePicker in ScaffoldDesktopApp.init() generates place change event with apropriate ProxyListPlace.
public void onValueChange(ValueChangeEvent<ProxyListPlace> event) {
placeController.goTo(event.getValue());
}
ApplicationMasterActivities class creates appropriate Activity in Master area by checking EntityProxy type contained in ProxyListPlace object.
public Activity getActivity(Place place) {
if (!(place instanceof ProxyListPlace)) {
return null;
}
ProxyListPlace listPlace = (ProxyListPlace) place;
return new ApplicationEntityTypesProcessor<Activity>() {
#Override
public void handlePet(PetProxy isNull) {
setResult(new PetListActivity(requests, ScaffoldApp.isMobile() ? PetMobileListView.instance() : PetListView.instance(), placeController));
}
#Override
public void handleOwner(OwnerProxy isNull) {
setResult(new OwnerListActivity(requests, ScaffoldApp.isMobile() ? OwnerMobileListView.instance() : OwnerListView.instance(), placeController));
}
}.process(listPlace.getProxyClass());
}
Navigation is created by listing all EntityProxy's in ScaffoldApp class
protected HashSet<ProxyListPlace> getTopPlaces() {
Set<Class<? extends EntityProxy>> types = ApplicationEntityTypesProcessor.getAll();
HashSet<ProxyListPlace> rtn = new HashSet<ProxyListPlace>(types.size());
for (Class<? extends EntityProxy> type : types) {
rtn.add(new ProxyListPlace(type));
}
return rtn;
}
To output meaningfull name in navigation menu they are rendered using ApplicationListPlaceRenderer
public String render(ProxyListPlace object) {
return new ApplicationEntityTypesProcessor<String>() {
#Override
public void handlePet(PetProxy isNull) {
setResult("Pets");
}
#Override
public void handleOwner(OwnerProxy isNull) {
setResult("Owners");
}
}.process(object.getProxyClass());
}
So you have to create new Activity.
public class SomeActivity extends Composite implements Activity{
private static SomeActivityUiBinder uiBinder = GWT
.create(SomeActivityUiBinder.class);
interface SomeActivityUiBinder extends UiBinder<Widget, SomeActivity> {
}
private AcceptsOneWidget display;
public SomeActivity() {
initWidget(uiBinder.createAndBindUi(this));
}
#Override
public String mayStop() {
return null;
}
#Override
public void onCancel() {
onStop();
}
#Override
public void onStop() {
this.display.setWidget(null);
}
#Override
public void start(AcceptsOneWidget panel, EventBus eventBus) {
this.display = panel;
this.display.setWidget(this);
}
}
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder" xmlns:g="urn:import:com.google.gwt.user.client.ui">
<g:HTMLPanel>
Hello world!
</g:HTMLPanel>
</ui:UiBinder>
Create appropriate EntityProxy. It is only to obey ProxyListPlace mechanism.
public interface SomeEntityProxy extends EntityProxy {
}
Create SomeActivity in A
#Override
public Activity getActivity(Place place) {
if (!(place instanceof ProxyListPlace)) {
return null;
}
Activity activity = super.getActivity(place);
if (activity == null) {
ProxyListPlace listPlace = (ProxyListPlace) place;
if (SomeEntityProxy.class.equals(listPlace.getProxyClass())) {
activity = new SomeActivity();
}
}
return activity;
}
Add place to navigation in ScaffoldApp or override getTopPlaces in derived class.
rtn.add(new ProxyListPlace(SomeEntityProxy.class));
Set correct menu rendering text in ApplicationListPlaceRenderer
#Override
public String render(ProxyListPlace object) {
String label = super.render(object);
if(label == null) {
if (SomeEntityProxy.class.equals(object.getProxyClass())) {
label = "Some activity";
}
}
return label;
}
Code in GitHub.
GWT 2.1 introduced new classes that implements the Model-View-Places pattern (MVP). This pattern (and the GWT 2.1 concepts) are heavily based on best practices from developers who have build scalable GWT-based applications, so many people are migrating in this direction.
Roo generates a GWT 2.1 application; all of its navigational code is built on top of Activities and Places. The reason I bring this up is it sounds like you are attempting to side-step a lot of this navigational framework to implement your own. I'm not sure, but I believe your problem is coming from the fact that the MVP code is getting confused as a result.
My recommendation would be to work through the GWT MVP article linked above first. Do it completely separate of Roo, because the application that Roo generates is more complex. Once you have a good handle on it, go back through the Roo-generated application and it will likely make more sense.
You can create two div tags in your Porject.html file respectively with id firstdivtag_id1 and seconddivtag_id2.
Display first page by using
RootPanel.get("firstdivtag_id1").add(Panel1);
And then to switch over to another panel use
RootPanel.get("seconddivtag_id2").add(Panel2);