diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/NoCodeWidgetRenderer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/NoCodeWidgetRenderer.java index a51b23f5..aceeb26a 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/NoCodeWidgetRenderer.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/NoCodeWidgetRenderer.java @@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.actions.dashboard.widgets; import java.io.Serializable; import java.util.HashMap; +import java.util.List; import java.util.Map; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.logging.QLogger; @@ -35,6 +36,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.AbstractWi import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.AbstractWidgetValueSource; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.QNoCodeWidgetMetaData; import com.kingsrook.qqq.backend.core.modules.backend.implementations.utils.BackendQueryFilterUtils; +import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair; @@ -55,18 +57,12 @@ public class NoCodeWidgetRenderer extends AbstractWidgetRenderer { QNoCodeWidgetMetaData widgetMetaData = (QNoCodeWidgetMetaData) input.getWidgetMetaData(); - //////////////////////////////////////////// - // build context by evaluating all values // - //////////////////////////////////////////// - Map context = new HashMap<>(); - context.put("utils", new NoCodeWidgetVelocityUtils(context, input)); - context.put("input", input); - - for(Map.Entry entry : input.getQueryParams().entrySet()) - { - context.put(entry.getKey(), entry.getValue()); - } + Map context = initContext(input); + context.putAll(input.getQueryParams()); + /////////////////////////////////////////////// + // populate context by evaluating all values // + /////////////////////////////////////////////// for(AbstractWidgetValueSource valueSource : widgetMetaData.getValues()) { try @@ -86,8 +82,34 @@ public class NoCodeWidgetRenderer extends AbstractWidgetRenderer ///////////////////////////////////////////// // build content by evaluating all outputs // ///////////////////////////////////////////// + List outputs = widgetMetaData.getOutputs(); + String content = renderOutputs(context, outputs); + + return (new RenderWidgetOutput(new RawHTML(widgetMetaData.getLabel(), content))); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public Map initContext(RenderWidgetInput input) + { + Map context = new HashMap<>(); + context.put("utils", new NoCodeWidgetVelocityUtils(context, input)); + context.put("input", input); + return context; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public String renderOutputs(Map context, List outputs) throws QException + { StringBuilder content = new StringBuilder(); - for(AbstractWidgetOutput output : widgetMetaData.getOutputs()) + for(AbstractWidgetOutput output : CollectionUtils.nonNullList(outputs)) { boolean conditionPassed = true; if(output.getCondition() != null) @@ -106,8 +128,7 @@ public class NoCodeWidgetRenderer extends AbstractWidgetRenderer LOG.trace("Condition failed - not rendering this output."); } } - - return (new RenderWidgetOutput(new RawHTML(widgetMetaData.getLabel(), content.toString()))); + return (content.toString()); } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/NoCodeWidgetVelocityUtils.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/NoCodeWidgetVelocityUtils.java index f79d3213..1b01e702 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/NoCodeWidgetVelocityUtils.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/NoCodeWidgetVelocityUtils.java @@ -277,4 +277,24 @@ public class NoCodeWidgetVelocityUtils { return String.valueOf(input.setScale(digits, RoundingMode.HALF_UP)); } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public Object ifElse(Object ifObject, Object elseObject) + { + if(StringUtils.hasContent(ValueUtils.getValueAsString(ifObject))) + { + return (ifObject); + } + else if(StringUtils.hasContent(ValueUtils.getValueAsString(elseObject))) + { + return (elseObject); + } + + return (""); + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/processes/RunProcessAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/processes/RunProcessAction.java index 43225d02..3aad0ffd 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/processes/RunProcessAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/processes/RunProcessAction.java @@ -31,6 +31,7 @@ import java.util.Map; import java.util.Optional; import java.util.UUID; import com.kingsrook.qqq.backend.core.actions.ActionHelper; +import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.NoCodeWidgetRenderer; import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction; @@ -50,7 +51,9 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput; import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.processes.NoCodeWidgetFrontendComponentMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData; @@ -154,6 +157,7 @@ public class RunProcessAction { LOG.trace("Breaking process [" + process.getName() + "] at frontend step (as requested by caller): " + step.getName()); processFrontendStepFieldDefaultValues(processState, frontendStep); + processFrontendComponents(processState, frontendStep); processState.setNextStepName(step.getName()); break STEP_LOOP; } @@ -231,6 +235,26 @@ public class RunProcessAction + /******************************************************************************* + ** + *******************************************************************************/ + private void processFrontendComponents(ProcessState processState, QFrontendStepMetaData frontendStep) throws QException + { + for(QFrontendComponentMetaData component : CollectionUtils.nonNullList(frontendStep.getComponents())) + { + if(component instanceof NoCodeWidgetFrontendComponentMetaData noCodeWidgetComponent) + { + NoCodeWidgetRenderer noCodeWidgetRenderer = new NoCodeWidgetRenderer(); + Map context = noCodeWidgetRenderer.initContext(null); + context.putAll(processState.getValues()); + String html = noCodeWidgetRenderer.renderOutputs(context, noCodeWidgetComponent.getOutputs()); + processState.getValues().put(frontendStep.getName() + ".html", html); + } + } + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QCustomPossibleValueProvider.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QCustomPossibleValueProvider.java index 5f2cdc17..2d7cf7c7 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QCustomPossibleValueProvider.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QCustomPossibleValueProvider.java @@ -23,7 +23,14 @@ package com.kingsrook.qqq.backend.core.actions.values; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceInput; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue; +import com.kingsrook.qqq.backend.core.utils.CollectionUtils; +import com.kingsrook.qqq.backend.core.utils.StringUtils; +import com.kingsrook.qqq.backend.core.utils.ValueUtils; /******************************************************************************* @@ -32,12 +39,65 @@ import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleVal *******************************************************************************/ public interface QCustomPossibleValueProvider { - /******************************************************************************* ** *******************************************************************************/ QPossibleValue getPossibleValue(Serializable idValue); - // todo - get/search list of possible values + /******************************************************************************* + ** + *******************************************************************************/ + List> search(SearchPossibleValueSourceInput input) throws QException; + + /******************************************************************************* + ** The input list of ids might come through as a type that isn't the same as + ** the type of the ids in the enum (e.g., strings from a frontend, integers + ** in an enum). So, this method looks maps a list of input ids to the requested type. + *******************************************************************************/ + default List convertInputIdsToIdType(Class type, List inputIdList) + { + List rs = new ArrayList<>(); + if(CollectionUtils.nullSafeIsEmpty(inputIdList)) + { + return (rs); + } + + for(Serializable serializable : inputIdList) + { + rs.add(ValueUtils.getValueAsType(type, serializable)); + } + + return (rs); + } + + + /******************************************************************************* + ** + *******************************************************************************/ + default boolean doesPossibleValueMatchSearchInput(Class idType, QPossibleValue possibleValue, SearchPossibleValueSourceInput input) + { + List idsInType = convertInputIdsToIdType(idType, input.getIdList()); + + boolean match = false; + if(input.getIdList() != null) + { + if(idsInType.contains(possibleValue.getId())) + { + match = true; + } + } + else + { + if(StringUtils.hasContent(input.getSearchTerm())) + { + match = possibleValue.getLabel().toLowerCase().startsWith(input.getSearchTerm().toLowerCase()); + } + else + { + match = true; + } + } + return match; + } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/SearchPossibleValueSourceAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/SearchPossibleValueSourceAction.java index 0a6ad024..c034e1f7 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/SearchPossibleValueSourceAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/SearchPossibleValueSourceAction.java @@ -26,6 +26,7 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader; import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.logging.QLogger; @@ -275,8 +276,12 @@ public class SearchPossibleValueSourceAction { try { - // QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource); - // return (formatPossibleValue(possibleValueSource, customPossibleValueProvider.getPossibleValue(value))); + QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource); + List> possibleValues = customPossibleValueProvider.search(input); + + SearchPossibleValueSourceOutput output = new SearchPossibleValueSourceOutput(); + output.setResults(possibleValues); + return (output); } catch(Exception e) { diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/values/SearchPossibleValueSourceInput.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/values/SearchPossibleValueSourceInput.java index 6d4b27f0..65b7c2bc 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/values/SearchPossibleValueSourceInput.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/values/SearchPossibleValueSourceInput.java @@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.actions.values; import java.io.Serializable; +import java.util.ArrayList; import java.util.List; import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; @@ -31,7 +32,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; /******************************************************************************* ** Input for the Search possible value source action *******************************************************************************/ -public class SearchPossibleValueSourceInput extends AbstractActionInput +public class SearchPossibleValueSourceInput extends AbstractActionInput implements Cloneable { private String possibleValueSourceName; private QQueryFilter defaultQueryFilter; @@ -52,6 +53,33 @@ public class SearchPossibleValueSourceInput extends AbstractActionInput + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public SearchPossibleValueSourceInput clone() + { + try + { + SearchPossibleValueSourceInput clone = (SearchPossibleValueSourceInput) super.clone(); + if(defaultQueryFilter != null) + { + clone.setDefaultQueryFilter(defaultQueryFilter.clone()); + } + if(idList != null) + { + clone.setIdList(new ArrayList<>(idList)); + } + return clone; + } + catch(CloneNotSupportedException e) + { + throw new AssertionError(); + } + } + + + /******************************************************************************* ** Getter for possibleValueSourceName ** @@ -253,5 +281,4 @@ public class SearchPossibleValueSourceInput extends AbstractActionInput this.limit = limit; return (this); } - } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/nocode/HtmlWrapper.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/nocode/HtmlWrapper.java index 26aeeec8..50ea0e14 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/nocode/HtmlWrapper.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/nocode/HtmlWrapper.java @@ -23,7 +23,9 @@ package com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode; import java.io.Serializable; +import java.util.Arrays; import java.util.Objects; +import com.kingsrook.qqq.backend.core.utils.StringUtils; /******************************************************************************* @@ -43,6 +45,13 @@ public class HtmlWrapper implements Serializable
""", ""); + public static final String STYLE_BIG_CENTERED = "font-size: 2rem; font-weight: 400; line-height: 1.625; text-align: center; padding-bottom: 8px; "; + public static final String STYLE_MEDIUM_CENTERED = "font-size: 1.5rem; font-weight: 400; line-height: 1.625; text-align: center; padding-bottom: 4px; "; + public static final String STYLE_INDENT_1 = "padding-left: 1rem; "; + public static final String STYLE_INDENT_2 = "padding-left: 2rem; "; + public static final String STYLE_FLOAT_RIGHT = "float: right; "; + public static final String STYLE_RED = "color: red; "; + /******************************************************************************* @@ -56,6 +65,27 @@ public class HtmlWrapper implements Serializable + /******************************************************************************* + ** + *******************************************************************************/ + public static HtmlWrapper divWithStyles(String... styles) + { + String style = StringUtils.join("", Arrays.asList(styles)); + return (new HtmlWrapper("
", "
")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static HtmlWrapper width(String amount) + { + return (new HtmlWrapper("
", "
")); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -66,6 +96,16 @@ public class HtmlWrapper implements Serializable + /******************************************************************************* + ** + *******************************************************************************/ + public static String styleWidth(String amount) + { + return ("width: " + amount); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -76,4 +116,47 @@ public class HtmlWrapper implements Serializable + Objects.requireNonNullElse(suffix, "") + "\n"); } + + + /******************************************************************************* + ** Getter for prefix + ** + *******************************************************************************/ + public String getPrefix() + { + return prefix; + } + + + + /******************************************************************************* + ** Setter for prefix + ** + *******************************************************************************/ + public void setPrefix(String prefix) + { + this.prefix = prefix; + } + + + + /******************************************************************************* + ** Getter for suffix + ** + *******************************************************************************/ + public String getSuffix() + { + return suffix; + } + + + + /******************************************************************************* + ** Setter for suffix + ** + *******************************************************************************/ + public void setSuffix(String suffix) + { + this.suffix = suffix; + } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/NoCodeWidgetFrontendComponentMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/NoCodeWidgetFrontendComponentMetaData.java new file mode 100644 index 00000000..9f0b1e05 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/NoCodeWidgetFrontendComponentMetaData.java @@ -0,0 +1,94 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.model.metadata.processes; + + +import java.util.ArrayList; +import java.util.List; +import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.AbstractWidgetOutput; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class NoCodeWidgetFrontendComponentMetaData extends QFrontendComponentMetaData +{ + private List outputs = new ArrayList<>(); + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public NoCodeWidgetFrontendComponentMetaData() + { + setType(QComponentType.HTML); + } + + + + /******************************************************************************* + ** Fluent setter to add a single output + *******************************************************************************/ + public NoCodeWidgetFrontendComponentMetaData withOutput(AbstractWidgetOutput output) + { + if(this.outputs == null) + { + this.outputs = new ArrayList<>(); + } + this.outputs.add(output); + return (this); + } + + + + /******************************************************************************* + ** Getter for outputs + *******************************************************************************/ + public List getOutputs() + { + return (this.outputs); + } + + + + /******************************************************************************* + ** Setter for outputs + *******************************************************************************/ + public void setOutputs(List outputs) + { + this.outputs = outputs; + } + + + + /******************************************************************************* + ** Fluent setter for outputs + *******************************************************************************/ + public NoCodeWidgetFrontendComponentMetaData withOutputs(List outputs) + { + this.outputs = outputs; + return (this); + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QComponentType.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QComponentType.java index 785d40dd..b9517d6d 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QComponentType.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QComponentType.java @@ -35,7 +35,8 @@ public enum QComponentType DOWNLOAD_FORM, RECORD_LIST, PROCESS_SUMMARY_RESULTS, - GOOGLE_DRIVE_SELECT_FOLDER; + GOOGLE_DRIVE_SELECT_FOLDER, + HTML; /////////////////////////////////////////////////////////////////////////// // keep these values in sync with QComponentType.ts in qqq-frontend-core // /////////////////////////////////////////////////////////////////////////// diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java index 7b6ab79b..c3b3b898 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java @@ -50,6 +50,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput; +import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceInput; import com.kingsrook.qqq.backend.core.model.automation.RecordAutomationInput; import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType; @@ -1093,6 +1094,17 @@ public class TestUtils { return (new QPossibleValue<>(idValue, "Custom[" + idValue + "]")); } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public List> search(SearchPossibleValueSourceInput input) + { + return (new ArrayList<>()); + } }