/**
* Get more info at : www.jrebirth.org .
* Copyright JRebirth.org © 2011-2013
* Contact : sebastien.bordes@jrebirth.org
*
* 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 org.jrebirth.af.core.ui;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Node;
import org.jrebirth.af.api.concurrent.RunType;
import org.jrebirth.af.api.exception.CoreException;
import org.jrebirth.af.api.facade.JRebirthEventType;
import org.jrebirth.af.api.ui.Model;
import org.jrebirth.af.api.ui.annotation.AutoRelease;
import org.jrebirth.af.api.ui.annotation.CreateViewIntoJAT;
import org.jrebirth.af.api.wave.Wave;
import org.jrebirth.af.core.component.behavior.AbstractBehavioredComponent;
import org.jrebirth.af.core.concurrent.JRebirth;
import org.jrebirth.af.core.util.ClassUtility;
/**
*
* The interface <strong>AbstractBaseModel</strong>.
*
* Base implementation of the model.
*
* @author Sébastien Bordes
*
* @param <M> the class type of the current model
*/
public abstract class AbstractBaseModel<M extends Model> extends AbstractBehavioredComponent<Model> implements Model {
/** Flag used to determine if a view has been already displayed, useful to manage first time animation. */
private boolean viewDisplayed;
/** Force the creation of the View into JAT if set to true (useful when the view has got a WebView). */
protected boolean createViewIntoJAT;
/**
* Default Constructor.
*/
public AbstractBaseModel() {
super();
// Initialize the protected field with provided annotation (if present)
final CreateViewIntoJAT cvij = this.getClass().getAnnotation(CreateViewIntoJAT.class);
this.createViewIntoJAT = cvij == null ? false : cvij.value();
}
/**
* {@inheritDoc}
*/
@Override
protected final void ready() throws CoreException {
// Initialize the current model
initInternalModel();
// Model and InnerModels are OK, let's prepare the view
JRebirth.run(this.createViewIntoJAT ? RunType.JAT_SYNC : RunType.SAME, this::prepareView);
// Allow to release the model if the root business object doesn't exist anymore
attachParentListener();
// Bind Object properties to view widget ones
bindInternal();
}
/**
* Prepare the view by constructing the root node and all its children.
*
* By default this method will be called into JTP but could be done into JAT synchronously by setting the field createViewIntoJAT
*/
protected abstract void prepareView();
/**
* Initialize the model.
*
* @throws CoreException if the creation of the view fails
*/
protected abstract void initInternalModel() throws CoreException;
/**
* Initialize method to implement for adding custom processes.
*
* This method is a hook to manage generic code before initializing current model.
*
* You must implement the {@link #initModel()} method to setup your model.
*/
protected abstract void initModel();
/**
* Bind current object to view's widget.
*
* This method is a hook to manage generic code before binding model's object.
*
* You must implement the {@link #bind()} method to add your bindings.
*/
protected abstract void bindInternal();
/**
* Bind method to implement for adding custom bindings.
*/
protected abstract void bind();
/**
* Show the view.<br />
* In example : start the show transition
*
* This method is a hook to manage generic code before initializing the view's node tree. It will call {@link #org.jrebirth.af.core.ui.View.start()} or
* {@link #org.jrebirth.af.core.ui.View.reload()} method
*
* You must implement the {@link #showView()} method to setup your view.
*/
protected final void showInternalView(final Wave wave) {
// Call user code
showView();
// Sometimes view can be null
if (getView() != null) {
if (this.viewDisplayed) {
// Reload the view
getView().reload();
} else {
// Start the view for the first time
getView().start();
this.viewDisplayed = true;
}
}
// Propagate the show view to all Inner Model
if (getInnerComponentList().isPresent()) {
getInnerComponentList().get().stream()
.filter(s -> s instanceof AbstractBaseModel<?>)
.forEach((model) -> ((AbstractBaseModel<?>) model).doShowView(wave));
}
}
/**
* Perform custom user action <b>before</b> showing the view.
*/
protected abstract void showView();
/**
* Hide the view.<br />
* In example : start the hide transition
*
* Will call the {@link #org.jrebirth.af.core.ui.View.hide()} method
*/
protected final void hideInternalView(final Wave wave) {
// Call user code
hideView();
// Sometimes view can be null
if (getView() != null) {
getView().hide();
}
// Propagate the show view to all Inner Model
if (getInnerComponentList().isPresent()) {
getInnerComponentList().get().stream()
.filter(s -> s instanceof AbstractBaseModel<?>)
.forEach((model) -> ((AbstractBaseModel<?>) model).doHideView(wave));
}
}
/**
* Perform custom action <b>before</b> hiding the view.
*/
protected abstract void hideView();
/**
* {@inheritDoc}
*/
@Override
public Node getRootNode() {
return getView() == null ? null : getView().getRootNode();
}
/**
* {@inheritDoc}
*/
@Override
protected abstract void processWave(final Wave wave);
/**
* {@inheritDoc}
*/
@Override
protected void finalize() throws Throwable {
getLocalFacade().getGlobalFacade().trackEvent(JRebirthEventType.DESTROY_MODEL, null, this.getClass());
super.finalize();
}
/**
* Attach a custom listener that will release the mode when the rootNode is removed from its parent.
*/
protected void attachParentListener() {
final AutoRelease ar = ClassUtility.getLastClassAnnotation(this.getClass(), AutoRelease.class);
// Only manage automatic release when the annotation exists with true value
if (ar != null && ar.value() && getRootNode() != null) { // TODO check rootnode null when using NullView
// Allow to release the model if the root business object doesn't exist anymore
getRootNode().parentProperty().addListener(new ChangeListener<Node>() {
@Override
public void changed(final ObservableValue<? extends Node> observable, final Node oldValue, final Node newValue) {
if (newValue == null) {
release();
getRootNode().parentProperty().removeListener(this);
}
}
});
}
}
}