From d1e4091eb4f894e74e49f75b97356aeb7fc5b482 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Mon, 18 Mar 2024 12:28:23 -0500 Subject: [PATCH] CE-936 - Add support for managing associations on insert/edit screens, via childRecordList widget --- .../widgets/ChildRecordListRenderer.java | 127 ++++++++++++------ .../frontend/QFrontendWidgetMetaData.java | 15 +++ .../javalin/QJavalinImplementation.java | 23 +++- 3 files changed, 120 insertions(+), 45 deletions(-) diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/ChildRecordListRenderer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/ChildRecordListRenderer.java index b6f69d4c..e5b2e956 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/ChildRecordListRenderer.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/ChildRecordListRenderer.java @@ -137,7 +137,7 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer *******************************************************************************/ public Builder withCanAddChildRecord(boolean b) { - widgetMetaData.withDefaultValue("canAddChildRecord", true); + widgetMetaData.withDefaultValue("canAddChildRecord", b); return (this); } @@ -151,6 +151,17 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer widgetMetaData.withDefaultValue("disabledFieldsForNewChildRecords", new HashSet<>(disabledFieldsForNewChildRecords)); return (this); } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public Builder withManageAssociationName(String manageAssociationName) + { + widgetMetaData.withDefaultValue("manageAssociationName", manageAssociationName); + return (this); + } } @@ -178,52 +189,60 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer maxRows = ValueUtils.getValueAsInteger(input.getWidgetMetaData().getDefaultValues().containsKey("maxRows")); } - //////////////////////////////////////////////////////// - // fetch the record that we're getting children for. // - // e.g., the left-side of the join, with the input id // - //////////////////////////////////////////////////////// - GetInput getInput = new GetInput(); - getInput.setTableName(join.getLeftTable()); - getInput.setPrimaryKey(id); - GetOutput getOutput = new GetAction().execute(getInput); - QRecord record = getOutput.getRecord(); - - if(record == null) + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // fetch the record that we're getting children for. e.g., the left-side of the join, with the input id // + // but - only try this if we were given an id. note, this widget could be called for on an INSERT screen, where we don't have a record yet // + // but we still want to be able to return all the other data in here that otherwise comes from the widget meta data, join, etc. // + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + int totalRows = 0; + QRecord primaryRecord = null; + QQueryFilter filter = null; + QueryOutput queryOutput = new QueryOutput(new QueryInput()); + if(StringUtils.hasContent(id)) { - throw (new QNotFoundException("Could not find " + (leftTable == null ? "" : leftTable.getLabel()) + " with primary key " + id)); - } + GetInput getInput = new GetInput(); + getInput.setTableName(join.getLeftTable()); + getInput.setPrimaryKey(id); + GetOutput getOutput = new GetAction().execute(getInput); + primaryRecord = getOutput.getRecord(); - //////////////////////////////////////////////////////////////////// - // set up the query - for the table on the right side of the join // - //////////////////////////////////////////////////////////////////// - QQueryFilter filter = new QQueryFilter(); - for(JoinOn joinOn : join.getJoinOns()) - { - filter.addCriteria(new QFilterCriteria(joinOn.getRightField(), QCriteriaOperator.EQUALS, List.of(record.getValue(joinOn.getLeftField())))); - } - filter.setOrderBys(join.getOrderBys()); - filter.setLimit(maxRows); + if(primaryRecord == null) + { + throw (new QNotFoundException("Could not find " + (leftTable == null ? "" : leftTable.getLabel()) + " with primary key " + id)); + } - QueryInput queryInput = new QueryInput(); - queryInput.setTableName(join.getRightTable()); - queryInput.setShouldTranslatePossibleValues(true); - queryInput.setShouldGenerateDisplayValues(true); - queryInput.setFilter(filter); - QueryOutput queryOutput = new QueryAction().execute(queryInput); + //////////////////////////////////////////////////////////////////// + // set up the query - for the table on the right side of the join // + //////////////////////////////////////////////////////////////////// + filter = new QQueryFilter(); + for(JoinOn joinOn : join.getJoinOns()) + { + filter.addCriteria(new QFilterCriteria(joinOn.getRightField(), QCriteriaOperator.EQUALS, List.of(primaryRecord.getValue(joinOn.getLeftField())))); + } + filter.setOrderBys(join.getOrderBys()); + filter.setLimit(maxRows); - QValueFormatter.setBlobValuesToDownloadUrls(rightTable, queryOutput.getRecords()); + QueryInput queryInput = new QueryInput(); + queryInput.setTableName(join.getRightTable()); + queryInput.setShouldTranslatePossibleValues(true); + queryInput.setShouldGenerateDisplayValues(true); + queryInput.setFilter(filter); + queryOutput = new QueryAction().execute(queryInput); - int totalRows = queryOutput.getRecords().size(); - if(maxRows != null && (queryOutput.getRecords().size() == maxRows)) - { - ///////////////////////////////////////////////////////////////////////////////////// - // if the input said to only do some max, and the # of results we got is that max, // - // then do a count query, for displaying 1-n of // - ///////////////////////////////////////////////////////////////////////////////////// - CountInput countInput = new CountInput(); - countInput.setTableName(join.getRightTable()); - countInput.setFilter(filter); - totalRows = new CountAction().execute(countInput).getCount(); + QValueFormatter.setBlobValuesToDownloadUrls(rightTable, queryOutput.getRecords()); + + totalRows = queryOutput.getRecords().size(); + if(maxRows != null && (queryOutput.getRecords().size() == maxRows)) + { + ///////////////////////////////////////////////////////////////////////////////////// + // if the input said to only do some max, and the # of results we got is that max, // + // then do a count query, for displaying 1-n of // + ///////////////////////////////////////////////////////////////////////////////////// + CountInput countInput = new CountInput(); + countInput.setTableName(join.getRightTable()); + countInput.setFilter(filter); + totalRows = new CountAction().execute(countInput).getCount(); + } } String tablePath = input.getInstance().getTablePath(rightTable.getName()); @@ -239,10 +258,14 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer // new child records must have values from the join-ons // ////////////////////////////////////////////////////////// Map defaultValuesForNewChildRecords = new HashMap<>(); - for(JoinOn joinOn : join.getJoinOns()) + if(primaryRecord != null) { - defaultValuesForNewChildRecords.put(joinOn.getRightField(), record.getValue(joinOn.getLeftField())); + for(JoinOn joinOn : join.getJoinOns()) + { + defaultValuesForNewChildRecords.put(joinOn.getRightField(), primaryRecord.getValue(joinOn.getLeftField())); + } } + widgetData.setDefaultValuesForNewChildRecords(defaultValuesForNewChildRecords); Map widgetValues = input.getWidgetMetaData().getDefaultValues(); @@ -250,6 +273,22 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer { widgetData.setDisabledFieldsForNewChildRecords((Set) widgetValues.get("disabledFieldsForNewChildRecords")); } + else + { + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // if there are no disabled fields specified - then normally any fields w/ a default value get implicitly disabled // + // but - if we didn't look-up the primary record, then we'll want to explicit disable fields from joins // + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + if(primaryRecord == null) + { + Set implicitlyDisabledFields = new HashSet<>(); + widgetData.setDisabledFieldsForNewChildRecords(implicitlyDisabledFields); + for(JoinOn joinOn : join.getJoinOns()) + { + implicitlyDisabledFields.add(joinOn.getRightField()); + } + } + } } return (new RenderWidgetOutput(widgetData)); diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendWidgetMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendWidgetMetaData.java index 374650bf..4d5e5725 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendWidgetMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendWidgetMetaData.java @@ -22,6 +22,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.frontend; +import java.io.Serializable; import java.util.List; import java.util.Map; import com.fasterxml.jackson.annotation.JsonInclude; @@ -60,6 +61,7 @@ public class QFrontendWidgetMetaData protected Map icons; protected Map helpContent; + protected Map defaultValues; private final boolean hasPermission; @@ -95,6 +97,7 @@ public class QFrontendWidgetMetaData } this.helpContent = widgetMetaData.getHelpContent(); + this.defaultValues = widgetMetaData.getDefaultValues(); hasPermission = PermissionsHelper.hasWidgetPermission(actionInput, name); } @@ -274,4 +277,16 @@ public class QFrontendWidgetMetaData { return helpContent; } + + + + /******************************************************************************* + ** Getter for defaultValues + ** + *******************************************************************************/ + public Map getDefaultValues() + { + return defaultValues; + } + } diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java index 94ae0e71..b9b833b3 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java @@ -779,9 +779,30 @@ public class QJavalinImplementation { String fieldName = formParam.getKey(); List values = formParam.getValue(); + String value = values.get(0); + + if("associations".equals(fieldName) && StringUtils.hasContent(value)) + { + JSONObject associationsJSON = new JSONObject(value); + for(String key : associationsJSON.keySet()) + { + JSONArray associatedRecords = associationsJSON.getJSONArray(key); + for(int i = 0; i < associatedRecords.length(); i++) + { + QRecord associatedRecord = new QRecord(); + JSONObject recordJSON = associatedRecords.getJSONObject(i); + for(String k : recordJSON.keySet()) + { + associatedRecord.withValue(k, ValueUtils.getValueAsString(recordJSON.get(k))); + } + record.withAssociatedRecord(key, associatedRecord); + } + } + continue; + } + if(CollectionUtils.nullSafeHasContents(values)) { - String value = values.get(0); if(StringUtils.hasContent(value)) { record.setValue(fieldName, value);