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 41KB

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