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.

EnableAndDisableButtonsToIndicateState.asciidoc 8.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. ---
  2. title: Enable And Disable Buttons To Indicate State
  3. order: 59
  4. layout: page
  5. ---
  6. [[enable-and-disable-buttons-to-indicate-state]]
  7. = Enable and disable buttons to indicate state
  8. Most user interfaces have actions that can only be performed if certain
  9. conditions are met. In other cases, the actions can be performed at any
  10. time in principle, but don’t really make any sense to in certain
  11. situations. And quite often, there are actions that really need to be
  12. performed, e.g. to prevent data loss.
  13. A good example of this is a typical CRUD form for entering items into a
  14. database, with buttons for saving, reverting (i.e. discarding changes)
  15. and deleting items:
  16. image:img/potus1.png[POTUS Database CRUD example]
  17. The above image illustrates a typical UI for adding, modifying and
  18. deleting data: A table listing the available items above, and a form for
  19. editing the selected item below. The same form is also used to enter new
  20. items. The _Add new_ button prepares the form for entering a new item.
  21. Clicking a table row selects the corresponding item for editing.
  22. [[disabling-actions-to-prevent-errors]]
  23. Disabling actions to prevent errors
  24. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  25. Naturally, the Save action in the UI depicted above can only be
  26. performed if an existing item has been selected, or if the _“Add new”_
  27. button has been clicked to create a new item. Assuming there are
  28. required fields (which there nearly always are), the _Save_ action can
  29. only be successfully performed when all these have been properly filled
  30. in. Let’s call these two requirements the *_technical criteria_* for
  31. performing the _Save_ action.
  32. Similarly, the _Delete_ action can only be performed if an existing,
  33. previously saved item is selected. Attempting to delete a nonexistent
  34. (yet to be saved) item would result in an error. Thus, selection of an
  35. existing item is a technical criterion for the _Delete_ action.
  36. So how do we handle these criteria in our code? An unfortunately common
  37. solution is to display a pop-up error message explaining the situation.
  38. The problem with this approach is that the user’s time is wasted
  39. invoking an unperformable action and in being forced to dismiss an
  40. annoying pop-up window (usually by clicking “OK” or something to that
  41. effect). Also, users tend to ignore popups and just click them away
  42. without reading the message, so they might not even be aware that the
  43. action wasn’t performed.
  44. A clearly superior approach is to simply *disable actions until their
  45. criteria are fulfilled*. By disabling actions that cannot be currently
  46. performed, the user gets a clear visual indication of this situation, is
  47. spared the trouble of attempting in vain to perform the action, and the
  48. nuisance of an error message.
  49. image:img/potus2.png[Save and Revert actions disabled when they cannot be
  50. successfully
  51. performed.]
  52. [[disablingenabling-actions-to-indicate-state]]
  53. Disabling/enabling actions to indicate state
  54. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  55. The action criteria discussed so far are only the purely _technical_
  56. criteria for performing the _Save_ and _Delete_ actions. They are simply
  57. there to prevent an exception from being thrown or a database constraint
  58. being violated. Looking beyond the _technical_ requirements, neither the
  59. _Save_ action or the _Revert_ action actually _do_ anything unless there
  60. are *unsaved changes* in the form, so it doesn’t really make sense do
  61. perform them at that time, even though they wouldn't result in an error.
  62. We could call the existence of unsaved changes the *_logical criteria_*
  63. for the _Save_ and _Revert_ actions.
  64. On the other hand, if there _are_ unsaved changes, then either _Save_ or
  65. _Revert_ should be performed to either save the changes or revert the
  66. fields to their original values, and you definitely want your users to
  67. be aware of this state.
  68. It might seem unlikely that a user would be unaware of the state of the
  69. form he or she is currently filling in, but out in The Real World, your
  70. users will be constantly distracted by co-workers, incoming emails,
  71. internet porn, coffee breaks and shiny things. They probably have “a
  72. hunch” about whether they already clicked _Save_ or not, but even then
  73. they might have some doubts about whether that action was _successfully
  74. performed_. In the end, any uncertainty about whether their precious
  75. data is safely stored is a tiny source of unnecessary stress for your
  76. users.
  77. The following graphic illustrates a UI that does not, in any way,
  78. indicate the current state of the form:
  79. image:img/disabled-before.png[UI without form state indication]
  80. Thus, both of these states (unsaved changes or not) should be indicated
  81. to the user somehow. The solution, again, is *disabling and enabling*
  82. the corresponding actions: The _Save/Cancel_ buttons are *disabled*
  83. until any change is made in the form. As soon as changes are detected,
  84. and the new values have been validated, the _Save/Cancel_ buttons are
  85. *enabled*. When either one is clicked, both are *disabled* again to
  86. indicate that the action was successfully performed.
  87. With this approach we add even more information about the current state
  88. of the application to the buttons themselves. Not only are we indicating
  89. when actions *_technically can_* be performed, but we also indicate when
  90. they *_logically make sense_* to perform, and, in cases like the
  91. _Save/Cancel_ actions in the example above, we also notify the user
  92. about actions that *_probably should_* be performed to prevent data
  93. loss. This is a great deal of information being *_subtly_* and
  94. *_non-intrusively_* conveyed to the user, without resorting to annoying
  95. popups, simply by enabling and disabling buttons.
  96. image:img/disabled-after.png[UI with form state indication]
  97. [[how-to-do-this-in-a-vaadin-application]]
  98. How to do this in a Vaadin application
  99. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  100. To implement the above functionality, we need to be able to trigger the
  101. button-toggling code for changes in the following states:
  102. * Item selection
  103. * Field validation
  104. * Unsaved changes
  105. The first one, whether or not an item has been selected and loaded into
  106. the form is quite trivial of course. You can check for that in the same
  107. code that handles item selection.
  108. The second one is really easy if you’ve bound the fields with a
  109. *FieldGroup*, since in that case you can use the *isValid()* method on
  110. the *FieldGroup* to check if all fields are valid or not. Empty required
  111. fields cause this to return false, as do any validators you’ve
  112. explicitly added.
  113. The third one is a bit trickier, since a change listener has to be added
  114. to each field separately, and the type of listener you need to add
  115. depends on the type of field. For most field components, a
  116. *ValueChangeListener* is fine, since it triggers a notification when the
  117. field’s value changes, such as when a different item is selected in a
  118. *ComboBox*. However, for the various text field components (*TextField,
  119. TextArea and PasswordField*) you’ll be better off with a
  120. *TextChangeListener*, since you’ll want to trigger the button-toggling
  121. code as soon as any change is made to the field’s text content, and a
  122. *ValueChangeListener* won’t do that.
  123. Luckily, adding the change listeners can be done in a fairly simple loop
  124. over the components in a layout, or the fields bound through a
  125. *FieldGroup*. The appropriate type of listener can be chosen based on
  126. whether the component implements the *FieldEvents.TextChangeNotifier*
  127. interface:
  128. [source,java]
  129. ....
  130. TextChangeListener textListener = new TextChangeListener() {
  131. @Override
  132. public void textChange(TextChangeEvent event) {
  133. formHasChanged();
  134. }
  135. };
  136. ValueChangeListener valueListener = new ValueChangeListener() {
  137. @Override
  138. public void valueChange(ValueChangeEvent event) {
  139. formHasChanged();
  140. }
  141. };
  142. for (Field f : fieldGroup.getFields()) {
  143. if (f instanceof TextChangeNotifier) {
  144. ((TextChangeNotifier) f).addTextChangeListener(textListener);
  145. } else {
  146. f.addValueChangeListener(valueListener);
  147. }
  148. }
  149. ....
  150. [source,java]
  151. ....
  152. public void formHasChanged() {
  153. btnRevert.setEnabled(true);
  154. boolean allFieldsValid = fieldGroup.isValid();
  155. btnSave.setEnabled(allFieldsValid);
  156. }
  157. ....