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

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