How to refresh dynamically a RecordType inside in an interface?

Certified Senior Developer

Hi,

Could you tell me how a filtered RecordType grid (placed inside an interface) can be refreshed please ?

My RecordType data are retrieved from the a!recordData() function, and is filtered on the field "deleted" (all rows are visible if delete = "false").

I have a button that calls an Expression Rule to update the underlying table to set the "deleted" field of the selected row to "true".

(I use the WriteToDataStoreEntity Smartservice).

After the table is updated in database, I have click to click on the "refresh" icon of the RecordType to see that all is working fine : the lines are hidded.

How would you refresh the RecordType, without a manual user action please ?

(I intentionnaly do not use any process model for this example)

  Discussion posts and replies are publicly visible

Parents
  • the best solution I can think is to use a local variable that refreshes

  • 0
    Certified Senior Developer
    in reply to ManuelHTG

    Thank you Manuel for your reply, but  a!recordData() is set to a local variable.

    But while this local variable is refreshed (with refreshvariable function), the RecordType is never refreshed.

    Am I missing something ?

  • 0
    Certified Lead Developer
    in reply to cedric01

    Could you post some of your SAIL code?  I would just ask that you use a code box:

  • 0
    Certified Senior Developer
    in reply to Mike Schmitt

      local!data: a!recordData(
    	  recordType: 'recordType!vehicles',
    	  filters: a!queryLogicalExpression(
    		operator: "AND",
    		filters: {
    		  a!queryFilter(
    			field: 'recordType!vehicles.fields.deleted',
    			operator: "=",
    			value: false
    		  ),
    		},
    		ignoreFiltersWithEmptyValues: true
    	  )
      ),
    	
      a!gridField(
    	label: "",
    	data: local!data,
    	columns: {
    		...
    	}
    
    
    /* I have tried this too : */
    
      local!data: a!refreshVariable(
        value: a!recordData(
    	  recordType: 'recordType!vehicles',
    	  filters: a!queryLogicalExpression(
    		operator: "AND",
    		filters: {
    		  a!queryFilter(
    			field: 'recordType!vehicles.fields.deleted',
    			operator: "=",
    			value: false
    		  ),
    		},
    		ignoreFiltersWithEmptyValues: true
    	  )
        ),
        refreshOnReferencedVarChange: true,
        refreshOnVarChange: ri!selectedvehicle.deleted
      ),

  • 0
    Certified Lead Developer
    in reply to cedric01

    Can i see the code you're using for the write to data store entity, and/or where the value of ri!selectedVehicle is changed?  (though it sounds like both things are happening in the same-ish place)

  • 0
    Certified Senior Developer
    in reply to Mike Schmitt

    I'm setting the RI, just before to save data :

    a!buttonArrayLayout(
    	buttons: {
    	  a!buttonWidget(
    		label: "",
    		icon: "arrow-right",
    		saveInto: {
    		  a!save(ri!selectedvehicle.deleted, true),
    		  rule!VH_SaveVehicle(ri!selectedvehicle)
    		},
    	  )
    	},
    )
    
    ER for the saving data : VH_SaveVehicle
    
    a!writeToDataStoreEntity(
      dataStoreEntity: cons!VH_ENTITY_VEHICLE,
      valueToStore: ri!selectedvehicle
    ),

  • 0
    Certified Lead Developer
    in reply to cedric01

    Sorry for the late reply  - Community doesn't see fit to notify me that you've replied to me for some reason.  Anyway.  I usually use a special local variable to update at the end of a data change (by utilizing the onSuccess parameter of the WTDS rule), which then causes subsequent variable values to be refreshed.  This can slot straight into your code -- I haven't used a!recordData as much, so I'm making a little bit of an assumption that it'll work the same way here.

    /* make the following changes to your form local variables: */
      local!refreshCounter: 0,
      local!data: a!refreshVariable(
        value: a!recordData(
          recordType: 'recordType!vehicles',
          filters: a!queryLogicalExpression(
            operator: "AND",
            filters: {
              a!queryFilter(
                field: 'recordType!vehicles.fields.deleted',
                operator: "=",
                value: false
              ),
            },
            ignoreFiltersWithEmptyValues: true
          )
        ),
        refreshOnReferencedVarChange: true,
        refreshOnVarChange: {
          /* ri!selectedvehicle.deleted */
          local!refreshCounter
        }
      ),
    
    /* then one small change to your helper rule call (or, if it's used in other places and you don't want to risk new changes, just perform the WTDS action here until you can tell whether it'll work for you) */
    a!buttonArrayLayout(
      buttons: {
        a!buttonWidget(
          label: "",
          icon: "arrow-right",
          saveInto: {
            a!save(ri!selectedvehicle.deleted, true),
            rule!VH_SaveVehicle(
              selectedVehicle: ri!selectedvehicle,
              refreshCounter: local!refreshCounter /* Added */
            )
          }
        )
      },
    )
    
    /* Then the change within the helper rule itself */
    a!writeToDataStoreEntity(
      dataStoreEntity: cons!VH_ENTITY_VEHICLE,
      valueToStore: ri!selectedvehicle,
      onSuccess: {
        a!save(
          ri!refreshCounter, /* integer */
          ri!refreshCounter + 1
        )
      }
    ),

  • 0
    Certified Senior Developer
    in reply to Mike Schmitt

    Thank you Mike, I have tested with the new local variable but unfortunately, it does not work. (but the variable refreshCounter is well incremented when the onSuccess event occurs in the ER).

    Maybe you're right, it is the a!recordData that is the problem ?

    Would you have any other idea or workaround ?

  • 0
    Certified Lead Developer
    in reply to cedric01

    that might be it, but just in case, could you post a current representative sample of your code?  What happens if you try basically the same setup but using data you get from a queryEntity rule?

Reply Children
  • 0
    Certified Senior Developer
    in reply to Mike Schmitt

    Mike, I can share you the full code, is it what you need ?

    For your suggestion, I'm afraid I can't use anything else than a!recordData, as queryEntity is not compatible with a RecordType. 

    a!localVariables(
      local!selectedRows,
      local!selection,
      local!refreshCounter: 0,
      
      local!data: a!refreshVariable(
        value: a!recordData(
          recordType:'recordType!CJT_R_Vehicles',
          filters: a!queryLogicalExpression(
            operator: "AND",
            filters: {
              a!queryFilter(
                field: 'recordType!CJT_R_Vehicles.fields.deleted',
                operator: "=",
                value: false
              ),
            },
            ignoreFiltersWithEmptyValues: true
          )
        ),
        refreshOnReferencedVarChange: true,
        refreshOnVarChange: {
          local!refreshCounter
        }
      ),
    
      a!formLayout(
        label: "Grid with RecordType",
        contents: {
          a!gridField(
            label: "Vehicles",
            labelPosition: "COLLAPSED",
            data: local!data,
            columns: {
              a!gridColumn(
                label: "Id",
                sortField: 'recordType!CJT_R_Vehicles.fields.id',
                value: fv!row['recordType!CJT_R_Vehicles.fields.id'],
                align: "END"
              ),
              a!gridColumn(
                label: "Make",
                sortField: 'recordType!CJT_R_Vehicles.fields.make',
                value: a!linkField(
                  links: {
                    a!recordLink(
                      label: fv!row['recordType!CJT_R_Vehicles.fields.make'],
                      recordType: 'recordType!CJT_R_Vehicles',
                      identifier: fv!identifier
                    )
                  }
                )
              ),
              a!gridColumn(
                label: "Model",
                sortField: 'recordType!CJT_R_Vehicles.fields.model',
                value: fv!row['recordType!CJT_R_Vehicles.fields.model']
              ),
              a!gridColumn(
                label: "Vin",
                sortField: 'recordType!CJT_R_Vehicles.fields.vin',
                value: fv!row['recordType!CJT_R_Vehicles.fields.vin']
              ),
              a!gridColumn(
                label: "Isavailable",
                sortField: 'recordType!CJT_R_Vehicles.fields.isavailable',
                value: fv!row['recordType!CJT_R_Vehicles.fields.isavailable']
              ),
              a!gridColumn(
                label: "Dateacquired",
                sortField: 'recordType!CJT_R_Vehicles.fields.dateacquired',
                value: fv!row['recordType!CJT_R_Vehicles.fields.dateacquired'],
                align: "END"
              ),
              a!gridColumn(
                label: "Year",
                sortField: 'recordType!CJT_R_Vehicles.fields.year',
                value: fv!row['recordType!CJT_R_Vehicles.fields.year'],
                align: "END"
              ),
              a!gridColumn(
                label: "Deleted",
                sortField: 'recordType!CJT_R_Vehicles.fields.deleted',
                value: fv!row['recordType!CJT_R_Vehicles.fields.deleted']
                align: "END"
              )
            },
    
            initialSorts: {
              a!sortInfo(
                field: 'recordType!CJT_R_Vehicles.fields.id'
              )
            },
            showWhen: true,
            selectable: true,
            selectionStyle: "ROW_HIGHLIGHT",
            selectionValue: local!selection,
            
            selectionsaveInto: {
              a!save(local!selectedRows, index(fv!selectedRows, length(fv!selectedRows), null)),
              a!save(local!selection, index(save!value, length(save!value), null)),
              a!save(ri!selectedVehicle,
                index(
                  fv!selectedRows,
                  length(fv!selectedRows),
                  null
                )
              )
            },
            validations: {},
            refreshAfter: "RECORD_ACTION",
            userFilters: {
            },
            showSearchBox: true,
            showRefreshButton: true,
            showExportButton: false,
            recordActions: {}
          )
        },
        buttons: a!buttonLayout(
          secondaryButtons: {
            a!buttonWidget(
              label: "Delete selected",
              saveInto: {
                a!save(
                  ri!selectedVehicle.deleted, true
                ),
                rule!CJT_ER_SaveVehicle(
                  vehicle: ri!selectedVehicle,
                  refreshCounter: local!refreshCounter
                )
              },
              submit: false,
              style: "PRIMARY",
              showWhen: true,
              confirmMessage: "Do you confirm deactivating this row?",
              disabled: rule!SHARED_IsNullOrEmpty(ri!selectedVehicle)
            )
          }
        )
      )
    )

  • 0
    Certified Lead Developer
    in reply to cedric01

    What's the data source for your record type?  If it's coming from the DB, it's something you could use a QE rule for [though this might force you to forego certain features of using record type data directly in a grid].  If it's process-backed, however, then i'm afraid you're right.  Anyway, i'll check through the code now and see if any suggestions occur to me.

  • 0
    Certified Lead Developer
    in reply to Mike Schmitt

    Ok, I've been able to confirm your reported behavior by setting up a small sample interface that does basically the same thing you're doing.  I'm afraid this *might* be expected behavior of a!recordData(), given that there's a built-in refresh button on the interface that might be intended to be the *only* refresh functionality for the data. 

    Hhowever I agree that it does seem unintuitive that this data won't refresh as part of a refreshVariable, especially when we want to do it intentionally.  You might want to open a case with Appian Support, reporting this as a possible bug, and at least an oversight in actual behavior versus expected behavior for designers.

    Edit: to confirm, I also created a random dynamic link to increment the refresh counter in my example, and did it 20 - 30 seconds after the (successful) DB write was completed, and it still doesn't cause the loaded data to refresh.  If this is the "official" behavior, it's certainly surprising and unintuitive.

    Edit 2: per the documentation, it appears as if it might not really be supported functionality to store queried recordData in a local variable (even though fortunately it does seem to work); this would imply to me that it's not subject to the refresh conditions as defined in a!refreshVariable().

  • 0
    Certified Senior Developer
    in reply to Mike Schmitt

    The data source is coming from a DB MySQL table (from the vehicle Appian example)

  • 0
    Certified Senior Developer
    in reply to Mike Schmitt

    Thank you Mike for your feeback. Your 2/ point is very interesting and could explain the issue.

    But if I can not use the recordData function in my case, is there any other way to query a RecordType ?

    (The recordData is handy in my case, as I can apply filter on the RecordType...)

  • 0
    Certified Lead Developer
    in reply to cedric01

    The only other way I'm used to is to build your own record filtering (which isn't very hard) and populate the grid using data pulled straight from a!queryEntity().  You give up some of the OOB "record list" functionality and gain more flexibility, including, i expect, the ability to have the grid automatically refresh after your deactivation has been fired.

  • 0
    Certified Senior Developer
    in reply to Mike Schmitt

    In our project, we have coded a lot of grids exactly like you've described it, but we still have a lot of RecordType grids, and these will probably remain like this.

    But thank you Mike, your feedback is great.

  • 0
    Appian Employee
    in reply to cedric01

    a!recordData() does not query the record type, but instead helps define the query for a grid. To query data from a record type, use a!queryRecordType(). As Mike has said, you can use a!recordData() directly in a!gridField() and then use the refresh parameters on gridField

  • 0
    Certified Senior Developer
    in reply to Danny Verb

    Thank you Danny, but I'm afraid that a!queryRecordType() does not work with a RecordType-based-grid.

    (I have already posted this issue in another post but never had a reply)

    Here is my complete code, I would really appreciate if you could tell me why this code does not work please :

    a!localVariables(
      local!selectedRows,
      local!selection,
      local!refreshCounter: 0,
      
      local!pagingInfo: a!pagingInfo(1, 10),
      local!data : a!queryRecordType(
        recordType: 'recordType!CJT_R_Vehicles',
        selection: {
          'recordType!CJT_R_Vehicles.fields.id',
          'recordType!CJT_R_Vehicles.fields.model',
          'recordType!CJT_R_Vehicles.fields.vin',
          'recordType!CJT_R_Vehicles.fields.isavailable',
          'recordType!CJT_R_Vehicles.fields.dateacquired',
          'recordType!CJT_R_Vehicles.fields.year',
          'recordType!CJT_R_Vehicles.fields.deleted',
        },
        filters: {
          a!queryFilter(
            field: 'recordType!CJT_R_Vehicles.fields.deleted',
            operator: "=",
            value: false
          )
        },
        pagingInfo: local!pagingInfo,
        fetchTotalCount: true
      ),
    
      a!formLayout(
        label: "Grid with RecordType",
        contents: {
          a!gridField(
            label: "Vehicles",
            labelPosition: "COLLAPSED",
            data: local!data,
            columns: {
              a!gridColumn(
                label: "Id",
                sortField: 'recordType!CJT_R_Vehicles.fields.id',
                value: fv!row['recordType!CJT_R_Vehicles.fields.id'],
                align: "END"
              ),
              a!gridColumn(
                label: "Make",
                sortField: 'recordType!CJT_R_Vehicles.fields.make',
                value: a!linkField(
                  links: {
                    a!recordLink(
                      label: fv!row['recordType!CJT_R_Vehicles.fields.make'],
                      recordType: 'recordType!CJT_R_Vehicles',
                      identifier: fv!identifier
                    )
                  }
                )
              ),
              a!gridColumn(
                label: "Model",
                sortField: 'recordType!CJT_R_Vehicles.fields.model',
                value: fv!row['recordType!CJT_R_Vehicles.fields.model']
              ),
              a!gridColumn(
                label: "Vin",
                sortField: 'recordType!CJT_R_Vehicles.fields.vin',
                value: fv!row['recordType!CJT_R_Vehicles.fields.vin']
              ),
              a!gridColumn(
                label: "Isavailable",
                sortField: 'recordType!CJT_R_Vehicles.fields.isavailable',
                value: fv!row['recordType!CJT_R_Vehicles.fields.isavailable']
              ),
              a!gridColumn(
                label: "Dateacquired",
                sortField: 'recordType!CJT_R_Vehicles.fields.dateacquired',
                value: fv!row['recordType!CJT_R_Vehicles.fields.dateacquired'],
                align: "END"
              ),
              a!gridColumn(
                label: "Year",
                sortField: 'recordType!CJT_R_Vehicles.fields.year',
                value: fv!row['recordType!CJT_R_Vehicles.fields.year'],
                align: "END"
              ),
              a!gridColumn(
                label: "Deleted",
                sortField: 'recordType!CJT_R_Vehicles.fields.deleted',
                value: fv!row['recordType!CJT_R_Vehicles.fields.deleted']
                align: "END"
              )
            },
    
            initialSorts: {
              a!sortInfo(
                field: 'recordType!CJT_R_Vehicles.fields.id'
              )
            },
            showWhen: true,
            selectable: true,
            selectionStyle: "ROW_HIGHLIGHT",
            selectionValue: local!selection,
            
            selectionsaveInto: {
              a!save(local!selectedRows, index(fv!selectedRows, length(fv!selectedRows), null)),
              a!save(local!selection, index(save!value, length(save!value), null)),
              a!save(ri!selectedVehicle,
                index(
                  fv!selectedRows,
                  length(fv!selectedRows),
                  null
                )
              )
            },
            validations: {},
            refreshAfter: "RECORD_ACTION",
            userFilters: {
            },
            showSearchBox: true,
            showRefreshButton: true,
            showExportButton: false,
            recordActions: {}
          )
        },
        buttons: a!buttonLayout(
          secondaryButtons: {
            a!buttonWidget(
              label: "Delete selected",
              saveInto: {
                a!save(
                  ri!selectedVehicle.deleted, true
                ),
                rule!CJT_ER_SaveVehicle(
                  vehicle: ri!selectedVehicle,
                  refreshCounter: local!refreshCounter
                )
              },
              submit: false,
              style: "PRIMARY",
              showWhen: true,
              confirmMessage: "Do you confirm deactivating this row?",
              disabled: rule!SHARED_IsNullOrEmpty(ri!selectedVehicle)
            )
          }
        )
      )
    )

    That code above throws this error :

    Could not display interface. Please check definition and inputs.
    Interface Definition: Expression evaluation error at function a!gridField [line 53]: A grid component [label=“Vehicles”] has an invalid value for “showSearchBox”. “userFilters” and “showSearchBox” may only be specified when “data” is sourced from a Record Type.

  • 0
    Certified Lead Developer
    in reply to Danny Verb

    - a!queryRecordType() doesn't cause the grid to invoke the "OOB record list" features though, right?  As far as I understood, the only way to do that was to use a!recordData().  And of course, due to that and the clarificaton you provided, it doesn't seem like there's actually a way to cause the "OOB-style" record list grid to be refreshed via external input - can you confirm that this is the expected behavior?  It seems artificially limiting, though I could understand if it's not possible due to stuff on the back-end.