You can enable users to select individual items in a result matrix dashboard portlet and run an action (trigger an event) on a button click.

In this example, we have a Dashboard with a result matrix portlet listing LPG items. Users can select any of these items and run their recalculation.

Under the hood, there are two logics associated with this Dashboard. One logic (main) builds the Dashboard content. An Option Input allows the user to select one of the Live Price Grids that exist in the partition. When the Dashboard is refreshed, a result matrix displays the LPG Items, one row per item, with SKU and lastUpdateDate.

The user can select one or multiple rows, and click on the “Calc PGI” button. When the user clicks the button, we run a different logic (action), which reads the list of the selected SKUs (i.e. the LPG Items) and triggers the calculation of each of them.

After about one minute, to allow time for the backend processing, if you refresh the dashboard, you’ll see that the lastUpdateDate of the selected rows have changes, proving that the calculation has been done.

The “action” logic can do anything we allow in the Groovy Sandbox. Here we just recalculate the LPG items. But it could as easily create a new Live Price Grid or Price List with the selected SKUs.

Dashboard Logic

The Dashboard logic contains the following elements:

api.findPriceGrids(null, null)


def labels = api.getElement("pg_list")*.label.sort()

api.option("Pick an LPG", labels)


api.booleanUserEntry("Activate Embedded")


api.booleanUserEntry("Activate Select")


def label = api.getElement("input_pg_label")

if (label == null || api.syntaxCheck) return

api.getElement("pg_list").find { label.equals(it.label) }


def pg = api.getElement("pg")

if (pg == null || api.syntaxCheck) return


def fields = ["sku", "activePrice", "submitDate", "lastUpdateDate"]
def sortField = "sku"
def filter = Filter.equal("priceGridId", pg.id)

api.find("PGI", 0, 200, sortField, /* fields, */ filter)


def pg = api.getElement("pg")

if (pg == null || api.syntaxCheck) return


def matrix = api.newMatrix([
  "sku", "activePrice", "lastUpdateDate", "download", "action"
])

matrix.setTitle(pg.label)
matrix.setEnableClientFilter(true)
matrix.setColumnFormat("lastUpdateDate", FieldFormatType.DATETIME)
matrix.setPreferenceName("UltimateResultMatrix_uuid42")
matrix.addColumnTranslation("sku", [en: "sku (click leads to Master Data)"])

api.getElement("pg_items").each { pgi ->
  def row = [:]
  row.sku = matrix.linkCell(pgi.sku, AppPages.MD_PRODUCTS_PAGE, pgi.sku)
  row.activePrice = pgi.activePrice
  row.lastUpdateDate = pgi.lastUpdateDate
  row.download = createDownloadLink(matrix, pg, pgi)
  row.action = createCalcPgiAction(matrix, pg, pgi)
  matrix.addRow(row)
}

if (api.getElement("input_activate_click")) {
  addClickEvent(matrix)
}

if (api.getElement("input_activate_select")) {
  addRowSelectionBackEndAction(matrix, pg)
}

return matrix

// helper methods (they make the above code more readable)

def createDownloadLink(def matrix, def pg, def pgi) {
  matrix.downloadButton("Download (whole PG)",
                        "/pricegridmanager.fetchpdf/" + pg.id,
                        null /*payload*/)
}

def createCalcPgiAction(def matrix, def pg, def pgi) {
  matrix.backEndAction("Recalculate (this item)",
                       "/pricegridmanager.update/" + pg.id,
                       api.toJson([data:[typedId: pgi.typedId]]),
                       "success",
                       "failure")
}

def addClickEvent(def matrix) {
  matrix
    .onRowSelection()
      .triggerEvent(api.dashboardWideEvent("myEvent"))
      .withColValueAsEventDataAttr("sku", "sku_attribute")
}

def addRowSelectionBackEndAction(def matrix, def pg) {
  matrix
    .rowSelectionBackEndAction("SKUs")
      .withLogicName("SC_UltimateMatrix_calc")
      .withColumns("sku")
      .addFormulaInput("PG_label", pg.label)
      .addFormulaInput("PG_id", pg.id)
      .withButtonLabel("Calc PGI")
      .withButtonTooltip("(Re)Calculate the selected Price Grid Items (SKUs)")
      .withSuccessMessage("Success")
      .withFailureMessage("Sorry")
}


if (!api.getElement("input_activate_click")) return

api.dashboard("JJ_embedded_event_chart")

	// Show the dashboard embedded ...
	.showEmbedded()
			
	// ... and reevaluate it on "myEvent"
	// Note: "dashboardWideEvent()" fce makes the event local
	// to the containing dashboard instance
	.andRecalculateOn(api.dashboardWideEvent("myEvent"))
			

	// Pull the "customerId" attribute out of the event payload and expose it
	// as the "Customer Id" input to the embedded dashboard
	.withEventDataAttr("sku_attribute").asParam("SKU")
	//.withEventDataAttr("bandBy").asParam("CustomerClass")

Action Logic

The action logic contains the following elements:

def pg_id = api.decimalUserEntry('PG_id')

// test value
if (pg_id == null) {
  pg_id = 5 // "Fourth Live Price Grid"
}

pg_id as Long


def matrix = api.inputMatrix("SKUs", "sku")

// Front End payload validation
if (!api.syntaxCheck) {
  validate(matrix) // throws exception if invalid
}

// test value
if (matrix == null) {
  matrix = [[sku: "MB-0007"]]
}

matrix*.sku



def validate(def matrix) {
  if (matrix == null) {
    exception(matrix, "is missing or null")
  }
  if (matrix.size() == 0) {
    exception(matrix, "should not be empty")
  }
  if (! (matrix instanceof List) || ! (matrix[0] instanceof Map)) {
    exception(matrix, "should be an Array of Maps")
  }
  if (matrix[0].keySet() == ["sku"] as Set) {
    // that's expected for backend action
  }
  else if (matrix[0].keySet() == ["sku", "selected"] as Set) {
    // that's expected for UI InputMatrix
  }
  else {
    exception(matrix, "should have only 'sku' values")
  }
  if (matrix*.sku.find {!it instanceof String}) {
     exception(matrix, "should have'sku' values of type String")
  }
}

def exception(def matrix, def msg) {
  api.throwException("'SKUs' InputMatrix " + msg + " - " + api.toJson(matrix))
}


def pg_id = api.getElement("input_pgid")
def sku_list = api.getElement("input_skus")

api.triggerPriceGridCalculation(pg_id, [skusToRecalc: sku_list])

return null