diff --git a/base-component/tools/screen/System/LogViewer.xml b/base-component/tools/screen/System/LogViewer.xml index fbd0ba730..8ec77b842 100644 --- a/base-component/tools/screen/System/LogViewer.xml +++ b/base-component/tools/screen/System/LogViewer.xml @@ -112,7 +112,7 @@ along with this software (see the LICENSE.md file). If not, see - + diff --git a/base-component/tools/screen/System/Visit/VisitList.xml b/base-component/tools/screen/System/Visit/VisitList.xml index 3ca3409e8..2297c13a8 100644 --- a/base-component/tools/screen/System/Visit/VisitList.xml +++ b/base-component/tools/screen/System/Visit/VisitList.xml @@ -43,6 +43,10 @@ along with this software (see the LICENSE.md file). If not, see + + + + @@ -70,7 +74,7 @@ along with this software (see the LICENSE.md file). If not, see - + diff --git a/base-component/tools/screen/Tools/Entity/SqlScriptRunner.xml b/base-component/tools/screen/Tools/Entity/SqlScriptRunner.xml new file mode 100644 index 000000000..3c5797440 --- /dev/null +++ b/base-component/tools/screen/Tools/Entity/SqlScriptRunner.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base-component/tools/screen/Tools/Service/ServiceLoadRunner.xml b/base-component/tools/screen/Tools/Service/ServiceLoadRunner.xml new file mode 100644 index 000000000..04bd02da6 --- /dev/null +++ b/base-component/tools/screen/Tools/Service/ServiceLoadRunner.xml @@ -0,0 +1,488 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +

All delay, run, etc times are in milliseconds (ms), rates in runs per second

+ + + + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + + + +
+
Exec Index {{executorInfo.execIndex}}
+
Total Threads {{executorInfo.totalThreads}}
+
Active Threads {{executorInfo.activeThreads}}
+
Running {{executorInfo.running}}
+ +
+ CPU Load {{serverStatus.System.Load}} (avg last minute)
+
+ {{serverStatus.System.CPU}} {{serverStatus.System.Processors}} threads
+
+ Heap RAM {{serverStatus.Heap.Used}}/{{serverStatus.Heap.Committed}}/{{serverStatus.Heap.Max}}
+
+ Java GC {{serverStatus.JavaStats.GcCount}} GCs {{serverStatus.JavaStats.GcTimeSeconds}}s
+
+
+
+
+
Service & Parameters
+
Began
+
Target
+
Current
+
Run Delay
+
Ramp Delay
+
Bin Length
+
Bins Keep
+
Run Count
+
Error Count
+
Time Average
+
Std Dev
+
Min
+
Max
+
Runs/Sec Avg
+
Runs/Sec Time
+
Busy Pct
+
Last Run
+
+
+
+
+
+
{{serviceInfo.serviceName}}
+
{{serviceInfo.parametersExpr}}
+
+
{{moqui.format(serviceInfo.beginTime, null, 'time')}} - {{(Date.now() - serviceInfo.beginTime)/1000}}s
+
{{serviceInfo.targetThreads}}
+
{{serviceInfo.currentThreads}}
+
{{serviceInfo.runDelayMs}}
+
{{serviceInfo.rampDelayMs}}
+
{{serviceInfo.timeBinLength}}
+
{{serviceInfo.timeBinsKeep}}
+
{{serviceInfo.runCount}}
+
{{serviceInfo.errorCount}}
+
{{moqui.format(serviceInfo.timeAverage, null, "bigdecimal")}}
+
{{moqui.format(serviceInfo.timeStdDev, null, "bigdecimal")}}
+
{{serviceInfo.minTime}}
+
{{serviceInfo.maxTime}}
+
{{moqui.format(serviceInfo.runsPerSecAvg, null, "bigdecimal")}}
+
{{moqui.format(serviceInfo.runsPerSecTime, null, "bigdecimal")}}
+
{{moqui.format(serviceInfo.busyPercent, null, "bigdecimal")}}
+
{{serviceInfo.lastRunTimeStr}}
+
+
+
+ +
+
+

Average Run Time

+ +
+
+

Runs per Second

+ +
+
+

Entity Finds per Second

+ +
+
+

Entity Writes per Second

+ +
+
+ +
+
+
+
Service & Parameters
+
+
Entity One
+
Entity List
+
Entity Iterator
+
Entity Count
+
Entity Create
+
Entity Update
+
Entity Delete
+
Service View
+
Service Other
+
+
+
+
+ +
+
+
+ +
+ +
{{JSON.stringify(serviceInfo.lastResult, null, 2)}}
+
+
+ + ]]>
Only supported in the 'qvt' render mode, go to the same URL under /qapps (instead of /apps or /vapps)

+ ]]>
+
+
diff --git a/base-component/tools/screen/Tools/StatusFlows.xml b/base-component/tools/screen/Tools/StatusFlows.xml new file mode 100644 index 000000000..311b60d81 --- /dev/null +++ b/base-component/tools/screen/Tools/StatusFlows.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base-component/tools/screen/Tools/dashboard.xml b/base-component/tools/screen/Tools/dashboard.xml index d45b78de6..222e29bf2 100644 --- a/base-component/tools/screen/Tools/dashboard.xml +++ b/base-component/tools/screen/Tools/dashboard.xml @@ -31,6 +31,7 @@ along with this software (see the LICENSE.md file). If not, see + @@ -46,21 +47,21 @@ along with this software (see the LICENSE.md file). If not, see + text="${childResource.displayName ?: childResource.description ?: childResource.name} (${childResource.childMethods})" + url-type="plain" link-type="anchor" target-window="_blank"/> + target-window="_blank" text="${resource.displayName ?: resource.name} (${resource.childMethods})" + tooltip="${resource.description ?: ''}"/> + text="${masterEntityName}" link-type="anchor" target-window="_blank"/> diff --git a/base-component/webroot/build.gradle b/base-component/webroot/build.gradle index 5229a54d1..dafb8bd9a 100644 --- a/base-component/webroot/build.gradle +++ b/base-component/webroot/build.gradle @@ -20,8 +20,8 @@ buildscript { gradlePluginPortal() } dependencies { - classpath 'com.github.ben-manes:gradle-versions-plugin:0.42.0' - classpath "org.gradle-webtools.minify:gradle-minify-plugin:1.3.1" + classpath 'com.github.ben-manes:gradle-versions-plugin:0.47.0' + classpath "org.gradle-webtools.minify:gradle-minify-plugin:1.3.2" } } apply plugin: "org.gradlewebtools.minify" @@ -88,12 +88,12 @@ def fileMap = [ "typeahead.js/0.11.1/typeahead.jquery.min.js":"", - "vue/2.6.10/vue.js":"", - "vue/2.6.10/vue.min.js":"" + "vue/2.7.14/vue.js":"", + "vue/2.7.14/vue.min.js":"" ] def jsdelivrBase = "https://cdn.jsdelivr.net/npm/" -def jsdelivrList = ["quasar@1.15.20/dist/quasar.min.css", "quasar@1.15.20/dist/quasar.umd.min.js"] +def jsdelivrList = ["quasar@1.22.10/dist/quasar.min.css", "quasar@1.22.10/dist/quasar.umd.min.js"] String getTargetPath(String sourcePath, String targetPath) { if (!targetPath) { diff --git a/base-component/webroot/screen/includes/WebrootVue.qvt.ftl b/base-component/webroot/screen/includes/WebrootVue.qvt.ftl index 004000b06..5a18b5ad2 100644 --- a/base-component/webroot/screen/includes/WebrootVue.qvt.ftl +++ b/base-component/webroot/screen/includes/WebrootVue.qvt.ftl @@ -99,7 +99,11 @@ along with this software (see the LICENSE.md file). If not, see <#-- NOTE: don't use v-html for histItem.message, may contain input repeated back so need to encode for security (make sure scripts not run, etc) --> + <#-- TODO: histItem.icon see https://v1.quasar.dev/vue-components/banner--> {{histItem.time}} {{histItem.message}} + diff --git a/base-component/webroot/screen/webroot/Login.ftl b/base-component/webroot/screen/webroot/Login.ftl index 7dcb811f0..43444317a 100644 --- a/base-component/webroot/screen/webroot/Login.ftl +++ b/base-component/webroot/screen/webroot/Login.ftl @@ -39,15 +39,15 @@ <#-- people know what to do

${ec.l10n.localize("Enter your username and password to sign in")}

--> <#-- not needed for this request: --> - disabled="disabled" - required="required" class="form-control top" id="login_form_username" + required="required" class="form-control top" placeholder="${ec.l10n.localize("Username")}" aria-label="${ec.l10n.localize("Username")}"> <#-- secondFactorRequired will only be set if a user is pre-authenticated, and in that case password not required again --> <#if secondFactorRequired> - + <#else> @@ -56,16 +56,16 @@ <#if expiredCredentials>

WARNING: Your password has expired

<#if passwordChangeRequired>

WARNING: Password change required

-
@@ -74,18 +74,19 @@

${ec.l10n.localize("Enter details to change your password")}

- disabled="disabled" + required="required" class="form-control top" placeholder="${ec.l10n.localize("Username")}" aria-label="${ec.l10n.localize("Username")}"> <#-- secondFactorRequired will only be set if a user is pre-authenticated, and in that case password not required again --> <#if secondFactorRequired> - + <#else> + placeholder="${ec.l10n.localize("Old Password")}" aria-label="${ec.l10n.localize("Old Password")}"> <#-- FUTURE: fancy JS to validate PW as it is entered or on blur --> + if (tabName === "login") { $("#login_form_code").focus(); } + else if (tabName === "change") { $("#change_form_code").focus(); } + else if (tabName === "reset") { $("#reset_form_username").focus(); } + <#else> + if (tabName === "login") { $("#login_form_username").focus(); } + else if (tabName === "change") { $("#change_form_username").focus(); } + else if (tabName === "reset") { $("#reset_form_username").focus(); } + }); $('a[href="' + (location.hash || '${initialTab!"#login"}') + '"]').tab('show'); }) diff --git a/base-component/webroot/screen/webroot/css/WebrootVue.qvt.css b/base-component/webroot/screen/webroot/css/WebrootVue.qvt.css index 99099e090..1791f7a61 100644 --- a/base-component/webroot/screen/webroot/css/WebrootVue.qvt.css +++ b/base-component/webroot/screen/webroot/css/WebrootVue.qvt.css @@ -21,7 +21,7 @@ h6, .text-h6 { font-size:1.0rem; line-height:1.0rem; letter-spacing:initial; mar p { margin-bottom:8px; } /* orig 16px */ pre { text-wrap: normal; white-space: pre-line; word-wrap: normal; word-break: normal; } -body.dev #top { background: #0c1b29!important; } +body.dev #top { background: #5d5d5d!important; } body.test #top { background: #224422!important; } /* example for header that changes light and dark along with rest of screen: @@ -38,7 +38,9 @@ body.test.body--dark #top { background:#224422!important; color:white; } .body--dark .q-menu { background-color:black; } .body--dark img.invertible { -webkit-filter: invert(90%); filter: invert(90%); } -.q-dark, .q-card { background:transparent; } +@media (min-width:576px) { + .q-dark, .q-card { background:transparent; } +} .q-card__actions h5 { font-size: 1.1rem; display:inline-block; margin-block-start:0; margin-block-end:0; } .q-dialog .q-card { background-color:white; } .body--dark .q-dialog .q-card { background-color:black; } diff --git a/base-component/webroot/screen/webroot/js/MoquiLib.js b/base-component/webroot/screen/webroot/js/MoquiLib.js index 7ce729a26..9ea697027 100644 --- a/base-component/webroot/screen/webroot/js/MoquiLib.js +++ b/base-component/webroot/screen/webroot/js/MoquiLib.js @@ -200,8 +200,10 @@ var moqui = { return moment(value).format(format); } else if (type === "bigdecimal" || type === "currency") { // TODO format numbers with format string, localize - return value.toFixed(2).replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,'); + if (moqui.isNumber(value)) value = value.toFixed(2); + return ("" + value).replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,'); } else if (type === "long" || type === "integer" || type === "double" || type === "float") { + if (!moqui.isString(value)) value = "" + value; // TODO format numbers with format string, localize return value.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,'); } else { diff --git a/base-component/webroot/screen/webroot/js/WebrootVue.qvt.js b/base-component/webroot/screen/webroot/js/WebrootVue.qvt.js index f6871f698..df3a7bbfb 100644 --- a/base-component/webroot/screen/webroot/js/WebrootVue.qvt.js +++ b/base-component/webroot/screen/webroot/js/WebrootVue.qvt.js @@ -141,9 +141,13 @@ moqui.handleAjaxError = function(jqXHR, textStatus, errorThrown, responseText) { /* Override moqui.notifyGrowl */ moqui.notifyGrowl = function(jsonObj) { if (!jsonObj) return; - // TODO: jsonObj.link, jsonObj.icon - moqui.webrootVue.$q.notify($.extend({}, moqui.notifyOptsInfo, { type:jsonObj.type, message:jsonObj.title })); - moqui.webrootVue.addNotify(jsonObj.title, jsonObj.type); + // TODO: jsonObj.icon + moqui.webrootVue.$q.notify($.extend({}, moqui.notifyOptsInfo, { type:jsonObj.type, message:jsonObj.title, + actions: [ + { label: 'View', color: 'white', handler: function () { moqui.webrootVue.setUrl(jsonObj.link); } } + ] + })); + moqui.webrootVue.addNotify(jsonObj.title, jsonObj.type, jsonObj.link, jsonObj.icon); }; /* ========== component loading methods ========== */ @@ -1005,7 +1009,7 @@ Vue.component('m-form-paginate', { name: "mFormPaginate", props: { paginate:Object, formList:Object }, template: - '
' + + '
' + '' + @@ -1852,9 +1856,38 @@ Vue.component('m-chart', { mounted: function() { var vm = this; moqui.loadScript('https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js', function(err) { - if (err) return; + if (err) { + console.error("Error loading m-chart script: " + err); + return; + } vm.instance = new Chart(vm.$refs.canvas, vm.config); }, function() { return !!window.Chart; }); + }, + watch: { + config: function (val) { + if (this.instance) { + // console.info("updating m-chart") + if (val.type) this.instance.type = val.type; + if (val.labels) this.instance.labels = val.labels; + if (val.data) this.instance.data = val.data; + if (val.options) this.instance.options = val.options; + this.instance.update(); + } + } + } +}); +/* Lazy loading Mermaid JS wrapper component; for config options see https://mermaid.js.org/config/usage.html */ +Vue.component('m-mermaid', { + name: 'mMermaid', + props: { config:{type:Object,'default': function() { return {startOnLoad:true,securityLevel:'loose'} }}, + height:{type:String,'default':'400px'}, width:{type:String,'default':'100%'} }, + template: '
', + mounted: function() { + var vm = this; + moqui.loadScript('https://cdnjs.cloudflare.com/ajax/libs/mermaid/9.3.0/mermaid.min.js', function(err) { + if (err) return; + mermaid.init(vm.config, vm.$refs.mermaid); + }, function() { return !!window.mermaid; }); } }); /* Lazy loading CK Editor wrapper component, based on https://github.com/ckeditor/ckeditor4-vue */ @@ -2229,13 +2262,13 @@ moqui.webrootVue = new Vue({ this.urlListeners.push(urlListenerFunction); }, - addNotify: function(message, type) { + addNotify: function(message, type, link, icon) { var histList = this.notifyHistoryList.slice(0); var nowDate = new Date(); var nh = nowDate.getHours(); if (nh < 10) nh = '0' + nh; var nm = nowDate.getMinutes(); if (nm < 10) nm = '0' + nm; // var ns = nowDate.getSeconds(); if (ns < 10) ns = '0' + ns; - histList.unshift({message:message, type:type, time:(nh + ':' + nm)}); // + ':' + ns + histList.unshift({message:message, type:type, time:(nh + ':' + nm), link:link, icon:icon}); // + ':' + ns while (histList.length > 25) { histList.pop(); } this.notifyHistoryList = histList; }, @@ -2384,6 +2417,14 @@ moqui.webrootVue = new Vue({ if (resp.loggedIn) { this.reLoginPostLogin(); } + }, + qLayoutMinHeight: function(offset) { + // "offset" is a Number (pixels) that refers to the total + // height of header + footer that occupies on screen, + // based on the QLayout "view" prop configuration + + // this is actually what the default style-fn does in Quasar + return { minHeight: offset ? `calc(100vh - ${offset}px)` : '100vh' } } }, watch: { diff --git a/template/screen-macro/DefaultScreenMacros.qvt.ftl b/template/screen-macro/DefaultScreenMacros.qvt.ftl index 3ec5cfc17..834ce2be0 100644 --- a/template/screen-macro/DefaultScreenMacros.qvt.ftl +++ b/template/screen-macro/DefaultScreenMacros.qvt.ftl @@ -69,6 +69,15 @@ along with this software (see the LICENSE.md file). If not, see <#macro "section-include"> <#if sri.doBoundaryComments()> + <#assign sectionNode = sri.getSectionIncludedNode(.node)> + <#if sectionNode["@paginate"]! == "true"> + <#assign listName = sectionNode["@list"]> + <#assign listObj = context.get(listName)> + <#assign pagParms = Static["org.moqui.util.CollectionUtilities"].paginateParameters(listObj?size, listName, context)> + + <#t> pageSize:${context[listName + "PageSize"]?c}, pageMaxIndex:${context[listName + "PageMaxIndex"]?c}, + <#lt> pageRangeLow:${context[listName + "PageRangeLow"]?c}, pageRangeHigh:${context[listName + "PageRangeHigh"]?c} }"> + ${sri.renderSectionInclude(.node)} <#if sri.doBoundaryComments()> @@ -1128,9 +1137,9 @@ ${sri.renderIncludeScreen(.node["@location"], .node["@share-scope"]!)} <#assign formDisabled = formListUrlInfo.disableLink> <#if isServerStatic><#-- client rendered, static --> - <#-- TODO: form-list server-static needs to be revisited still for Quasar --> <#assign hiddenParameterMap = sri.getFormHiddenParameters(formNode)> <#assign hiddenParameterKeys = hiddenParameterMap.keySet()> + <#-- TODO: form-list server-static needs to be revisited still for Quasar --> <#t> :skip-form="${skipForm?c}" :skip-header="${skipHeader?c}" :header-form="${needHeaderForm?c}" <#t> :header-dialog="${isHeaderDialog?c}" :saved-finds="${(formNode["@saved-finds"]! == "true")?c}" @@ -1257,22 +1266,17 @@ ${sri.renderIncludeScreen(.node["@location"], .node["@share-scope"]!)} <#-- first-row fields --> <#if formListInfo.hasFirstRow()> - <#-- TODO change to wrap row, use something like sri.makeFormListSingleMap() which eliminates use inline of hiddenParameterKeys, hiddenParameterMap --> - <#t>${sri.pushSingleFormMapContext(formNode["@map-first-row"]!"")} <#assign listEntryIndex = "first"> - <#assign firstUrlInstance = sri.makeUrlByType(formNode["@transition-first-row"], "transition", null, "false")> - - <#if orderByField?has_content> - <#list hiddenParameterKeys as hiddenParameterKey> - <#assign hiddenFieldList = formListInfo.getListFirstRowHiddenFieldList()> - <#list hiddenFieldList as hiddenField><#recurse hiddenField["first-row-field"][0]/> - - <#assign listEntryIndex = ""> - <#t>${sri.popContext()}<#-- context was pushed for the form so pop here at the end --> + <#if formListInfo.isFirstRowForm()> + <#assign firstRowMap = sri.getSingleFormMap(formNode["@map-first-row"]!"")> + <#assign firstUrlInstance = sri.makeUrlByType(formNode["@transition-first-row"], "transition", null, "false")> + + <#t>${sri.pushSingleFormMapContext(formNode["@map-first-row"]!"")} <#assign ownerForm = formId + "_first"> - <#assign listEntryIndex = "first"> + <#assign fieldsJsName = "formProps.fields"> <#list mainColInfoList as columnFieldList> @@ -1282,30 +1286,27 @@ ${sri.renderIncludeScreen(.node["@location"], .node["@share-scope"]!)} + <#if formListInfo.isFirstRowForm()> + + <#assign ownerForm = formId> - <#assign listEntryIndex = ""> + <#assign fieldsJsName = ""> <#t>${sri.popContext()}<#-- context was pushed for the form so pop here at the end --> + <#assign listEntryIndex = ""> <#-- second-row fields --> <#if formListInfo.hasSecondRow()> + <#assign listEntryIndex = "second"> <#if formListInfo.isSecondRowForm()> - <#-- TODO change to wrap row, use something like sri.makeFormListSingleMap() which eliminates use inline of hiddenParameterKeys, hiddenParameterMap --> - <#t>${sri.pushSingleFormMapContext(formNode["@map-second-row"]!"")} - <#assign listEntryIndex = "second"> + <#assign secondRowMap = sri.getSingleFormMap(formNode["@map-second-row"]!"")> <#assign secondUrlInstance = sri.makeUrlByType(formNode["@transition-second-row"], "transition", null, "false")> - - <#if orderByField?has_content> - <#list hiddenParameterKeys as hiddenParameterKey> - <#assign hiddenFieldList = formListInfo.getListSecondRowHiddenFieldList()> - <#list hiddenFieldList as hiddenField><#recurse hiddenField["second-row-field"][0]/> - - <#assign listEntryIndex = ""> - <#t>${sri.popContext()}<#-- context was pushed for the form so pop here at the end --> + <#t>${sri.pushSingleFormMapContext(formNode["@map-second-row"]!"")} <#assign ownerForm = formId + "_second"> - <#assign listEntryIndex = "second"> + <#assign fieldsJsName = "formProps.fields"> <#list mainColInfoList as columnFieldList> @@ -1315,9 +1316,13 @@ ${sri.renderIncludeScreen(.node["@location"], .node["@share-scope"]!)} + <#if formListInfo.isSecondRowForm()> + + <#assign ownerForm = formId> - <#assign listEntryIndex = ""> + <#assign fieldsJsName = ""> <#t>${sri.popContext()}<#-- context was pushed for the form so pop here at the end --> + <#assign listEntryIndex = ""> <#-- the main list --> @@ -1338,7 +1343,7 @@ ${sri.renderIncludeScreen(.node["@location"], .node["@share-scope"]!)} <#assign ownerForm = formId + "_" + listEntry_index> <#assign fieldsJsName = "formProps.fields"> + :fields-initial="${Static["org.moqui.util.WebUtilities"].fieldValuesEncodeHtmlJsSafe(sri.makeFormListSingleMap(formListInfo, listEntry, formListUrlInfo, "row"))}"> <#if isMulti> <#assign ownerForm = formId> @@ -1382,22 +1387,17 @@ ${sri.renderIncludeScreen(.node["@location"], .node["@share-scope"]!)} <#-- last-row fields --> <#if formListInfo.hasLastRow()> - <#-- TODO change to wrap row, use something like sri.makeFormListSingleMap() which eliminates use inline of hiddenParameterKeys, hiddenParameterMap --> - <#t>${sri.pushSingleFormMapContext(formNode["@map-last-row"]!"")} <#assign listEntryIndex = "last"> - <#assign lastUrlInstance = sri.makeUrlByType(formNode["@transition-last-row"], "transition", null, "false")> - - <#if orderByField?has_content> - <#list hiddenParameterKeys as hiddenParameterKey> - <#assign hiddenFieldList = formListInfo.getListLastRowHiddenFieldList()> - <#list hiddenFieldList as hiddenField><#recurse hiddenField["last-row-field"][0]/> - - <#assign listEntryIndex = ""> - <#t>${sri.popContext()}<#-- context was pushed for the form so pop here at the end --> + <#if formListInfo.isLastRowForm()> + <#assign lastRowMap = sri.getSingleFormMap(formNode["@map-last-row"]!"")> + <#assign lastUrlInstance = sri.makeUrlByType(formNode["@transition-last-row"], "transition", null, "false")> + + <#t>${sri.pushSingleFormMapContext(formNode["@map-last-row"]!"")} <#assign ownerForm = formId + "_last"> - <#assign listEntryIndex = "last"> + <#assign fieldsJsName = "formProps.fields">
<#list mainColInfoList as columnFieldList>
@@ -1407,9 +1407,13 @@ ${sri.renderIncludeScreen(.node["@location"], .node["@share-scope"]!)}
+ <#if formListInfo.isLastRowForm()> +
+ <#assign ownerForm = formId> - <#assign listEntryIndex = ""> + <#assign fieldsJsName = ""> <#t>${sri.popContext()}<#-- context was pushed for the form so pop here at the end --> + <#assign listEntryIndex = ""> <#-- end/footer --> @@ -1997,6 +2001,7 @@ a => A, d => D, y => Y <#t><#if fieldsJsName?has_content> v-model="${fieldsJsName}.${name}"<#if formDisabled!> disable <#t><#if .node["@cols"]?has_content> cols="${.node["@cols"]}"<#else> style="width:100%;" <#t> rows="${.node["@rows"]!"3"}"<#if .node["@read-only"]! == "true"> readonly="readonly" + <#t><#if .node["@autogrow"]! == "true"> autogrow <#t><#if .node["@maxlength"]?has_content> maxlength="${.node["@maxlength"]}"<#if ownerForm?has_content> form="${ownerForm}"> <#if .node?parent["@tooltip"]?has_content>${ec.getResource().expand(.node?parent["@tooltip"], "")} <#if !fieldsJsName?has_content>${sri.getFieldValueString(.node)?html} @@ -2111,6 +2116,7 @@ a => A, d => D, y => Y <#t><#if widgetType == "drop-down"> <#assign ddFieldNode = widgetNode?parent?parent> <#assign allowMultiple = ec.getResource().expandNoL10n(widgetNode["@allow-multiple"]!, "") == "true"> + <#assign isListOptions = widgetNode["list-options"]?has_content> <#assign isDynamicOptions = widgetNode["dynamic-options"]?has_content> <#assign options = sri.getFieldOptions(widgetNode)> <#assign currentValue = sri.getFieldValuePlainString(ddFieldNode, "")> @@ -2123,7 +2129,7 @@ a => A, d => D, y => Y <#if !optionsHasCurrent && widgetNode["@current-description"]?has_content><#assign currentDescription = ec.getResource().expand(widgetNode["@current-description"], "")> <#t><#if allowMultiple> <#list currentValueList as listValue> - <#t><#if isDynamicOptions> + <#t><#if isDynamicOptions && !isListOptions> <#assign doNode = widgetNode["dynamic-options"][0]> <#assign transValue = sri.getFieldTransitionValue(doNode["@transition"], doNode, listValue, doNode["@label-field"]!"label", alwaysGet)!> <#t><#if transValue?has_content>${transValue}<#elseif listValue?has_content>${listValue}<#if listValue_has_next>, @@ -2133,7 +2139,7 @@ a => A, d => D, y => Y <#t> <#else> - <#t><#if isDynamicOptions> + <#t><#if isDynamicOptions && !isListOptions> <#assign doNode = widgetNode["dynamic-options"][0]> <#assign transValue = sri.getFieldTransitionValue(doNode["@transition"], doNode, currentValue, doNode["@label-field"]!"label", alwaysGet)!> <#t><#if transValue?has_content>${transValue}<#elseif currentValue?has_content>${currentValue} diff --git a/template/screen-macro/DefaultScreenMacros.vuet.ftl b/template/screen-macro/DefaultScreenMacros.vuet.ftl index c3ea2ce00..e25470efc 100644 --- a/template/screen-macro/DefaultScreenMacros.vuet.ftl +++ b/template/screen-macro/DefaultScreenMacros.vuet.ftl @@ -67,6 +67,15 @@ along with this software (see the LICENSE.md file). If not, see <#macro "section-include"> <#if sri.doBoundaryComments()> + <#assign sectionNode = sri.getSectionIncludedNode(.node)> + <#if sectionNode["@paginate"]! == "true"> + <#assign listName = sectionNode["@list"]> + <#assign listObj = context.get(listName)> + <#assign pagParms = Static["org.moqui.util.CollectionUtilities"].paginateParameters(listObj?size, listName, context)> + + <#t> pageSize:${context[listName + "PageSize"]?c}, pageMaxIndex:${context[listName + "PageMaxIndex"]?c}, + <#lt> pageRangeLow:${context[listName + "PageRangeLow"]?c}, pageRangeHigh:${context[listName + "PageRangeHigh"]?c} }"> + ${sri.renderSectionInclude(.node)} <#if sri.doBoundaryComments()> @@ -1967,6 +1976,7 @@ ${sri.getFieldValueString(.node)?html} <#t><#if widgetType == "drop-down"> <#assign ddFieldNode = widgetNode?parent?parent> <#assign allowMultiple = ec.getResource().expandNoL10n(widgetNode["@allow-multiple"]!, "") == "true"> + <#assign isListOptions = widgetNode["list-options"]?has_content> <#assign isDynamicOptions = widgetNode["dynamic-options"]?has_content> <#assign options = sri.getFieldOptions(widgetNode)> <#assign currentValue = sri.getFieldValuePlainString(ddFieldNode, "")> @@ -1979,7 +1989,7 @@ ${sri.getFieldValueString(.node)?html} <#if !optionsHasCurrent && widgetNode["@current-description"]?has_content><#assign currentDescription = ec.getResource().expand(widgetNode["@current-description"], "")> <#t><#if allowMultiple> <#list currentValueList as listValue> - <#t><#if isDynamicOptions> + <#t><#if isDynamicOptions && !isListOptions> <#assign doNode = widgetNode["dynamic-options"][0]> <#assign transValue = sri.getFieldTransitionValue(doNode["@transition"], doNode, listValue, doNode["@label-field"]!"label", alwaysGet)!> <#t><#if transValue?has_content>${transValue}<#elseif listValue?has_content>${listValue}<#if listValue_has_next>, @@ -1989,7 +1999,7 @@ ${sri.getFieldValueString(.node)?html} <#t> <#else> - <#t><#if isDynamicOptions> + <#t><#if isDynamicOptions && !isListOptions> <#assign doNode = widgetNode["dynamic-options"][0]> <#assign transValue = sri.getFieldTransitionValue(doNode["@transition"], doNode, currentValue, doNode["@label-field"]!"label", alwaysGet)!> <#t><#if transValue?has_content>${transValue}<#elseif currentValue?has_content>${currentValue}