/** * Copyright (C) 2011 BonitaSoft S.A. * BonitaSoft, 32 rue Gustave Eiffel - 38000 Grenoble * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2.0 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.bonitasoft.web.toolkit.client.ui.component.core; import static com.google.gwt.query.client.GQuery.$; import java.util.ArrayList; import java.util.List; import org.bonitasoft.web.toolkit.client.ViewController; import org.bonitasoft.web.toolkit.client.ui.action.Action; import org.bonitasoft.web.toolkit.client.ui.component.Refreshable; import org.bonitasoft.web.toolkit.client.ui.html.HTML; import org.bonitasoft.web.toolkit.client.ui.utils.Filler; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.Widget; /** * @author Séverin Moussel * */ public abstract class AbstractComponent extends Widget implements Node { /** * Indicate if the HTML of the VisualElement has already been generated. */ protected boolean generated = false; /** * List of fillers to apply to this component.<br /> * A filler is a definition of how to fill the component with data. */ protected final List<Filler<? extends Object>> fillers = new ArrayList<Filler<? extends Object>>(); /** * An action to perform on load.<br /> * Load is triggered while the component is inserted in the DOM. */ private Action onLoadAction = null; /** * A tooltip to set for the whole component.<br /> * Tooltips are created by inserting a "title" attribute on root DOM elements. */ protected String tooltip; /** * Indicate if the onLoad event must run the fillers. */ private boolean fillOnLoad = true; /** * Indicate if the onLoad event must run the fillers. */ private boolean fillOnRefresh = false; /** * Default constructor. */ public AbstractComponent() { super(); } // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // GENERATE HTML // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Called before generating the DOM.<br /> * This method is meant to be overridden but it's not mandatory.<br /> * In this method you can only modify member variables. */ protected void preProcessHtml() { } /** * Called after generating the DOM.<br /> * This method is meant to be overridden but it's not mandatory.<br /> * In this method you can modify the DOM generated. */ protected void postProcessHtml() { } /** * Check if the component has already been generated. * * @return This method returns TRUE if the component has already been generated, otherwise FALSE */ public final boolean isGenerated() { return generated; } // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // LOAD // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Indicate if the component must run fillers on load. * * @param fillOnLoad * TRUE to fill the component on load, otherwise FALSE. * @return This method returns the component itself to allow cascading calls. */ public AbstractComponent setFillOnLoad(final boolean fillOnLoad) { this.fillOnLoad = fillOnLoad; if (!this.fillOnLoad) { ViewController.getInstance().unregisterOnLoadEvent(this); } else if (fillers.size() > 0) { ViewController.getInstance().registerOnLoadEvent(this); } return this; } /** * Indicate if the component must run fillers on page refresh. * * @param fillOnRefresh * TRUE to fill the component on page refresh, otherwise FALSE. * @return This method returns the component itself to allow cascading calls. */ public AbstractComponent setFillOnRefresh(final boolean fillOnRefresh) { if (!(this instanceof Refreshable)) { throw new ComponentException(this, "Can't register a non Refreshable component for automatic page refresh"); } this.fillOnRefresh = fillOnRefresh; if (!this.fillOnRefresh) { ViewController.getInstance().unregisterOnPageRefreshEvent((Refreshable) this); } else if (fillers.size() > 0) { ViewController.getInstance().registerOnPageRefreshEvent((Refreshable) this); } return this; } /** * Register an action to run on onLoad event.<br /> * The onLoad event is triggered while the component is inserted in the document. * * @param action * The action to run on load */ public final void onLoad(final Action action) { onLoadAction = action; ViewController.getInstance().registerOnLoadEvent(this); } /** * Override of onLoad method to run fillers if needed. */ @Override protected void onLoad() { if (fillOnLoad) { runFillers(); } } /** * Trigger the onLoad event. */ public final void triggerLoad() { this.onLoad(); if (onLoadAction != null) { onLoadAction.execute(); } } /** * Display a loader over the component. * * @return This method returns the component itself to allow cascading calls. */ public final AbstractComponent startLoading() { return this; } /** * Hide the loader over the component. * * @return This method returns the component itself to allow cascading calls. */ public final AbstractComponent stopLoading() { return this; } // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // FILLERS // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Add a filler on this component.<br /> * Fillers are definitions on how to read and write data. * * @param filler * The filler to add * @return This method returns the component itself to allow cascading calls. */ public final AbstractComponent addFiller(final Filler<?> filler) { if (filler != null) { fillers.add(filler); } if (fillOnLoad) { ViewController.getInstance().registerOnLoadEvent(this); } if (fillOnRefresh) { ViewController.getInstance().registerOnPageRefreshEvent((Refreshable) this); } return this; } /** * Remove all fillers. * * @return This method returns "this" to allow cascading calls. */ public final AbstractComponent resetFillers() { fillers.clear(); return this; } /** * Remove all fillers and add the defined ones. * * @param fillers * The fillers to set. * @return This method returns "this" to allow cascading calls. */ public final AbstractComponent setFillers(final List<Filler<? extends Object>> fillers) { this.fillers.clear(); this.fillers.addAll(fillers); ViewController.getInstance().registerOnLoadEvent(this); if (fillOnLoad) { ViewController.getInstance().registerOnLoadEvent(this); } if (fillOnRefresh) { ViewController.getInstance().registerOnPageRefreshEvent((Refreshable) this); } return this; } /** * Remove all fillers and add the defined one. * * @param filler * The filler to set * @return This method returns "this" to allow cascading calls. */ public final AbstractComponent setFiller(final Filler<?> filler) { resetFillers(); addFiller(filler); return this; } /** * Execute the fillers of this component. */ protected final void runFillers() { runFillers(null); } public final List<Filler<? extends Object>> getFillers() { return fillers; } /** * Execute the fillers of this component, and call the action at the end. */ protected final void runFillers(final Action callback) { if (!generated) { return; } for (final Filler<? extends Object> filler : fillers) { filler.setTarget(this); filler.setOnFinishCallback(callback); filler.run(); } } // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // GET ELEMENTS // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Construct the DOM tree using a set of com.google.gwt.user.client.Element. * * @return This function returns the root Elements of this VisualElement. */ @Override public final List<Element> getElements() { final List<Element> elements = _getElements(); if (tooltip != null) { for (final Element e : elements) { e.setAttribute("title", tooltip); } } return elements; } /** * Construct the DOM tree using a set of com.google.gwt.user.client.Element. * * @return This function returns the root Elements of this VisualElement. */ protected abstract List<Element> _getElements(); // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // DOM TOOLS // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Check if the component is already inserted in the main Document DOM tree. * * @return This method return TRUE if the component is in the Document DOM, otherwise FALSE. */ public final boolean isInDom() { for (final Element e : getElements()) { if (!this.isInDom(e)) { return false; } } return true; } /** * Check if the passed element is already inserted in the main Document DOM tree. * * @param e * The element to check * @return This method return TRUE if the element is in the Document DOM, otherwise FALSE. */ protected boolean isInDom(final Element e) { if (e == null) { return false; } if ("html".equalsIgnoreCase(e.getTagName())) { return true; } return $(e).closest("html").length() > 0; // return this.isInDom((Element) e.getParentElement()); } /** * Append a component in a root element. * * @param rootElement * The root element where to append the component. * @param component * The component to append. */ protected static final void appendComponentToHtml(final Element rootElement, final AbstractComponent component) { if (component != null) { HTML.append(rootElement, component.getElements()); } } /** * Prepend a component in a root element. * * @param rootElement * The root element where to prepend the component. * @param component * The component to prepend. */ protected static final void prependComponentToHtml(final Element rootElement, final AbstractComponent component) { if (component != null) { HTML.prepend(rootElement, component.getElements()); } } // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // OPTIONS // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Set the tooltip to display on this component. * * @return This method returns "this" to allow cascading calls. */ public final String getTooltip() { return tooltip; } /** * Define the tooltip to display on mouse over this component. * * @param tooltip * The tooltip to display. * @return This method returns "this" to allow cascading calls. */ public final AbstractComponent setTooltip(final String tooltip) { this.tooltip = tooltip; if (isGenerated()) { if (tooltip == null) { for (final Element e : getElements()) { e.removeAttribute("title"); } } else { for (final Element e : getElements()) { e.setAttribute("title", tooltip); } } } return this; } }