Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

Form.java 37KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.ui;
  5. import java.util.Collection;
  6. import java.util.Collections;
  7. import java.util.HashMap;
  8. import java.util.Iterator;
  9. import java.util.LinkedList;
  10. import com.vaadin.data.Buffered;
  11. import com.vaadin.data.Item;
  12. import com.vaadin.data.Property;
  13. import com.vaadin.data.Validatable;
  14. import com.vaadin.data.Validator;
  15. import com.vaadin.data.Validator.InvalidValueException;
  16. import com.vaadin.data.util.BeanItem;
  17. import com.vaadin.terminal.CompositeErrorMessage;
  18. import com.vaadin.terminal.ErrorMessage;
  19. import com.vaadin.terminal.PaintException;
  20. import com.vaadin.terminal.PaintTarget;
  21. import com.vaadin.terminal.gwt.client.ui.VForm;
  22. /**
  23. * Form component provides easy way of creating and managing sets fields.
  24. *
  25. * <p>
  26. * <code>Form</code> is a container for fields implementing {@link Field}
  27. * interface. It provides support for any layouts and provides buffering
  28. * interface for easy connection of commit and discard buttons. All the form
  29. * fields can be customized by adding validators, setting captions and icons,
  30. * setting immediateness, etc. Also direct mechanism for replacing existing
  31. * fields with selections is given.
  32. * </p>
  33. *
  34. * <p>
  35. * <code>Form</code> provides customizable editor for classes implementing
  36. * {@link com.vaadin.data.Item} interface. Also the form itself implements this
  37. * interface for easier connectivity to other items. To use the form as editor
  38. * for an item, just connect the item to form with
  39. * {@link Form#setItemDataSource(Item)}. If only a part of the item needs to be
  40. * edited, {@link Form#setItemDataSource(Item,Collection)} can be used instead.
  41. * After the item has been connected to the form, the automatically created
  42. * fields can be customized and new fields can be added. If you need to connect
  43. * a class that does not implement {@link com.vaadin.data.Item} interface, most
  44. * properties of any class following bean pattern, can be accessed trough
  45. * {@link com.vaadin.data.util.BeanItem}.
  46. * </p>
  47. *
  48. * @author IT Mill Ltd.
  49. * @version
  50. * @VERSION@
  51. * @since 3.0
  52. */
  53. @SuppressWarnings("serial")
  54. @ClientWidget(VForm.class)
  55. public class Form extends AbstractField implements Item.Editor, Buffered, Item,
  56. Validatable {
  57. private Object propertyValue;
  58. /**
  59. * Layout of the form.
  60. */
  61. private Layout layout;
  62. /**
  63. * Item connected to this form as datasource.
  64. */
  65. private Item itemDatasource;
  66. /**
  67. * Ordered list of property ids in this editor.
  68. */
  69. private final LinkedList<Object> propertyIds = new LinkedList<Object>();
  70. /**
  71. * Current buffered source exception.
  72. */
  73. private Buffered.SourceException currentBufferedSourceException = null;
  74. /**
  75. * Is the form in write trough mode.
  76. */
  77. private boolean writeThrough = true;
  78. /**
  79. * Is the form in read trough mode.
  80. */
  81. private boolean readThrough = true;
  82. /**
  83. * Mapping from propertyName to corresponding field.
  84. */
  85. private final HashMap<Object, Field> fields = new HashMap<Object, Field>();
  86. /**
  87. * Form may act as an Item, its own properties are stored here.
  88. */
  89. private final HashMap<Object, Property> ownProperties = new HashMap<Object, Property>();
  90. /**
  91. * Field factory for this form.
  92. */
  93. private FormFieldFactory fieldFactory;
  94. /**
  95. * Visible item properties.
  96. */
  97. private Collection<Object> visibleItemProperties;
  98. /**
  99. * Form needs to repaint itself if child fields value changes due possible
  100. * change in form validity.
  101. */
  102. private final ValueChangeListener fieldValueChangeListener = new ValueChangeListener() {
  103. public void valueChange(com.vaadin.data.Property.ValueChangeEvent event) {
  104. requestRepaint();
  105. }
  106. };
  107. private Layout formFooter;
  108. /**
  109. * If this is true, commit implicitly calls setValidationVisible(true).
  110. */
  111. private boolean validationVisibleOnCommit = true;
  112. // special handling for gridlayout; remember initial cursor pos
  113. private int gridlayoutCursorX = -1;
  114. private int gridlayoutCursorY = -1;
  115. /**
  116. * Contructs a new form with default layout.
  117. *
  118. * <p>
  119. * By default the form uses <code>OrderedLayout</code> with
  120. * <code>form</code>-style.
  121. * </p>
  122. *
  123. * @param formLayout
  124. * the layout of the form.
  125. */
  126. public Form() {
  127. this(null);
  128. setValidationVisible(false);
  129. }
  130. /**
  131. * Contructs a new form with given layout.
  132. *
  133. * @param formLayout
  134. * the layout of the form.
  135. */
  136. public Form(Layout formLayout) {
  137. this(formLayout, DefaultFieldFactory.get());
  138. }
  139. /**
  140. * Contructs a new form with given {@link Layout} and
  141. * {@link FormFieldFactory}.
  142. *
  143. * @param formLayout
  144. * the layout of the form.
  145. * @param fieldFactory
  146. * the FieldFactory of the form.
  147. */
  148. public Form(Layout formLayout, FormFieldFactory fieldFactory) {
  149. super();
  150. setLayout(formLayout);
  151. setFormFieldFactory(fieldFactory);
  152. setValidationVisible(false);
  153. setWidth(100, UNITS_PERCENTAGE);
  154. }
  155. /* Documented in interface */
  156. @Override
  157. public String getTag() {
  158. return "form";
  159. }
  160. /* Documented in interface */
  161. @Override
  162. public void paintContent(PaintTarget target) throws PaintException {
  163. super.paintContent(target);
  164. layout.paint(target);
  165. if (formFooter != null) {
  166. formFooter.paint(target);
  167. }
  168. }
  169. /**
  170. * The error message of a Form is the error of the first field with a
  171. * non-empty error.
  172. *
  173. * Empty error messages of the contained fields are skipped, because an
  174. * empty error indicator would be confusing to the user, especially if there
  175. * are errors that have something to display. This is also the reason why
  176. * the calculation of the error message is separate from validation, because
  177. * validation fails also on empty errors.
  178. */
  179. @Override
  180. public ErrorMessage getErrorMessage() {
  181. // Reimplement the checking of validation error by using
  182. // getErrorMessage() recursively instead of validate().
  183. ErrorMessage validationError = null;
  184. if (isValidationVisible()) {
  185. for (final Iterator<Object> i = propertyIds.iterator(); i.hasNext();) {
  186. Object f = fields.get(i.next());
  187. if (f instanceof AbstractComponent) {
  188. AbstractComponent field = (AbstractComponent) f;
  189. validationError = field.getErrorMessage();
  190. if (validationError != null) {
  191. // Show caption as error for fields with empty errors
  192. if ("".equals(validationError.toString())) {
  193. validationError = new Validator.InvalidValueException(
  194. field.getCaption());
  195. }
  196. break;
  197. } else if (f instanceof Field && !((Field) f).isValid()) {
  198. // Something is wrong with the field, but no proper
  199. // error is given. Generate one.
  200. validationError = new Validator.InvalidValueException(
  201. field.getCaption());
  202. break;
  203. }
  204. }
  205. }
  206. }
  207. // Return if there are no errors at all
  208. if (getComponentError() == null && validationError == null
  209. && currentBufferedSourceException == null) {
  210. return null;
  211. }
  212. // Throw combination of the error types
  213. return new CompositeErrorMessage(new ErrorMessage[] {
  214. getComponentError(), validationError,
  215. currentBufferedSourceException });
  216. }
  217. /**
  218. * Controls the making validation visible implicitly on commit.
  219. *
  220. * Having commit() call setValidationVisible(true) implicitly is the default
  221. * behaviour. You can disable the implicit setting by setting this property
  222. * as false.
  223. *
  224. * It is useful, because you usually want to start with the form free of
  225. * errors and only display them after the user clicks Ok. You can disable
  226. * the implicit setting by setting this property as false.
  227. *
  228. * @param makeVisible
  229. * If true (default), validation is made visible when commit() is
  230. * called. If false, the visibility is left as it is.
  231. */
  232. public void setValidationVisibleOnCommit(boolean makeVisible) {
  233. validationVisibleOnCommit = makeVisible;
  234. }
  235. /**
  236. * Is validation made automatically visible on commit?
  237. *
  238. * See setValidationVisibleOnCommit().
  239. *
  240. * @return true if validation is made automatically visible on commit.
  241. */
  242. public boolean isValidationVisibleOnCommit() {
  243. return validationVisibleOnCommit;
  244. }
  245. /*
  246. * Commit changes to the data source Don't add a JavaDoc comment here, we
  247. * use the default one from the interface.
  248. */
  249. @Override
  250. public void commit() throws Buffered.SourceException {
  251. LinkedList<SourceException> problems = null;
  252. // Only commit on valid state if so requested
  253. if (!isInvalidCommitted() && !isValid()) {
  254. /*
  255. * The values are not ok and we are told not to commit invalid
  256. * values
  257. */
  258. if (validationVisibleOnCommit) {
  259. setValidationVisible(true);
  260. }
  261. // Find the first invalid value and throw the exception
  262. validate();
  263. }
  264. // Try to commit all
  265. for (final Iterator<Object> i = propertyIds.iterator(); i.hasNext();) {
  266. try {
  267. final Field f = (fields.get(i.next()));
  268. // Commit only non-readonly fields.
  269. if (!f.isReadOnly()) {
  270. f.commit();
  271. }
  272. } catch (final Buffered.SourceException e) {
  273. if (problems == null) {
  274. problems = new LinkedList<SourceException>();
  275. }
  276. problems.add(e);
  277. }
  278. }
  279. // No problems occurred
  280. if (problems == null) {
  281. if (currentBufferedSourceException != null) {
  282. currentBufferedSourceException = null;
  283. requestRepaint();
  284. }
  285. return;
  286. }
  287. // Commit problems
  288. final Throwable[] causes = new Throwable[problems.size()];
  289. int index = 0;
  290. for (final Iterator<SourceException> i = problems.iterator(); i
  291. .hasNext();) {
  292. causes[index++] = i.next();
  293. }
  294. final Buffered.SourceException e = new Buffered.SourceException(this,
  295. causes);
  296. currentBufferedSourceException = e;
  297. requestRepaint();
  298. throw e;
  299. }
  300. /*
  301. * Discards local changes and refresh values from the data source Don't add
  302. * a JavaDoc comment here, we use the default one from the interface.
  303. */
  304. @Override
  305. public void discard() throws Buffered.SourceException {
  306. LinkedList<SourceException> problems = null;
  307. // Try to discard all changes
  308. for (final Iterator<Object> i = propertyIds.iterator(); i.hasNext();) {
  309. try {
  310. (fields.get(i.next())).discard();
  311. } catch (final Buffered.SourceException e) {
  312. if (problems == null) {
  313. problems = new LinkedList<SourceException>();
  314. }
  315. problems.add(e);
  316. }
  317. }
  318. // No problems occurred
  319. if (problems == null) {
  320. if (currentBufferedSourceException != null) {
  321. currentBufferedSourceException = null;
  322. requestRepaint();
  323. }
  324. return;
  325. }
  326. // Discards problems occurred
  327. final Throwable[] causes = new Throwable[problems.size()];
  328. int index = 0;
  329. for (final Iterator<SourceException> i = problems.iterator(); i
  330. .hasNext();) {
  331. causes[index++] = i.next();
  332. }
  333. final Buffered.SourceException e = new Buffered.SourceException(this,
  334. causes);
  335. currentBufferedSourceException = e;
  336. requestRepaint();
  337. throw e;
  338. }
  339. /*
  340. * Is the object modified but not committed? Don't add a JavaDoc comment
  341. * here, we use the default one from the interface.
  342. */
  343. @Override
  344. public boolean isModified() {
  345. for (final Iterator<Object> i = propertyIds.iterator(); i.hasNext();) {
  346. final Field f = fields.get(i.next());
  347. if (f != null && f.isModified()) {
  348. return true;
  349. }
  350. }
  351. return false;
  352. }
  353. /*
  354. * Is the editor in a read-through mode? Don't add a JavaDoc comment here,
  355. * we use the default one from the interface.
  356. */
  357. @Override
  358. public boolean isReadThrough() {
  359. return readThrough;
  360. }
  361. /*
  362. * Is the editor in a write-through mode? Don't add a JavaDoc comment here,
  363. * we use the default one from the interface.
  364. */
  365. @Override
  366. public boolean isWriteThrough() {
  367. return writeThrough;
  368. }
  369. /*
  370. * Sets the editor's read-through mode to the specified status. Don't add a
  371. * JavaDoc comment here, we use the default one from the interface.
  372. */
  373. @Override
  374. public void setReadThrough(boolean readThrough) {
  375. if (readThrough != this.readThrough) {
  376. this.readThrough = readThrough;
  377. for (final Iterator<Object> i = propertyIds.iterator(); i.hasNext();) {
  378. (fields.get(i.next())).setReadThrough(readThrough);
  379. }
  380. }
  381. }
  382. /*
  383. * Sets the editor's read-through mode to the specified status. Don't add a
  384. * JavaDoc comment here, we use the default one from the interface.
  385. */
  386. @Override
  387. public void setWriteThrough(boolean writeThrough) throws SourceException,
  388. InvalidValueException {
  389. if (writeThrough != this.writeThrough) {
  390. this.writeThrough = writeThrough;
  391. for (final Iterator<Object> i = propertyIds.iterator(); i.hasNext();) {
  392. (fields.get(i.next())).setWriteThrough(writeThrough);
  393. }
  394. }
  395. }
  396. /**
  397. * Adds a new property to form and create corresponding field.
  398. *
  399. * @see com.vaadin.data.Item#addItemProperty(Object, Property)
  400. */
  401. public boolean addItemProperty(Object id, Property property) {
  402. // Checks inputs
  403. if (id == null || property == null) {
  404. throw new NullPointerException("Id and property must be non-null");
  405. }
  406. // Checks that the property id is not reserved
  407. if (propertyIds.contains(id)) {
  408. return false;
  409. }
  410. propertyIds.add(id);
  411. ownProperties.put(id, property);
  412. // Gets suitable field
  413. final Field field = fieldFactory.createField(this, id, this);
  414. if (field == null) {
  415. return false;
  416. }
  417. // Configures the field
  418. try {
  419. field.setPropertyDataSource(property);
  420. String caption = id.toString();
  421. if (caption.length() > 50) {
  422. caption = caption.substring(0, 47) + "...";
  423. }
  424. if (caption.length() > 0) {
  425. caption = "" + Character.toUpperCase(caption.charAt(0))
  426. + caption.substring(1, caption.length());
  427. }
  428. field.setCaption(caption);
  429. } catch (final Throwable ignored) {
  430. return false;
  431. }
  432. addField(id, field);
  433. return true;
  434. }
  435. /**
  436. * Adds the field to form.
  437. *
  438. * <p>
  439. * The property id must not be already used in the form.
  440. * </p>
  441. *
  442. * <p>
  443. * This field is added to the form layout in the default position (the
  444. * position used by {@link Layout#addComponent(Component)} method. In the
  445. * special case that the underlying layout is a custom layout, string
  446. * representation of the property id is used instead of the default
  447. * location.
  448. * </p>
  449. *
  450. * @param propertyId
  451. * the Property id the the field.
  452. * @param field
  453. * the New field added to the form.
  454. */
  455. public void addField(Object propertyId, Field field) {
  456. if (propertyId != null && field != null) {
  457. fields.put(propertyId, field);
  458. field.addListener(fieldValueChangeListener);
  459. if (!propertyIds.contains(propertyId)) {
  460. // adding a field directly
  461. propertyIds.addLast(propertyId);
  462. }
  463. field.setReadThrough(readThrough);
  464. field.setWriteThrough(writeThrough);
  465. if (isImmediate() && field instanceof AbstractComponent) {
  466. ((AbstractComponent) field).setImmediate(true);
  467. }
  468. if (layout instanceof CustomLayout) {
  469. ((CustomLayout) layout).addComponent(field, propertyId
  470. .toString());
  471. } else {
  472. layout.addComponent(field);
  473. }
  474. requestRepaint();
  475. }
  476. }
  477. /**
  478. * The property identified by the property id.
  479. *
  480. * <p>
  481. * The property data source of the field specified with property id is
  482. * returned. If there is a (with specified property id) having no data
  483. * source, the field is returned instead of the data source.
  484. * </p>
  485. *
  486. * @see com.vaadin.data.Item#getItemProperty(Object)
  487. */
  488. public Property getItemProperty(Object id) {
  489. final Field field = fields.get(id);
  490. if (field == null) {
  491. // field does not exist or it is not (yet) created for this property
  492. return ownProperties.get(id);
  493. }
  494. final Property property = field.getPropertyDataSource();
  495. if (property != null) {
  496. return property;
  497. } else {
  498. return field;
  499. }
  500. }
  501. /**
  502. * Gets the field identified by the propertyid.
  503. *
  504. * @param propertyId
  505. * the id of the property.
  506. */
  507. public Field getField(Object propertyId) {
  508. return fields.get(propertyId);
  509. }
  510. /* Documented in interface */
  511. public Collection getItemPropertyIds() {
  512. return Collections.unmodifiableCollection(propertyIds);
  513. }
  514. /**
  515. * Removes the property and corresponding field from the form.
  516. *
  517. * @see com.vaadin.data.Item#removeItemProperty(Object)
  518. */
  519. public boolean removeItemProperty(Object id) {
  520. ownProperties.remove(id);
  521. final Field field = fields.get(id);
  522. if (field != null) {
  523. propertyIds.remove(id);
  524. fields.remove(id);
  525. layout.removeComponent(field);
  526. field.removeListener(fieldValueChangeListener);
  527. return true;
  528. }
  529. return false;
  530. }
  531. /**
  532. * Removes all properties and fields from the form.
  533. *
  534. * @return the Success of the operation. Removal of all fields succeeded if
  535. * (and only if) the return value is <code>true</code>.
  536. */
  537. public boolean removeAllProperties() {
  538. final Object[] properties = propertyIds.toArray();
  539. boolean success = true;
  540. for (int i = 0; i < properties.length; i++) {
  541. if (!removeItemProperty(properties[i])) {
  542. success = false;
  543. }
  544. }
  545. return success;
  546. }
  547. /* Documented in the interface */
  548. public Item getItemDataSource() {
  549. return itemDatasource;
  550. }
  551. /**
  552. * Sets the item datasource for the form.
  553. *
  554. * <p>
  555. * Setting item datasource clears any fields, the form might contain and
  556. * adds all the properties as fields to the form.
  557. * </p>
  558. *
  559. * @see com.vaadin.data.Item.Viewer#setItemDataSource(Item)
  560. */
  561. public void setItemDataSource(Item newDataSource) {
  562. setItemDataSource(newDataSource, newDataSource != null ? newDataSource
  563. .getItemPropertyIds() : null);
  564. }
  565. /**
  566. * Set the item datasource for the form, but limit the form contents to
  567. * specified properties of the item.
  568. *
  569. * <p>
  570. * Setting item datasource clears any fields, the form might contain and
  571. * adds the specified the properties as fields to the form, in the specified
  572. * order.
  573. * </p>
  574. *
  575. * @see com.vaadin.data.Item.Viewer#setItemDataSource(Item)
  576. */
  577. public void setItemDataSource(Item newDataSource, Collection propertyIds) {
  578. if (layout instanceof GridLayout) {
  579. GridLayout gl = (GridLayout) layout;
  580. if (gridlayoutCursorX == -1) {
  581. // first setItemDataSource, remember initial cursor
  582. gridlayoutCursorX = gl.getCursorX();
  583. gridlayoutCursorY = gl.getCursorY();
  584. } else {
  585. // restore initial cursor
  586. gl.setCursorX(gridlayoutCursorX);
  587. gl.setCursorY(gridlayoutCursorY);
  588. }
  589. }
  590. // Removes all fields first from the form
  591. removeAllProperties();
  592. // Sets the datasource
  593. itemDatasource = newDataSource;
  594. // If the new datasource is null, just set null datasource
  595. if (itemDatasource == null) {
  596. return;
  597. }
  598. // Adds all the properties to this form
  599. for (final Iterator i = propertyIds.iterator(); i.hasNext();) {
  600. final Object id = i.next();
  601. final Property property = itemDatasource.getItemProperty(id);
  602. if (id != null && property != null) {
  603. final Field f = fieldFactory.createField(itemDatasource, id,
  604. this);
  605. if (f != null) {
  606. f.setPropertyDataSource(property);
  607. addField(id, f);
  608. }
  609. }
  610. }
  611. }
  612. /**
  613. * Gets the layout of the form.
  614. *
  615. * <p>
  616. * By default form uses <code>OrderedLayout</code> with <code>form</code>
  617. * -style.
  618. * </p>
  619. *
  620. * @return the Layout of the form.
  621. */
  622. public Layout getLayout() {
  623. return layout;
  624. }
  625. /**
  626. * Sets the layout of the form.
  627. *
  628. * <p>
  629. * By default form uses <code>OrderedLayout</code> with <code>form</code>
  630. * -style.
  631. * </p>
  632. *
  633. * @param newLayout
  634. * the Layout of the form.
  635. */
  636. public void setLayout(Layout newLayout) {
  637. // Use orderedlayout by default
  638. if (newLayout == null) {
  639. newLayout = new FormLayout();
  640. }
  641. // reset cursor memory
  642. gridlayoutCursorX = -1;
  643. gridlayoutCursorY = -1;
  644. // Move fields from previous layout
  645. if (layout != null) {
  646. final Object[] properties = propertyIds.toArray();
  647. for (int i = 0; i < properties.length; i++) {
  648. Field f = getField(properties[i]);
  649. layout.removeComponent(f);
  650. if (newLayout instanceof CustomLayout) {
  651. ((CustomLayout) newLayout).addComponent(f, properties[i]
  652. .toString());
  653. } else {
  654. newLayout.addComponent(f);
  655. }
  656. }
  657. layout.setParent(null);
  658. }
  659. // Replace the previous layout
  660. newLayout.setParent(this);
  661. layout = newLayout;
  662. }
  663. /**
  664. * Sets the form field to be selectable from static list of changes.
  665. *
  666. * <p>
  667. * The list values and descriptions are given as array. The value-array must
  668. * contain the current value of the field and the lengths of the arrays must
  669. * match. Null values are not supported.
  670. * </p>
  671. *
  672. * @param propertyId
  673. * the id of the property.
  674. * @param values
  675. * @param descriptions
  676. * @return the select property generated
  677. */
  678. public Select replaceWithSelect(Object propertyId, Object[] values,
  679. Object[] descriptions) {
  680. // Checks the parameters
  681. if (propertyId == null || values == null || descriptions == null) {
  682. throw new NullPointerException("All parameters must be non-null");
  683. }
  684. if (values.length != descriptions.length) {
  685. throw new IllegalArgumentException(
  686. "Value and description list are of different size");
  687. }
  688. // Gets the old field
  689. final Field oldField = fields.get(propertyId);
  690. if (oldField == null) {
  691. throw new IllegalArgumentException("Field with given propertyid '"
  692. + propertyId.toString() + "' can not be found.");
  693. }
  694. final Object value = oldField.getPropertyDataSource() == null ? oldField
  695. .getValue()
  696. : oldField.getPropertyDataSource().getValue();
  697. // Checks that the value exists and check if the select should
  698. // be forced in multiselect mode
  699. boolean found = false;
  700. boolean isMultiselect = false;
  701. for (int i = 0; i < values.length && !found; i++) {
  702. if (values[i] == value
  703. || (value != null && value.equals(values[i]))) {
  704. found = true;
  705. }
  706. }
  707. if (value != null && !found) {
  708. if (value instanceof Collection) {
  709. for (final Iterator it = ((Collection) value).iterator(); it
  710. .hasNext();) {
  711. final Object val = it.next();
  712. found = false;
  713. for (int i = 0; i < values.length && !found; i++) {
  714. if (values[i] == val
  715. || (val != null && val.equals(values[i]))) {
  716. found = true;
  717. }
  718. }
  719. if (!found) {
  720. throw new IllegalArgumentException(
  721. "Currently selected value '" + val
  722. + "' of property '"
  723. + propertyId.toString()
  724. + "' was not found");
  725. }
  726. }
  727. isMultiselect = true;
  728. } else {
  729. throw new IllegalArgumentException("Current value '" + value
  730. + "' of property '" + propertyId.toString()
  731. + "' was not found");
  732. }
  733. }
  734. // Creates the new field matching to old field parameters
  735. final Select newField = new Select();
  736. if (isMultiselect) {
  737. newField.setMultiSelect(true);
  738. }
  739. newField.setCaption(oldField.getCaption());
  740. newField.setReadOnly(oldField.isReadOnly());
  741. newField.setReadThrough(oldField.isReadThrough());
  742. newField.setWriteThrough(oldField.isWriteThrough());
  743. // Creates the options list
  744. newField.addContainerProperty("desc", String.class, "");
  745. newField.setItemCaptionPropertyId("desc");
  746. for (int i = 0; i < values.length; i++) {
  747. Object id = values[i];
  748. final Item item;
  749. if (id == null) {
  750. id = newField.addItem();
  751. item = newField.getItem(id);
  752. newField.setNullSelectionItemId(id);
  753. } else {
  754. item = newField.addItem(id);
  755. }
  756. if (item != null) {
  757. item.getItemProperty("desc").setValue(
  758. descriptions[i].toString());
  759. }
  760. }
  761. // Sets the property data source
  762. final Property property = oldField.getPropertyDataSource();
  763. oldField.setPropertyDataSource(null);
  764. newField.setPropertyDataSource(property);
  765. // Replaces the old field with new one
  766. layout.replaceComponent(oldField, newField);
  767. fields.put(propertyId, newField);
  768. newField.addListener(fieldValueChangeListener);
  769. oldField.removeListener(fieldValueChangeListener);
  770. return newField;
  771. }
  772. /**
  773. * Notifies the component that it is connected to an application
  774. *
  775. * @see com.vaadin.ui.Component#attach()
  776. */
  777. @Override
  778. public void attach() {
  779. super.attach();
  780. layout.attach();
  781. }
  782. /**
  783. * Notifies the component that it is detached from the application.
  784. *
  785. * @see com.vaadin.ui.Component#detach()
  786. */
  787. @Override
  788. public void detach() {
  789. super.detach();
  790. layout.detach();
  791. }
  792. /**
  793. * Tests the current value of the object against all registered validators
  794. *
  795. * @see com.vaadin.data.Validatable#isValid()
  796. */
  797. @Override
  798. public boolean isValid() {
  799. boolean valid = true;
  800. for (final Iterator<Object> i = propertyIds.iterator(); i.hasNext();) {
  801. valid &= (fields.get(i.next())).isValid();
  802. }
  803. return valid && super.isValid();
  804. }
  805. /**
  806. * Checks the validity of the validatable.
  807. *
  808. * @see com.vaadin.data.Validatable#validate()
  809. */
  810. @Override
  811. public void validate() throws InvalidValueException {
  812. super.validate();
  813. for (final Iterator<Object> i = propertyIds.iterator(); i.hasNext();) {
  814. (fields.get(i.next())).validate();
  815. }
  816. }
  817. /**
  818. * Checks the validabtable object accept invalid values.
  819. *
  820. * @see com.vaadin.data.Validatable#isInvalidAllowed()
  821. */
  822. @Override
  823. public boolean isInvalidAllowed() {
  824. return true;
  825. }
  826. /**
  827. * Should the validabtable object accept invalid values.
  828. *
  829. * @see com.vaadin.data.Validatable#setInvalidAllowed(boolean)
  830. */
  831. @Override
  832. public void setInvalidAllowed(boolean invalidValueAllowed)
  833. throws UnsupportedOperationException {
  834. throw new UnsupportedOperationException();
  835. }
  836. /**
  837. * Sets the component's to read-only mode to the specified state.
  838. *
  839. * @see com.vaadin.ui.Component#setReadOnly(boolean)
  840. */
  841. @Override
  842. public void setReadOnly(boolean readOnly) {
  843. super.setReadOnly(readOnly);
  844. for (final Iterator i = propertyIds.iterator(); i.hasNext();) {
  845. (fields.get(i.next())).setReadOnly(readOnly);
  846. }
  847. }
  848. /**
  849. * Sets the field factory of Form.
  850. *
  851. * <code>FieldFactory</code> is used to create fields for form properties.
  852. * By default the form uses BaseFieldFactory to create Field instances.
  853. *
  854. * @param fieldFactory
  855. * the New factory used to create the fields.
  856. * @see Field
  857. * @see FormFieldFactory
  858. * @deprecated use {@link #setFormFieldFactory(FormFieldFactory)} instead
  859. */
  860. @Deprecated
  861. public void setFieldFactory(FieldFactory fieldFactory) {
  862. this.fieldFactory = fieldFactory;
  863. }
  864. /**
  865. * Sets the field factory used by this Form to genarate Fields for
  866. * properties.
  867. *
  868. * {@link FormFieldFactory} is used to create fields for form properties.
  869. * {@link DefaultFieldFactory} is used by default.
  870. *
  871. * @param fieldFactory
  872. * the new factory used to create the fields.
  873. * @see Field
  874. * @see FormFieldFactory
  875. */
  876. public void setFormFieldFactory(FormFieldFactory fieldFactory) {
  877. this.fieldFactory = fieldFactory;
  878. }
  879. /**
  880. * Get the field factory of the form.
  881. *
  882. * @return the FormFieldFactory Factory used to create the fields.
  883. */
  884. public FormFieldFactory getFormFieldFactory() {
  885. return fieldFactory;
  886. }
  887. /**
  888. * Get the field factory of the form.
  889. *
  890. * @return the FieldFactory Factory used to create the fields.
  891. */
  892. public FieldFactory getFieldFactory() {
  893. if (fieldFactory instanceof FieldFactory) {
  894. return (FieldFactory) fieldFactory;
  895. }
  896. return null;
  897. }
  898. /**
  899. * Gets the field type.
  900. *
  901. * @see com.vaadin.ui.AbstractField#getType()
  902. */
  903. @Override
  904. public Class getType() {
  905. if (getPropertyDataSource() != null) {
  906. return getPropertyDataSource().getType();
  907. }
  908. return Object.class;
  909. }
  910. /**
  911. * Sets the internal value.
  912. *
  913. * This is relevant when the Form is used as Field.
  914. *
  915. * @see com.vaadin.ui.AbstractField#setInternalValue(java.lang.Object)
  916. */
  917. @Override
  918. protected void setInternalValue(Object newValue) {
  919. // Stores the old value
  920. final Object oldValue = propertyValue;
  921. // Sets the current Value
  922. super.setInternalValue(newValue);
  923. propertyValue = newValue;
  924. // Ignores form updating if data object has not changed.
  925. if (oldValue != newValue) {
  926. setFormDataSource(newValue, getVisibleItemProperties());
  927. }
  928. }
  929. /**
  930. * Gets the first field in form.
  931. *
  932. * @return the Field.
  933. */
  934. private Field getFirstField() {
  935. Object id = null;
  936. if (getItemPropertyIds() != null) {
  937. id = getItemPropertyIds().iterator().next();
  938. }
  939. if (id != null) {
  940. return getField(id);
  941. }
  942. return null;
  943. }
  944. /**
  945. * Updates the internal form datasource.
  946. *
  947. * Method setFormDataSource.
  948. *
  949. * @param data
  950. * @param properties
  951. */
  952. protected void setFormDataSource(Object data, Collection properties) {
  953. // If data is an item use it.
  954. Item item = null;
  955. if (data instanceof Item) {
  956. item = (Item) data;
  957. } else if (data != null) {
  958. item = new BeanItem(data);
  959. }
  960. // Sets the datasource to form
  961. if (item != null && properties != null) {
  962. // Shows only given properties
  963. this.setItemDataSource(item, properties);
  964. } else {
  965. // Shows all properties
  966. this.setItemDataSource(item);
  967. }
  968. }
  969. /**
  970. * Returns the visibleProperties.
  971. *
  972. * @return the Collection of visible Item properites.
  973. */
  974. public Collection getVisibleItemProperties() {
  975. return visibleItemProperties;
  976. }
  977. /**
  978. * Sets the visibleProperties.
  979. *
  980. * @param visibleProperties
  981. * the visibleProperties to set.
  982. */
  983. public void setVisibleItemProperties(Collection visibleProperties) {
  984. visibleItemProperties = visibleProperties;
  985. Object value = getValue();
  986. if (value == null) {
  987. value = itemDatasource;
  988. }
  989. setFormDataSource(value, getVisibleItemProperties());
  990. }
  991. /**
  992. * Sets the visibleProperties.
  993. *
  994. * @param visibleProperties
  995. * the visibleProperties to set.
  996. */
  997. public void setVisibleItemProperties(Object[] visibleProperties) {
  998. LinkedList<Object> v = new LinkedList<Object>();
  999. for (int i = 0; i < visibleProperties.length; i++) {
  1000. v.add(visibleProperties[i]);
  1001. }
  1002. setVisibleItemProperties(v);
  1003. }
  1004. /**
  1005. * Focuses the first field in the form.
  1006. *
  1007. * @see com.vaadin.ui.Component.Focusable#focus()
  1008. */
  1009. @Override
  1010. public void focus() {
  1011. final Field f = getFirstField();
  1012. if (f != null) {
  1013. f.focus();
  1014. }
  1015. }
  1016. /**
  1017. * Sets the Tabulator index of this Focusable component.
  1018. *
  1019. * @see com.vaadin.ui.Component.Focusable#setTabIndex(int)
  1020. */
  1021. @Override
  1022. public void setTabIndex(int tabIndex) {
  1023. super.setTabIndex(tabIndex);
  1024. for (final Iterator i = getItemPropertyIds().iterator(); i.hasNext();) {
  1025. (getField(i.next())).setTabIndex(tabIndex);
  1026. }
  1027. }
  1028. /**
  1029. * Setting the form to be immediate also sets all the fields of the form to
  1030. * the same state.
  1031. */
  1032. @Override
  1033. public void setImmediate(boolean immediate) {
  1034. super.setImmediate(immediate);
  1035. for (Iterator<Field> i = fields.values().iterator(); i.hasNext();) {
  1036. Field f = i.next();
  1037. if (f instanceof AbstractComponent) {
  1038. ((AbstractComponent) f).setImmediate(immediate);
  1039. }
  1040. }
  1041. }
  1042. /** Form is empty if all of its fields are empty. */
  1043. @Override
  1044. protected boolean isEmpty() {
  1045. for (Iterator<Field> i = fields.values().iterator(); i.hasNext();) {
  1046. Field f = i.next();
  1047. if (f instanceof AbstractField) {
  1048. if (!((AbstractField) f).isEmpty()) {
  1049. return false;
  1050. }
  1051. }
  1052. }
  1053. return true;
  1054. }
  1055. /**
  1056. * Adding validators directly to form is not supported.
  1057. *
  1058. * Add the validators to form fields instead.
  1059. */
  1060. @Override
  1061. public void addValidator(Validator validator) {
  1062. throw new UnsupportedOperationException();
  1063. }
  1064. /**
  1065. * Returns a layout that is rendered below normal form contents. This area
  1066. * can be used for example to include buttons related to form contents.
  1067. *
  1068. * @return layout rendered below normal form contents.
  1069. */
  1070. public Layout getFooter() {
  1071. if (formFooter == null) {
  1072. formFooter = new HorizontalLayout();
  1073. formFooter.setParent(this);
  1074. }
  1075. return formFooter;
  1076. }
  1077. /**
  1078. * Sets the layout that is rendered below normal form contens.
  1079. *
  1080. * @param newFormFooter
  1081. * the new Layout
  1082. */
  1083. public void setFooter(Layout newFormFooter) {
  1084. if (formFooter != null) {
  1085. formFooter.setParent(null);
  1086. }
  1087. formFooter = newFormFooter;
  1088. formFooter.setParent(this);
  1089. }
  1090. @Override
  1091. public void setEnabled(boolean enabled) {
  1092. super.setEnabled(enabled);
  1093. if (getParent() != null && !getParent().isEnabled()) {
  1094. // some ancestor still disabled, don't update children
  1095. return;
  1096. } else {
  1097. getLayout().requestRepaintAll();
  1098. }
  1099. }
  1100. }