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.

Form.java 34KB

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