Skip to main content
  1. Work Notes/
  2. Common Snippets/

E9 Code Block: Validate Through an Interface Before Submit

·876 words·5 mins
Ecology E9 Code Block Interface Validation Submit Guard
Author
molefool
Table of Contents

Core Idea
#

In E9 workflow forms, submit validation should start from the submit event, not from a button.

Use WfForm.registerCheckEvent(WfForm.OPER_SUBMIT, fn) to intercept the submit action. Inside the callback, call the interface asynchronously. Call callback() only when the business validation passes. If the interface has no response, returns an incomplete structure, or returns a business error, do not call callback(); the submit action stays blocked.

The response path is:

code-block JS -> /api/esb/oa/execute -> interface -> application -> event -> data.responsebody -> code-block JS

The important pitfall is that in E9, if the HTTP layer, OA interface layer, application layer, or event layer fails directly, the code block usually cannot reliably read data.responsebody. Design the interface to “always return successfully” at the transport/framework level, then put business failures into responsebody.

Submit Guard Template
#

jQuery(function () {
  var submitting = false;

  WfForm.registerCheckEvent(WfForm.OPER_SUBMIT, function (callback) {
    if (submitting) {
      WfForm.showMessage("Validation is running. Please do not submit repeatedly.", 2, 5);
      return;
    }

    submitting = true;

    var payload = buildPayload();

    if (!payload.lines.length) {
      submitting = false;
      WfForm.showMessage("No rows to submit.", 2, 5);
      return;
    }

    jQuery.ajax({
      url: "/api/esb/oa/execute",
      type: "POST",
      dataType: "json",
      timeout: 120000,
      cache: false,
      data: {
        eventkey: "EVENT_KEY",
        params: JSON.stringify(payload)
      },
      success: function (res) {
        if (!hasValidResponseBody(res)) {
          WfForm.showMessage("The interface did not return a valid validation result.", 2, 8);
          return;
        }

        if (hasBusinessError(res)) {
          writeErrorToDetail(res);
          WfForm.showMessage("Validation failed. Check the detail-row remarks.", 2, 8);
          return;
        }

        WfForm.showMessage("Validation passed. Continue submitting.", 3, 5);
        callback();
      },
      error: function () {
        WfForm.showMessage("Interface call failed. Contact the administrator.", 2, 8);
      },
      complete: function () {
        submitting = false;
      }
    });
  });
});

Build the Request Payload
#

Read main-field values directly. For detail rows, first list row indexes, then build the array required by the interface.

function rowField(fieldId, rowIndex) {
  return fieldId + "_" + rowIndex;
}

function getValue(fieldMark) {
  return WfForm.getFieldValue(fieldMark) || "";
}

function getRows(detailMark) {
  var rowStr = WfForm.getDetailAllRowIndexStr(detailMark) || "";
  return rowStr ? rowStr.split(",").filter(Boolean) : [];
}

function buildPayload() {
  var detailMark = "detail_1";
  var mainType = WfForm.convertFieldNameToId("main_type");
  var lineNo = WfForm.convertFieldNameToId("line_no", detailMark);
  var itemCode = WfForm.convertFieldNameToId("item_code", detailMark);
  var qty = WfForm.convertFieldNameToId("qty", detailMark);

  var rows = getRows(detailMark);
  var lines = [];

  for (var i = 0; i < rows.length; i++) {
    lines.push({
      line: getValue(rowField(lineNo, rows[i])),
      itemCode: getValue(rowField(itemCode, rows[i])),
      qty: getValue(rowField(qty, rows[i]))
    });
  }

  return {
    type: getValue(mainType),
    lines: lines
  };
}

Parse the Response
#

The code block should only depend on data.responsebody. It may be an array or a single object, so normalize it first.

function normalizeList(value) {
  if (!value) return [];
  return jQuery.isArray(value) ? value : [value];
}

function getResponseBodyList(res) {
  if (!res || !res.data || res.data.responsebody == null) {
    return [];
  }
  return normalizeList(res.data.responsebody);
}

function hasValidResponseBody(res) {
  return getResponseBodyList(res).length > 0;
}

function getBodyLine(item) {
  return jQuery.trim(String(item.line || item.LINE_NO || item.WMS_LINE_ID || ""));
}

function getBodyMsg(item) {
  return jQuery.trim(String(item.msg || item.message || item.PROCESS_MESSAGE || ""));
}

function getBodyStatus(item) {
  return jQuery.trim(String(item.status || item.STATUS || "")).toUpperCase();
}

function isErrorItem(item) {
  var msg = getBodyMsg(item);
  var status = getBodyStatus(item);
  return !!msg || (!!status && status !== "S");
}

function hasBusinessError(res) {
  var list = getResponseBodyList(res);
  for (var i = 0; i < list.length; i++) {
    if (isErrorItem(list[i] || {})) {
      return true;
    }
  }
  return false;
}

Write Errors Back to Detail Rows
#

The interface should return a stable row key for each failed row. The code block maps that key back to the current OA detail row and writes the message into a remark field.

function writeErrorToDetail(res) {
  var detailMark = "detail_1";
  var lineNo = WfForm.convertFieldNameToId("line_no", detailMark);
  var remark = WfForm.convertFieldNameToId("remark", detailMark);
  var rows = getRows(detailMark);
  var rowMap = {};
  var data = {};

  for (var i = 0; i < rows.length; i++) {
    rowMap[getValue(rowField(lineNo, rows[i]))] = rows[i];
  }

  var list = getResponseBodyList(res);
  for (var j = 0; j < list.length; j++) {
    var item = list[j] || {};
    if (!isErrorItem(item)) continue;

    var rowIndex = rowMap[getBodyLine(item)];
    if (rowIndex == null) continue;

    data[rowField(remark, rowIndex)] = {
      value: getBodyMsg(item) || "Validation failed"
    };
  }

  WfForm.changeMoreField(data);
}

Interface Response Contract
#

Do not express business failure through exceptions, HTTP 500, or event execution failure. The code block needs a response shape that reliably reaches the success callback.

Recommended shape:

{
  "code": "100",
  "data": {
    "responsebody": [
      {
        "line": "OA-202607030001-1",
        "status": "S",
        "msg": ""
      },
      {
        "line": "OA-202607030001-2",
        "status": "E",
        "msg": "Insufficient stock"
      }
    ]
  }
}

Decision rules:

  • Only treat the interface result as available when data.responsebody exists.
  • A row passes when status is empty or S and msg is empty.
  • A row fails when status is not S or msg is not empty.
  • Missing structure, interface error, and timeout are all treated as failed validation; do not continue submit.

Notes
#

  • registerCheckEvent performs async validation; call callback() only when the business result passes.
  • Do not continue submit in the Ajax error branch; the response path is already unreliable there.
  • The interface, application, and event layers should catch exceptions and return a successful wrapper, with business errors in responsebody.
  • Match detail errors by a stable row key, not by array order, because users may add or delete detail rows.
  • Use a submitting flag to prevent repeated submit clicks.
  • If writing back a detail field should trigger linkage, call WfForm.triggerFieldAllLinkage(fieldMark) after WfForm.changeMoreField.

Related