Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    Issue with dialog to cancel, save, or discard edits in ListGrid

    Hello,

    I have a ListGrid in a "details" section in a "master detail" interface. The "master" section up top is a ListGrid where the user can select a record. When they select a record, there are 4 tabs down below in the "detail" section where the user can view details about the record they select above. Two of those tabs contain additional ListGrids the user can edit.

    If I am editing one of the ListGrids in the details section and I click onto another record in the Master section above, an updateDetails() function fires that updates the details section below with the new "master" record. If I've made any edits in my ListGrid in the details section, I receive an Ok/Save/Cancel prompt regarding the edits.

    Clicking Ok is not a problem, the edits are discarded and the ListGrid re-populates with the new data. However, clicking Cancel is a problem. Also, clicking Save is a problem if there are any validation errors on Save. In those two cases, focus returns to the Grid in the details section but the rest of the page has been updated to show the new Master record above and the other tabs in the details section refresh to show this new data as well.

    How can I capture this state and correct the issue so that, if I return the user to the grid where I was editing, the rest of the screen returns to the same state as well and shows the details for the original "Master" record?

    #2
    Hello,
    Quck question: How is the data in your detail grid being populated / changed when the updateDetails method fires?
    If you are using a filter operation, a simple way to perform the conversion would be to have the filterData() call occur before any other changes are made to the detail area, and pass in a callback that updates the rest of the 'detail' components.
    If the user has outstanding edits, the prompt to save them will be shown to the user, giving them an opportunity to drop the edits (in which case the filter will continue) cancel (suppressing the filter), or save edits (in which case the filter will continue on successful save only).
    The callback will only fire if the filter occurs, so you will not be updating the rest of the 'detail' components unless the user successfully saves his edits or agrees to drop them.

    So your code might look a little like this:

    Code:
    function updateDetails () {
        var criteria = masterGrid.getSelectedRecord();
        detailGrid.filterData(criteria, "completeUpdate()");
    }
    
    function completeUpdate() {
        // code to perform updates to other 'detail' components
    }

    Please let us know if this doesn't work for your usage and we can take another look

    Thanks
    Isomorphic Software

    Comment


      #3
      Hi, it's not the result of a filter operation but just the result of the click event on the grid that selects a new record in the Master Grid.

      Comment


        #4
        Hi Senordhuff,
        It would be really helpful if you could post some code to show us exactly what you mean.

        Firstly, could you show us how the updateDetails() method gets called. (For example is this a response to recordClick on the grid? If so, what does that method look like).
        Secondly - your updateDetails() method must call some code to display data derived from the selected record in the Master grid in the detail grids.
        How are you doing this? Is it a simple call to setData(), for example?
        If you could show us the code snippet from inside the updateDetails() method that makes the detail grid(s) display the new data, we can get probably a better idea of exactly what's happening and give you a better answer.

        Thanks for your help
        Isomorphic Software

        Comment


          #5
          Sure, sorry if I wasn't clear enough. Imagine if you added two new tabs to the details section of your Showcase Application. And, those two new tabs both had ListGrids in them. You'd then run into the same issue I am whenever you clicked a master record (and fired the recordClick event in that master grid) in your Showcase Application after making edits to one of those two new grids in the details section.

          So, my updateDetails function has the following snippet:

          Code:
          isc.RPCManager.startQueue();
              	var criteria = new Object();
          	criteria.fundAssetID = record.fundAssetID;
          	criteria.assetID = record.assetID;
          	assetScenarioGrid.fetchData(criteria);
          	criteria.fundID=record.fundID;
          	setFundProfileForm(criteria);
          	fundAssetTradeGrid.fetchData(criteria);
          isc.RPCManager.sendQueue();
          
          fundAssetCustomizationEditor.editRecord(record);
          fathSave.editRecord(record);
          assetScenarioSave.editRecord(record);
          assetFixedDataEditor.editRecord(record);
          assetNotesEditor.editRecord(record);
          I could put those calls to "editRecord" into the callback to fundAssetTradeGrid or assetScenarioGrid. But, those calls to editRecord would need to go after both grids have finished fetching data. So, that would involve chaining requests together I think. Is there any way to avoid that. I think this situation is a bit more challenging because there are two different ListGrids in my details section and not just one.

          Comment


            #6
            Hello,
            Yes - the fact that you're performing a fetch on 2 grids, either or both of which may be being edited does make the approach suggested above (using the callback from fetchData() or filterData()) a little more tricky. You'd have to either chain the events together, or add code to revert the fetchData() call on the first grid, if the second fetchData() call failed to complete.

            I think for your case the simplest solution is going to be to perform a check for editing (and option to save / cancel) yourself.
            You can find out if a grid is showing an editor by calling the (currently undocumented) method 'ListGrid.getEditRow()'. This will return the index of the row being edited, or -1 if the editor is not showing. FYI - this method will be documented in future SmartClient releases and is safe to use.
            You can then throw up a SmartClient Dialog giving the user whichever options are most appropriate for your application. Here's a code snippet that would give the user an option to discard edits, save, or cancel.
            On successful save or discard of edits your code would proceed to update your detail tabs as normal.

            Code:
            // Master grid definition
            ListGrid.create({
            
                ... // Various propreties for your grid
                
                recordClick : "this.updateDetails_checkAssetScenarioEdits()",
                
                // Helper method, checkForEdits:
                // If the grid passed in is being edited, give the user the option to save or discard the
                // edits.
                // On successful save, or discard, fire callback
                checkForEdits : function (listGrid, callback) {
                    if (listGrid.getEditRow() >= 0) {
                        isc.confirm("This action will discard all unsaved edited values for this list.", 
                                null,
                                // Apply properties to the dialog to handle save / cancel clicks
                                {   targetGrid:listGrid,
                                    callback:callback,
                                    // Show "OK", "Save" and "Cancel" buttons
                                    buttons:[isc.Dialog.OK, // fires 'okClick'
                                            // Custom save button
                                            {title:"Save", width:75,
                                             click:"this.topElement.saveClick();"},
                                             isc.Dialog.CANCEL // fires 'cancelClick'
                                    ],
                                    
                                    okClick : function () {
                                        this.hide();
                                        // drop edits on the target grid (and hide editor)
                                        this.targetGrid.cancelEditing();
                                        // continue with the callback
                                        this.fireCallback(this.callback);
                                    },
                                    saveClick : function () {
                                        this.hide();
                                        // Save edits on the target grid, passing a callback to notify
                                        // us of successful save.
                                        this.targetGrid.saveEdits(null, this.getID() + ".saveComplete()");
                                    },
                                    saveComplete : function () {
                                        // Hide target grid editor
                                        this.targetGrid.endEditing();
                                        // Fire the callback we were passed
                                        this.fireCallback(callback);
                                    },
                                    cancelClick : function () {
                                        // Do nothing if the user clicked cancel - we don't want
                                        // to fire the callback in this case.
                                        this.hide();
                                    }
            
                                }
                        );
                    // If no editor was showing, fire the callback right away
                    } else {
                        this.fireCallback(callback);
                    }
                },
                
                // We need to prompt the user to save outstanding edits. This is an asynchronous
                // prompt, therefore this method is split into several parts
                
                // Check for edits on the assetScenario grid
                updateDetails_checkAssetScenarioEdits : function () {
                    this.checkForEdits(assetScenarioGrid, 
                                       // Continue by checking for edits on fundAssets grid
                                       this.getID() + ".updateDetails_checkFundAssetEdits()");    
                },
                
                // Check for edits on the fundAssets grid
                updateDetails_checkFundAssetEdits : function () {
                    this.checkForEdits(fundAssetTradeGrid,
                                       // Continue by updating details as usual
                                       this.getID() + ".updateDetails_performUpdate()");        
                },
                
                // At this point we have no outstanding edits on detail grids, so can
                // proceed with update of all detail components
                updateDetails_performUpdate : function () {
                    // Code to actually updateDetails in here (your previous 'updateDetails' method)
                }
            
            }); // end of master grid instantiation block
            Let us know if this doesn't make sense

            Thanks
            Isomorphic Software

            Comment


              #7
              Hi,

              We are making progress.

              I had to add some an additional function for the recordclick event as follows:

              Code:
                  recordClick : "this.updateDetails_recordClick();",
                  updateDetails_recordClick : function () {
                  	nextDetailFundAssetRecord=fundAssetsGrid.getSelectedRecord();
                  	if(detailFundAssetRecord!=null){	
                  		fundAssetsGrid.deselectAllRecords();
                  		fundAssetsGrid.selectRecord(detailFundAssetRecord);
                  	}
                  	this.updateDetails_checkAssetScenarioEdits();
                  },
              This is necessary because I want to keep the original row highlighted until it is "safe" to highlight the next row. So, I'm putting the "next" record into a global variable and then, in my updateDetails function, I selectRecord on that "next" record to complete the operation.

              The issue I am seeing, however, is when one of the detail grids fails validation. So, I blank out a required field in my assetScenarioGrid. Then, I try to click on a different record in the Master Grid. I receive the Ok/Save/Cancel Dialog and I click Save. Then, the validation fails. But, the Master grid has, in the meantime, updated to highlight the "next" record.

              Also, I am seeing that, when I click Save and there are no validation errors, then the grid edits are saved successfully. But, the next time I click a new record, I am prompted with Ok/Save/Cancel again even if I haven't made any additional edits?

              One more question, do I need to turn set "stopOnErrors:false" on my details grids or should I still leave them with "stopOnErrors:true"?

              I suppose one solution to this issue is to force the user to click Cancel and then click a Save button in the details section to save all changes. That would be a workaround but I'd prefer to resolve these issues with clicking save.

              Comment


                #8
                Hi again,

                From your description, it sounds like this is just an issue of timing. The approach I suggested above had 'updateDetails' split into 3 functions - the first 2 functions were checking for edits on the detail grids, and only continuing if the edits were either discarded or saved (and the save successfully completed, without validation errors).

                The functionality you're describing here is:
                1 - determine the clicked row (newly selected row)
                2 - revert selection to whatever was previously selected until we know that any 'saves' were successful
                3 - on successful save, change selection back to the row saved out in step 1.

                So it appears that if step 3 is performed as part of the third "updateDetails" method (in my example code, "updateDetails_performUpdate()"), you should not see the change in selection on the master grid unless the save successfully completed on both detail-grids.
                It sounds to me that this would resolve the case you describe - When "updateDetails_check<listGrid>Edits()" fires - if 'checkForEdits()' on one of the detailGrids trips a save which fails validation, I would not expect the callback ("updateDetails_performUpdate()") to ever be fired, so if you're "step 3" code was part of that method, the master grid would never update to hilight the "next" record (remembered in step 1).
                Does this make sense, or am I missing something in your usage?

                On the same note: De-selecting and then re-selecting the record seems a little inefficient. If you want you can probably clean this up a bit by setting 'selectionType' to "none" on the grid, and then managing the selection entirely yourself. So your rowClick() method would remember the record you want to select, and then at the appropriate point in your code you can just call "fundAssetsGrid.selection.selectSingle(record)" to perform the selection. No need for the "step 2" of reverting selection to whatever was selected before the user clicked the row.

                ---
                On your second issue, one of 2 things must be happening: Either the custom message set up via the explicit call to isc.confirm() is firing, or the default "dropped edit values" message is being shown. You can easily determine which by modifying the text of your custom dialog.

                The code in "checkForEdits()" in my previous post performs this check for the grid being edited:
                if (listGrid.getEditRow() >= 0) {
                This is actually a check for the case where the editor is showing, meaning that the custom confirmation message will show up every time the user attempts to move to a different record in the 'master' grid, while the editor is showing on one of the detail grids. If this is not the behavior you want let us know and we can show you how to perform other checks for pending edits.

                If you're seeing the standard 'dropped edit values' confirmation message, this implies you have outstanding edits for the row in question after the save completes. I wouldn't expect to see this in your usage, but it is possible. If this is the case, we probably need to take a closer look at your code - in this case, please copy and paste the whole of your saving function(s) into this forum thread, from the record-click handler through to completion of updateDetails so we can really see what's going on.


                ----
                On the stopOnErrors setting: The appropriate setting depends on what behavior you want for your usage.
                This property determines whether - when a user attempts to save an edited cell that fails validation - the user will be notified of the validation failure via an alert and have focus returned to the edit cell. (If set to false, the row will not be saved, and will be styled with a different style to indicate the validation failure - but the grid will not be forced back into edit mode, and no alert will be displayed).
                I suspect it is appropriate to leave this set to true in this application. It will make it very clear why a save failed to complete, whether that save was tripped by the user attempting to select another row from the master grid, or from hitting enter at the end of an edit.

                I hope this helps!

                Isomorphic Software

                Comment


                  #9
                  Hi,

                  I updated my code to set selectionType:"none" on the grid and use selectSingle and that is working fine. After making that change, I'm no longer seeing any issues with validation failures also so perhaps I was managing record selections improperly?

                  The only remaining issue is that the custom confirmation message from the Master grid shows up every time I click a row ( not the standard "dropped edit values" message). But, it only begins to show up after I've made an edit to a grid. I can click from row to row with no messages. But, if I edit one row, then it shows up every time I click a new record after that. So, if there is another way to perform checks for edits that would resolve this issue, please let me know.

                  On the final issue, I have stopOnErrors:true and that is the setting I want.

                  Thanks for all the help with this issue.

                  Comment


                    #10
                    I just uncovered another issue. First, I had to update the call to this.targetGrid.saveEdits() to call this.targetGrid.saveAllEdits() because I am using saveAllEdits() (based on another support ticket) and overriding the default saveAllEdits() behavior in my assetScenarioGrid as follows:

                    Code:
                    	saveAllEdits : function () {
                    		        if (validateScenariosTotal()) return this.Super("saveAllEdits", arguments);
                    	    	    isc.warn("<spring:message code="label.as.sumvalidation"/>");
                    	 }
                    The validateScenariosTotal() function is used to ensure the total rows in the grid add up to 100% in the probability column for all rows with includeScenario=true

                    Code:
                    function validateScenariosTotal(){
                    	  	var total = 0;
                    	  	var include;
                    	  	var record;
                    	  	var probability;
                    
                        	for(var i=0;i<assetScenarioGrid.getTotalRows();i++){
                        	
                        		if(i+1>assetScenarioGrid.data.getLength()){
                        			record = null;
                        		}else{
                        			record = assetScenarioGrid.data.get(i);
                        		}
                    
                    			//skip record if marked for deletion
                    			if(assetScenarioGrid.getEditValue(i, "markDelete")!=null && assetScenarioGrid.getEditValue(i, "markDelete")){
                    				continue;
                    			}	    	
                    	    	//get include
                    			if(assetScenarioGrid.getEditValue(i, "includeScenario") != null){
                    				include = new Boolean(assetScenarioGrid.getEditValue(i, "includeScenario"));
                    			}else{
                    				if(record != null){
                    					include = new Boolean(record.includeScenario);
                    				}else{
                    					include = false;
                    				}
                    			}
                    			
                    			//get probability
                    			if(assetScenarioGrid.getEditValue(i, "probability")!=null){
                    				probability = assetScenarioGrid.getEditValue(i, "probability");
                    
                    			}else{
                    				if(record!=null){
                    					probability = record.probability;
                    				} else {
                    					probability = 0;
                    				}
                    			}
                    
                    		    if(include == true){
                    				total = total + parseInt(probability);
                          		}
                        	}
                    			
                    	    if(total == 100){
                          		return true;
                        	}else{
                          		return false;
                        	}
                    	}
                    When clicking a save button beneath assetScenarioGrid, this validateScenariosTotal() function works fine. However, when this function is fired as a result of clicking on a new record in the Master Grid, the assetScenarioGrid.getEditValue(i, "probability") is a null value when it should pick up the edit I made for the current record (i) that is being edited as I loop over the rows in the grid. So, it looks like there is some issue with the getEditValue function behaving differently in these two different situations?

                    Comment


                      #11
                      Hi, I looked into simply removing the "Save" button from my dialog box as a workaround until we resolve this issue. But, when I click cancel on my dialog box and then click Cancel on the detail grid where I have changes, I still get prompted again with the dialog box when I try to switch rows. So, I'll wait to hear if you have any suggestions on this issue and brainstorm on other workarounds.

                      Comment


                        #12
                        Ah! I've just realized why you're seeing your confirmation message even when you have no pending edits:
                        If you call 'getEditRow()' and the grid is not being edited, it will return null.
                        The check we wrote above was
                        if (grid.getEditRow() >= 0) { ... show the discard edits confirmation
                        The problem here is that in Javascript the expression foo >= 0 where foo is some variable with value null actually evaluates to true.
                        You're going to have to modify the check to something like this:
                        Code:
                        var editRow = grid.getEditRow();
                        if (editRow != null && editRow >=0) {
                         ...
                        }
                        This should resolve the problem with the confirmation message re-showing after edits have been discarded, or successfully saved.
                        The remaining issue you're facing now is that the latest entry in your edit row is failing to be picked up.
                        I'll address this in a new post shortly. Please let us know if the above check resolves this first issue for you
                        Thanks

                        Isomorphic Software

                        Comment


                          #13
                          Hi Again
                          You can resolve the issue with the 'save' click from your save-dialog failing to pick up your latest edits by making sure you fire 'endEditing' on the grid before calling 'saveAllEdits'

                          The "endEditing" method will is responsible for hiding the current editor before the save occurs, and will also ensure that the most recent value you entered is included in the edit values that get validated, then submitted to the server.

                          In other words, your confirmation dialog box's saveClick method should look like this:

                          Code:
                             saveClick : function () {
                                 this.hide();
                                 // Save edits on the target grid, passing a callback to notify
                                 // us of successful save.
                                 this.targetGrid.endEditing();
                                 this.targetGrid.saveAllEdits(null, this.getID() + ".saveComplete()");
                             },
                          One note on this - the "endEditing" method will usually save the edits to the server in addition to hiding the editor. In your grid this does not occur because you have "autoSaveEdits" set to false. Instead it updates the local edit values, pending your programmatic call to "saveAllEdits()"

                          Thanks
                          Isomorphic Software

                          Comment


                            #14
                            Hello, using getAllEditRows() seems to have resolved all issues with checking for edits on the grids.

                            Is there any similar way to checkForEdits on the DynamicForms that are present in my details section?

                            Comment


                              #15
                              Hi
                              The method 'DynamicForm.valuesHaveChanged()' can be used to determine whether the user has edited values in a form.
                              It will return true if the values have changed since the last call to 'DynamicForm.rememberValues()' - a helper method that is automatically called when the values for the form are set programmatically via (for example) setValues() or in this case by starting to edit a new record.

                              This should work for your purposes - please let us know if you run into problems
                              thanks
                              Isomorphic Software

                              Comment

                              Working...
                              X