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.

VFormLayout.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  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.client.ui;
  17. import java.util.ArrayList;
  18. import java.util.HashMap;
  19. import java.util.List;
  20. import java.util.Map;
  21. import com.google.gwt.aria.client.Roles;
  22. import com.google.gwt.dom.client.Element;
  23. import com.google.gwt.event.dom.client.ClickEvent;
  24. import com.google.gwt.event.dom.client.ClickHandler;
  25. import com.google.gwt.user.client.DOM;
  26. import com.google.gwt.user.client.ui.FlexTable;
  27. import com.google.gwt.user.client.ui.HTML;
  28. import com.google.gwt.user.client.ui.SimplePanel;
  29. import com.google.gwt.user.client.ui.Widget;
  30. import com.vaadin.client.BrowserInfo;
  31. import com.vaadin.client.ComponentConnector;
  32. import com.vaadin.client.Focusable;
  33. import com.vaadin.client.StyleConstants;
  34. import com.vaadin.client.VTooltip;
  35. import com.vaadin.client.WidgetUtil.ErrorUtil;
  36. import com.vaadin.client.ui.aria.AriaHelper;
  37. import com.vaadin.shared.AbstractComponentState;
  38. import com.vaadin.shared.ComponentConstants;
  39. import com.vaadin.shared.ui.ComponentStateUtil;
  40. import com.vaadin.shared.ui.ErrorLevel;
  41. import com.vaadin.shared.ui.MarginInfo;
  42. /**
  43. * Two col Layout that places caption on left col and field on right col.
  44. */
  45. public class VFormLayout extends SimplePanel {
  46. private static final String CLASSNAME = "v-formlayout";
  47. /** For internal use only. May be removed or replaced in the future. */
  48. public VFormLayoutTable table;
  49. public VFormLayout() {
  50. super();
  51. setStyleName(CLASSNAME);
  52. addStyleName(StyleConstants.UI_LAYOUT);
  53. table = new VFormLayoutTable();
  54. setWidget(table);
  55. }
  56. /**
  57. * Parses the stylenames from shared state
  58. *
  59. * @param state
  60. * shared state of the component
  61. * @param enabled
  62. * @return An array of stylenames
  63. */
  64. private String[] getStylesFromState(AbstractComponentState state,
  65. boolean enabled) {
  66. List<String> styles = new ArrayList<>();
  67. if (ComponentStateUtil.hasStyles(state)) {
  68. for (String name : state.styles) {
  69. styles.add(name);
  70. }
  71. }
  72. if (!enabled) {
  73. styles.add(StyleConstants.DISABLED);
  74. }
  75. return styles.toArray(new String[styles.size()]);
  76. }
  77. public class VFormLayoutTable extends FlexTable implements ClickHandler {
  78. private static final int COLUMN_CAPTION = 0;
  79. private static final int COLUMN_ERRORFLAG = 1;
  80. public static final int COLUMN_WIDGET = 2;
  81. private Map<Widget, Caption> widgetToCaption = new HashMap<>();
  82. private Map<Widget, ErrorFlag> widgetToError = new HashMap<>();
  83. public VFormLayoutTable() {
  84. DOM.setElementProperty(getElement(), "cellPadding", "0");
  85. DOM.setElementProperty(getElement(), "cellSpacing", "0");
  86. Roles.getPresentationRole().set(getElement());
  87. }
  88. /*
  89. * (non-Javadoc)
  90. *
  91. * @see
  92. * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt
  93. * .event.dom.client.ClickEvent)
  94. */
  95. @Override
  96. public void onClick(ClickEvent event) {
  97. Caption caption = (Caption) event.getSource();
  98. if (caption.getOwner() != null) {
  99. if (caption.getOwner() instanceof Focusable) {
  100. ((Focusable) caption.getOwner()).focus();
  101. } else if (caption
  102. .getOwner() instanceof com.google.gwt.user.client.ui.Focusable) {
  103. ((com.google.gwt.user.client.ui.Focusable) caption
  104. .getOwner()).setFocus(true);
  105. }
  106. }
  107. }
  108. public void setMargins(MarginInfo margins) {
  109. Element margin = getElement();
  110. setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_TOP,
  111. margins.hasTop());
  112. setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_RIGHT,
  113. margins.hasRight());
  114. setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_BOTTOM,
  115. margins.hasBottom());
  116. setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_LEFT,
  117. margins.hasLeft());
  118. }
  119. public void setSpacing(boolean spacing) {
  120. setStyleName(getElement(), CLASSNAME + "-" + "spacing", spacing);
  121. }
  122. public void setRowCount(int rowNr) {
  123. for (int i = 0; i < rowNr; i++) {
  124. prepareCell(i, COLUMN_CAPTION);
  125. getCellFormatter().setStyleName(i, COLUMN_CAPTION,
  126. CLASSNAME + "-captioncell");
  127. prepareCell(i, 1);
  128. getCellFormatter().setStyleName(i, COLUMN_ERRORFLAG,
  129. CLASSNAME + "-errorcell");
  130. prepareCell(i, 2);
  131. getCellFormatter().setStyleName(i, COLUMN_WIDGET,
  132. CLASSNAME + "-contentcell");
  133. String rowstyles = CLASSNAME + "-row";
  134. if (i == 0) {
  135. rowstyles += " " + CLASSNAME + "-firstrow";
  136. }
  137. if (i == rowNr - 1) {
  138. rowstyles += " " + CLASSNAME + "-lastrow";
  139. }
  140. getRowFormatter().setStyleName(i, rowstyles);
  141. }
  142. while (getRowCount() != rowNr) {
  143. removeRow(rowNr);
  144. }
  145. }
  146. public void setChild(int rowNr, Widget childWidget, Caption caption,
  147. ErrorFlag error) {
  148. setWidget(rowNr, COLUMN_WIDGET, childWidget);
  149. setWidget(rowNr, COLUMN_CAPTION, caption);
  150. setWidget(rowNr, COLUMN_ERRORFLAG, error);
  151. widgetToCaption.put(childWidget, caption);
  152. widgetToError.put(childWidget, error);
  153. }
  154. public Caption getCaption(Widget childWidget) {
  155. return widgetToCaption.get(childWidget);
  156. }
  157. public ErrorFlag getError(Widget childWidget) {
  158. return widgetToError.get(childWidget);
  159. }
  160. public void cleanReferences(Widget oldChildWidget) {
  161. widgetToError.remove(oldChildWidget);
  162. widgetToCaption.remove(oldChildWidget);
  163. }
  164. public void updateCaption(Widget widget, AbstractComponentState state,
  165. boolean enabled) {
  166. final Caption c = widgetToCaption.get(widget);
  167. if (c != null) {
  168. c.updateCaption(state, enabled);
  169. }
  170. }
  171. public void updateError(Widget widget, String errorMessage,
  172. ErrorLevel errorLevel, boolean hideErrors) {
  173. final ErrorFlag e = widgetToError.get(widget);
  174. if (e != null) {
  175. e.updateError(errorMessage, errorLevel, hideErrors);
  176. }
  177. }
  178. }
  179. // TODO why duplicated here?
  180. public class Caption extends HTML {
  181. public static final String CLASSNAME = "v-caption";
  182. private final ComponentConnector owner;
  183. private Element requiredFieldIndicator;
  184. private Icon icon;
  185. private Element captionContent;
  186. /**
  187. *
  188. * @param component
  189. * optional owner of caption. If not set, getOwner will
  190. * return null
  191. */
  192. public Caption(ComponentConnector component) {
  193. super();
  194. owner = component;
  195. }
  196. private void setStyles(String[] styles) {
  197. String styleName = CLASSNAME;
  198. if (styles != null) {
  199. for (String style : styles) {
  200. if (StyleConstants.DISABLED.equals(style)) {
  201. // Add v-disabled also without classname prefix so
  202. // generic v-disabled CSS rules work
  203. styleName += " " + style;
  204. }
  205. styleName += " " + CLASSNAME + "-" + style;
  206. }
  207. }
  208. setStyleName(styleName);
  209. }
  210. public void updateCaption(AbstractComponentState state,
  211. boolean enabled) {
  212. // Update styles as they might have changed when the caption changed
  213. setStyles(getStylesFromState(state, enabled));
  214. boolean isEmpty = true;
  215. if (icon != null) {
  216. getElement().removeChild(icon.getElement());
  217. icon = null;
  218. }
  219. if (state.resources.containsKey(ComponentConstants.ICON_RESOURCE)) {
  220. icon = owner.getConnection().getIcon(state.resources
  221. .get(ComponentConstants.ICON_RESOURCE).getURL());
  222. DOM.insertChild(getElement(), icon.getElement(), 0);
  223. isEmpty = false;
  224. }
  225. if (state.caption != null) {
  226. if (captionContent == null) {
  227. captionContent = DOM.createSpan();
  228. AriaHelper.bindCaption(owner.getWidget(), captionContent);
  229. DOM.insertChild(getElement(), captionContent,
  230. icon == null ? 0 : 1);
  231. }
  232. String c = state.caption;
  233. if (c == null) {
  234. c = "";
  235. } else {
  236. isEmpty = false;
  237. }
  238. if (state.captionAsHtml) {
  239. captionContent.setInnerHTML(c);
  240. } else {
  241. captionContent.setInnerText(c);
  242. }
  243. } else {
  244. // TODO should span also be removed
  245. }
  246. if (state.description != null && captionContent != null) {
  247. addStyleDependentName("hasdescription");
  248. } else {
  249. removeStyleDependentName("hasdescription");
  250. }
  251. boolean required = owner instanceof HasRequiredIndicator
  252. && ((HasRequiredIndicator) owner)
  253. .isRequiredIndicatorVisible();
  254. AriaHelper.handleInputRequired(owner.getWidget(), required);
  255. if (required) {
  256. if (requiredFieldIndicator == null) {
  257. requiredFieldIndicator = DOM.createSpan();
  258. DOM.setInnerText(requiredFieldIndicator, "*");
  259. DOM.setElementProperty(requiredFieldIndicator, "className",
  260. "v-required-field-indicator");
  261. DOM.appendChild(getElement(), requiredFieldIndicator);
  262. // Hide the required indicator from screen reader, as this
  263. // information is set directly at the input field
  264. Roles.getTextboxRole()
  265. .setAriaHiddenState(requiredFieldIndicator, true);
  266. }
  267. } else {
  268. if (requiredFieldIndicator != null) {
  269. DOM.removeChild(getElement(), requiredFieldIndicator);
  270. requiredFieldIndicator = null;
  271. }
  272. }
  273. }
  274. /**
  275. * Returns Paintable for which this Caption belongs to.
  276. *
  277. * @return owner Widget
  278. */
  279. public ComponentConnector getOwner() {
  280. return owner;
  281. }
  282. }
  283. /** For internal use only. May be removed or replaced in the future. */
  284. public class ErrorFlag extends HTML implements HasErrorIndicatorElement {
  285. private static final String CLASSNAME = VFormLayout.CLASSNAME
  286. + "-error-indicator";
  287. Element errorIndicatorElement;
  288. private ComponentConnector owner;
  289. public ErrorFlag(ComponentConnector owner) {
  290. setStyleName(CLASSNAME);
  291. if (!BrowserInfo.get().isTouchDevice()) {
  292. sinkEvents(VTooltip.TOOLTIP_EVENTS);
  293. }
  294. this.owner = owner;
  295. }
  296. public ComponentConnector getOwner() {
  297. return owner;
  298. }
  299. public void updateError(String errorMessage, ErrorLevel errorLevel,
  300. boolean hideErrors) {
  301. boolean showError = null != errorMessage;
  302. if (hideErrors) {
  303. showError = false;
  304. }
  305. AriaHelper.handleInputInvalid(owner.getWidget(), showError);
  306. if (showError) {
  307. setErrorIndicatorElementVisible(true);
  308. // Hide the error indicator from screen reader, as this
  309. // information is set directly at the input field
  310. Roles.getFormRole().setAriaHiddenState(errorIndicatorElement,
  311. true);
  312. ErrorUtil.setErrorLevelStyle(errorIndicatorElement,
  313. StyleConstants.STYLE_NAME_ERROR_INDICATOR, errorLevel);
  314. } else {
  315. setErrorIndicatorElementVisible(false);
  316. }
  317. }
  318. @Override
  319. public Element getErrorIndicatorElement() {
  320. return errorIndicatorElement;
  321. }
  322. @Override
  323. public void setErrorIndicatorElementVisible(boolean visible) {
  324. if (visible) {
  325. if (errorIndicatorElement == null) {
  326. errorIndicatorElement = ErrorUtil
  327. .createErrorIndicatorElement();
  328. getElement().appendChild(errorIndicatorElement);
  329. }
  330. } else if (errorIndicatorElement != null) {
  331. getElement().removeChild(errorIndicatorElement);
  332. errorIndicatorElement = null;
  333. }
  334. }
  335. }
  336. }