Editable Grid SAIL

I tried the following SAIL recipe which worked well with Employees CDT as ri! parameter for adding rows. I thought deletion would also be done by the same call using write to datastore smart service in a kind of merging but it did not. I am trying to call another service for deletion with deleted ids stored in another ri! parameter. However I am getting into following two issues.

1) The deleted id is +1 more than rowid

2) When submit button is pressed I am getting error that my deleted ids array is null. 

Could somebody provide me details on what needs to be done?

 

=load(
a!formLayout(
label: "SAIL Example: Add,Update, or Remove Employee Data",
contents: {
a!gridLayout(
totalCount: count(ri!Employees),
headerCells: {
a!gridLayoutHeaderCell(label: "id" ),
a!gridLayoutHeaderCell(label: "First Name" ),
a!gridLayoutHeaderCell(label: "Last Name" ),
a!gridLayoutHeaderCell(label: "Department" ),
a!gridLayoutHeaderCell(label: "Title" ),
a!gridLayoutHeaderCell(label: "Phone Number" ),
a!gridLayoutHeaderCell(label: "Start Date", align: "RIGHT" ),
/* For the "Remove" column */
a!gridLayoutHeaderCell(label: "" )
},
/* Only needed when some columns need to be narrow */
columnConfigs: {
a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:3 ),
a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:3 ),
a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:3 ),
a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:3 ),
a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:3 ),
a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:3 ),
a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:2 ),
a!gridLayoutColumnConfig(width: "ICON")
},
rows: a!forEach(
items: ri!Employees,
expression: a!gridRowLayout(
contents: {
a!integerField(
/* Labels are not visible in grid cells but are necessary to meet accessibility requirements */
label: "first name " & fv!index,
value: fv!item.id,
saveInto: fv!item.id,
required: true
),
/* For the First Name Column*/
a!textField(
/* Labels are not visible in grid cells but are necessary to meet accessibility requirements */
label: "first name " & fv!index,
value: fv!item.firstName,
saveInto: fv!item.firstName,
required: true
),
/* For the Last Name Column*/
a!textField(
label: "last name " & fv!index,
value: fv!item.lastName,
saveInto: fv!item.lastName,
required:true
),
/* For the Department Column*/
a!dropdownField(
label: "department " & fv!index,
placeholderLabel: "-- Select -- ",
choiceLabels: { "Corporate", "Engineering", "Finance", "Human Resources", "Professional Services", "Sales" },
choiceValues: { "Corporate", "Engineering", "Finance", "HR", "Professional Services", "Sales" },
value: fv!item.department,
saveInto: fv!item.department,
required:true
),
/* For the Title Column*/
a!textField(
label: "title " & fv!index,
value: fv!item.title,
saveInto: fv!item.title,
required:true
),
/* For the Phone Number Column*/
a!textField(
label: "phone number " & fv!index,
placeholder:"123-456-7890",
value: fv!item.phoneNumber,
saveInto: fv!item.phoneNumber
),
/* For the Start Date Column*/
a!dateField(
label: "start date " & fv!index,
value: fv!item.startDate,
saveInto: fv!item.startDate,
required:true,
align: "RIGHT"
),
/* For the Removal Column*/
a!imageField(
label: "delete " & fv!index,
images: a!documentImage(
document: a!iconIndicator("REMOVE"),
altText: "Remove Employee",
caption: "Remove " & fv!item.firstName & " " & fv!item.lastName,
link: a!dynamicLink(
value: fv!index,
saveInto: {
a!save(ri!Employees, remove(ri!Employees, save!value)),
a!save(ri!DeletedEmployees, append(ri!DeletedEmployees, fv!item.id))  -- ( fv!item.id -1 works logging in correct ids in to array for deletion)
}
)
),
size: "ICON"
)
},
id: fv!index
)
),
addRowlink: a!dynamicLink(
label: "Add Employee",
/*
* For your use case, set the value to a blank instance of your CDT using
* the type constructor, e.g. type!Employee(). Only specify the field
* if you want to give it a default value e.g. startDate: today()+1.
*/
value: 'type!{urn:com:appian:types}Employee'(),
saveInto: {
a!save(ri!Employees, append(ri!Employees,save!value))
}
)
)
},
buttons: a!buttonLayout(
primaryButtons: a!buttonWidgetSubmit(
label: "Submit",
saveInto:{ a!writeToDataStoreEntity(
dataStoreEntity: cons!DS_ENTITY_REF_EMPLOYEE,
valueToStore: ri!Employees
),
a!deleteFromDataStoreEntities(
entity: cons!DS_ENTITY_REF_EMPLOYEE,              -- This failes
identifiers: ri!DeletedEmployees
)
}
)
)
)
)

  Discussion posts and replies are publicly visible

  • Editable grid functionality is to read the data from a CDT and allow adds/deletes inline. So, in may case, my grid shows 20 rows having returned from database. I delete one of it. I can submit the transaction to DB first and then add. But, decided to add a new row before hitting submit. I should end up essentially 20 rows in database, and, I should have a new row added and a old row removed. So, in my example, I deleted rowid 56 and added rowid 144 with new information. Hope that explains.
  • How do I use it in DB smart service? I essentially would like my data set bound to the grid with resulting rows after additions/deletions inline to be merged with database table(s). Could you please point me to documentation on this functionality?
  • Can you please try replacing your code with this piece of code :

     

    a!deleteFromDataStoreEntities(
    dataToDelete: {
    entity: cons!DS_ENTITY_REF_EMPLOYEE,
    identifiers: ri!DeletedEmployees
    },
    onSuccess: "",
    onError: ""
    )

     

    Also verify your DeletedEmployees variable type and do check whether it is storing correct value.

  • After a little investigation, I could get the deletion done using the following syntax. a!deleteFromDataStoreEntities(
    a!entityDataIdentifiers(
    entity: cons!DS_ENTITY_REF_EMPLOYEE,
    identifiers: ri!DeletedEmployees
    )
    ). I can get the SAIL to do both insert and delete, but, deletes are getting added back as new rows which I am still
    investigating. Moreover, the row.id (primary id) being logged is not the correct one. It seems there is something internal reorganization
    of primary keys that I would have to skim out in order to make the pattern work appropriate. Thanks for your help. I had to use a!entityDataIdentifiers for the deletion to work.
  • The following code worked for me in doing both insert and delete at the same time. I used with function which refreshed after a delete has happened which comes just after the "X" sign is activated on a record. It actually mimics doing insert and deletes at separate time periods which most probably is appropriate for editable grids. I am still figuring out on why the row.id is not being logged properly.

    =load(
    with(
    a!formLayout(
    label: "SAIL Example: Add,Update, or Remove Employee Data",
    contents: {
    a!gridLayout(
    totalCount: count(ri!Employees),
    headerCells: {
    a!gridLayoutHeaderCell(label: "id" ),
    a!gridLayoutHeaderCell(label: "First Name" ),
    a!gridLayoutHeaderCell(label: "Last Name" ),
    a!gridLayoutHeaderCell(label: "Department" ),
    a!gridLayoutHeaderCell(label: "Title" ),
    a!gridLayoutHeaderCell(label: "Phone Number" ),
    a!gridLayoutHeaderCell(label: "Start Date", align: "RIGHT" ),
    /* For the "Remove" column */
    a!gridLayoutHeaderCell(label: "" )
    },
    /* Only needed when some columns need to be narrow */
    columnConfigs: {
    a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:3 ),
    a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:3 ),
    a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:3 ),
    a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:3 ),
    a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:3 ),
    a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:3 ),
    a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:2 ),
    a!gridLayoutColumnConfig(width: "ICON")
    },
    rows: a!forEach(
    items: ri!Employees,
    expression: a!gridRowLayout(
    contents: {
    a!integerField(
    /* Labels are not visible in grid cells but are necessary to meet accessibility requirements */
    label: "first name " & fv!index,
    value: fv!item.id,
    saveInto: fv!item.id,
    required: true
    ),
    /* For the First Name Column*/
    a!textField(
    /* Labels are not visible in grid cells but are necessary to meet accessibility requirements */
    label: "first name " & fv!index,
    value: fv!item.firstName,
    saveInto: fv!item.firstName,
    required: true
    ),
    /* For the Last Name Column*/
    a!textField(
    label: "last name " & fv!index,
    value: fv!item.lastName,
    saveInto: fv!item.lastName,
    required:true
    ),
    /* For the Department Column*/
    a!dropdownField(
    label: "department " & fv!index,
    placeholderLabel: "-- Select -- ",
    choiceLabels: { "Corporate", "Engineering", "Finance", "Human Resources", "Professional Services", "Sales" },
    choiceValues: { "Corporate", "Engineering", "Finance", "HR", "Professional Services", "Sales" },
    value: fv!item.department,
    saveInto: fv!item.department,
    required:true
    ),
    /* For the Title Column*/
    a!textField(
    label: "title " & fv!index,
    value: fv!item.title,
    saveInto: fv!item.title,
    required:true
    ),
    /* For the Phone Number Column*/
    a!textField(
    label: "phone number " & fv!index,
    placeholder:"123-456-7890",
    value: fv!item.phoneNumber,
    saveInto: fv!item.phoneNumber
    ),
    /* For the Start Date Column*/
    a!dateField(
    label: "start date " & fv!index,
    value: fv!item.startDate,
    saveInto: fv!item.startDate,
    required:true,
    align: "RIGHT"
    ),
    /* For the Removal Column*/
    a!imageField(
    label: "delete " & fv!index,
    images: a!documentImage(
    document: a!iconIndicator("REMOVE"),
    altText: "Remove Employee",
    caption: "Remove " & fv!item.firstName & " " & fv!item.lastName,
    link: a!dynamicLink(
    value: fv!index,
    saveInto: {
    a!save(ri!Employees, remove(ri!Employees, save!value)),
    a!save(ri!DeletedEmployees, append(ri!DeletedEmployees, fv!item.id)),
    a!deleteFromDataStoreEntities(
    a!entityDataIdentifiers(
    entity: cons!DS_ENTITY_REF_EMPLOYEE,
    identifiers: ri!DeletedEmployees
    )
    )
    }
    )
    ),
    size: "ICON"
    )
    },
    id: fv!index
    )
    )
    ,
    addRowlink: a!dynamicLink(
    label: "Add Employee",
    /*
    * For your use case, set the value to a blank instance of your CDT using
    * the type constructor, e.g. type!Employee(). Only specify the field
    * if you want to give it a default value e.g. startDate: today()+1.
    */
    value: 'type!{urn:com:appian:types}Employee'(),
    saveInto: {
    a!save(ri!Employees, append(ri!Employees,save!value))
    }
    )
    )
    },
    buttons: a!buttonLayout(
    primaryButtons: a!buttonWidgetSubmit(
    label: "Submit",
    saveInto:{ a!writeToDataStoreEntity(
    dataStoreEntity: cons!DS_ENTITY_REF_EMPLOYEE,
    valueToStore: ri!Employees
    )
    }
    )
    )
    )
    )
    )
  • I tried to make the inline editable grid work to log the correct primary ids. What I understood so far is that 1) The editable fv.index is being used for validations and also fv.item.id is actually the next row id instead of the one being deleted. Also, it seems there may be some dependency on primary ids to be sequential without any deletes. Who would be the guru on this subject to discuss on the right code pattern that works as expected?
  • I could make this work with the following code. I had to replace fv!index with fv!item and remove deleted ids from employees at submit time. I am working with a table where the surrogate keys are arbitrary and count does not match with next id. There seems to be a validation in REMOVE wherein it does not allow me to delete a row whose id does not math with the count of items in the grid. Only draw back is that user would not see the deleted row going out of screen till the period of pressing SUBMIT button. Is there any refresh function which could be called to update the grid?

    =load(
    a!formLayout(
    label: "SAIL Example: Add,Update, or Remove Employee Data",
    contents: {
    a!gridLayout(
    totalCount: count(ri!Employees),
    headerCells: {
    a!gridLayoutHeaderCell(label: "id" ),
    a!gridLayoutHeaderCell(label: "First Name" ),
    a!gridLayoutHeaderCell(label: "Last Name" ),
    a!gridLayoutHeaderCell(label: "Department" ),
    a!gridLayoutHeaderCell(label: "Title" ),
    a!gridLayoutHeaderCell(label: "Phone Number" ),
    a!gridLayoutHeaderCell(label: "Start Date", align: "RIGHT" ),
    /* For the "Remove" column */
    a!gridLayoutHeaderCell(label: "" )
    },
    /* Only needed when some columns need to be narrow */
    columnConfigs: {
    a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:3 ),
    a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:3 ),
    a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:3 ),
    a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:3 ),
    a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:3 ),
    a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:3 ),
    a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:2 ),
    a!gridLayoutColumnConfig(width: "ICON")
    },
    rows: a!forEach(
    items: ri!Employees,
    expression: a!gridRowLayout(
    contents: {
    a!integerField(
    /* Labels are not visible in grid cells but are necessary to meet accessibility requirements */
    label: "id " & fv!item.id,
    value: fv!item.id,
    saveInto: fv!item.id,
    required: true
    ),
    /* For the First Name Column*/
    a!textField(
    /* Labels are not visible in grid cells but are necessary to meet accessibility requirements */
    label: "first name " & fv!item.id,
    value: fv!item.firstName,
    saveInto: fv!item.firstName,
    required: true
    ),
    /* For the Last Name Column*/
    a!textField(
    label: "last name " & fv!item.id,
    value: fv!item.lastName,
    saveInto: fv!item.lastName,
    required:true
    ),
    /* For the Department Column*/
    a!dropdownField(
    label: "department " & fv!item.id,
    placeholderLabel: "-- Select -- ",
    choiceLabels: { "Corporate", "Engineering", "Finance", "Human Resources", "Professional Services", "Sales" },
    choiceValues: { "Corporate", "Engineering", "Finance", "HR", "Professional Services", "Sales" },
    value: fv!item.department,
    saveInto: fv!item.department,
    required:true
    ),
    /* For the Title Column*/
    a!textField(
    label: "title " & fv!item.id,
    value: fv!item.title,
    saveInto: fv!item.title,
    required:true
    ),
    /* For the Phone Number Column*/
    a!textField(
    label: "phone number " & fv!item.id,
    placeholder:"123-456-7890",
    value: fv!item.phoneNumber,
    saveInto: fv!item.phoneNumber
    ),
    /* For the Start Date Column*/
    a!dateField(
    label: "start date " & fv!item.id,
    value: fv!item.startDate,
    saveInto: fv!item.startDate,
    required:true,
    align: "RIGHT"
    ),
    /* For the Removal Column*/
    a!imageField(
    label: "delete " & fv!item.id,
    images: a!documentImage(
    document: a!iconIndicator("REMOVE"),
    altText: "Remove Employee",
    caption: "Remove " & fv!item.firstName & " " & fv!item.lastName,
    link: a!dynamicLink(
    value: fv!item.id,
    saveInto: {
    a!save(ri!DeletedEmployees, append(ri!DeletedEmployees, fv!item.id)),
    a!deleteFromDataStoreEntities(
    a!entityDataIdentifiers(
    entity: cons!DS_ENTITY_REF_EMPLOYEE,
    identifiers: ri!DeletedEmployees
    )
    )
    }
    )
    ),
    size: "ICON"
    )
    },
    id: fv!item.id
    )
    )
    ,
    addRowlink: a!dynamicLink(
    label: "Add Employee",
    value: 'type!{urn:com:appian:types}Employee'(),
    saveInto: {
    a!save(ri!Employees, append(ri!Employees,save!value))
    }
    )
    )
    },
    buttons: a!buttonLayout(
    primaryButtons: a!buttonWidgetSubmit(
    label: "Submit",
    saveInto:{ a!save(ri!Employees, remove(ri!Employees, wherecontains(ri!DeletedEmployees, ri!Employees.id))),
    a!save(ri!DeletedEmployees, {}),
    a!writeToDataStoreEntity(
    dataStoreEntity: cons!DS_ENTITY_REF_EMPLOYEE,
    valueToStore: ri!Employees
    )
    }
    )
    )
    )
    )
  • I could make the functionality work well by doing some changes. The following code works very well for inserts/deletes at the same time and also does not depend on the surrogate keys ( entity primary keys ) for being contiguous and match the count of rows.

    =load(
    a!formLayout(
    label: "SAIL Example: Add,Update, or Remove Employee Data",
    contents: {
    a!gridLayout(
    totalCount: count(ri!Employees),
    headerCells: {
    a!gridLayoutHeaderCell(label: "id" ),
    a!gridLayoutHeaderCell(label: "First Name" ),
    a!gridLayoutHeaderCell(label: "Last Name" ),
    a!gridLayoutHeaderCell(label: "Department" ),
    a!gridLayoutHeaderCell(label: "Title" ),
    a!gridLayoutHeaderCell(label: "Phone Number" ),
    a!gridLayoutHeaderCell(label: "Start Date", align: "RIGHT" ),
    /* For the "Remove" column */
    a!gridLayoutHeaderCell(label: "" )
    },
    /* Only needed when some columns need to be narrow */
    columnConfigs: {
    a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:3 ),
    a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:3 ),
    a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:3 ),
    a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:3 ),
    a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:3 ),
    a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:3 ),
    a!gridLayoutColumnConfig(width: "DISTRIBUTE", weight:2 ),
    a!gridLayoutColumnConfig(width: "ICON")
    },
    rows: a!forEach(
    items: ri!Employees,
    expression: a!gridRowLayout(
    contents: {
    a!integerField(
    /* Labels are not visible in grid cells but are necessary to meet accessibility requirements */
    label: "id " & fv!item.id,
    value: fv!item.id,
    saveInto: fv!item.id,
    required: true
    ),
    /* For the First Name Column*/
    a!textField(
    /* Labels are not visible in grid cells but are necessary to meet accessibility requirements */
    label: "first name " & fv!item.id,
    value: fv!item.firstName,
    saveInto: fv!item.firstName,
    required: true
    ),
    /* For the Last Name Column*/
    a!textField(
    label: "last name " & fv!item.id,
    value: fv!item.lastName,
    saveInto: fv!item.lastName,
    required:true
    ),
    /* For the Department Column*/
    a!dropdownField(
    label: "department " & fv!item.id,
    placeholderLabel: "-- Select -- ",
    choiceLabels: { "Corporate", "Engineering", "Finance", "Human Resources", "Professional Services", "Sales" },
    choiceValues: { "Corporate", "Engineering", "Finance", "HR", "Professional Services", "Sales" },
    value: fv!item.department,
    saveInto: fv!item.department,
    required:true
    ),
    /* For the Title Column*/
    a!textField(
    label: "title " & fv!item.id,
    value: fv!item.title,
    saveInto: fv!item.title,
    required:true
    ),
    /* For the Phone Number Column*/
    a!textField(
    label: "phone number " & fv!item.id,
    placeholder:"123-456-7890",
    value: fv!item.phoneNumber,
    saveInto: fv!item.phoneNumber
    ),
    /* For the Start Date Column*/
    a!dateField(
    label: "start date " & fv!item.id,
    value: fv!item.startDate,
    saveInto: fv!item.startDate,
    required:true,
    align: "RIGHT"
    ),
    /* For the Removal Column*/
    a!imageField(
    label: "delete " & fv!item.id,
    images: a!documentImage(
    document: a!iconIndicator("REMOVE"),
    altText: "Remove Employee",
    caption: "Remove " & fv!item.firstName & " " & fv!item.lastName,
    link: a!dynamicLink(
    value: fv!item.id,
    saveInto: {
    a!save(ri!DeletedEmployees, append(ri!DeletedEmployees, fv!item.id)),
    a!save(ri!Employees, remove(ri!Employees, wherecontains(ri!DeletedEmployees, ri!Employees.id))),
    a!deleteFromDataStoreEntities(
    a!entityDataIdentifiers(
    entity: cons!DS_ENTITY_REF_EMPLOYEE,
    identifiers: ri!DeletedEmployees
    )
    )
    }
    )
    ),
    size: "ICON"
    )
    },
    id: fv!item.id
    )
    )
    ,
    addRowlink: a!dynamicLink(
    label: "Add Employee",
    /*
    * For your use case, set the value to a blank instance of your CDT using
    * the type constructor, e.g. type!Employee(). Only specify the field
    * if you want to give it a default value e.g. startDate: today()+1.
    */
    value: 'type!{urn:com:appian:types}Employee'(),
    saveInto: {
    a!save(ri!Employees, append(ri!Employees,save!value))
    }
    )
    )
    },
    buttons: a!buttonLayout(
    primaryButtons: a!buttonWidgetSubmit(
    label: "Submit",
    saveInto:{
    a!save(ri!DeletedEmployees, {}),
    a!writeToDataStoreEntity(
    dataStoreEntity: cons!DS_ENTITY_REF_EMPLOYEE,
    valueToStore: ri!Employees
    )
    }
    )
    )
    )
    )