Announcement Announcement Module
Collapse
No announcement yet.
ShuttleList and Dirty Forms Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • ShuttleList and Dirty Forms

    So, I have a simple form with a bound ShuttleList on it. This causes me three problems. One is that the ShuttleList bindings replace the entire list on the form object instead of adding and removing items. The second is that if I make a change in the ShuttleList selected items, my form goes dirty. But when I revert my changes the ShuttleList selections go back to the correct starting point, but the form remains dirty. The last one is how to properly refresh the ShuttleList items. This could be related to the dirty form problem above. I am using a RefreshableValueHolder to supply the ShuttleList with its data. If I put my list in a detail form and refresh its data on a form object change event, that I get the data for the last form object, not the current one. And that to get the ShuttleList to show the updated changes, I have to set the form object to null, and back to what it was. Anyone have any suggestions or solutions to one or more of these problems? *mutter self*

  • #2
    Originally posted by adamarmistead View Post
    So, I have a simple form with a bound ShuttleList on it. This causes me three problems. One is that the ShuttleList bindings replace the entire list on the form object instead of adding and removing items. The second is that if I make a change in the ShuttleList selected items, my form goes dirty. But when I revert my changes the ShuttleList selections go back to the correct starting point, but the form remains dirty. The last one is how to properly refresh the ShuttleList items. This could be related to the dirty form problem above. I am using a RefreshableValueHolder to supply the ShuttleList with its data. If I put my list in a detail form and refresh its data on a form object change event, that I get the data for the last form object, not the current one. And that to get the ShuttleList to show the updated changes, I have to set the form object to null, and back to what it was. Anyone have any suggestions or solutions to one or more of these problems? *mutter self*
    Hmm, I use ShuttleListBinding frequently... I'll see if I've done any local tinkering .

    Comment


    • #3
      That would be great. Here's more data on the problem I'm seeing. I'm currently adding this to my binder selection strategy:

      Code:
      <entry>
        <key>
          <value type="java.lang.Class">
            org.springframework.richclient.components.ShuttleList
          </value>
        </key>
        <bean class="org.springframework.richclient.form.binding.swing.ShuttleListBinder"/>
      </entry>
      and creating my shuttlelist like this:

      Code:
      refreshableValueHolder = new RefreshableValueHolder(new Closure() {
        @Override
        public Object call(Object o) {
          if(client != null) {
            boolean editingNewFormObject = isEditingNewFormObject();
            Document selectedDocument = (Document)(editingNewFormObject
                        ? getFormObject() : (getSelectedIndex() >= 0 && !getMasterEventList().isEmpty())
                        ? getMasterEventList().get(getSelectedIndex()) : null);
      
            System.out.println("isEditingNewFormObject = " + editingNewFormObject);
            System.out.println("selectedDocument = " + selectedDocument);
            if(selectedDocument != null) {
              System.out.println("date = " + selectedDocument.getDocumentDate());
            }
      
            allImages = documentService.getClientAndInboxImages(client.getClientCode());
            if(selectedDocument != null) {
              for(DocumentPath path : selectedDocument.getPaths()) {
                if(!allImages.contains(path)) {
                  allImages.add(path);
                }
              }
            }
            return allImages;
          } else {
            return emptyList;
          }
        }
      }, false, false);
      
      builder.addBinding(bf.createBoundShuttleList("paths", refreshableValueHolder, "fileName"));
      and if, for example, this is in a standard form editing a client object I set my form object with setFormObject(selectedClient), then call refreshableValueHolder.refresh(). This seems to work right.

      If for example I'm using a master/detail table form I add a form object changed listener to the detail form like this, which doesn't work right:

      Code:
      addFormObjectChangeListener(new PropertyChangeListener() {
        @Override
        public void propertyChange(PropertyChangeEvent e) {
          // This properly loads the data for the new form object.
          refreshableValueHolder.refresh();
      
          // Shouldn't need this, but otherwise the shuttlelist data is for
          // the last form object, not the current one.
          int index = getDetailForm().getSelectedIndex();
          getDetailForm().setSelectedIndex(-1);
          getDetailForm().setSelectedIndex(index);
        }
      });
      With either set up, the form is not dirty until I change the selection in the shuttle list, then after hitting revert it stays dirty.

      Comment


      • #4
        I can update my refreshableValueHolder in my master/detail form to the values it should have for the selected object before or after the new object is selected.

        To update it before, I added a selection listener to the master table. I can get the selected index on a selected change event and update the data in the shuttlelist to what it needs to be for the next form object, before the form object is set to the new selection in the detail form, where my shuttlelist is. This of course causes the form to be dirty, just before setting the new object, which doesn't work, this causes my "are you sure you want to select something else your form is still dirty" dialog to appear on every change. So no good.

        To update it after, I added a form object change listener to the detail form and update my refreshableValueHolder when a new object is set. I get the current form object for my detail form in the refreshableValueHolder and load and return the new data. But, since the list had incorrect data when the form object was set, the form model thinks nothing was selected for the new object, so the items that should be selected in the shuttlelist are not selected and the form is not dirty. The only way I've found to update this is to set the form object to null and back to what its supposed to be. This doesn't seem right either. Guess I haven't figured out how to use the shuttlelist properly yet.

        Still haven't made any progress yet on figuring out why revert leaves the form dirty though.

        Comment


        • #5
          This works better to refresh the shuttlelist after I refresh the refreshableValueHolder with new data.

          Code:
          addFormObjectChangeListener(new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent e) {
              // This properly loads the data for the new form object.
              refreshableValueHolder.refresh();
          
              // Shouldn't need this, but otherwise the shuttlelist data is for
              // the last form object, not the current one.
              Object formObject = getDetailForm().getFormObject();
              getDetailForm().removeFormObjectChangeListener(this);
              getDetailForm().setFormObject(null);
              getDetailForm().setFormObject(formObject);
              getDetailForm().addFormObjectChangeListener(this);
            }
          });
          And I fixed my problem with the shuttlelist replacing the collection in the form object by storing my form object in the database using the hibernate merge() method instead of saveOrUdpate(). So now I have a workaround for everything but my form staying dirty after a revert. Still no leads there.

          Comment


          • #6
            Well, I have manged to pry out what is dirty, but still not why...

            If i have 3 items in my shuttlelist [item1, item2, item3] and initially [item1] is unselected, and [item2,item3] are selected and I call isDirty on my form model and print out the values held in the AbstractFormModels dirtyValueAndFormModels hashset I get this, as expected:

            form isDirty = false
            dirtyValueAndFormModels = []

            If in my shuttlelist i unselect item3 and do the same i get this:

            form isDirty = true
            dirtyValueAndFormModels = [org.springframework.binding.form.support.DefaultFi eldMetadata@f7bd29]

            teasing out more info i get this from the metatada object:

            DefaultFieldMetadata values:
            User Custom Metadata = {}
            Type = interface java.util.List
            dirty = true
            enabled = true
            read only = false
            FormModelMediatingValueModel.originalValue = [item2, item3] hashCode = -926391514
            FormModelMediatingValueModel.getValue() = [item2] hashCode = 614673936
            FormModelMediatingValueModel.isDirty() = true

            once again, expected. Then I hit revert and do this again and find this:

            form isDirty = true
            dirtyValueAndFormModels = [org.springframework.binding.form.support.DefaultFi eldMetadata@f7bd29]
            DefaultFieldMetadata values:
            User Custom Metadata = {}
            Type = interface java.util.List
            dirty = true
            enabled = true
            read only = false
            FormModelMediatingValueModel.originalValue = [item2, item3] hashCode = -416405384
            FormModelMediatingValueModel.getValue() = [item2, item3] hashCode = -926391514
            FormModelMediatingValueModel.isDirty() = true

            showing that there is still a DefaultFieldMetadata object registered making my form dirty and that it is holding a List of the originally selected items, but that the FormModelMediatingValueModel it contains is still dirty. According to the hashCodes it looks like after hitting revert, my value model's value got reset to the original list, but for some reason the originalValue field, despite having the correct items in it, now has a new List. *mutter self*

            Comment


            • #7
              Trying to find out why my original value is getting changed out with a new list. The FormModelMediatingValueModel uses the ValueChangeDectector to determine if objects are equal and this by default uses == to do this, so my lists are obviously going to be seen as having changed despite containing the proper values if the collection object has been changed out. I added a value change listener to the FormModelMediatingValueModel to see what was changing and what caused the changes, giving me this:

              Collections.EmptyList [] hashCode = 1
              refreshableValueHolder.list = [item1,item2,item3] hashCode = -435502077
              FormModelMediatingValueModel.originalValue = [item2,item3] hashCode = 828738420
              FormModelMediatingValueModel.getValue() = [item2] hashCode = 1310929149


              value changed from [item2,item3] hashCode = -2142360014 to [] hashCode = 1
              value changed from [] hashCode = 1 to [item2] hashCode = 1310929149
              value changed from [item2] hashCode = 1310929149 to [item2,item3] hashCode = 828738420
              value changed from [item2,item3] hashCode = 828738420 to [item2,item3] hashCode = 828738420
              value changed from [item2] hashCode = 1310929149 to [item2,item3] hashCode = -2142360014
              value changed from [item2,item3] hashCode = -2142360014 to [] hashCode = 1
              value changed from [] hashCode = 1 to [item2] hashCode = 1310929149
              value changed from [item2] hashCode = 1310929149 to [item2,item3] hashCode = 828738420
              value changed from [item2,item3] hashCode = 828738420 to [item2,item3] hashCode = 828738420
              value changed from [item2,item3] hashCode = 828738420 to [item2,item3] hashCode = -2142360014

              which frankly seems a bit excessive, and a stack trace from the property change listener gives me this:

              Code:
              java.lang.IllegalStateException: Who did this?
              	at org.chd.hydra.form.ClientImagesEditorForm$2.propertyChange(ClientImagesEditorForm.java:354)
              	at org.springframework.binding.value.support.AbstractValueModel.fireValueChangeEvent(AbstractValueModel.java:172)
              	at org.springframework.binding.value.support.AbstractValueModel.fireValueChange(AbstractValueModel.java:144)
              	at org.springframework.binding.value.support.ValueHolder.setValue(ValueHolder.java:59)
              	at org.springframework.binding.value.support.AbstractValueModel.setValueSilently(AbstractValueModel.java:54)
              	at org.springframework.binding.form.support.FormModelMediatingValueModel.setValueSilently(FormModelMediatingValueModel.java:126)
              	at org.springframework.binding.value.support.AbstractValueModelWrapper.setValueSilently(AbstractValueModelWrapper.java:41)
              	at org.springframework.binding.form.support.DefaultFormModel$ValidatingFormValueModel.setValueSilently(DefaultFormModel.java:404)
              	at org.springframework.richclient.form.binding.swing.ShuttleListBinding.updateSelectionHolderFromList(ShuttleListBinding.java:307)
              	at org.springframework.richclient.form.binding.swing.ShuttleListBinding$ListSelectedValueMediator.valueChanged(ShuttleListBinding.java:444)
              	at javax.swing.JList.fireSelectionValueChanged(Unknown Source)
              	at javax.swing.JList$ListSelectionHandler.valueChanged(Unknown Source)
              	at javax.swing.DefaultListSelectionModel.fireValueChanged(Unknown Source)
              	at javax.swing.DefaultListSelectionModel.fireValueChanged(Unknown Source)
              	at javax.swing.DefaultListSelectionModel.fireValueChanged(Unknown Source)
              	at javax.swing.DefaultListSelectionModel.changeSelection(Unknown Source)
              	at javax.swing.DefaultListSelectionModel.changeSelection(Unknown Source)
              	at javax.swing.DefaultListSelectionModel.removeSelectionIntervalImpl(Unknown Source)
              	at javax.swing.DefaultListSelectionModel.clearSelection(Unknown Source)
              	at javax.swing.JList.setSelectedIndices(Unknown Source)
              	at org.springframework.richclient.components.ShuttleList.setSelectedIndices(ShuttleList.java:604)
              	at org.springframework.richclient.form.binding.swing.ShuttleListBinding.setSelectedValue(ShuttleListBinding.java:215)
              	at org.springframework.richclient.form.binding.swing.ShuttleListBinding$ListSelectedValueMediator$1.propertyChange(ShuttleListBinding.java:436)
              	at org.springframework.binding.value.support.AbstractValueModel.fireValueChangeEvent(AbstractValueModel.java:172)
              	at org.springframework.binding.value.support.AbstractValueModel.fireValueChange(AbstractValueModel.java:144)
              	at org.springframework.binding.value.support.ValueHolder.setValue(ValueHolder.java:59)
              	at org.springframework.binding.form.support.FormModelMediatingValueModel.propertyChange(FormModelMediatingValueModel.java:135)
              	at org.springframework.binding.value.support.AbstractValueModel.fireValueChangeEvent(AbstractValueModel.java:172)
              	at org.springframework.binding.value.support.AbstractValueModel.fireValueChange(AbstractValueModel.java:144)
              	at org.springframework.binding.value.support.BufferedValueModel.setValue(BufferedValueModel.java:147)
              	at org.springframework.binding.value.support.BufferedValueModel.revert(BufferedValueModel.java:233)
              	at org.springframework.binding.value.support.BufferedValueModel$CommitTriggerHandler.revert(BufferedValueModel.java:270)
              	at org.springframework.binding.value.CommitTrigger.revert(CommitTrigger.java:54)
              	at org.springframework.binding.form.support.AbstractFormModel.revert(AbstractFormModel.java:624)
              	at org.springframework.richclient.form.AbstractForm.revert(AbstractForm.java:761)
              	at org.springframework.richclient.form.AbstractForm$3.doExecuteCommand(AbstractForm.java:584)
              	at org.springframework.richclient.command.ActionCommand.execute(ActionCommand.java:219)
              	at org.springframework.richclient.command.ActionCommand$1.actionPerformed(ActionCommand.java:132)
              	at javax.swing.AbstractButton.fireActionPerformed(Unknown Source)
              	at javax.swing.AbstractButton$Handler.actionPerformed(Unknown Source)
              	at javax.swing.DefaultButtonModel.fireActionPerformed(Unknown Source)
              	at javax.swing.DefaultButtonModel.setPressed(Unknown Source)
              	at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(Unknown Source)
              	at org.pushingpixels.substance.internal.utils.RolloverButtonListener.mouseReleased(RolloverButtonListener.java:124)
              	at java.awt.Component.processMouseEvent(Unknown Source)
              	at javax.swing.JComponent.processMouseEvent(Unknown Source)
              	at java.awt.Component.processEvent(Unknown Source)
              	at java.awt.Container.processEvent(Unknown Source)
              	at java.awt.Component.dispatchEventImpl(Unknown Source)
              	at java.awt.Container.dispatchEventImpl(Unknown Source)
              	at java.awt.Component.dispatchEvent(Unknown Source)
              	at java.awt.LightweightDispatcher.retargetMouseEvent(Unknown Source)
              	at java.awt.LightweightDispatcher.processMouseEvent(Unknown Source)
              	at java.awt.LightweightDispatcher.dispatchEvent(Unknown Source)
              	at java.awt.Container.dispatchEventImpl(Unknown Source)
              	at java.awt.Window.dispatchEventImpl(Unknown Source)
              	at java.awt.Component.dispatchEvent(Unknown Source)
              	at java.awt.EventQueue.dispatchEvent(Unknown Source)
              	at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
              	at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
              	at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
              	at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
              	at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
              	at java.awt.EventDispatchThread.run(Unknown Source)
              Not positive yet but it looks like the binding is swapping out the lists when I hit revert. Am I doing something fundamentally wrong with the way I'm using the shuttlelist, because this feels like I'm heading towards finding that the binding is broken but this from Lieven would seem to contradict that:

              Hmm, I use ShuttleListBinding frequently... I'll see if I've done any local tinkering .

              Comment


              • #8
                ShuttleListBinding was the problem. The collectionsEqual() method is missing a return true after checking that all the items in the list are equal, causing all calls to return false unless the collections being compared were not null and have different sizes, yet are the same collection, which I don't see how you get that case to happen anyway. So essentially this method always returned false. Here's how it should look:

                Code:
                protected boolean collectionsEqual(Collection a1, Collection a2) {
                  if(a1 != null && a2 != null && a1.size() == a2.size()) {
                    // Loop over each element and compare them using our comparator
                    Iterator iterA1 = a1.iterator();
                    Iterator iterA2 = a2.iterator();
                    while(iterA1.hasNext()) {
                      if(!equalByComparator(iterA1.next(), iterA2.next())) {
                        return false;
                      }
                    }
                    return true; // This line was missing.
                  } else if(a1 == null && a2 == null) {
                    return true;
                  }
                  return false;
                }
                Also, you can remove and re-add the list selection listener in the setSelectedValue() method, if you assign it to a variable when it is created, to avoid calling the updateSelectionHolderFromList() method once for every item in your selected list when updating the list of selectable items for your shuttle list and only call it once like this:

                Code:
                protected void setSelectedValue(final PropertyChangeListener silentValueChangeHandler) {
                  final int[] indices = indicesOf(selectedItemsHolder.getValue());
                  if(indices.length < 1) {
                    list.clearSelection();
                  } else {
                    // Remove the list selection listener as we will update the selection holder from
                    // the list in one step afterwards to avoid n (number of selected items) calls to
                    // updateSelectionHolderFromList().
                    list.removeListSelectionListener(mediator);
                    list.setSelectedIndices(indices);
                    list.addListSelectionListener(mediator);
                
                    // The selection may now be different than what is reflected in collection property
                    // if this is SINGLE_INTERVAL_SELECTION, so modify if needed...
                    updateSelectionHolderFromList(silentValueChangeHandler);
                  }
                }

                Comment

                Working...
                X