feat: implement advanced bidirectional field referencing and cross-module path resolution
- Core Logic Enhancements:
- Implement bidirectional field referencing between <FIELDS>, <LAYOUT>, and <TITLES> tags in .frml files, enabling seamless navigation from definitions to usages and vice versa.
- Add robust support for AJAX-OPTION field mapping:
- SRC attribute: Links to field definitions within defs/ajax.xml datasets.
- TARGET attribute: Links to local field definitions within the same form.
- Implement global grid resolution: GRID-ID now searches across the current file and all recursively included files (<INCLUDE>).
- Enhance deep recursive search for fields/sections within nested tags like <SECTION>, <ROW>, and <FIELD-LIST>.
- Path Resolution & Helpers (DynFormPathUtils):
- Added support for module-relative paths starting with # (mapping to view/frm/).
- Added support for cross-module paths starting with / (mapping to WEB-INF/app/module/{module}/view/frm/).
- Implemented auto-correction for common keyboard typos (Thai 'ิ' instead of /).
- Added specialized helpers for locating ajax.xml and included files within the framework's structure.
- Smart Completion Enhancements:
- Added context-aware completion for TARGET and SRC fields in AJAX update-fields.
- Enabled global GRID-ID completion by scanning all included resources.
- Improved dataset completion to include both local and AJAX-defined datasets.
- Test Resources:
- Added a comprehensive set of real-world examples (bdgt04, bdgt05, bdgt06) in DevResources/full-examples/ to validate complex cross-module and master-detail scenarios.
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<FORMS xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="xsd/#dynf_form_def.xsd">
|
||||
<INCLUDES>
|
||||
<INCLUDE FILE="#grids/grid-construct-budget.frml"></INCLUDE>
|
||||
</INCLUDES>
|
||||
<DATASETS>
|
||||
<DATASET ID="DS-MASTER">
|
||||
<SCHEMA>APP</SCHEMA>
|
||||
<TABLENAME>PROJECTS</TABLENAME>
|
||||
<KEYFIELDS>PROJ_ID</KEYFIELDS>
|
||||
<SQL>
|
||||
<SELECT>SELECT PROJ.PROJ_ID
|
||||
, PROJ.PROJ_TYPE
|
||||
, PROJ.PROJ_YEAR
|
||||
, PROJ.PJM_CODE
|
||||
, PROJ.STM_CODE
|
||||
, PROJ.BTM_CODE
|
||||
, PROJ.ACM_CODE
|
||||
, BUD.GET_ACTIVITY(PROJ.ACM_CODE) ACM_NAME
|
||||
, PROJ.PLAN_TYPE
|
||||
, PROJ.PROJ_DURATION
|
||||
, PROJ.PROJ_START_YEAR
|
||||
, PROJ.PROJ_END_YEAR
|
||||
, PROJ.PROJ_FOR_STAFF
|
||||
|
||||
, PROJ.PROJ_FROM_YEAR
|
||||
, 0 BDGT_TOTAL
|
||||
</SELECT>
|
||||
<FROM>FROM PROJECTS PROJ
|
||||
INNER JOIN REFER_CODE RFC ON RFC.RFG_GRP='PROJ-TYPE' AND RFC.RFC_CODE=PROJ.PROJ_TYPE AND RFC.RFC_FLAG = 'GENERAL'
|
||||
</FROM>
|
||||
<FILTER>WHERE STM_CODE = :STM_CODE AND PROJ.PROJ_YEAR = :PROJ_YEAR</FILTER>
|
||||
<ORDER>ORDER BY RFC.RFC_ORDER, PROJ.PROJ_ID</ORDER>
|
||||
</SQL>
|
||||
<FIELDS>
|
||||
<FIELD NAME="PROJ_ID" TYPE="TEXT" LABEL="รหัสอ้างอิงโครงการ" WIDTH="32"/>
|
||||
<FIELD NAME="PROJ_TYPE" TYPE="TEXT" LABEL="ประเภทโครงการ" WIDTH="25"/>
|
||||
<FIELD NAME="PROJ_YEAR" TYPE="NUMBER" LABEL="ปีงบประมาณ" WIDTH="15"/>
|
||||
<FIELD NAME="PJM_CODE" TYPE="TEXT" LABEL="รหัสโครงการ (BUD)" WIDTH="20"/>
|
||||
<FIELD NAME="STM_CODE" TYPE="TEXT" LABEL="รหัสหน่วยงาน" WIDTH="10"/>
|
||||
<FIELD NAME="BTM_CODE" TYPE="TEXT" LABEL="รหัสประเภทงบประมาณ" WIDTH="10"/>
|
||||
<FIELD NAME="ACM_CODE" TYPE="TEXT" LABEL="รหัสกิจกรรม" WIDTH="20"/>
|
||||
<FIELD NAME="PLAN_TYPE" TYPE="TEXT" LABEL="ประเภทแผน" WIDTH="15"/>
|
||||
<FIELD NAME="PROJ_DURATION" TYPE="NUMBER" LABEL="ระยะเวลาดำเนินการ (วัน)" WIDTH="15"/>
|
||||
<FIELD NAME="PROJ_START_YEAR" TYPE="NUMBER" LABEL="ปีที่เริ่มโครงการ (ก่อสร้าง)" WIDTH="15"/>
|
||||
<FIELD NAME="PROJ_END_YEAR" TYPE="NUMBER" LABEL="ปีที่สิ้นสุดโครงการ (ก่อสร้าง)" WIDTH="15"/>
|
||||
<FIELD NAME="PROJ_FOR_STAFF" TYPE="NUMBER" LABEL="จำนวนบุคลากรที่รองรับ" WIDTH="15"/>
|
||||
|
||||
<FIELD NAME="PROJ_FROM_YEAR" TYPE="NUMBER" LABEL="ต่อเนื่องจากปี" WIDTH="15"/>
|
||||
<FIELD NAME="CREATE_BY" TYPE="TEXT" LABEL="ผู้สร้างรายการ" WIDTH="50"/>
|
||||
<FIELD NAME="CREATE_TIME" TYPE="DATE" LABEL="เวลาที่สร้างรายการ" WIDTH="0"/>
|
||||
<FIELD NAME="UPDATE_BY" TYPE="TEXT" LABEL="ผู้แก้ไขรายการล่าสุด" WIDTH="50"/>
|
||||
<FIELD NAME="UPDATE_TIME" TYPE="DATE" LABEL="เวลาที่แก้ไขรายการล่าสุด" WIDTH="0"/>
|
||||
</FIELDS>
|
||||
<SUBDATASETS>
|
||||
<SUBDATASET NAME="ACTIVITIES" DATASET-ID="DS-ACTIVITY-TREE" LINK-FIELDS="PROJ_ID"/>
|
||||
</SUBDATASETS>
|
||||
</DATASET>
|
||||
<DATASET ID="DS-ACTIVITY-TREE">
|
||||
<SCHEMA>APP</SCHEMA>
|
||||
<TABLENAME>V_ACTIVITY_TREE</TABLENAME>
|
||||
<SQL>
|
||||
<SELECT>
|
||||
SELECT PACT.PROJ_ID
|
||||
, PACT.PROJ_YEAR
|
||||
, PACT.PROJ_TYPE
|
||||
, PACT.PROJ_GROUP
|
||||
, ACTT.ACM_CODE
|
||||
, ACTT.ACM_NAME
|
||||
, ACTT.ACM_SEQ
|
||||
, ACTT.NODE_LEVEL+1 NODE_LEVEL
|
||||
, ACTT.MAIN_NODE
|
||||
, ACTT.ACM_UNIT
|
||||
, ACTT.ACM_START_YEAR
|
||||
, ACTT.ACM_END_YEAR
|
||||
, ACTT.NODE_TYPE
|
||||
, PACT.BDGT_COUNT
|
||||
, PACT.BDGT_TOTAL
|
||||
</SELECT>
|
||||
<FROM>
|
||||
FROM BGT.V_ACTIVITY_TREE ACTT
|
||||
INNER JOIN (SELECT DISTINCT ACM.ACM_CODE
|
||||
FROM BUD.ACTIVITY_M ACM
|
||||
INNER JOIN BUD.ACTIVITY_CTRL_H ACH ON ACM.ACM_CODE = ACH.ACH_CODE
|
||||
START WITH ACM.ACM_CODE IN (SELECT ACM_CODE FROM V_PROJECT_ACTIVITY PACT WHERE PACT.PROJ_ID = :PROJ_ID)
|
||||
CONNECT BY PRIOR ACH.ACH_CTRL_CODE = ACH.ACH_CODE) FLTR ON FLTR.ACM_CODE = ACTT.ACM_CODE
|
||||
LEFT OUTER JOIN V_PROJECT_ACTIVITY PACT ON PACT.ACM_CODE = ACTT.ACM_CODE AND PACT.PROJ_ID = :PROJ_ID
|
||||
</FROM>
|
||||
<ORDER>
|
||||
ORDER BY ACTT.ACM_SEQ
|
||||
</ORDER>
|
||||
</SQL>
|
||||
</DATASET>
|
||||
</DATASETS>
|
||||
|
||||
<FORM>
|
||||
<FORM_BROWSE DATAID="DS-MASTER">
|
||||
<HEADER NAVI="N" EDIT="N" ADD="N" DELETE="N" VIEW="N"/>
|
||||
<PAGESIZE>0</PAGESIZE>
|
||||
<FIELDS>
|
||||
<FIELD NAME="$itemno" LABEL="no" ALIGN="right" WIDTH="5em"/>
|
||||
<FIELD NAME="ACM_CODE" LABEL="project.const_code" ALIGN="left">
|
||||
<DATA-FORMATTER>
|
||||
<![CDATA[(value,row,idx)=>{return $(`<div class="col-24 row text-nowrap"><div class="col offset-${+(row.node_level)-1}">${value}</div></div>`);}]]>
|
||||
</DATA-FORMATTER>
|
||||
</FIELD>
|
||||
<FIELD NAME="ACM_NAME" LABEL="project.const_name" ALIGN="left">
|
||||
<DATA-FORMATTER>
|
||||
<![CDATA[(value,row,idx)=>{return $(`<div class="col-24 row text-nowrap"><div class="col offset-${+(row.node_level)-1}">${value}</div></div>`);}]]>
|
||||
</DATA-FORMATTER>
|
||||
</FIELD>
|
||||
<FIELD NAME="BDGT_COUNT" LABEL="pbdg.acm_count" ALIGN="right" WIDTH="10em">
|
||||
<DATA-FORMATTER>
|
||||
<![CDATA[(value, row, idx) => {
|
||||
if (row["node_type"] === "D") {
|
||||
return `<div class="col-24 text-nowrap">${formatNumber(value)} ${row["acm_unit"]}</div>`;
|
||||
// return formatNumber(value)+" "+row["acm_unit"];
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}]]>
|
||||
</DATA-FORMATTER>
|
||||
</FIELD>
|
||||
<FIELD NAME="BDGT_TOTAL" LABEL="project.bgt_amount" ALIGN="right" WIDTH="10em">
|
||||
<DATA-FORMATTER>
|
||||
<![CDATA[(value, row, idx) => {return formatNumber(value);}]]>
|
||||
</DATA-FORMATTER>
|
||||
</FIELD>
|
||||
<COMMAND-BUTTONS>
|
||||
<BUTTON NAME="btnEditor" CLASS="btn btn-warning btn-editor" ICON-CLASS="fa fa-edit white">
|
||||
<FILTER><![CDATA[({$ctx,row})=>{return ["D","P"].includes(row["node_type"]);}]]></FILTER>
|
||||
<EVENT ON="click"><![CDATA[
|
||||
({ev, row}) => {
|
||||
console.log(row);
|
||||
if (row.node_type === "D") {
|
||||
row.pageTitle = $("h4.page-title").html();
|
||||
$PageCtx.saveSessionData("edit",row);
|
||||
let proj = {proj_id: row.proj_id, acm_code: row.acm_code, type: row.proj_type.toLowerCase()};
|
||||
let data = base64(JSON.stringify(proj));
|
||||
let editUrl = `/bdgt05/bgt0501010-general?edit&data=${data}`;
|
||||
$WebNavi.goto(editUrl);
|
||||
}
|
||||
}
|
||||
]]></EVENT>
|
||||
</BUTTON>
|
||||
<BUTTON NAME="btnViewer" CLASS="btn btn-primary btn-viewer" ICON-CLASS="fa fa-file-alt white">
|
||||
<FILTER><![CDATA[({$ctx,row})=>{return ["D","P"].includes(row["node_type"]);}]]></FILTER>
|
||||
<EVENT ON="click"><![CDATA[({ev,row})=>{ console.log(row);}]]></EVENT>
|
||||
</BUTTON>
|
||||
</COMMAND-BUTTONS>
|
||||
</FIELDS>
|
||||
<FILTERS AUTO-APPLY="N" ALLOW-NO-FILTER="Y">
|
||||
<FIELDS>
|
||||
<FIELD NAME="PROJ_YEAR" CAPTION="project.year" INPUTTYPE="TEXT" READONLY="Y"/>
|
||||
</FIELDS>
|
||||
<LAYOUT>
|
||||
<ROW>
|
||||
<FIELD NAME="PROJ_YEAR" LAYOUT_WIDTH="12" VAL_WIDTH="5" CAPT_WIDTH="14"/>
|
||||
</ROW>
|
||||
</LAYOUT>
|
||||
</FILTERS>
|
||||
<FOOTER SHOW="Y"/>
|
||||
<BUTTONS>
|
||||
<BUTTON NAME="btnButton1" CAPTION="ส่งกลับแก้ไข" CLASS="btn btn-outline-warning" SECTION="RIGHT"/>
|
||||
<BUTTON NAME="btnButton2" CAPTION="ส่ง ..... พิจารณา ต่อไป" CLASS="btn btn-warning" SECTION="RIGHT"/>
|
||||
</BUTTONS>
|
||||
<SCRIPT>
|
||||
<EVENTS>
|
||||
<AFTER-LOAD>
|
||||
<![CDATA[
|
||||
($ctx,data)=>{
|
||||
let src = [...data]; // clone data to src
|
||||
data.splice(0); // clear data element
|
||||
|
||||
let id = 1;
|
||||
for (let item of src) {
|
||||
item["node_type"] = "P";
|
||||
item["node_level"] = "0";
|
||||
item["$itemno"] = id++;
|
||||
data.push(item);
|
||||
if (isArray(item.activities)) {
|
||||
for (let sitem of item.activities) {
|
||||
sitem["$itemno"] = id++;
|
||||
data.push(sitem);
|
||||
}
|
||||
}
|
||||
}
|
||||
// console.log(data);
|
||||
}
|
||||
]]>
|
||||
</AFTER-LOAD>
|
||||
</EVENTS>
|
||||
|
||||
<INITIALIZE>
|
||||
<![CDATA[
|
||||
{
|
||||
// console.log("init activity grid");
|
||||
const fieldFormat = (value,row) => {
|
||||
if (row["node_type"] === "P") {
|
||||
return "";
|
||||
}
|
||||
return formatNumber(value, 0);
|
||||
}
|
||||
const summary = (field, data) => {
|
||||
let total = 0;
|
||||
for (let id=0; id < data.length; id++) {
|
||||
let row = data[id];
|
||||
if (row["node_type"] === "D") {
|
||||
total += (+row[field]) || 0;
|
||||
}
|
||||
}
|
||||
if ($PageCtx.$dataGrid.load && !$PageCtx.$dataGrid.reload) {
|
||||
$PageCtx.$dataGrid.reload = true;
|
||||
for (let id=data.length-1; id>=0; id--) {
|
||||
let row = data[id];
|
||||
if (row["node_type"] === "C") {
|
||||
let grpTotal = 0;
|
||||
let mainNode = row["acm_code"];
|
||||
let nodes = [];
|
||||
nodes.push($$("main_node="+mainNode,data));
|
||||
for (let node of nodes.flat()) {
|
||||
grpTotal += +node[field];
|
||||
}
|
||||
data[id][field] = grpTotal;
|
||||
}
|
||||
}
|
||||
$PageCtx.$dataGrid.load(data);
|
||||
$PageCtx.$dataGrid.reload = false;
|
||||
}
|
||||
return formatNumber(total, 0);
|
||||
}
|
||||
|
||||
const $ctx = $PageCtx;
|
||||
const gridFields = [$$("field=bdgt_total", $ctx.gridColumn.flat())].flat();
|
||||
for (let field of gridFields) {
|
||||
field.formatter = fieldFormat;
|
||||
field.footerFormatter = function (data) {
|
||||
return summary(this.field, data);
|
||||
}
|
||||
}
|
||||
|
||||
$ctx.rowStyle = function (row, index) {
|
||||
if (row["node_type"] === "C") {
|
||||
return {classes : `group-level-${row["node_level"]}`};
|
||||
}
|
||||
if (row["node_type"] === "P") {
|
||||
return {classes : `group-level-0`};
|
||||
}
|
||||
return {}; // Default style for other rows
|
||||
}
|
||||
}
|
||||
]]>
|
||||
</INITIALIZE>
|
||||
</SCRIPT>
|
||||
|
||||
|
||||
</FORM_BROWSE>
|
||||
</FORM>
|
||||
</FORMS>
|
||||
Reference in New Issue
Block a user