/* * * * Copyright (c) 2016. David Sowerby * * * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * * the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * * specific language governing permissions and limitations under the License. * */ package uk.q3c.krail.core.view; import com.google.inject.Inject; import com.vaadin.ui.Component; import com.vaadin.ui.UI; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.q3c.krail.core.i18n.DescriptionKey; import uk.q3c.krail.core.i18n.I18NKey; import uk.q3c.krail.core.i18n.LabelKey; import uk.q3c.krail.core.i18n.Translate; import uk.q3c.krail.core.view.component.AfterViewChangeBusMessage; import uk.q3c.krail.core.view.component.ViewChangeBusMessage; import uk.q3c.util.ID; import javax.annotation.Nonnull; import java.io.Serializable; import java.util.Optional; import static com.google.common.base.Preconditions.checkNotNull; /** * Provides default View behaviour suitable for most view implementations. Override methods as necessary for your needs. This is the default sequence: * <p> * <ol><li>{@link #init()} does nothing by default, override if you need to prepare the view in some way</li> * <li>{@link #beforeBuild} does nothing by default. Example use might be to reset #componentsConstructed dependent on url parameter values, forcing a rebuild * under certain conditions</li> * <li>{@link #buildView} delegates to sub-classes to provide component construction in {@link #doBuild}, then sets {@link #componentsConstructed}</li> * <li>{@link #afterBuild} calls {@link #setIds} to provide debug Ids, unless {@link #idsAssigned} is true, and then calls {@link #loadData} </li> * <li>if you need to load data, one good way to do that is to annotate your sub-class with @Listener, and provide a @Andler annoated method to load the data. * data loading process</li> * </ol> * <p> * Note: The {@link #rootComponent} must be set by sub-classes by an implementation of {@link #doBuild} */ public abstract class ViewBase implements KrailView, Serializable { private static Logger log = LoggerFactory.getLogger(ViewBase.class); private final Translate translate; protected I18NKey nameKey = LabelKey.Unnamed; protected I18NKey descriptionKey = DescriptionKey.No_description_provided; private boolean componentsConstructed; private boolean dirty; private boolean idsAssigned; private Component rootComponent; @Inject protected ViewBase(Translate translate) { super(); this.translate = translate; } public Translate getTranslate() { return translate; } public boolean isComponentsConstructed() { return componentsConstructed; } public boolean isDirty() { return dirty; } public void setDirty(boolean dirty) { this.dirty = dirty; } /** * {@inheritDoc} */ @SuppressFBWarnings("ACEM_ABSTRACT_CLASS_EMPTY_METHODS") public void init() { } /** * If {@link #idsAssigned} is false, {@link #setIds()} - the view components have already been constructed in {@link #buildView} */ @Override public void afterBuild(AfterViewChangeBusMessage busMessage) { if (!idsAssigned) { setIds(); idsAssigned = true; } loadData(busMessage); } /** * You only need to override / implement this method if you are using TestBench, or another testing tool which looks for debug ids. If you do override it * to add your own subclass ids, make sure you call super */ protected void setIds() { getRootComponent().setId(ID.getId(Optional.empty(), this, getRootComponent())); } @Override public Component getRootComponent() { if (rootComponent == null) { throw new ViewBuildException("Root component cannot be null in " + getClass().getName() + ". Has your " + "buildView() method called " + "setRootComponent()?"); } return rootComponent; } public void setRootComponent(@Nonnull Component rootComponent) { checkNotNull(rootComponent); this.rootComponent = rootComponent; } /** * Default does nothing, overload to load your data * * @param busMessage */ @SuppressFBWarnings("ACEM_ABSTRACT_CLASS_EMPTY_METHODS") protected void loadData(AfterViewChangeBusMessage busMessage) { } /** * {@inheritDoc} */ @SuppressFBWarnings("ACEM_ABSTRACT_CLASS_EMPTY_METHODS") @Override public void beforeBuild(ViewChangeBusMessage busMessage) { } /** * {@inheritDoc} */ @Override public void buildView(ViewChangeBusMessage busMessage) { if (!componentsConstructed) { doBuild(busMessage); } componentsConstructed = true; } /** * Implement this method to construct your components. You must also set {@link #rootComponent} (this is the component which will be placed in the parent * {@link UI}, and is usually a layout * * @param busMessage * a message sent by the Event Bus to signify a chnage of View */ protected abstract void doBuild(ViewChangeBusMessage busMessage); /** * {@inheritDoc} */ @Override public void rebuild() { componentsConstructed = false; } public I18NKey getNameKey() { return nameKey; } public void setNameKey(I18NKey nameKey) { this.nameKey = nameKey; } public I18NKey getDescriptionKey() { return descriptionKey; } public void setDescriptionKey(I18NKey descriptionKey) { this.descriptionKey = descriptionKey; } public String getName() { return translate.from(nameKey); } public String getDescription() { return translate.from(descriptionKey); } }