package org.vaadin.touchkit.ui;
import java.io.Serializable;
import java.lang.reflect.Method;
import org.vaadin.touchkit.gwt.client.vcom.navigation.NavigationButtonRpc;
import org.vaadin.touchkit.gwt.client.vcom.navigation.NavigationButtonSharedState;
import com.vaadin.shared.Connector;
import com.vaadin.ui.AbstractComponent;
import com.vaadin.ui.Button;
import com.vaadin.ui.Component;
import com.vaadin.ui.HasComponents.ComponentAttachEvent;
import com.vaadin.ui.HasComponents.ComponentAttachListener;
import com.vaadin.util.ReflectTools;
/**
* The NavigationButton is a Button implementation optimized to be used inside a
* {@link NavigationManager} or generally in touch devices.
* <p>
* Clicking button will automatically navigate to the target view, if defined in
* this button (via constructor or {@link #setTargetView(Component)} ). On the
* client side, the {@link NavigationManager} will start animation immediately
* when clicked, while the client is waiting for a response from the server.
* <p>
* If the button does not have a target view, {@link NavigationButton} will
* still cause a immediate client-side animation, and in this case you MUST make
* sure to navigate to a view in the {@link NavigationButtonClickListener},
* otherwise the user will be stuck on an empty view.
* <p>
* Note that navigation will only work when the button is used inside a
* {@link NavigationManager}, otherwise it will work as a regular {@link Button}.
*/
public class NavigationButton extends AbstractComponent {
private ComponentAttachListener componentAttachListener;
/**
* Constructs a new NavigationButton with the given caption.
* <p>
* NOTE that if you do not specify a target view later with
* {@link #setTargetView(Component)}, OR navigate to a view when the button
* is clicked (in a {@link NavigationButtonClickListener}, the user will be
* stuck on a empty view.
*
* @param caption
* the caption
*/
public NavigationButton(String caption) {
this();
setCaption(caption);
}
/**
* Constructs a button with the specified target view, and sets the caption
* to equal that of the target view.
*
* @param targetView
* the view to navigate to when pressed
*/
public NavigationButton(Component targetView) {
this(targetView.getCaption());
setTargetView(targetView);
}
/**
* Constructs a button with the specified target view, and sets the caption
* explicitly.
*
* @param caption
* the caption
* @param targetView
* the view to navigate to when pressed
*/
public NavigationButton(String caption, Component targetView) {
this(caption);
setTargetView(targetView);
}
/**
* Creates a navigation button without any caption nor target view.
* <p>
* NOTE that if you do not specify a target view later with
* {@link #setTargetView(Component)}, OR navigate to a view when the button
* is clicked (in a {@link NavigationButtonClickListener}, the user will be
* stuck on a empty view.
*/
public NavigationButton() {
registerRpc(new NavigationButtonRpc() {
@Override
public void click() {
NavigationButton.this.click();
}
});
}
@Override
public NavigationButtonSharedState getState() {
return (NavigationButtonSharedState) super.getState();
}
@Override
public void beforeClientResponse(boolean initial) {
super.beforeClientResponse(initial);
// Steal caption from target view if not explicitly defined
String caption = getState().caption;
AbstractComponent targetView = (AbstractComponent) getState()
.getTargetView();
String targetViewCaption = getState().getTargetViewCaption();
if (caption == null) {
caption = targetViewCaption;
if (caption == null && targetView != null) {
caption = targetView.getCaption();
}
}
getState().caption = caption;
if (getState().getTargetViewCaption() == null) {
if (targetView == null) {
getState().setTargetViewCaption(caption);
}
}
}
/**
* Gets the {@link NavigationManager} in which this button is contained.
*
* @return the {@link NavigationManager} or null if not inside one
*/
public NavigationManager getNavigationManager() {
Component p = getParent();
while (p != null && !(p instanceof NavigationManager)) {
p = p.getParent();
}
return (NavigationManager) p;
}
/**
* Sets the view that will be navigated to when the button is pressed.
* <p>
* The client side widget communicates directly with a
* {@link NavigationManager} to make the actual navigation work.
* </p>
*
* @param targetView
* The view to navigate to when pressed.
*/
public void setTargetView(Component targetView) {
getState().setTargetView(targetView);
markAsDirty();
}
@Override
public void attach() {
super.attach();
NavigationManager navigationManager = getNavigationManager();
if (navigationManager != null) {
navigationManager
.addComponentAttachListener(getComponentAttachListener());
}
}
@Override
public void detach() {
if (componentAttachListener != null) {
getNavigationManager().removeComponentAttachListener(
componentAttachListener);
}
super.detach();
}
private ComponentAttachListener getComponentAttachListener() {
if (componentAttachListener == null) {
componentAttachListener = new ComponentAttachListener() {
@Override
public void componentAttachedToContainer(
ComponentAttachEvent event) {
Component attachedComponent = event.getAttachedComponent();
if (getTargetView() == attachedComponent) {
markAsDirty();
}
}
};
}
return componentAttachListener;
}
/**
* Gets the caption for this button.
* <p>
* If the caption is explicitly set, it will be used - otherwise the caption
* is fetched from the target view, if one is set.
* </p>
*
* @see com.vaadin.ui.AbstractComponent#getCaption()
*
* @return the caption
*/
@Override
public String getCaption() {
return super.getCaption();
}
/**
* Gets the target view that will be navigated to when the button is
* pressed.
*
* @return the target view
* @see #setTargetView(Component)
*/
public Component getTargetView() {
return (Component) getState().getTargetView();
}
/**
* Returns the caption that is expected to be on the view this button
* navigates to. Used for the placeholder, which is visible before the
* client has a chance to render the real target view.
* <p>
* If the caption is not set explicitly with
* {@link #setTargetViewCaption(String)}, the caption of target view is
* used. In case neither explicit target view caption or the target view is
* not defined the button caption is used.
*
* @return the caption that will be used for the placeholder of the target
* view.
*/
public String getTargetViewCaption() {
return getState().getTargetViewCaption();
}
/**
* Sets the caption that is expected to be on the view this button navigates
* to. This is used for the placeholder, which is visible before the client
* has a chance to render the real target view.
*
* @param targetViewCaption
* the explicit caption of the target view.
*/
public void setTargetViewCaption(String targetViewCaption) {
getState().setTargetViewCaption(targetViewCaption);
markAsDirty();
}
/**
* Click event. This event is triggered, when the navigation button is
* clicked.
*/
public class NavigationButtonClickEvent extends Component.Event {
/**
* Constructs a new NavigationButtonClickEvent
*
* @param source
* the Source of the event.
*/
public NavigationButtonClickEvent(Component source) {
super(source);
}
}
/**
* Interface for listening for a {@link NavigationButtonClickEvent} fired by
* a {@link Component}.
*/
public interface NavigationButtonClickListener extends Serializable {
public static final Method BUTTON_CLICK_METHOD = ReflectTools
.findMethod(NavigationButtonClickListener.class, "buttonClick",
NavigationButtonClickEvent.class);
/**
* Called when a {@link NavigationButton} has been clicked. A reference
* to the button is given by
* {@link NavigationButtonClickEvent#getSource()}.
*
* @param event
* An event containing information about the click.
*/
public void buttonClick(NavigationButtonClickEvent event);
}
/**
* Adds a navigation button click listener.
*
* @param listener
* the Listener to add.
*/
public void addClickListener(NavigationButtonClickListener listener) {
addListener(NavigationButtonClickEvent.class, listener,
NavigationButtonClickListener.BUTTON_CLICK_METHOD);
}
/**
* Removes a navigation button click listener.
*
* @param listener
* the Listener to remove.
*/
public void removeClickListener(NavigationButtonClickListener listener) {
removeListener(NavigationButtonClickEvent.class, listener,
NavigationButtonClickListener.BUTTON_CLICK_METHOD);
}
/**
* Simulates a button click, notifying all server-side listeners.
*
* No action is taken if the button is disabled.
*/
public void click() {
if (isEnabled() && !isReadOnly()) {
Connector targetView = getState().getTargetView();
if (targetView != null) {
getNavigationManager().navigateTo((Component) targetView);
}
fireClick();
}
}
/**
* Fires a click event to all listeners without any event details.
*
* In subclasses, override {@link #fireEvent(java.util.EventObject)} instead
* of this method.
*/
protected void fireClick() {
fireEvent(new NavigationButtonClickEvent(this));
}
/**
* Description in NavigationButton is show on the right side of the button.
* Normally with bit smaller and gray text.
*
* @see org.vaadin.touchkit.gwt.client.theme.StyleNames#NAVIGATION_BUTTON_DESC_PILL
* @see com.vaadin.ui.AbstractComponent#setDescription(java.lang.String)
*/
@Override
public void setDescription(String description) {
super.setDescription(description);
}
}