You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AbstractSingleComponentContainer.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. /*
  2. * Copyright 2000-2018 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.ui;
  17. import java.util.Collections;
  18. import java.util.Iterator;
  19. import org.jsoup.nodes.Element;
  20. import org.jsoup.select.Elements;
  21. import com.vaadin.server.ComponentSizeValidator;
  22. import com.vaadin.server.VaadinService;
  23. import com.vaadin.server.VaadinSession;
  24. import com.vaadin.shared.Registration;
  25. import com.vaadin.shared.ui.AbstractSingleComponentContainerState;
  26. import com.vaadin.ui.declarative.DesignContext;
  27. import com.vaadin.ui.declarative.DesignException;
  28. /**
  29. * Abstract base class for component containers that have only one child
  30. * component.
  31. *
  32. * For component containers that support multiple children, inherit
  33. * {@link AbstractComponentContainer} instead of this class.
  34. *
  35. * @since 7.0
  36. */
  37. public abstract class AbstractSingleComponentContainer extends AbstractComponent
  38. implements SingleComponentContainer {
  39. private Component content;
  40. @Override
  41. public int getComponentCount() {
  42. return (content != null) ? 1 : 0;
  43. }
  44. @Override
  45. public Iterator<Component> iterator() {
  46. if (content != null) {
  47. return Collections.singletonList(content).iterator();
  48. } else {
  49. return Collections.<Component> emptyList().iterator();
  50. }
  51. }
  52. /* documented in interface */
  53. @Override
  54. public Registration addComponentAttachListener(
  55. ComponentAttachListener listener) {
  56. return addListener(ComponentAttachEvent.class, listener,
  57. ComponentAttachListener.attachMethod);
  58. }
  59. /* documented in interface */
  60. @Override
  61. @Deprecated
  62. public void removeComponentAttachListener(
  63. ComponentAttachListener listener) {
  64. removeListener(ComponentAttachEvent.class, listener,
  65. ComponentAttachListener.attachMethod);
  66. }
  67. /* documented in interface */
  68. @Override
  69. public Registration addComponentDetachListener(
  70. ComponentDetachListener listener) {
  71. return addListener(ComponentDetachEvent.class, listener,
  72. ComponentDetachListener.detachMethod);
  73. }
  74. /* documented in interface */
  75. @Override
  76. @Deprecated
  77. public void removeComponentDetachListener(
  78. ComponentDetachListener listener) {
  79. removeListener(ComponentDetachEvent.class, listener,
  80. ComponentDetachListener.detachMethod);
  81. }
  82. /**
  83. * Fires the component attached event. This is called by the
  84. * {@link #setContent(Component)} method after the component has been set as
  85. * the content.
  86. *
  87. * @param component
  88. * the component that has been added to this container.
  89. */
  90. protected void fireComponentAttachEvent(Component component) {
  91. fireEvent(new ComponentAttachEvent(this, component));
  92. }
  93. /**
  94. * Fires the component detached event. This is called by the
  95. * {@link #setContent(Component)} method after the content component has
  96. * been replaced by other content.
  97. *
  98. * @param component
  99. * the component that has been removed from this container.
  100. */
  101. protected void fireComponentDetachEvent(Component component) {
  102. fireEvent(new ComponentDetachEvent(this, component));
  103. }
  104. @Override
  105. public Component getContent() {
  106. return content;
  107. }
  108. /**
  109. * Sets the content of this container. The content is a component that
  110. * serves as the outermost item of the visual contents.
  111. *
  112. * The content must always be set, either with a constructor parameter or by
  113. * calling this method.
  114. *
  115. * Previous versions of Vaadin used a {@link VerticalLayout} with margins
  116. * enabled as the default content but that is no longer the case.
  117. *
  118. * @param content
  119. * a component (typically a layout) to use as content
  120. */
  121. @Override
  122. public void setContent(Component content) {
  123. // Make sure we're not adding the component inside it's own content
  124. if (isOrHasAncestor(content)) {
  125. throw new IllegalArgumentException(
  126. "Component cannot be added inside it's own content");
  127. }
  128. Component oldContent = getContent();
  129. if (oldContent == content) {
  130. // do not set the same content twice
  131. return;
  132. }
  133. if (oldContent != null && equals(oldContent.getParent())) {
  134. oldContent.setParent(null);
  135. fireComponentDetachEvent(oldContent);
  136. }
  137. this.content = content;
  138. if (content != null) {
  139. removeFromParent(content);
  140. content.setParent(this);
  141. fireComponentAttachEvent(content);
  142. }
  143. markAsDirty();
  144. }
  145. /**
  146. * Utility method for removing a component from its parent (if possible).
  147. *
  148. * @param content
  149. * component to remove
  150. */
  151. // TODO move utility method elsewhere?
  152. public static void removeFromParent(Component content)
  153. throws IllegalArgumentException {
  154. // Verify the appropriate session is locked
  155. UI parentUI = content.getUI();
  156. if (parentUI != null) {
  157. VaadinSession parentSession = parentUI.getSession();
  158. if (parentSession != null && !parentSession.hasLock()) {
  159. String message = "Cannot remove from parent when the session is not locked.";
  160. if (VaadinService.isOtherSessionLocked(parentSession)) {
  161. message += " Furthermore, there is another locked session, indicating that the component might be about to be moved from one session to another.";
  162. }
  163. throw new IllegalStateException(message);
  164. }
  165. }
  166. HasComponents parent = content.getParent();
  167. if (parent instanceof ComponentContainer) {
  168. // If the component already has a parent, try to remove it
  169. ComponentContainer oldParent = (ComponentContainer) parent;
  170. oldParent.removeComponent(content);
  171. } else if (parent instanceof SingleComponentContainer) {
  172. SingleComponentContainer oldParent = (SingleComponentContainer) parent;
  173. if (oldParent.getContent() == content) {
  174. oldParent.setContent(null);
  175. }
  176. } else if (parent != null) {
  177. throw new IllegalArgumentException(
  178. "Content is already attached to another parent");
  179. }
  180. }
  181. // the setHeight()/setWidth() methods duplicated and simplified from
  182. // AbstractComponentContainer
  183. @Override
  184. public void setWidth(float width, Unit unit) {
  185. /*
  186. * child tree repaints may be needed, due to our fall back support for
  187. * invalid relative sizes
  188. */
  189. boolean dirtyChild = false;
  190. boolean childrenMayBecomeUndefined = false;
  191. if (getWidth() == SIZE_UNDEFINED && width != SIZE_UNDEFINED) {
  192. // children currently in invalid state may need repaint
  193. dirtyChild = getInvalidSizedChild(false);
  194. } else if ((width == SIZE_UNDEFINED && getWidth() != SIZE_UNDEFINED)
  195. || (unit == Unit.PERCENTAGE
  196. && getWidthUnits() != Unit.PERCENTAGE
  197. && !ComponentSizeValidator
  198. .parentCanDefineWidth(this))) {
  199. /*
  200. * relative width children may get to invalid state if width becomes
  201. * invalid. Width may also become invalid if units become percentage
  202. * due to the fallback support
  203. */
  204. childrenMayBecomeUndefined = true;
  205. dirtyChild = getInvalidSizedChild(false);
  206. }
  207. super.setWidth(width, unit);
  208. repaintChangedChildTree(dirtyChild, childrenMayBecomeUndefined, false);
  209. }
  210. private void repaintChangedChildTree(boolean invalidChild,
  211. boolean childrenMayBecomeUndefined, boolean vertical) {
  212. if (getContent() == null) {
  213. return;
  214. }
  215. boolean needRepaint = false;
  216. if (childrenMayBecomeUndefined) {
  217. // if became invalid now
  218. needRepaint = !invalidChild && getInvalidSizedChild(vertical);
  219. } else if (invalidChild) {
  220. // if not still invalid
  221. needRepaint = !getInvalidSizedChild(vertical);
  222. }
  223. if (needRepaint) {
  224. getContent().markAsDirtyRecursive();
  225. }
  226. }
  227. private boolean getInvalidSizedChild(final boolean vertical) {
  228. Component content = getContent();
  229. if (content == null) {
  230. return false;
  231. }
  232. if (vertical) {
  233. return !ComponentSizeValidator.checkHeights(content);
  234. } else {
  235. return !ComponentSizeValidator.checkWidths(content);
  236. }
  237. }
  238. @Override
  239. public void setHeight(float height, Unit unit) {
  240. /*
  241. * child tree repaints may be needed, due to our fall back support for
  242. * invalid relative sizes
  243. */
  244. boolean dirtyChild = false;
  245. boolean childrenMayBecomeUndefined = false;
  246. if (getHeight() == SIZE_UNDEFINED && height != SIZE_UNDEFINED) {
  247. // children currently in invalid state may need repaint
  248. dirtyChild = getInvalidSizedChild(true);
  249. } else if ((height == SIZE_UNDEFINED && getHeight() != SIZE_UNDEFINED)
  250. || (unit == Unit.PERCENTAGE
  251. && getHeightUnits() != Unit.PERCENTAGE
  252. && !ComponentSizeValidator
  253. .parentCanDefineHeight(this))) {
  254. /*
  255. * relative height children may get to invalid state if height
  256. * becomes invalid. Height may also become invalid if units become
  257. * percentage due to the fallback support.
  258. */
  259. childrenMayBecomeUndefined = true;
  260. dirtyChild = getInvalidSizedChild(true);
  261. }
  262. super.setHeight(height, unit);
  263. repaintChangedChildTree(dirtyChild, childrenMayBecomeUndefined, true);
  264. }
  265. /*
  266. * (non-Javadoc)
  267. *
  268. * @see com.vaadin.ui.AbstractComponent#readDesign(org.jsoup.nodes .Element,
  269. * com.vaadin.ui.declarative.DesignContext)
  270. */
  271. @Override
  272. public void readDesign(Element design, DesignContext designContext) {
  273. // process default attributes
  274. super.readDesign(design, designContext);
  275. readDesignChildren(design.children(), designContext);
  276. }
  277. /**
  278. * Reads the content component from the list of child elements of a design.
  279. * The list must be empty or contain a single element; if the design
  280. * contains multiple child elements, a DesignException is thrown. This
  281. * method should be overridden by subclasses whose design may contain
  282. * non-content child elements.
  283. *
  284. * @since 7.5.0
  285. *
  286. * @param children
  287. * the child elements of the design that is being read
  288. * @param context
  289. * the DesignContext instance used to parse the design
  290. *
  291. * @throws DesignException
  292. * if there are multiple child elements
  293. * @throws DesignException
  294. * if a child element could not be parsed as a Component
  295. */
  296. protected void readDesignChildren(Elements children,
  297. DesignContext context) {
  298. if (children.size() > 1) {
  299. throw new DesignException("The container of type " + getClass()
  300. + " can have only one child component.");
  301. } else if (children.size() == 1) {
  302. setContent(context.readDesign(children.first()));
  303. }
  304. }
  305. /*
  306. * (non-Javadoc)
  307. *
  308. * @see com.vaadin.ui.AbstractComponent#writeDesign(org.jsoup.nodes.Element
  309. * , com.vaadin.ui.declarative.DesignContext)
  310. */
  311. @Override
  312. public void writeDesign(Element design, DesignContext designContext) {
  313. // write default attributes (also clears children and attributes)
  314. super.writeDesign(design, designContext);
  315. AbstractSingleComponentContainer def = designContext
  316. .getDefaultInstance(this);
  317. if (!designContext.shouldWriteChildren(this, def)) {
  318. return;
  319. }
  320. // handle child component
  321. Component child = getContent();
  322. if (child != null) {
  323. Element childNode = designContext.createElement(child);
  324. design.appendChild(childNode);
  325. }
  326. }
  327. @Override
  328. protected AbstractSingleComponentContainerState getState() {
  329. return (AbstractSingleComponentContainerState) super.getState();
  330. }
  331. @Override
  332. protected AbstractSingleComponentContainerState getState(
  333. boolean markAsDirty) {
  334. return (AbstractSingleComponentContainerState) super.getState(
  335. markAsDirty);
  336. }
  337. }